@webadeva/eslint-config 0.0.0-stage → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Gobind Singh
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,82 @@
1
- # Temporary Holding Version
1
+ # @webadeva/eslint-config
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ [![npm version](https://img.shields.io/npm/v/@webadeva/eslint-config)](https://www.npmjs.com/package/@webadeva/eslint-config)
4
+ [![Release](https://github.com/webadeva/eslint-config/actions/workflows/release.yml/badge.svg)](https://github.com/webadeva/eslint-config/actions/workflows/release.yml)
5
+ [![License: MIT](https://img.shields.io/npm/l/@webadeva/eslint-config)](LICENSE)
6
+
7
+ Shared ESLint flat configs for TypeScript projects, with type-aware rules, Prettier, and presets for React, Next.js, and Storybook.
8
+
9
+ ## Requirements
10
+
11
+ - Node.js `>=20.19`
12
+ - ESLint `>=10.13`
13
+ - Prettier `>=3`
14
+ - TypeScript `>=5 <7`, installed in your project. TypeScript 7 is not supported, because typescript-eslint needs the compiler API it removed.
15
+
16
+ ## Install
17
+
18
+ ```bash
19
+ pnpm add -D @webadeva/eslint-config eslint prettier typescript@6
20
+ ```
21
+
22
+ Use `typescript@6`, not a bare `typescript`. A bare install pulls the latest version, which may be TypeScript 7 (or later), and that isn't supported.
23
+
24
+ ## Usage
25
+
26
+ Create an ESLint config file. It can be `eslint.config.ts`, `eslint.config.js`, or `eslint.config.mjs` (use `.mjs` if your project is not `"type": "module"`). For `eslint.config.ts`, also install [`jiti`](https://github.com/unjs/jiti), which ESLint uses to load TypeScript config files. The presets include types, so a TypeScript config is type-checked too.
27
+
28
+ ```js
29
+ import config from "@webadeva/eslint-config";
30
+
31
+ export default config;
32
+ ```
33
+
34
+ | Preset | Import | Includes |
35
+ | --------- | ----------------------------------- | ---------------------------- |
36
+ | Base | `@webadeva/eslint-config` | TypeScript, Prettier, import |
37
+ | React | `@webadeva/eslint-config/react` | Base + React |
38
+ | Next.js | `@webadeva/eslint-config/next` | React + Next.js |
39
+ | Storybook | `@webadeva/eslint-config/storybook` | Next.js + Storybook |
40
+
41
+ `@webadeva/eslint-config/base` is an alias of the base preset. Only the base preset can be loaded with `require()`. The others are ESM-only because `@eslint-react` is.
42
+
43
+ ## Customizing
44
+
45
+ Add your own config objects after the preset:
46
+
47
+ ```js
48
+ import config from "@webadeva/eslint-config/react";
49
+
50
+ export default [
51
+ ...config,
52
+ {
53
+ rules: { "no-console": "off" },
54
+ settings: { webadeva: { maxLines: { warn: 250, error: 300 } } },
55
+ },
56
+ ];
57
+ ```
58
+
59
+ `maxLines` sets the file length limits. By default a file warns above 150 lines of code and errors above 175. See [docs/rules.md](docs/rules.md) for all options.
60
+
61
+ ## TypeScript and typed linting
62
+
63
+ Type-aware rules run on every file that typed linting can cover:
64
+
65
+ - **Files in a tsconfig** (the nearest `tsconfig.json`, including project references) get every rule. That includes JS files when `allowJs` is on.
66
+ - **Up to 8 root-level files outside any tsconfig**, such as config files and scripts, are typed with a [built-in fallback tsconfig](tsconfig.default.json). Root-level `.jsx`/`.tsx` files are included only if `@types/react` is installed.
67
+ - **Other files outside any tsconfig** get every rule except the type-aware ones. TS files in this situation are reported by `webadeva/typescript-file-in-project`, so they don't go unchecked unnoticed.
68
+
69
+ ## Defaults worth knowing
70
+
71
+ - These paths are ignored: `dist`, `*.config.*`, `**/__tests__/**`, `**/__mocks__/**`.
72
+ - Prettier runs as a lint rule and uses your Prettier config.
73
+ - The React preset detects your React version, including in monorepos.
74
+ - Some rules are opinionated: `no-console` warns, and arrow functions are preferred over `function`.
75
+
76
+ ## What's included
77
+
78
+ [`@eslint/js`](https://eslint.org/docs/latest/rules/), [typescript-eslint](https://typescript-eslint.io/) (recommended + stylistic, type-checked), [Prettier](https://github.com/prettier/eslint-plugin-prettier), [import](https://github.com/import-js/eslint-plugin-import), [eslint-comments](https://github.com/eslint-community/eslint-plugin-eslint-comments), [prefer-arrow-functions](https://github.com/JamieMason/eslint-plugin-prefer-arrow-functions), [@eslint-react](https://eslint-react.xyz/), [react-hooks](https://react.dev/reference/eslint-plugin-react-hooks), [react-refresh](https://github.com/ArnaudBarre/eslint-plugin-react-refresh), [Next.js](https://nextjs.org/docs/app/api-reference/config/eslint), [Storybook](https://github.com/storybookjs/eslint-plugin-storybook), and [this package's own rules](docs/rules.md).
79
+
80
+ ## Contributing
81
+
82
+ See [CONTRIBUTING.md](CONTRIBUTING.md).
package/dist/index.cjs ADDED
@@ -0,0 +1,384 @@
1
+ //#region \0rolldown/runtime.js
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __copyProps = (to, from, except, desc) => {
9
+ if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
10
+ key = keys[i];
11
+ if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
12
+ get: ((k) => from[k]).bind(null, key),
13
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
14
+ });
15
+ }
16
+ return to;
17
+ };
18
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule || !__hasOwnProp.call(mod, "default") ? __defProp(target, "default", {
19
+ value: mod,
20
+ enumerable: true
21
+ }) : target, mod));
22
+ //#endregion
23
+ let _eslint_community_eslint_plugin_eslint_comments = require("@eslint-community/eslint-plugin-eslint-comments");
24
+ _eslint_community_eslint_plugin_eslint_comments = __toESM(_eslint_community_eslint_plugin_eslint_comments, 1);
25
+ let _eslint_js = require("@eslint/js");
26
+ _eslint_js = __toESM(_eslint_js, 1);
27
+ let eslint_plugin_import = require("eslint-plugin-import");
28
+ eslint_plugin_import = __toESM(eslint_plugin_import, 1);
29
+ let eslint_plugin_prefer_arrow_functions = require("eslint-plugin-prefer-arrow-functions");
30
+ eslint_plugin_prefer_arrow_functions = __toESM(eslint_plugin_prefer_arrow_functions, 1);
31
+ let eslint_plugin_prettier = require("eslint-plugin-prettier");
32
+ eslint_plugin_prettier = __toESM(eslint_plugin_prettier, 1);
33
+ let globals = require("globals");
34
+ globals = __toESM(globals, 1);
35
+ let node_module = require("node:module");
36
+ let node_fs = require("node:fs");
37
+ let node_path = require("node:path");
38
+ //#region src/plugin/rules/max-lines.ts
39
+ const DEFAULTS = {
40
+ enabled: true,
41
+ warn: 150,
42
+ error: 175,
43
+ skipBlankLines: true,
44
+ skipComments: true
45
+ };
46
+ const SETTINGS_ERROR = "@webadeva/eslint-config: `settings.webadeva.maxLines` must be `false` or `{ enabled?, warn?, error?, skipBlankLines?, skipComments? }`";
47
+ const isLimit = (value) => {
48
+ return value === false || typeof value === "number" && Number.isInteger(value) && value >= 0;
49
+ };
50
+ const readSettings = (context) => {
51
+ const maxLines = context.settings["webadeva"]?.maxLines;
52
+ if (maxLines === false) return void 0;
53
+ const settings = {
54
+ ...DEFAULTS,
55
+ ...maxLines
56
+ };
57
+ if (typeof settings.enabled !== "boolean") throw new Error(`${SETTINGS_ERROR}; \`enabled\` must be a boolean.`);
58
+ if (!settings.enabled) return void 0;
59
+ if (!isLimit(settings.warn) || !isLimit(settings.error)) throw new Error(`${SETTINGS_ERROR}; \`warn\` and \`error\` must be non-negative integers or \`false\`.`);
60
+ if (typeof settings.skipBlankLines !== "boolean" || typeof settings.skipComments !== "boolean") throw new Error(`${SETTINGS_ERROR}; \`skipBlankLines\` and \`skipComments\` must be booleans.`);
61
+ if (settings.warn !== false && settings.error !== false && settings.warn >= settings.error) throw new Error(`${SETTINGS_ERROR}; \`warn\` (${settings.warn}) must be lower than \`error\` (${settings.error}).`);
62
+ return settings;
63
+ };
64
+ const addLines = (lines, { start, end }) => {
65
+ for (let { line } = start; line <= end.line; line++) lines.add(line);
66
+ };
67
+ const countLines = (sourceCode, { skipBlankLines, skipComments }) => {
68
+ const lines = /* @__PURE__ */ new Set();
69
+ for (const token of sourceCode.ast.tokens) addLines(lines, token.loc);
70
+ if (!skipComments) {
71
+ for (const comment of sourceCode.getAllComments()) if (comment.loc) addLines(lines, comment.loc);
72
+ }
73
+ if (!skipBlankLines) {
74
+ const lastLine = sourceCode.lines.at(-1) === "" ? sourceCode.lines.length - 1 : sourceCode.lines.length;
75
+ sourceCode.lines.slice(0, lastLine).forEach((text, index) => {
76
+ if (text.trim() === "") lines.add(index + 1);
77
+ });
78
+ }
79
+ return lines.size;
80
+ };
81
+ const createMaxLinesRule = (level, description, message) => {
82
+ return {
83
+ meta: {
84
+ type: "suggestion",
85
+ docs: { description },
86
+ messages: { tooLong: message },
87
+ schema: []
88
+ },
89
+ create: (context) => {
90
+ const settings = readSettings(context);
91
+ const max = settings?.[level];
92
+ if (!settings || max === false || max === void 0) return {};
93
+ const upperBound = level === "warn" ? settings.error : false;
94
+ return { "Program:exit": () => {
95
+ const { sourceCode } = context;
96
+ const count = countLines(sourceCode, settings);
97
+ if (count <= max) return;
98
+ if (upperBound !== false && count > upperBound) return;
99
+ const lastLine = sourceCode.lines.length;
100
+ context.report({
101
+ loc: {
102
+ start: {
103
+ line: 1,
104
+ column: 0
105
+ },
106
+ end: {
107
+ line: lastLine,
108
+ column: sourceCode.lines[lastLine - 1]?.length ?? 0
109
+ }
110
+ },
111
+ messageId: "tooLong",
112
+ data: {
113
+ count: String(count),
114
+ max: String(max)
115
+ }
116
+ });
117
+ } };
118
+ }
119
+ };
120
+ };
121
+ //#endregion
122
+ //#region src/plugin/index.ts
123
+ const plugin = {
124
+ meta: { name: "@webadeva/eslint-config" },
125
+ rules: {
126
+ "max-lines": createMaxLinesRule("error", "Enforce a hard limit on lines of code per file.", "File has {{count}} lines of code, over the limit of {{max}}. Split it into smaller modules."),
127
+ "max-lines-warning": createMaxLinesRule("warn", "Warn when a file grows past the soft lines-of-code limit.", "File has {{count}} lines of code, over the soft limit of {{max}}. Consider splitting it into smaller modules."),
128
+ "typescript-file-in-project": {
129
+ meta: {
130
+ type: "problem",
131
+ docs: { description: "Report TypeScript files that no tsconfig includes, since type-aware rules skip them." },
132
+ messages: { notInProject: "This file is not included in any tsconfig, so it is not type-checked and type-aware lint rules are skipped. Add it to a tsconfig's `include`." },
133
+ schema: []
134
+ },
135
+ create: (context) => {
136
+ return { Program: () => {
137
+ context.report({
138
+ loc: {
139
+ line: 1,
140
+ column: 0
141
+ },
142
+ messageId: "notInProject"
143
+ });
144
+ } };
145
+ }
146
+ }
147
+ }
148
+ };
149
+ //#endregion
150
+ //#region src/utils/typescript.ts
151
+ const cwd = process.cwd();
152
+ const projectRequire = (0, node_module.createRequire)((0, node_path.join)(cwd, "package.json"));
153
+ const packageRequire = (0, node_module.createRequire)(require("url").pathToFileURL(__filename).href);
154
+ const loadTypeScript = () => {
155
+ let version;
156
+ try {
157
+ ({version} = projectRequire("typescript/package.json"));
158
+ } catch {
159
+ throw new Error("@webadeva/eslint-config: TypeScript is not installed in this project. This config is for TypeScript projects; install it with `pnpm add -D typescript@6` (or the npm/yarn equivalent).");
160
+ }
161
+ if (Number.parseInt(version, 10) >= 7) throw new Error(`@webadeva/eslint-config: TypeScript ${version} is not supported. typescript-eslint needs the TypeScript compiler API, which TypeScript 7 no longer ships. Install typescript@6.`);
162
+ return projectRequire("typescript");
163
+ };
164
+ let typescript;
165
+ /**
166
+ * Loads the project's TypeScript, throwing a readable error when it is
167
+ * missing or unsupported. Call it before importing anything that loads
168
+ * TypeScript itself (typescript-eslint, @eslint-react), which would otherwise
169
+ * fail with a bare "Cannot find module 'typescript'".
170
+ */
171
+ const ensureTypeScript = () => {
172
+ typescript ??= loadTypeScript();
173
+ return typescript;
174
+ };
175
+ const LINTABLE_FILE = /\.(?:[cm]?[jt]s|[jt]sx)$/;
176
+ const isLintableFile = (filePath) => {
177
+ return LINTABLE_FILE.test(filePath);
178
+ };
179
+ const nearestConfigCache = /* @__PURE__ */ new Map();
180
+ const findNearestConfig = (dir) => {
181
+ if (nearestConfigCache.has(dir)) return nearestConfigCache.get(dir);
182
+ const candidate = (0, node_path.join)(dir, "tsconfig.json");
183
+ const parent = (0, node_path.dirname)(dir);
184
+ const found = (0, node_fs.existsSync)(candidate) ? candidate : parent === dir ? void 0 : findNearestConfig(parent);
185
+ nearestConfigCache.set(dir, found);
186
+ return found;
187
+ };
188
+ const projectFilesCache = /* @__PURE__ */ new Map();
189
+ const getProjectFiles = (configPath, seen = /* @__PURE__ */ new Set()) => {
190
+ const cached = projectFilesCache.get(configPath);
191
+ if (cached) return cached;
192
+ const files = /* @__PURE__ */ new Set();
193
+ if (seen.has(configPath)) return files;
194
+ seen.add(configPath);
195
+ const ts = ensureTypeScript();
196
+ const parsed = ts.getParsedCommandLineOfConfigFile(configPath, void 0, {
197
+ ...ts.sys,
198
+ onUnRecoverableConfigFileDiagnostic: () => {}
199
+ });
200
+ for (const fileName of parsed?.fileNames ?? []) files.add((0, node_path.resolve)(fileName));
201
+ for (const reference of parsed?.projectReferences ?? []) {
202
+ const referencePath = ts.resolveProjectReferencePath(reference);
203
+ for (const fileName of getProjectFiles(referencePath, seen)) files.add(fileName);
204
+ }
205
+ projectFilesCache.set(configPath, files);
206
+ return files;
207
+ };
208
+ const isInProject = (filePath) => {
209
+ const configPath = findNearestConfig((0, node_path.dirname)(filePath));
210
+ return configPath !== void 0 && getProjectFiles(configPath).has((0, node_path.resolve)(filePath));
211
+ };
212
+ /** The tsconfig typescript-eslint uses for files outside every project. */
213
+ const defaultProjectConfig = packageRequire.resolve("@webadeva/eslint-config/tsconfig.default.json");
214
+ const canResolve = (id) => {
215
+ try {
216
+ projectRequire.resolve(id);
217
+ return true;
218
+ } catch {
219
+ return false;
220
+ }
221
+ };
222
+ const DEFAULT_PROJECT_FILE_LIMIT = 8;
223
+ const GLOB_SPECIAL_CHARS = /[*?[\]{}()!+@\\]/;
224
+ const DEFAULT_PROJECT_FILE = canResolve("@types/react/package.json") ? /\.[cm]?[jt]sx?$/ : /\.[cm]?[jt]s$/;
225
+ /**
226
+ * Root-level files that no tsconfig includes, linted with type information
227
+ * through typescript-eslint's default project (`allowDefaultProject`).
228
+ */
229
+ const defaultProjectFiles = (0, node_fs.readdirSync)(cwd, { withFileTypes: true }).filter((entry) => {
230
+ return entry.isFile() && DEFAULT_PROJECT_FILE.test(entry.name) && !GLOB_SPECIAL_CHARS.test(entry.name) && !isInProject((0, node_path.join)(cwd, entry.name));
231
+ }).map((entry) => {
232
+ return entry.name;
233
+ }).sort().slice(0, DEFAULT_PROJECT_FILE_LIMIT);
234
+ const defaultProjectPaths = new Set(defaultProjectFiles.map((name) => {
235
+ return (0, node_path.join)(cwd, name);
236
+ }));
237
+ const TYPESCRIPT_FILE = /\.[cm]?tsx?$/;
238
+ const isTypeScriptFile = (filePath) => {
239
+ return TYPESCRIPT_FILE.test(filePath);
240
+ };
241
+ /** Whether typescript-eslint can provide type information for this file. */
242
+ const hasTypeInformation = (filePath) => {
243
+ return defaultProjectPaths.has((0, node_path.resolve)(filePath)) || isInProject(filePath);
244
+ };
245
+ //#endregion
246
+ //#region src/configs/base.ts
247
+ ensureTypeScript();
248
+ const require$1 = (0, node_module.createRequire)(require("url").pathToFileURL(__filename).href);
249
+ const tseslint$1 = require$1("typescript-eslint");
250
+ const tsPlugin = require$1("@typescript-eslint/eslint-plugin");
251
+ const baseConfigs = [
252
+ { ignores: [
253
+ "**/__tests__/**/**/*",
254
+ "**/__mocks__/**/**/*",
255
+ "dist",
256
+ "*.config.*",
257
+ "node_modules/**/*.*",
258
+ "dist/**/*.*",
259
+ "!.storybook",
260
+ "!**/.storybook/**"
261
+ ] },
262
+ {
263
+ name: "webadeva/plugin",
264
+ plugins: { webadeva: plugin }
265
+ },
266
+ _eslint_js.default.configs.recommended,
267
+ ...tseslint$1.configs.recommendedTypeChecked,
268
+ {
269
+ files: ["**/*.{js,mjs,cjs,ts,jsx,tsx}"],
270
+ languageOptions: {
271
+ globals: {
272
+ ...globals.default.browser,
273
+ ...globals.default.node
274
+ },
275
+ parser: tseslint$1.parser,
276
+ parserOptions: {
277
+ ecmaVersion: 2020,
278
+ sourceType: "module",
279
+ projectService: {
280
+ allowDefaultProject: defaultProjectFiles,
281
+ defaultProject: defaultProjectConfig
282
+ },
283
+ tsconfigRootDir: process.cwd()
284
+ }
285
+ },
286
+ plugins: {
287
+ prettier: eslint_plugin_prettier.default,
288
+ "prefer-arrow-functions": eslint_plugin_prefer_arrow_functions.default,
289
+ "@typescript-eslint": tseslint$1.plugin,
290
+ "eslint-comments": _eslint_community_eslint_plugin_eslint_comments.default,
291
+ import: eslint_plugin_import.default
292
+ },
293
+ rules: {
294
+ ..._eslint_js.default.configs.recommended.rules,
295
+ ...tsPlugin.configs["recommended-type-checked"]?.rules,
296
+ ...tsPlugin.configs["stylistic-type-checked"]?.rules,
297
+ ...(eslint_plugin_prettier.default.configs?.["recommended"])?.rules,
298
+ camelcase: "error",
299
+ "no-undef": "off",
300
+ "no-unused-vars": "off",
301
+ "no-console": "warn",
302
+ "consistent-return": "off",
303
+ "object-shorthand": "off",
304
+ "no-process-exit": "off",
305
+ "no-underscore-dangle": "off",
306
+ "class-methods-use-this": "off",
307
+ "prefer-destructuring": ["warn", {
308
+ object: true,
309
+ array: false
310
+ }],
311
+ "@typescript-eslint/no-unused-vars": ["error", {
312
+ argsIgnorePattern: "^_$",
313
+ varsIgnorePattern: "^_$"
314
+ }],
315
+ "@typescript-eslint/no-unsafe-assignment": "off",
316
+ "@typescript-eslint/no-unsafe-member-access": "off",
317
+ "@typescript-eslint/no-explicit-any": "off",
318
+ "@typescript-eslint/prefer-for-of": "off",
319
+ "@typescript-eslint/no-unsafe-argument": "warn",
320
+ "@typescript-eslint/no-unsafe-call": "warn",
321
+ "@typescript-eslint/no-unsafe-return": "warn",
322
+ "@typescript-eslint/prefer-nullish-coalescing": "warn",
323
+ "@typescript-eslint/prefer-optional-chain": "warn",
324
+ "@typescript-eslint/consistent-indexed-object-style": "off",
325
+ "prefer-arrow-callback": "error",
326
+ "eslint-comments/require-description": ["error"],
327
+ "prefer-arrow-functions/prefer-arrow-functions": ["warn", {
328
+ allowNamedFunctions: false,
329
+ classPropertiesAllowed: false,
330
+ disallowPrototype: false,
331
+ returnStyle: "explicit",
332
+ singleReturnOnly: false
333
+ }]
334
+ }
335
+ },
336
+ {
337
+ name: "webadeva/max-lines",
338
+ files: ["**/*.{js,mjs,cjs,ts,mts,cts,jsx,tsx}"],
339
+ rules: {
340
+ "webadeva/max-lines-warning": "warn",
341
+ "webadeva/max-lines": "error"
342
+ }
343
+ }
344
+ ];
345
+ //#endregion
346
+ //#region src/utils/files-matching.ts
347
+ /**
348
+ * ESLint accepts `(filePath) => boolean` matchers in `files`, but its types
349
+ * only allow glob strings.
350
+ */
351
+ const filesMatching = (matcher) => {
352
+ return [matcher];
353
+ };
354
+ //#endregion
355
+ //#region src/configs/untyped.ts
356
+ ensureTypeScript();
357
+ const tseslint = (0, node_module.createRequire)(require("url").pathToFileURL(__filename).href)("typescript-eslint");
358
+ /**
359
+ * Turns off type-aware rules for files no TypeScript project includes (e.g.
360
+ * JS files without `allowJs`), which would otherwise fail to parse. Must come
361
+ * after every config that enables type-aware rules.
362
+ */
363
+ const untypedFilesConfigs = (disableTypeChecked = []) => {
364
+ const files = filesMatching((filePath) => {
365
+ return isLintableFile(filePath) && !hasTypeInformation(filePath);
366
+ });
367
+ return [...[tseslint.configs.disableTypeChecked, ...disableTypeChecked].map((config) => {
368
+ return {
369
+ ...config,
370
+ files
371
+ };
372
+ }), {
373
+ name: "webadeva/typescript-file-not-in-project",
374
+ files: filesMatching((filePath) => {
375
+ return isTypeScriptFile(filePath) && !hasTypeInformation(filePath);
376
+ }),
377
+ rules: { "webadeva/typescript-file-in-project": "error" }
378
+ }];
379
+ };
380
+ //#endregion
381
+ //#region src/index.ts
382
+ const config = [...baseConfigs, ...untypedFilesConfigs()];
383
+ //#endregion
384
+ module.exports = config;
@@ -0,0 +1,4 @@
1
+ import { Linter } from "eslint";
2
+ //#region src/index.d.ts
3
+ declare const config: Linter.Config[];
4
+ export = config;
@@ -0,0 +1,5 @@
1
+ import { Linter } from "eslint";
2
+ //#region src/index.d.ts
3
+ declare const config: Linter.Config[];
4
+ //#endregion
5
+ export { config as default };
package/dist/index.mjs ADDED
@@ -0,0 +1,5 @@
1
+ import { n as baseConfigs, t as untypedFilesConfigs } from "./untyped-Wrh4hLMj.mjs";
2
+ //#region src/index.ts
3
+ const config = [...baseConfigs, ...untypedFilesConfigs()];
4
+ //#endregion
5
+ export { config as default };
@@ -0,0 +1,9 @@
1
+ import nextPlugin from "@next/eslint-plugin-next";
2
+ //#region src/configs/next.ts
3
+ const nextConfigs = [{
4
+ plugins: { "@next/next": nextPlugin },
5
+ rules: { "@next/next/no-img-element": "warn" },
6
+ settings: { next: { rootDir: "." } }
7
+ }];
8
+ //#endregion
9
+ export { nextConfigs as t };
@@ -0,0 +1,5 @@
1
+ import { Linter } from "eslint";
2
+ //#region src/next.d.ts
3
+ declare const config: Linter.Config[];
4
+ //#endregion
5
+ export { config as default };
package/dist/next.mjs ADDED
@@ -0,0 +1,12 @@
1
+ import { n as baseConfigs, t as untypedFilesConfigs } from "./untyped-Wrh4hLMj.mjs";
2
+ import { n as reactDisableTypeChecked, t as reactConfigs } from "./react-C-VV0l5P.mjs";
3
+ import { t as nextConfigs } from "./next-CWjuCwpN.mjs";
4
+ //#region src/next.ts
5
+ const config = [
6
+ ...baseConfigs,
7
+ ...reactConfigs,
8
+ ...nextConfigs,
9
+ ...untypedFilesConfigs(reactDisableTypeChecked)
10
+ ];
11
+ //#endregion
12
+ export { config as default };
@@ -0,0 +1,59 @@
1
+ import { r as ensureTypeScript } from "./untyped-Wrh4hLMj.mjs";
2
+ import { createRequire } from "node:module";
3
+ import { readFileSync, readdirSync } from "node:fs";
4
+ import { join } from "node:path";
5
+ import pluginReactHooks from "eslint-plugin-react-hooks";
6
+ import pluginReactRefresh from "eslint-plugin-react-refresh";
7
+ //#region src/utils/detect-react-version.ts
8
+ const require = createRequire(import.meta.url);
9
+ const FALLBACK_VERSION = "19.0";
10
+ const readReactVersion = (fromDir) => {
11
+ const pkgPath = require.resolve("react/package.json", { paths: [fromDir] });
12
+ return String(require(pkgPath).version);
13
+ };
14
+ const detectReactVersion = () => {
15
+ const cwd = process.cwd();
16
+ try {
17
+ return readReactVersion(cwd);
18
+ } catch {}
19
+ try {
20
+ const ws = JSON.parse(readFileSync(join(cwd, "package.json"), "utf8")).workspaces ?? [];
21
+ const patterns = Array.isArray(ws) ? ws : ws.packages ?? [];
22
+ for (const pattern of patterns) {
23
+ const baseDir = join(cwd, String(pattern).replace(/\/\*.*$/, ""));
24
+ try {
25
+ const entries = readdirSync(baseDir, { withFileTypes: true });
26
+ for (const entry of entries) {
27
+ if (!entry.isDirectory()) continue;
28
+ try {
29
+ return readReactVersion(join(baseDir, entry.name));
30
+ } catch {}
31
+ }
32
+ } catch {}
33
+ }
34
+ } catch {}
35
+ return FALLBACK_VERSION;
36
+ };
37
+ //#endregion
38
+ //#region src/configs/react.ts
39
+ ensureTypeScript();
40
+ const { default: eslintReact } = await import("@eslint-react/eslint-plugin");
41
+ const reactConfigs = [
42
+ eslintReact.configs["recommended-typescript"],
43
+ pluginReactHooks.configs.flat["recommended-latest"],
44
+ pluginReactRefresh.configs.recommended,
45
+ {
46
+ settings: { react: { version: detectReactVersion() } },
47
+ rules: {
48
+ "react-refresh/only-export-components": ["warn", { allowConstantExport: true }],
49
+ "react-hooks/exhaustive-deps": "warn",
50
+ "react-hooks/rules-of-hooks": "error",
51
+ "react-hooks/set-state-in-effect": "off",
52
+ "@eslint-react/set-state-in-effect": "off",
53
+ "@eslint-react/dom-no-unknown-property": ["error", { ignore: ["css"] }]
54
+ }
55
+ }
56
+ ];
57
+ const reactDisableTypeChecked = [eslintReact.configs["disable-type-checked"]];
58
+ //#endregion
59
+ export { reactDisableTypeChecked as n, reactConfigs as t };
@@ -0,0 +1,5 @@
1
+ import { Linter } from "eslint";
2
+ //#region src/react.d.ts
3
+ declare const config: Linter.Config[];
4
+ //#endregion
5
+ export { config as default };
package/dist/react.mjs ADDED
@@ -0,0 +1,10 @@
1
+ import { n as baseConfigs, t as untypedFilesConfigs } from "./untyped-Wrh4hLMj.mjs";
2
+ import { n as reactDisableTypeChecked, t as reactConfigs } from "./react-C-VV0l5P.mjs";
3
+ //#region src/react.ts
4
+ const config = [
5
+ ...baseConfigs,
6
+ ...reactConfigs,
7
+ ...untypedFilesConfigs(reactDisableTypeChecked)
8
+ ];
9
+ //#endregion
10
+ export { config as default };
@@ -0,0 +1,5 @@
1
+ import { Linter } from "eslint";
2
+ //#region src/storybook.d.ts
3
+ declare const config: Linter.Config[];
4
+ //#endregion
5
+ export { config as default };
@@ -0,0 +1,22 @@
1
+ import { n as baseConfigs, t as untypedFilesConfigs } from "./untyped-Wrh4hLMj.mjs";
2
+ import { n as reactDisableTypeChecked, t as reactConfigs } from "./react-C-VV0l5P.mjs";
3
+ import { t as nextConfigs } from "./next-CWjuCwpN.mjs";
4
+ import storybook from "eslint-plugin-storybook";
5
+ //#region src/configs/storybook.ts
6
+ const storybookConfigs = [...storybook.configs["flat/recommended"], { rules: {
7
+ "node/no-unsupported-features/es-syntax": "off",
8
+ "node/no-missing-import": "off",
9
+ "react/jsx-props-no-spreading": "off",
10
+ "react/react-in-jsx-scope": "off"
11
+ } }];
12
+ //#endregion
13
+ //#region src/storybook.ts
14
+ const config = [
15
+ ...baseConfigs,
16
+ ...reactConfigs,
17
+ ...nextConfigs,
18
+ ...storybookConfigs,
19
+ ...untypedFilesConfigs(reactDisableTypeChecked)
20
+ ];
21
+ //#endregion
22
+ export { config as default };
@@ -0,0 +1,353 @@
1
+ import { createRequire } from "node:module";
2
+ import eslintComments from "@eslint-community/eslint-plugin-eslint-comments";
3
+ import pluginJs from "@eslint/js";
4
+ import importPlugin from "eslint-plugin-import";
5
+ import preferArrowFunctions from "eslint-plugin-prefer-arrow-functions";
6
+ import prettier from "eslint-plugin-prettier";
7
+ import globals from "globals";
8
+ import { existsSync, readdirSync } from "node:fs";
9
+ import { dirname, join, resolve } from "node:path";
10
+ //#region src/plugin/rules/max-lines.ts
11
+ const DEFAULTS = {
12
+ enabled: true,
13
+ warn: 150,
14
+ error: 175,
15
+ skipBlankLines: true,
16
+ skipComments: true
17
+ };
18
+ const SETTINGS_ERROR = "@webadeva/eslint-config: `settings.webadeva.maxLines` must be `false` or `{ enabled?, warn?, error?, skipBlankLines?, skipComments? }`";
19
+ const isLimit = (value) => {
20
+ return value === false || typeof value === "number" && Number.isInteger(value) && value >= 0;
21
+ };
22
+ const readSettings = (context) => {
23
+ const maxLines = context.settings["webadeva"]?.maxLines;
24
+ if (maxLines === false) return void 0;
25
+ const settings = {
26
+ ...DEFAULTS,
27
+ ...maxLines
28
+ };
29
+ if (typeof settings.enabled !== "boolean") throw new Error(`${SETTINGS_ERROR}; \`enabled\` must be a boolean.`);
30
+ if (!settings.enabled) return void 0;
31
+ if (!isLimit(settings.warn) || !isLimit(settings.error)) throw new Error(`${SETTINGS_ERROR}; \`warn\` and \`error\` must be non-negative integers or \`false\`.`);
32
+ if (typeof settings.skipBlankLines !== "boolean" || typeof settings.skipComments !== "boolean") throw new Error(`${SETTINGS_ERROR}; \`skipBlankLines\` and \`skipComments\` must be booleans.`);
33
+ if (settings.warn !== false && settings.error !== false && settings.warn >= settings.error) throw new Error(`${SETTINGS_ERROR}; \`warn\` (${settings.warn}) must be lower than \`error\` (${settings.error}).`);
34
+ return settings;
35
+ };
36
+ const addLines = (lines, { start, end }) => {
37
+ for (let { line } = start; line <= end.line; line++) lines.add(line);
38
+ };
39
+ const countLines = (sourceCode, { skipBlankLines, skipComments }) => {
40
+ const lines = /* @__PURE__ */ new Set();
41
+ for (const token of sourceCode.ast.tokens) addLines(lines, token.loc);
42
+ if (!skipComments) {
43
+ for (const comment of sourceCode.getAllComments()) if (comment.loc) addLines(lines, comment.loc);
44
+ }
45
+ if (!skipBlankLines) {
46
+ const lastLine = sourceCode.lines.at(-1) === "" ? sourceCode.lines.length - 1 : sourceCode.lines.length;
47
+ sourceCode.lines.slice(0, lastLine).forEach((text, index) => {
48
+ if (text.trim() === "") lines.add(index + 1);
49
+ });
50
+ }
51
+ return lines.size;
52
+ };
53
+ const createMaxLinesRule = (level, description, message) => {
54
+ return {
55
+ meta: {
56
+ type: "suggestion",
57
+ docs: { description },
58
+ messages: { tooLong: message },
59
+ schema: []
60
+ },
61
+ create: (context) => {
62
+ const settings = readSettings(context);
63
+ const max = settings?.[level];
64
+ if (!settings || max === false || max === void 0) return {};
65
+ const upperBound = level === "warn" ? settings.error : false;
66
+ return { "Program:exit": () => {
67
+ const { sourceCode } = context;
68
+ const count = countLines(sourceCode, settings);
69
+ if (count <= max) return;
70
+ if (upperBound !== false && count > upperBound) return;
71
+ const lastLine = sourceCode.lines.length;
72
+ context.report({
73
+ loc: {
74
+ start: {
75
+ line: 1,
76
+ column: 0
77
+ },
78
+ end: {
79
+ line: lastLine,
80
+ column: sourceCode.lines[lastLine - 1]?.length ?? 0
81
+ }
82
+ },
83
+ messageId: "tooLong",
84
+ data: {
85
+ count: String(count),
86
+ max: String(max)
87
+ }
88
+ });
89
+ } };
90
+ }
91
+ };
92
+ };
93
+ //#endregion
94
+ //#region src/plugin/index.ts
95
+ const plugin = {
96
+ meta: { name: "@webadeva/eslint-config" },
97
+ rules: {
98
+ "max-lines": createMaxLinesRule("error", "Enforce a hard limit on lines of code per file.", "File has {{count}} lines of code, over the limit of {{max}}. Split it into smaller modules."),
99
+ "max-lines-warning": createMaxLinesRule("warn", "Warn when a file grows past the soft lines-of-code limit.", "File has {{count}} lines of code, over the soft limit of {{max}}. Consider splitting it into smaller modules."),
100
+ "typescript-file-in-project": {
101
+ meta: {
102
+ type: "problem",
103
+ docs: { description: "Report TypeScript files that no tsconfig includes, since type-aware rules skip them." },
104
+ messages: { notInProject: "This file is not included in any tsconfig, so it is not type-checked and type-aware lint rules are skipped. Add it to a tsconfig's `include`." },
105
+ schema: []
106
+ },
107
+ create: (context) => {
108
+ return { Program: () => {
109
+ context.report({
110
+ loc: {
111
+ line: 1,
112
+ column: 0
113
+ },
114
+ messageId: "notInProject"
115
+ });
116
+ } };
117
+ }
118
+ }
119
+ }
120
+ };
121
+ //#endregion
122
+ //#region src/utils/typescript.ts
123
+ const cwd = process.cwd();
124
+ const projectRequire = createRequire(join(cwd, "package.json"));
125
+ const packageRequire = createRequire(import.meta.url);
126
+ const loadTypeScript = () => {
127
+ let version;
128
+ try {
129
+ ({version} = projectRequire("typescript/package.json"));
130
+ } catch {
131
+ throw new Error("@webadeva/eslint-config: TypeScript is not installed in this project. This config is for TypeScript projects; install it with `pnpm add -D typescript@6` (or the npm/yarn equivalent).");
132
+ }
133
+ if (Number.parseInt(version, 10) >= 7) throw new Error(`@webadeva/eslint-config: TypeScript ${version} is not supported. typescript-eslint needs the TypeScript compiler API, which TypeScript 7 no longer ships. Install typescript@6.`);
134
+ return projectRequire("typescript");
135
+ };
136
+ let typescript;
137
+ /**
138
+ * Loads the project's TypeScript, throwing a readable error when it is
139
+ * missing or unsupported. Call it before importing anything that loads
140
+ * TypeScript itself (typescript-eslint, @eslint-react), which would otherwise
141
+ * fail with a bare "Cannot find module 'typescript'".
142
+ */
143
+ const ensureTypeScript = () => {
144
+ typescript ??= loadTypeScript();
145
+ return typescript;
146
+ };
147
+ const LINTABLE_FILE = /\.(?:[cm]?[jt]s|[jt]sx)$/;
148
+ const isLintableFile = (filePath) => {
149
+ return LINTABLE_FILE.test(filePath);
150
+ };
151
+ const nearestConfigCache = /* @__PURE__ */ new Map();
152
+ const findNearestConfig = (dir) => {
153
+ if (nearestConfigCache.has(dir)) return nearestConfigCache.get(dir);
154
+ const candidate = join(dir, "tsconfig.json");
155
+ const parent = dirname(dir);
156
+ const found = existsSync(candidate) ? candidate : parent === dir ? void 0 : findNearestConfig(parent);
157
+ nearestConfigCache.set(dir, found);
158
+ return found;
159
+ };
160
+ const projectFilesCache = /* @__PURE__ */ new Map();
161
+ const getProjectFiles = (configPath, seen = /* @__PURE__ */ new Set()) => {
162
+ const cached = projectFilesCache.get(configPath);
163
+ if (cached) return cached;
164
+ const files = /* @__PURE__ */ new Set();
165
+ if (seen.has(configPath)) return files;
166
+ seen.add(configPath);
167
+ const ts = ensureTypeScript();
168
+ const parsed = ts.getParsedCommandLineOfConfigFile(configPath, void 0, {
169
+ ...ts.sys,
170
+ onUnRecoverableConfigFileDiagnostic: () => {}
171
+ });
172
+ for (const fileName of parsed?.fileNames ?? []) files.add(resolve(fileName));
173
+ for (const reference of parsed?.projectReferences ?? []) {
174
+ const referencePath = ts.resolveProjectReferencePath(reference);
175
+ for (const fileName of getProjectFiles(referencePath, seen)) files.add(fileName);
176
+ }
177
+ projectFilesCache.set(configPath, files);
178
+ return files;
179
+ };
180
+ const isInProject = (filePath) => {
181
+ const configPath = findNearestConfig(dirname(filePath));
182
+ return configPath !== void 0 && getProjectFiles(configPath).has(resolve(filePath));
183
+ };
184
+ /** The tsconfig typescript-eslint uses for files outside every project. */
185
+ const defaultProjectConfig = packageRequire.resolve("@webadeva/eslint-config/tsconfig.default.json");
186
+ const canResolve = (id) => {
187
+ try {
188
+ projectRequire.resolve(id);
189
+ return true;
190
+ } catch {
191
+ return false;
192
+ }
193
+ };
194
+ const DEFAULT_PROJECT_FILE_LIMIT = 8;
195
+ const GLOB_SPECIAL_CHARS = /[*?[\]{}()!+@\\]/;
196
+ const DEFAULT_PROJECT_FILE = canResolve("@types/react/package.json") ? /\.[cm]?[jt]sx?$/ : /\.[cm]?[jt]s$/;
197
+ /**
198
+ * Root-level files that no tsconfig includes, linted with type information
199
+ * through typescript-eslint's default project (`allowDefaultProject`).
200
+ */
201
+ const defaultProjectFiles = readdirSync(cwd, { withFileTypes: true }).filter((entry) => {
202
+ return entry.isFile() && DEFAULT_PROJECT_FILE.test(entry.name) && !GLOB_SPECIAL_CHARS.test(entry.name) && !isInProject(join(cwd, entry.name));
203
+ }).map((entry) => {
204
+ return entry.name;
205
+ }).sort().slice(0, DEFAULT_PROJECT_FILE_LIMIT);
206
+ const defaultProjectPaths = new Set(defaultProjectFiles.map((name) => {
207
+ return join(cwd, name);
208
+ }));
209
+ const TYPESCRIPT_FILE = /\.[cm]?tsx?$/;
210
+ const isTypeScriptFile = (filePath) => {
211
+ return TYPESCRIPT_FILE.test(filePath);
212
+ };
213
+ /** Whether typescript-eslint can provide type information for this file. */
214
+ const hasTypeInformation = (filePath) => {
215
+ return defaultProjectPaths.has(resolve(filePath)) || isInProject(filePath);
216
+ };
217
+ //#endregion
218
+ //#region src/configs/base.ts
219
+ ensureTypeScript();
220
+ const require = createRequire(import.meta.url);
221
+ const tseslint$1 = require("typescript-eslint");
222
+ const tsPlugin = require("@typescript-eslint/eslint-plugin");
223
+ const baseConfigs = [
224
+ { ignores: [
225
+ "**/__tests__/**/**/*",
226
+ "**/__mocks__/**/**/*",
227
+ "dist",
228
+ "*.config.*",
229
+ "node_modules/**/*.*",
230
+ "dist/**/*.*",
231
+ "!.storybook",
232
+ "!**/.storybook/**"
233
+ ] },
234
+ {
235
+ name: "webadeva/plugin",
236
+ plugins: { webadeva: plugin }
237
+ },
238
+ pluginJs.configs.recommended,
239
+ ...tseslint$1.configs.recommendedTypeChecked,
240
+ {
241
+ files: ["**/*.{js,mjs,cjs,ts,jsx,tsx}"],
242
+ languageOptions: {
243
+ globals: {
244
+ ...globals.browser,
245
+ ...globals.node
246
+ },
247
+ parser: tseslint$1.parser,
248
+ parserOptions: {
249
+ ecmaVersion: 2020,
250
+ sourceType: "module",
251
+ projectService: {
252
+ allowDefaultProject: defaultProjectFiles,
253
+ defaultProject: defaultProjectConfig
254
+ },
255
+ tsconfigRootDir: process.cwd()
256
+ }
257
+ },
258
+ plugins: {
259
+ prettier,
260
+ "prefer-arrow-functions": preferArrowFunctions,
261
+ "@typescript-eslint": tseslint$1.plugin,
262
+ "eslint-comments": eslintComments,
263
+ import: importPlugin
264
+ },
265
+ rules: {
266
+ ...pluginJs.configs.recommended.rules,
267
+ ...tsPlugin.configs["recommended-type-checked"]?.rules,
268
+ ...tsPlugin.configs["stylistic-type-checked"]?.rules,
269
+ ...(prettier.configs?.["recommended"])?.rules,
270
+ camelcase: "error",
271
+ "no-undef": "off",
272
+ "no-unused-vars": "off",
273
+ "no-console": "warn",
274
+ "consistent-return": "off",
275
+ "object-shorthand": "off",
276
+ "no-process-exit": "off",
277
+ "no-underscore-dangle": "off",
278
+ "class-methods-use-this": "off",
279
+ "prefer-destructuring": ["warn", {
280
+ object: true,
281
+ array: false
282
+ }],
283
+ "@typescript-eslint/no-unused-vars": ["error", {
284
+ argsIgnorePattern: "^_$",
285
+ varsIgnorePattern: "^_$"
286
+ }],
287
+ "@typescript-eslint/no-unsafe-assignment": "off",
288
+ "@typescript-eslint/no-unsafe-member-access": "off",
289
+ "@typescript-eslint/no-explicit-any": "off",
290
+ "@typescript-eslint/prefer-for-of": "off",
291
+ "@typescript-eslint/no-unsafe-argument": "warn",
292
+ "@typescript-eslint/no-unsafe-call": "warn",
293
+ "@typescript-eslint/no-unsafe-return": "warn",
294
+ "@typescript-eslint/prefer-nullish-coalescing": "warn",
295
+ "@typescript-eslint/prefer-optional-chain": "warn",
296
+ "@typescript-eslint/consistent-indexed-object-style": "off",
297
+ "prefer-arrow-callback": "error",
298
+ "eslint-comments/require-description": ["error"],
299
+ "prefer-arrow-functions/prefer-arrow-functions": ["warn", {
300
+ allowNamedFunctions: false,
301
+ classPropertiesAllowed: false,
302
+ disallowPrototype: false,
303
+ returnStyle: "explicit",
304
+ singleReturnOnly: false
305
+ }]
306
+ }
307
+ },
308
+ {
309
+ name: "webadeva/max-lines",
310
+ files: ["**/*.{js,mjs,cjs,ts,mts,cts,jsx,tsx}"],
311
+ rules: {
312
+ "webadeva/max-lines-warning": "warn",
313
+ "webadeva/max-lines": "error"
314
+ }
315
+ }
316
+ ];
317
+ //#endregion
318
+ //#region src/utils/files-matching.ts
319
+ /**
320
+ * ESLint accepts `(filePath) => boolean` matchers in `files`, but its types
321
+ * only allow glob strings.
322
+ */
323
+ const filesMatching = (matcher) => {
324
+ return [matcher];
325
+ };
326
+ //#endregion
327
+ //#region src/configs/untyped.ts
328
+ ensureTypeScript();
329
+ const tseslint = createRequire(import.meta.url)("typescript-eslint");
330
+ /**
331
+ * Turns off type-aware rules for files no TypeScript project includes (e.g.
332
+ * JS files without `allowJs`), which would otherwise fail to parse. Must come
333
+ * after every config that enables type-aware rules.
334
+ */
335
+ const untypedFilesConfigs = (disableTypeChecked = []) => {
336
+ const files = filesMatching((filePath) => {
337
+ return isLintableFile(filePath) && !hasTypeInformation(filePath);
338
+ });
339
+ return [...[tseslint.configs.disableTypeChecked, ...disableTypeChecked].map((config) => {
340
+ return {
341
+ ...config,
342
+ files
343
+ };
344
+ }), {
345
+ name: "webadeva/typescript-file-not-in-project",
346
+ files: filesMatching((filePath) => {
347
+ return isTypeScriptFile(filePath) && !hasTypeInformation(filePath);
348
+ }),
349
+ rules: { "webadeva/typescript-file-in-project": "error" }
350
+ }];
351
+ };
352
+ //#endregion
353
+ export { baseConfigs as n, ensureTypeScript as r, untypedFilesConfigs as t };
package/package.json CHANGED
@@ -1,6 +1,131 @@
1
1
  {
2
2
  "name": "@webadeva/eslint-config",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.1.1",
4
+ "description": "Shared ESLint flat configs for TypeScript projects: type-aware rules, Prettier, and presets for React, Next.js, and Storybook.",
5
+ "keywords": [
6
+ "eslint",
7
+ "eslintconfig",
8
+ "eslint-config",
9
+ "eslint-flat-config",
10
+ "flat-config",
11
+ "typescript",
12
+ "typescript-eslint",
13
+ "prettier",
14
+ "react",
15
+ "nextjs",
16
+ "storybook",
17
+ "lint"
18
+ ],
19
+ "homepage": "https://github.com/webadeva/eslint-config#readme",
20
+ "bugs": {
21
+ "url": "https://github.com/webadeva/eslint-config/issues"
22
+ },
23
+ "repository": {
24
+ "type": "git",
25
+ "url": "git+https://github.com/webadeva/eslint-config.git"
26
+ },
27
+ "license": "MIT",
28
+ "author": {
29
+ "name": "Gobind Singh",
30
+ "email": "thesinghgobind@gmail.com",
31
+ "url": "https://www.thegobindsingh.com"
32
+ },
33
+ "type": "module",
34
+ "exports": {
35
+ ".": {
36
+ "import": "./dist/index.mjs",
37
+ "require": "./dist/index.cjs"
38
+ },
39
+ "./next": {
40
+ "import": "./dist/next.mjs"
41
+ },
42
+ "./react": {
43
+ "import": "./dist/react.mjs"
44
+ },
45
+ "./storybook": {
46
+ "import": "./dist/storybook.mjs"
47
+ },
48
+ "./package.json": "./package.json",
49
+ "./base": {
50
+ "import": "./dist/index.mjs",
51
+ "require": "./dist/index.cjs"
52
+ },
53
+ "./tsconfig.default.json": "./tsconfig.default.json"
54
+ },
55
+ "main": "./dist/index.cjs",
56
+ "module": "./dist/index.mjs",
57
+ "types": "./dist/index.d.cts",
58
+ "files": [
59
+ "dist",
60
+ "tsconfig.default.json"
61
+ ],
62
+ "engines": {
63
+ "node": ">=20.19.0"
64
+ },
65
+ "publishConfig": {
66
+ "access": "public"
67
+ },
68
+ "scripts": {
69
+ "build": "tsdown",
70
+ "dev": "tsdown --watch",
71
+ "typecheck": "tsc --noEmit",
72
+ "lint": "eslint .",
73
+ "format": "prettier --write .",
74
+ "format:check": "prettier --check .",
75
+ "test": "node --test \"src/tests/**/*.test.ts\"",
76
+ "check": "pnpm typecheck && pnpm lint && pnpm build && pnpm test",
77
+ "prepare": "husky",
78
+ "prepublishOnly": "pnpm check"
79
+ },
80
+ "peerDependencies": {
81
+ "eslint": ">=10.13.0",
82
+ "prettier": ">=3.0.0",
83
+ "typescript": ">=5.0.0 <7.0.0"
84
+ },
85
+ "peerDependenciesMeta": {
86
+ "eslint": {
87
+ "optional": false
88
+ },
89
+ "prettier": {
90
+ "optional": false
91
+ },
92
+ "typescript": {
93
+ "optional": false
94
+ }
95
+ },
96
+ "dependencies": {
97
+ "@eslint-community/eslint-plugin-eslint-comments": "^4.8.1",
98
+ "@eslint-react/eslint-plugin": "^5.28.2",
99
+ "@eslint/js": "^10.0.1",
100
+ "@next/eslint-plugin-next": "^16.4.2",
101
+ "@typescript-eslint/eslint-plugin": "^8.71.2",
102
+ "@typescript-eslint/parser": "^8.71.2",
103
+ "eslint-config-prettier": "^10.1.8",
104
+ "eslint-plugin-import": "^2.32.0",
105
+ "eslint-plugin-jsx-a11y": "^6.10.2",
106
+ "eslint-plugin-prefer-arrow-functions": "^3.10.2",
107
+ "eslint-plugin-prettier": "^5.5.7",
108
+ "eslint-plugin-react-hooks": "^7.1.1",
109
+ "eslint-plugin-react-refresh": "^0.5.7",
110
+ "eslint-plugin-storybook": "^10.6.2",
111
+ "globals": "^17.13.0",
112
+ "typescript-eslint": "^8.71.2"
113
+ },
114
+ "devDependencies": {
115
+ "@arethetypeswrong/core": "^0.18.5",
116
+ "@commitlint/cli": "^21.2.4",
117
+ "@commitlint/config-conventional": "^21.2.4",
118
+ "@tsconfig/strictest": "^2.0.8",
119
+ "@types/node": "^26.6.5",
120
+ "@types/react": "^19.3.0",
121
+ "eslint": "^10.13.0",
122
+ "husky": "^9.1.7",
123
+ "jiti": "^2.7.0",
124
+ "prettier": "^3.9.10",
125
+ "prettier-plugin-organize-imports": "^4.3.0",
126
+ "publint": "^0.3.25",
127
+ "tsdown": "^0.23.1",
128
+ "typescript": "6.0.3"
129
+ },
130
+ "packageManager": "pnpm@12.14.0"
131
+ }
@@ -0,0 +1,16 @@
1
+ {
2
+ "$schema": "https://www.schemastore.org/tsconfig",
3
+ "//": "Used by typescript-eslint for files that no tsconfig/jsconfig includes.",
4
+ "compilerOptions": {
5
+ "target": "ESNext",
6
+ "lib": ["ESNext", "DOM", "DOM.Iterable"],
7
+ "module": "Preserve",
8
+ "moduleResolution": "Bundler",
9
+ "jsx": "react-jsx",
10
+ "allowJs": true,
11
+ "checkJs": false,
12
+ "strict": true,
13
+ "skipLibCheck": true,
14
+ "noEmit": true
15
+ }
16
+ }