@exadev/eslint-config 2.9.0 → 2.10.0
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/README.md +68 -4
- package/dist/index.cjs +98 -2
- package/dist/index.d.cts +10 -3
- package/dist/index.d.ts +10 -3
- package/dist/index.js +97 -2
- package/package.json +23 -1
package/README.md
CHANGED
|
@@ -101,6 +101,63 @@ export default tseslint.config(
|
|
|
101
101
|
|
|
102
102
|
**`plugin.configs.recommended`/`plugin.configs.barrel` carry no `files`/`ignores` and are safe unscoped** -- `no-side-effects-in-index` and `no-non-barrel-reexport` each check `context.filename` themselves (self-scoping). For a barrel not at `src/index.ts`, or a project-specific exception, layer an override on top (e.g. `{ files: ['lib/other.ts'], rules: { 'exadev/no-non-barrel-reexport': 'off' } }`) rather than wiring all four rules individually.
|
|
103
103
|
|
|
104
|
+
## Optional React and Next.js support
|
|
105
|
+
|
|
106
|
+
`import exadev from '@exadev/eslint-config'` keeps working unchanged -- it's now literally `exadevConfig()` called with no arguments, no migration required. React/hooks/a11y and Next.js rule blocks are folded in automatically, with no separate import or config needed, gated on two independent, always-both-required conditions:
|
|
107
|
+
|
|
108
|
+
1. **The corresponding package must actually be resolvable.** `eslint-plugin-react`, `eslint-plugin-react-hooks`, `eslint-plugin-jsx-a11y`, and `@next/eslint-plugin-next` are all *optional* peer dependencies (`peerDependenciesMeta.<pkg>.optional: true`) -- install only whichever your project actually needs:
|
|
109
|
+
```sh
|
|
110
|
+
pnpm add -D eslint-plugin-react eslint-plugin-react-hooks eslint-plugin-jsx-a11y # React support
|
|
111
|
+
pnpm add -D @next/eslint-plugin-next # Next.js support
|
|
112
|
+
```
|
|
113
|
+
If none of these resolve, `@exadev/eslint-config`'s default export is byte-for-byte identical to the plain TypeScript ruleset -- nothing about the base package changes.
|
|
114
|
+
2. **For React specifically, the file must actually be `.jsx`/`.tsx`.** The React/hooks/a11y rule block is scoped to `files: ['**/*.jsx', '**/*.tsx']`, so even if `eslint-plugin-react` is resolvable only incidentally (e.g. hoisted as a transitive dependency of something unrelated in a monorepo, with zero real JSX anywhere in the linted project), its rules are never matched against a file that isn't JSX -- ESLint's flat-config `files` matching happens per linted file, at lint time, not at config-build time. `@next/eslint-plugin-next`'s block carries no such glob: its own presence is already an unambiguous signal on its own (nothing installs it except a real Next.js project).
|
|
115
|
+
|
|
116
|
+
### Explicit control
|
|
117
|
+
|
|
118
|
+
Two ways to override the automatic behaviour, for anyone who doesn't want to rely on it:
|
|
119
|
+
|
|
120
|
+
**`plugin.configs.react`/`plugin.configs.nextjs`** -- explicit tier selection, mirroring `plugin.configs.recommended`/`.barrel`. Unlike those two, which only ever reference this package's own always-present rules, selecting `configs.react`/`.nextjs` is itself an explicit request: it **throws** a clear, actionable error if the underlying peer isn't installed, rather than silently returning nothing.
|
|
121
|
+
```ts
|
|
122
|
+
import { plugin } from '@exadev/eslint-config';
|
|
123
|
+
import tseslint from 'typescript-eslint';
|
|
124
|
+
|
|
125
|
+
export default tseslint.config(
|
|
126
|
+
// ...your own config...
|
|
127
|
+
{
|
|
128
|
+
files: ['**/*.tsx'],
|
|
129
|
+
plugins: { exadev: plugin },
|
|
130
|
+
extends: [plugin.configs.react], // throws if eslint-plugin-react isn't installed
|
|
131
|
+
},
|
|
132
|
+
);
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
**`exadevConfig(options, ...userConfigs)`** -- the named factory export, for fine-grained tri-state control per feature:
|
|
136
|
+
|
|
137
|
+
```ts
|
|
138
|
+
// eslint.config.ts
|
|
139
|
+
import { exadevConfig } from '@exadev/eslint-config';
|
|
140
|
+
import tseslint from 'typescript-eslint';
|
|
141
|
+
|
|
142
|
+
export default tseslint.config(
|
|
143
|
+
{
|
|
144
|
+
languageOptions: {
|
|
145
|
+
parserOptions: { project: './tsconfig.json', tsconfigRootDir: import.meta.dirname },
|
|
146
|
+
},
|
|
147
|
+
},
|
|
148
|
+
...exadevConfig({ react: true, nextjs: false }),
|
|
149
|
+
// ...your own config...
|
|
150
|
+
);
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
| Value | React (`options.react`) | Next.js (`options.nextjs`) |
|
|
154
|
+
| --- | --- | --- |
|
|
155
|
+
| `true` | Force on -- throws if `eslint-plugin-react` isn't resolvable | Force on -- throws if `@next/eslint-plugin-next` isn't resolvable |
|
|
156
|
+
| `false` | Force off -- always `[]`, no resolution attempted | Force off -- always `[]`, no resolution attempted |
|
|
157
|
+
| `undefined` / omitted | Auto-detect (the default) | Auto-detect (the default) |
|
|
158
|
+
|
|
159
|
+
Trailing arguments are arbitrary flat-config objects, appended in order after everything else -- `exadevConfig({}, { rules: { 'no-console': 'warn' } })` is equivalent to spreading the default export plus one more config object.
|
|
160
|
+
|
|
104
161
|
## Rules
|
|
105
162
|
|
|
106
163
|
| Rule | Fixable | Description |
|
|
@@ -155,17 +212,23 @@ The `lint`/`typecheck`/`test`/`build` npm scripts wrap turbo tasks named `_lint`
|
|
|
155
212
|
|
|
156
213
|
## Architecture
|
|
157
214
|
|
|
158
|
-
`src/plugin.ts` builds
|
|
215
|
+
`src/plugin.ts` builds a `TSESLint.FlatConfig.Plugin` (`@typescript-eslint/utils`'s own type -- not ESLint's own `ESLint.Plugin`, which can't hold a rule built with `ESLintUtils.RuleCreator`) combining `src/rules/` into a flat `rules` map. `configs.recommended`, `.barrel`, `.react`, and `.nextjs` are getters in the object literal -- each references the fully-built `plugin` (`plugins: { exadev: plugin }`), which a plain property initializer can't do mid-construction. `recommended` ships `barrel-policy` at `mode: 'banned'`; `barrel` at `mode: 'single'`; `.react`/`.nextjs` call `buildReactConfig`/`buildNextjsConfig` with `enabled: true` (see below).
|
|
216
|
+
|
|
217
|
+
`src/config-types.ts` holds `ConfigValue`/`ConfigArrayValue` (`ConfigArrayValue = Extract<ConfigValue, unknown[]>`, the array-only member of ESLint's own config-value union), shared by every file below rather than redefined per file -- annotating a config array with the wider `ConfigValue` union directly broke `...exadev` with `TS2488` ("must have a Symbol.iterator method").
|
|
218
|
+
|
|
219
|
+
`src/optional-plugin.ts` is the lazy-resolution helper behind React/Next.js support: `tryRequire` wraps `createRequire(import.meta.url)` in try/catch, returning `unknown` (never a cast) so every call site narrows explicitly before use; `readFlatConfig` walks a property path through that `unknown` value via a real type guard, normalizing a stray legacy top-level `parserOptions` key into `languageOptions.parserOptions` along the way (confirmed necessary: `eslint-plugin-jsx-a11y`'s own `configs.recommended` export carries exactly this legacy shape, which flat config's schema rejects outright rather than ignores).
|
|
220
|
+
|
|
221
|
+
`src/react.ts`/`src/nextjs.ts` each export a `build*Config(options)` function: resolve the relevant optional peer(s) via `tryRequire`, extract their real flat config via `readFlatConfig`, and return an array of 0-or-more config blocks -- `[]` if unresolvable and not explicitly forced on, a thrown `Error` if explicitly forced on (`enabled: true`) and still unresolvable. `react.ts`'s blocks are scoped to `files: ['**/*.jsx', '**/*.tsx']`; `nextjs.ts`'s is not (see [Optional React and Next.js support](#optional-react-and-nextjs-support) for why).
|
|
159
222
|
|
|
160
|
-
`src/
|
|
223
|
+
`src/create-config.ts` is config assembly's single source of truth: `exadevConfig(options, ...userConfigs)` concatenates `recommendedTypeChecked` with both builders' output (each fed the matching tri-state option) and any trailing user configs; `defaultConfig` is `exadevConfig()` evaluated once, eagerly, at module load.
|
|
161
224
|
|
|
162
|
-
`src/index.ts` is the entry point: `export { default } from './
|
|
225
|
+
`src/index.ts` is the entry point, still a pure re-export barrel (required by `no-side-effects-in-index`/`no-non-barrel-reexport`, both of which assume this file contains nothing but `export ... from ...`): `export { defaultConfig as default, exadevConfig } from './create-config'; export { default as plugin } from './plugin';`. All exports share one root module, so importing `{ plugin }` alone still resolves `typescript-eslint` via the sibling re-export -- an accepted trade-off (an earlier separate-subpath split proved more awkward in practice). React/Next.js support never adds to this cost: none of the four optional packages are ever statically imported, only passed as a runtime string to `createRequire`'s resolver, so their absence never affects module evaluation for a consumer who doesn't use them.
|
|
163
226
|
|
|
164
227
|
`pnpm-workspace.yaml` declares an empty `packages: []` -- not a real workspace, just giving turbo a root for local task caching.
|
|
165
228
|
|
|
166
229
|
## Conventions
|
|
167
230
|
|
|
168
|
-
`eslint.config.ts` dogfoods
|
|
231
|
+
`eslint.config.ts` dogfoods this package's own factory export on itself (`import { exadevConfig } from './src/index'`), spreading `exadevConfig({ react: false, nextjs: false })` -- forced off explicitly, not the plain auto-detecting default, since `eslint-plugin-react`/`@next/eslint-plugin-next` are real devDependencies of *this* repo (needed to test `src/react.ts`/`src/nextjs.ts`'s own "package is resolvable" branch) even though this repo is neither a React nor a Next.js project. `no-side-effects-in-index` and `no-non-barrel-reexport` self-scope to `src/index.ts` internally, so no `files`/`ignores` wiring is needed here. Plugin construction lives in `src/plugin.ts` specifically so `src/index.ts` stays a pure re-export point.
|
|
169
232
|
|
|
170
233
|
`tsconfig.json` enables `verbatimModuleSyntax` (`import type`/`export type` required for type-only imports -- also enforced by `consistent-type-imports`) and `noUncheckedIndexedAccess` (narrow indexed access before use rather than asserting).
|
|
171
234
|
|
|
@@ -177,6 +240,7 @@ Conventional commits are enforced by commitlint, restricted to the type-enum def
|
|
|
177
240
|
- `src/index.ts` mixing a default export with a named one triggers rolldown's `MIXED_EXPORTS` warning: a raw CommonJS `require()` would see the raw exports object instead of the default. ESM `import` (the actual consumer path) resolves both correctly; `attw --pack` and `publint` report no problems, so the warning is accepted (see `tsdown.config.ts`).
|
|
178
241
|
- Husky hooks: `pre-commit` runs lint-staged (`eslint --fix` on staged `*.ts`), `commit-msg` runs commitlint, `pre-push` runs typecheck + test + build.
|
|
179
242
|
- The CI release job sets `HUSKY=0` (commit-msg hook skips the automated release commit) and blanks `NPM_TOKEN`/`NODE_AUTH_TOKEN` explicitly so an inherited token can't win over OIDC trusted publishing.
|
|
243
|
+
- A consumer who already has `eslint-plugin-react`/`@next/eslint-plugin-next` resolvable for unrelated reasons (e.g. hoisted in a monorepo) and writes `.jsx`/`.tsx` files may see new rule activity the moment they upgrade to a version of this package that ships React/Next.js support -- with zero action on their part. This is the normal, widely-accepted ESLint-ecosystem convention that adding rules to a shared/recommended config is a minor bump even though it can newly trip an existing `--max-warnings 0` gate, not a breaking change; see [Optional React and Next.js support](#optional-react-and-nextjs-support) for the `react`/`nextjs` options to force it off explicitly if needed.
|
|
180
244
|
|
|
181
245
|
## Contributing
|
|
182
246
|
|
package/dist/index.cjs
CHANGED
|
@@ -24,6 +24,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
24
24
|
enumerable: true
|
|
25
25
|
}) : target, mod));
|
|
26
26
|
//#endregion
|
|
27
|
+
let node_module = require("node:module");
|
|
27
28
|
let typescript_eslint = require("typescript-eslint");
|
|
28
29
|
typescript_eslint = __toESM(typescript_eslint, 1);
|
|
29
30
|
let node_path = require("node:path");
|
|
@@ -31,8 +32,85 @@ let _typescript_eslint_utils = require("@typescript-eslint/utils");
|
|
|
31
32
|
let typescript = require("typescript");
|
|
32
33
|
typescript = __toESM(typescript, 1);
|
|
33
34
|
let ts_api_utils = require("ts-api-utils");
|
|
35
|
+
//#region src/optional-plugin.ts
|
|
36
|
+
const nodeRequire = (0, node_module.createRequire)(require("url").pathToFileURL(__filename).href);
|
|
37
|
+
function tryRequire(specifier, requireFn = nodeRequire) {
|
|
38
|
+
try {
|
|
39
|
+
return requireFn(specifier);
|
|
40
|
+
} catch {
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
function isRecord(value) {
|
|
45
|
+
return typeof value === "object" && value !== null;
|
|
46
|
+
}
|
|
47
|
+
function isFlatConfig$1(value) {
|
|
48
|
+
return isRecord(value);
|
|
49
|
+
}
|
|
50
|
+
function normalizeLegacyParserOptions(record) {
|
|
51
|
+
if (!("parserOptions" in record)) return record;
|
|
52
|
+
const { parserOptions, languageOptions, ...rest } = record;
|
|
53
|
+
const existingLanguageOptions = isRecord(languageOptions) ? languageOptions : {};
|
|
54
|
+
return {
|
|
55
|
+
...rest,
|
|
56
|
+
languageOptions: {
|
|
57
|
+
...existingLanguageOptions,
|
|
58
|
+
parserOptions
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
function readFlatConfig(module, path) {
|
|
63
|
+
let current = module;
|
|
64
|
+
for (const key of path) {
|
|
65
|
+
if (!isRecord(current)) return void 0;
|
|
66
|
+
current = current[key];
|
|
67
|
+
}
|
|
68
|
+
if (!isRecord(current)) return void 0;
|
|
69
|
+
const normalized = normalizeLegacyParserOptions(current);
|
|
70
|
+
return isFlatConfig$1(normalized) ? normalized : void 0;
|
|
71
|
+
}
|
|
72
|
+
//#endregion
|
|
73
|
+
//#region src/nextjs.ts
|
|
74
|
+
const INSTALL_COMMAND$1 = "pnpm add -D @next/eslint-plugin-next";
|
|
75
|
+
function buildNextjsConfig(options = {}) {
|
|
76
|
+
if (options.enabled === false) return [];
|
|
77
|
+
const nextConfig = readFlatConfig(tryRequire("@next/eslint-plugin-next", options.requireFn), ["configs", "core-web-vitals"]);
|
|
78
|
+
if (options.enabled === true && nextConfig === void 0) throw new Error(`@exadev/eslint-config: Next.js support was explicitly requested but '@next/eslint-plugin-next' could not be resolved. Install it with: ${INSTALL_COMMAND$1}`);
|
|
79
|
+
return nextConfig === void 0 ? [] : [nextConfig];
|
|
80
|
+
}
|
|
81
|
+
//#endregion
|
|
82
|
+
//#region src/react.ts
|
|
83
|
+
const JSX_FILE_PATTERNS = ["**/*.jsx", "**/*.tsx"];
|
|
84
|
+
const INSTALL_COMMAND = "pnpm add -D eslint-plugin-react eslint-plugin-react-hooks eslint-plugin-jsx-a11y";
|
|
85
|
+
function isFlatConfig(value) {
|
|
86
|
+
return value !== void 0;
|
|
87
|
+
}
|
|
88
|
+
function buildReactConfig(options = {}) {
|
|
89
|
+
if (options.enabled === false) return [];
|
|
90
|
+
const reactConfig = readFlatConfig(tryRequire("eslint-plugin-react", options.requireFn), [
|
|
91
|
+
"configs",
|
|
92
|
+
"flat",
|
|
93
|
+
"recommended"
|
|
94
|
+
]);
|
|
95
|
+
if (options.enabled === true && reactConfig === void 0) throw new Error(`@exadev/eslint-config: React support was explicitly requested but 'eslint-plugin-react' could not be resolved. Install it with: ${INSTALL_COMMAND}`);
|
|
96
|
+
if (reactConfig === void 0) return [];
|
|
97
|
+
const hooksModule = tryRequire("eslint-plugin-react-hooks", options.requireFn);
|
|
98
|
+
return [
|
|
99
|
+
reactConfig,
|
|
100
|
+
readFlatConfig(hooksModule, [
|
|
101
|
+
"configs",
|
|
102
|
+
"flat",
|
|
103
|
+
"recommended-latest"
|
|
104
|
+
]) ?? readFlatConfig(hooksModule, ["configs", "recommended-latest"]),
|
|
105
|
+
readFlatConfig(tryRequire("eslint-plugin-jsx-a11y", options.requireFn), ["flatConfigs", "recommended"])
|
|
106
|
+
].filter(isFlatConfig).map((config) => ({
|
|
107
|
+
...config,
|
|
108
|
+
files: [...JSX_FILE_PATTERNS]
|
|
109
|
+
}));
|
|
110
|
+
}
|
|
111
|
+
//#endregion
|
|
34
112
|
//#region package.json
|
|
35
|
-
var version = "2.
|
|
113
|
+
var version = "2.10.0";
|
|
36
114
|
//#endregion
|
|
37
115
|
//#region src/rules/barrel-helpers.ts
|
|
38
116
|
const INDEX_BASENAME$1 = /^index\.[cm]?[tj]sx?$/;
|
|
@@ -1248,6 +1326,12 @@ const plugin = {
|
|
|
1248
1326
|
plugins: { exadev: plugin },
|
|
1249
1327
|
rules: { "exadev/barrel-policy": ["error", { mode: "single" }] }
|
|
1250
1328
|
};
|
|
1329
|
+
},
|
|
1330
|
+
get react() {
|
|
1331
|
+
return buildReactConfig({ enabled: true });
|
|
1332
|
+
},
|
|
1333
|
+
get nextjs() {
|
|
1334
|
+
return buildNextjsConfig({ enabled: true });
|
|
1251
1335
|
}
|
|
1252
1336
|
}
|
|
1253
1337
|
};
|
|
@@ -1312,5 +1396,17 @@ const recommendedTypeChecked = [
|
|
|
1312
1396
|
}
|
|
1313
1397
|
];
|
|
1314
1398
|
//#endregion
|
|
1315
|
-
|
|
1399
|
+
//#region src/create-config.ts
|
|
1400
|
+
function exadevConfig(options = {}, ...userConfigs) {
|
|
1401
|
+
return [
|
|
1402
|
+
...recommendedTypeChecked,
|
|
1403
|
+
...buildReactConfig({ enabled: options.react }),
|
|
1404
|
+
...buildNextjsConfig({ enabled: options.nextjs }),
|
|
1405
|
+
...userConfigs
|
|
1406
|
+
];
|
|
1407
|
+
}
|
|
1408
|
+
const defaultConfig = exadevConfig();
|
|
1409
|
+
//#endregion
|
|
1410
|
+
exports.default = defaultConfig;
|
|
1411
|
+
exports.exadevConfig = exadevConfig;
|
|
1316
1412
|
exports.plugin = plugin;
|
package/dist/index.d.cts
CHANGED
|
@@ -1,10 +1,17 @@
|
|
|
1
1
|
import { TSESLint } from "@typescript-eslint/utils";
|
|
2
|
-
//#region src/
|
|
2
|
+
//#region src/config-types.d.ts
|
|
3
3
|
type ConfigValue = NonNullable<TSESLint.FlatConfig.Plugin['configs']>[string];
|
|
4
4
|
type ConfigArrayValue = Extract<ConfigValue, unknown[]>;
|
|
5
|
-
|
|
5
|
+
//#endregion
|
|
6
|
+
//#region src/create-config.d.ts
|
|
7
|
+
interface ExadevConfigOptions {
|
|
8
|
+
readonly react?: boolean;
|
|
9
|
+
readonly nextjs?: boolean;
|
|
10
|
+
}
|
|
11
|
+
declare function exadevConfig(options?: ExadevConfigOptions, ...userConfigs: readonly TSESLint.FlatConfig.Config[]): ConfigArrayValue;
|
|
12
|
+
declare const defaultConfig: ConfigArrayValue;
|
|
6
13
|
//#endregion
|
|
7
14
|
//#region src/plugin.d.ts
|
|
8
15
|
declare const plugin: TSESLint.FlatConfig.Plugin;
|
|
9
16
|
//#endregion
|
|
10
|
-
export {
|
|
17
|
+
export { defaultConfig as default, exadevConfig, plugin };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,10 +1,17 @@
|
|
|
1
1
|
import { TSESLint } from "@typescript-eslint/utils";
|
|
2
|
-
//#region src/
|
|
2
|
+
//#region src/config-types.d.ts
|
|
3
3
|
type ConfigValue = NonNullable<TSESLint.FlatConfig.Plugin['configs']>[string];
|
|
4
4
|
type ConfigArrayValue = Extract<ConfigValue, unknown[]>;
|
|
5
|
-
|
|
5
|
+
//#endregion
|
|
6
|
+
//#region src/create-config.d.ts
|
|
7
|
+
interface ExadevConfigOptions {
|
|
8
|
+
readonly react?: boolean;
|
|
9
|
+
readonly nextjs?: boolean;
|
|
10
|
+
}
|
|
11
|
+
declare function exadevConfig(options?: ExadevConfigOptions, ...userConfigs: readonly TSESLint.FlatConfig.Config[]): ConfigArrayValue;
|
|
12
|
+
declare const defaultConfig: ConfigArrayValue;
|
|
6
13
|
//#endregion
|
|
7
14
|
//#region src/plugin.d.ts
|
|
8
15
|
declare const plugin: TSESLint.FlatConfig.Plugin;
|
|
9
16
|
//#endregion
|
|
10
|
-
export {
|
|
17
|
+
export { defaultConfig as default, exadevConfig, plugin };
|
package/dist/index.js
CHANGED
|
@@ -1,10 +1,88 @@
|
|
|
1
|
+
import { createRequire } from "node:module";
|
|
1
2
|
import tseslint from "typescript-eslint";
|
|
2
3
|
import { posix } from "node:path";
|
|
3
4
|
import { AST_NODE_TYPES, ESLintUtils, TSESLint } from "@typescript-eslint/utils";
|
|
4
5
|
import * as ts from "typescript";
|
|
5
6
|
import { isPropertyReadonlyInType, isTypeReference } from "ts-api-utils";
|
|
7
|
+
//#region src/optional-plugin.ts
|
|
8
|
+
const nodeRequire = createRequire(import.meta.url);
|
|
9
|
+
function tryRequire(specifier, requireFn = nodeRequire) {
|
|
10
|
+
try {
|
|
11
|
+
return requireFn(specifier);
|
|
12
|
+
} catch {
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
function isRecord(value) {
|
|
17
|
+
return typeof value === "object" && value !== null;
|
|
18
|
+
}
|
|
19
|
+
function isFlatConfig$1(value) {
|
|
20
|
+
return isRecord(value);
|
|
21
|
+
}
|
|
22
|
+
function normalizeLegacyParserOptions(record) {
|
|
23
|
+
if (!("parserOptions" in record)) return record;
|
|
24
|
+
const { parserOptions, languageOptions, ...rest } = record;
|
|
25
|
+
const existingLanguageOptions = isRecord(languageOptions) ? languageOptions : {};
|
|
26
|
+
return {
|
|
27
|
+
...rest,
|
|
28
|
+
languageOptions: {
|
|
29
|
+
...existingLanguageOptions,
|
|
30
|
+
parserOptions
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
function readFlatConfig(module, path) {
|
|
35
|
+
let current = module;
|
|
36
|
+
for (const key of path) {
|
|
37
|
+
if (!isRecord(current)) return void 0;
|
|
38
|
+
current = current[key];
|
|
39
|
+
}
|
|
40
|
+
if (!isRecord(current)) return void 0;
|
|
41
|
+
const normalized = normalizeLegacyParserOptions(current);
|
|
42
|
+
return isFlatConfig$1(normalized) ? normalized : void 0;
|
|
43
|
+
}
|
|
44
|
+
//#endregion
|
|
45
|
+
//#region src/nextjs.ts
|
|
46
|
+
const INSTALL_COMMAND$1 = "pnpm add -D @next/eslint-plugin-next";
|
|
47
|
+
function buildNextjsConfig(options = {}) {
|
|
48
|
+
if (options.enabled === false) return [];
|
|
49
|
+
const nextConfig = readFlatConfig(tryRequire("@next/eslint-plugin-next", options.requireFn), ["configs", "core-web-vitals"]);
|
|
50
|
+
if (options.enabled === true && nextConfig === void 0) throw new Error(`@exadev/eslint-config: Next.js support was explicitly requested but '@next/eslint-plugin-next' could not be resolved. Install it with: ${INSTALL_COMMAND$1}`);
|
|
51
|
+
return nextConfig === void 0 ? [] : [nextConfig];
|
|
52
|
+
}
|
|
53
|
+
//#endregion
|
|
54
|
+
//#region src/react.ts
|
|
55
|
+
const JSX_FILE_PATTERNS = ["**/*.jsx", "**/*.tsx"];
|
|
56
|
+
const INSTALL_COMMAND = "pnpm add -D eslint-plugin-react eslint-plugin-react-hooks eslint-plugin-jsx-a11y";
|
|
57
|
+
function isFlatConfig(value) {
|
|
58
|
+
return value !== void 0;
|
|
59
|
+
}
|
|
60
|
+
function buildReactConfig(options = {}) {
|
|
61
|
+
if (options.enabled === false) return [];
|
|
62
|
+
const reactConfig = readFlatConfig(tryRequire("eslint-plugin-react", options.requireFn), [
|
|
63
|
+
"configs",
|
|
64
|
+
"flat",
|
|
65
|
+
"recommended"
|
|
66
|
+
]);
|
|
67
|
+
if (options.enabled === true && reactConfig === void 0) throw new Error(`@exadev/eslint-config: React support was explicitly requested but 'eslint-plugin-react' could not be resolved. Install it with: ${INSTALL_COMMAND}`);
|
|
68
|
+
if (reactConfig === void 0) return [];
|
|
69
|
+
const hooksModule = tryRequire("eslint-plugin-react-hooks", options.requireFn);
|
|
70
|
+
return [
|
|
71
|
+
reactConfig,
|
|
72
|
+
readFlatConfig(hooksModule, [
|
|
73
|
+
"configs",
|
|
74
|
+
"flat",
|
|
75
|
+
"recommended-latest"
|
|
76
|
+
]) ?? readFlatConfig(hooksModule, ["configs", "recommended-latest"]),
|
|
77
|
+
readFlatConfig(tryRequire("eslint-plugin-jsx-a11y", options.requireFn), ["flatConfigs", "recommended"])
|
|
78
|
+
].filter(isFlatConfig).map((config) => ({
|
|
79
|
+
...config,
|
|
80
|
+
files: [...JSX_FILE_PATTERNS]
|
|
81
|
+
}));
|
|
82
|
+
}
|
|
83
|
+
//#endregion
|
|
6
84
|
//#region package.json
|
|
7
|
-
var version = "2.
|
|
85
|
+
var version = "2.10.0";
|
|
8
86
|
//#endregion
|
|
9
87
|
//#region src/rules/barrel-helpers.ts
|
|
10
88
|
const INDEX_BASENAME$1 = /^index\.[cm]?[tj]sx?$/;
|
|
@@ -1220,6 +1298,12 @@ const plugin = {
|
|
|
1220
1298
|
plugins: { exadev: plugin },
|
|
1221
1299
|
rules: { "exadev/barrel-policy": ["error", { mode: "single" }] }
|
|
1222
1300
|
};
|
|
1301
|
+
},
|
|
1302
|
+
get react() {
|
|
1303
|
+
return buildReactConfig({ enabled: true });
|
|
1304
|
+
},
|
|
1305
|
+
get nextjs() {
|
|
1306
|
+
return buildNextjsConfig({ enabled: true });
|
|
1223
1307
|
}
|
|
1224
1308
|
}
|
|
1225
1309
|
};
|
|
@@ -1284,4 +1368,15 @@ const recommendedTypeChecked = [
|
|
|
1284
1368
|
}
|
|
1285
1369
|
];
|
|
1286
1370
|
//#endregion
|
|
1287
|
-
|
|
1371
|
+
//#region src/create-config.ts
|
|
1372
|
+
function exadevConfig(options = {}, ...userConfigs) {
|
|
1373
|
+
return [
|
|
1374
|
+
...recommendedTypeChecked,
|
|
1375
|
+
...buildReactConfig({ enabled: options.react }),
|
|
1376
|
+
...buildNextjsConfig({ enabled: options.nextjs }),
|
|
1377
|
+
...userConfigs
|
|
1378
|
+
];
|
|
1379
|
+
}
|
|
1380
|
+
const defaultConfig = exadevConfig();
|
|
1381
|
+
//#endregion
|
|
1382
|
+
export { defaultConfig as default, exadevConfig, plugin };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@exadev/eslint-config",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.10.0",
|
|
4
4
|
"description": "Shared custom ESLint rules and plugin for ExaDev projects",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -38,21 +38,43 @@
|
|
|
38
38
|
"url": "git+https://github.com/ExaDev/eslint-config.git"
|
|
39
39
|
},
|
|
40
40
|
"peerDependencies": {
|
|
41
|
+
"@next/eslint-plugin-next": "^16.3.2",
|
|
41
42
|
"eslint": ">=10.0.0",
|
|
43
|
+
"eslint-plugin-jsx-a11y": "^6.10.2",
|
|
44
|
+
"eslint-plugin-react": "^7.37.5",
|
|
45
|
+
"eslint-plugin-react-hooks": "^7.1.1",
|
|
42
46
|
"typescript": ">=4.8.4",
|
|
43
47
|
"typescript-eslint": ">=8.0.0"
|
|
44
48
|
},
|
|
49
|
+
"peerDependenciesMeta": {
|
|
50
|
+
"@next/eslint-plugin-next": {
|
|
51
|
+
"optional": true
|
|
52
|
+
},
|
|
53
|
+
"eslint-plugin-jsx-a11y": {
|
|
54
|
+
"optional": true
|
|
55
|
+
},
|
|
56
|
+
"eslint-plugin-react": {
|
|
57
|
+
"optional": true
|
|
58
|
+
},
|
|
59
|
+
"eslint-plugin-react-hooks": {
|
|
60
|
+
"optional": true
|
|
61
|
+
}
|
|
62
|
+
},
|
|
45
63
|
"devDependencies": {
|
|
46
64
|
"@arethetypeswrong/cli": "^0.18.5",
|
|
47
65
|
"@commitlint/cli": "^21.2.1",
|
|
48
66
|
"@commitlint/config-conventional": "^21.2.0",
|
|
49
67
|
"@eslint/js": "^10.0.1",
|
|
68
|
+
"@next/eslint-plugin-next": "^16.3.2",
|
|
50
69
|
"@semantic-release/changelog": "^7.0.0",
|
|
51
70
|
"@semantic-release/git": "^11.0.1",
|
|
52
71
|
"@types/node": "^24.9.2",
|
|
53
72
|
"@typescript-eslint/rule-tester": "^8.67.0",
|
|
54
73
|
"@vitest/coverage-v8": "^4.1.10",
|
|
55
74
|
"eslint": "^10.8.0",
|
|
75
|
+
"eslint-plugin-jsx-a11y": "^6.10.2",
|
|
76
|
+
"eslint-plugin-react": "^7.37.5",
|
|
77
|
+
"eslint-plugin-react-hooks": "^7.1.1",
|
|
56
78
|
"husky": "^9.1.7",
|
|
57
79
|
"lint-staged": "^17.2.0",
|
|
58
80
|
"publint": "^0.3.21",
|