exadev-eslint-config 2.12.3 → 2.14.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 +9 -0
- package/dist/index.cjs +91 -1
- package/dist/index.js +90 -1
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -160,6 +160,14 @@ export default tseslint.config(
|
|
|
160
160
|
|
|
161
161
|
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.
|
|
162
162
|
|
|
163
|
+
## RFC 8785 canonical JSON formatting
|
|
164
|
+
|
|
165
|
+
Every JSON file (`**/*.json`, excluding `**/tsconfig*.json`, `**/turbo.json`, and `**/package.json`) is linted against [`eslint-plugin-json-canonical`](https://github.com/ExaDev/eslint-plugin-json-canonical)'s `configs.recommended` -- plain UTF-16 code-unit key ordering, canonical number formatting, and canonical string escaping, per [RFC 8785](https://www.rfc-editor.org/rfc/rfc8785). This is bundled unconditionally, the same way jsdoc/tsdoc support is: `eslint-plugin-json-canonical` is a plain dependency of this package, so every consumer already has it. There is no option to turn it off.
|
|
166
|
+
|
|
167
|
+
The plugin's own whitespace-collapsing rule (`no-insignificant-whitespace`, which reformats a document to a single compacted line) is deliberately not part of `configs.recommended` and is not enabled here either -- it stays available for a consumer to opt into directly for their own genuine canonicalization pass.
|
|
168
|
+
|
|
169
|
+
`**/tsconfig*.json` and `**/turbo.json` are excluded because they genuinely carry comments (TypeScript and turbo both accept them), which `@eslint/json`'s `json/json` language -- what this plugin's recommended config hard-codes -- has no concept of and fails to parse. `**/package.json` is excluded because its key order is a distinct, separately-optional concern (syncpack-style field-priority pinning, not RFC 8785's plain alphabetical order); a project wanting that instead reaches for `eslint-plugin-json-canonical` directly on its own terms, not through this package.
|
|
170
|
+
|
|
163
171
|
## Rules
|
|
164
172
|
|
|
165
173
|
| Rule | Fixable | Description |
|
|
@@ -170,6 +178,7 @@ Trailing arguments are arbitrary flat-config objects, appended in order after ev
|
|
|
170
178
|
| `no-non-barrel-reexport` | ✓ | Re-exports belong only in a barrel. Catches the split form across two statements (`import { x } from './y'; export { x };` or `export default x;`) which no AST selector alone can match. Autofix deletes the export and the now-pointless import when it was the import's only use. Self-scopes away from any index file. |
|
|
171
179
|
| `no-side-effects-in-index` | | A barrel file may contain only re-export statements -- nothing that could execute at import time. Self-scopes to any index file. |
|
|
172
180
|
| `barrel-direct-siblings-only` | | A barrel may re-export only from a direct sibling (`./module`), never a nested path, parent, or bare package specifier (mode 3). |
|
|
181
|
+
| `no-control-flow` | | Bans `if`/`switch`/`for`/`for-in`/`for-of`/`while`/`do-while`/the ternary operator outright. Not part of `recommended` or `barrel` -- ordinary code legitimately needs control flow, so this is opt-in, wired via a consumer's own `files` glob for the specific packages that want it (a composition-root package selecting an adapter/strategy by a validated key, say): a lookup table replaces a branch, a declarative array method (`map`/`filter`/`some`/`every`/...) replaces a loop. Requires no type information. |
|
|
173
182
|
| `no-pointless-reassignment` | ✓ | `const foo = bar` where both sides are plain identifiers and the alias adds no transformation. Autofix rewrites every read to the original name and deletes the declaration (including its `export` keyword, when exported). Still reported but deliberately not auto-fixable where collapsing the alias would change meaning: an explicit type annotation (`const exhaustive: never = item` -- the annotation is the point), a read where the original name is shadowed, a read as a shorthand object property, more than one declarator in the statement, or a source that is written to anywhere. |
|
|
174
183
|
| `no-object-assign` | ✓/suggestion | `Object.assign` does not check a source object's properties against the target's declared types, unlike object spread. A fresh object-literal target autofixes to `{ ...target, ...source }`; mutating an existing reassignable binding offers a suggestion only (changes the object's identity); a `const` binding or a non-statement call site gets a plain report with no fix. |
|
|
175
184
|
| `no-mutable-union-array-param` | ✓ | A function parameter typed as an array of a union (`(string \| number)[]`) accepts a narrower caller array (`number[]`) by covariance; calling `push`/`unshift`/`splice`/`fill`/`copyWithin` on it can then insert a value the caller's own array was never declared to hold. Autofix marks the parameter `readonly`, turning the mutating call into a real compile error to resolve deliberately. Requires no type information. |
|
package/dist/index.cjs
CHANGED
|
@@ -28,6 +28,8 @@ let eslint_plugin_jsdoc = require("eslint-plugin-jsdoc");
|
|
|
28
28
|
eslint_plugin_jsdoc = __toESM(eslint_plugin_jsdoc, 1);
|
|
29
29
|
let eslint_plugin_tsdoc = require("eslint-plugin-tsdoc");
|
|
30
30
|
eslint_plugin_tsdoc = __toESM(eslint_plugin_tsdoc, 1);
|
|
31
|
+
let eslint_plugin_json_canonical = require("eslint-plugin-json-canonical");
|
|
32
|
+
eslint_plugin_json_canonical = __toESM(eslint_plugin_json_canonical, 1);
|
|
31
33
|
let node_module = require("node:module");
|
|
32
34
|
let _eslint_js = require("@eslint/js");
|
|
33
35
|
_eslint_js = __toESM(_eslint_js, 1);
|
|
@@ -74,6 +76,22 @@ const jsdocAndTsdoc = [{
|
|
|
74
76
|
rules: { "tsdoc/syntax": "error" }
|
|
75
77
|
}];
|
|
76
78
|
//#endregion
|
|
79
|
+
//#region src/json-canonical.ts
|
|
80
|
+
function isSingleFlatConfig(value) {
|
|
81
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) && "language" in value;
|
|
82
|
+
}
|
|
83
|
+
const recommended = eslint_plugin_json_canonical.default.configs?.["recommended"];
|
|
84
|
+
if (!isSingleFlatConfig(recommended)) throw new Error("eslint-plugin-json-canonical: expected configs.recommended to be a single flat config object, got an array or undefined");
|
|
85
|
+
const jsonCanonicalConfig = [{
|
|
86
|
+
files: ["**/*.json"],
|
|
87
|
+
ignores: [
|
|
88
|
+
"**/tsconfig*.json",
|
|
89
|
+
"**/turbo.json",
|
|
90
|
+
"**/package.json"
|
|
91
|
+
],
|
|
92
|
+
...recommended
|
|
93
|
+
}];
|
|
94
|
+
//#endregion
|
|
77
95
|
//#region src/optional-plugin.ts
|
|
78
96
|
const nodeRequire = (0, node_module.createRequire)(require("url").pathToFileURL(__filename).href);
|
|
79
97
|
function tryRequire(specifier, requireFn = nodeRequire) {
|
|
@@ -159,7 +177,7 @@ function buildReactConfig(options = {}) {
|
|
|
159
177
|
}
|
|
160
178
|
//#endregion
|
|
161
179
|
//#region package.json
|
|
162
|
-
var version = "2.
|
|
180
|
+
var version = "2.14.0";
|
|
163
181
|
//#endregion
|
|
164
182
|
//#region src/rules/barrel-helpers.ts
|
|
165
183
|
const INDEX_BASENAME$1 = /^index\.[cm]?[tj]sx?$/;
|
|
@@ -516,6 +534,76 @@ const noArrayIsArrayMutation = createRule$7({
|
|
|
516
534
|
}
|
|
517
535
|
});
|
|
518
536
|
//#endregion
|
|
537
|
+
//#region src/rules/no-control-flow.ts
|
|
538
|
+
const noControlFlow = {
|
|
539
|
+
meta: {
|
|
540
|
+
type: "problem",
|
|
541
|
+
schema: [],
|
|
542
|
+
messages: {
|
|
543
|
+
ifStatement: "Use a lookup table, not an if/else branch.",
|
|
544
|
+
switchStatement: "Use a lookup table, not a switch branch.",
|
|
545
|
+
forStatement: "Use a lookup table or a declarative array method, not a for loop.",
|
|
546
|
+
forInStatement: "Use a lookup table or Object.entries, not a for-in loop.",
|
|
547
|
+
forOfStatement: "Use a declarative array method, not a for-of loop.",
|
|
548
|
+
whileStatement: "Use a lookup table or a declarative array method, not a while loop.",
|
|
549
|
+
doWhileStatement: "Use a lookup table or a declarative array method, not a do-while loop.",
|
|
550
|
+
conditionalExpression: "Use a lookup table, not a ternary."
|
|
551
|
+
}
|
|
552
|
+
},
|
|
553
|
+
create(context) {
|
|
554
|
+
return {
|
|
555
|
+
IfStatement(node) {
|
|
556
|
+
context.report({
|
|
557
|
+
node,
|
|
558
|
+
messageId: "ifStatement"
|
|
559
|
+
});
|
|
560
|
+
},
|
|
561
|
+
SwitchStatement(node) {
|
|
562
|
+
context.report({
|
|
563
|
+
node,
|
|
564
|
+
messageId: "switchStatement"
|
|
565
|
+
});
|
|
566
|
+
},
|
|
567
|
+
ForStatement(node) {
|
|
568
|
+
context.report({
|
|
569
|
+
node,
|
|
570
|
+
messageId: "forStatement"
|
|
571
|
+
});
|
|
572
|
+
},
|
|
573
|
+
ForInStatement(node) {
|
|
574
|
+
context.report({
|
|
575
|
+
node,
|
|
576
|
+
messageId: "forInStatement"
|
|
577
|
+
});
|
|
578
|
+
},
|
|
579
|
+
ForOfStatement(node) {
|
|
580
|
+
context.report({
|
|
581
|
+
node,
|
|
582
|
+
messageId: "forOfStatement"
|
|
583
|
+
});
|
|
584
|
+
},
|
|
585
|
+
WhileStatement(node) {
|
|
586
|
+
context.report({
|
|
587
|
+
node,
|
|
588
|
+
messageId: "whileStatement"
|
|
589
|
+
});
|
|
590
|
+
},
|
|
591
|
+
DoWhileStatement(node) {
|
|
592
|
+
context.report({
|
|
593
|
+
node,
|
|
594
|
+
messageId: "doWhileStatement"
|
|
595
|
+
});
|
|
596
|
+
},
|
|
597
|
+
ConditionalExpression(node) {
|
|
598
|
+
context.report({
|
|
599
|
+
node,
|
|
600
|
+
messageId: "conditionalExpression"
|
|
601
|
+
});
|
|
602
|
+
}
|
|
603
|
+
};
|
|
604
|
+
}
|
|
605
|
+
};
|
|
606
|
+
//#endregion
|
|
519
607
|
//#region src/rules/no-enum-number-widening.ts
|
|
520
608
|
const noEnumNumberWidening = _typescript_eslint_utils.ESLintUtils.RuleCreator((name) => `https://github.com/ExaDev/eslint-config/blob/main/src/rules/${name}.ts`)({
|
|
521
609
|
name: "no-enum-number-widening",
|
|
@@ -1356,6 +1444,7 @@ const plugin = {
|
|
|
1356
1444
|
"barrel-direct-siblings-only": barrelDirectSiblingsOnly,
|
|
1357
1445
|
"barrel-policy": barrelPolicy,
|
|
1358
1446
|
"no-array-isarray-mutation": noArrayIsArrayMutation,
|
|
1447
|
+
"no-control-flow": noControlFlow,
|
|
1359
1448
|
"no-enum-number-widening": noEnumNumberWidening,
|
|
1360
1449
|
"no-enum-reverse-lookup-widening": noEnumReverseLookupWidening,
|
|
1361
1450
|
"no-index-files": noIndexFiles,
|
|
@@ -1491,6 +1580,7 @@ function exadevConfig(options = {}, ...userConfigs) {
|
|
|
1491
1580
|
return toPublicConfigArray([
|
|
1492
1581
|
...recommendedTypeChecked,
|
|
1493
1582
|
...jsdocAndTsdoc,
|
|
1583
|
+
...jsonCanonicalConfig,
|
|
1494
1584
|
...buildReactConfig({ enabled: options.react }),
|
|
1495
1585
|
...buildNextjsConfig({ enabled: options.nextjs }),
|
|
1496
1586
|
...userConfigs
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import jsdoc from "eslint-plugin-jsdoc";
|
|
2
2
|
import tsdoc from "eslint-plugin-tsdoc";
|
|
3
|
+
import jsonCanonical from "eslint-plugin-json-canonical";
|
|
3
4
|
import { createRequire } from "node:module";
|
|
4
5
|
import js from "@eslint/js";
|
|
5
6
|
import tseslint from "typescript-eslint";
|
|
@@ -43,6 +44,22 @@ const jsdocAndTsdoc = [{
|
|
|
43
44
|
rules: { "tsdoc/syntax": "error" }
|
|
44
45
|
}];
|
|
45
46
|
//#endregion
|
|
47
|
+
//#region src/json-canonical.ts
|
|
48
|
+
function isSingleFlatConfig(value) {
|
|
49
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) && "language" in value;
|
|
50
|
+
}
|
|
51
|
+
const recommended = jsonCanonical.configs?.["recommended"];
|
|
52
|
+
if (!isSingleFlatConfig(recommended)) throw new Error("eslint-plugin-json-canonical: expected configs.recommended to be a single flat config object, got an array or undefined");
|
|
53
|
+
const jsonCanonicalConfig = [{
|
|
54
|
+
files: ["**/*.json"],
|
|
55
|
+
ignores: [
|
|
56
|
+
"**/tsconfig*.json",
|
|
57
|
+
"**/turbo.json",
|
|
58
|
+
"**/package.json"
|
|
59
|
+
],
|
|
60
|
+
...recommended
|
|
61
|
+
}];
|
|
62
|
+
//#endregion
|
|
46
63
|
//#region src/optional-plugin.ts
|
|
47
64
|
const nodeRequire = createRequire(import.meta.url);
|
|
48
65
|
function tryRequire(specifier, requireFn = nodeRequire) {
|
|
@@ -128,7 +145,7 @@ function buildReactConfig(options = {}) {
|
|
|
128
145
|
}
|
|
129
146
|
//#endregion
|
|
130
147
|
//#region package.json
|
|
131
|
-
var version = "2.
|
|
148
|
+
var version = "2.14.0";
|
|
132
149
|
//#endregion
|
|
133
150
|
//#region src/rules/barrel-helpers.ts
|
|
134
151
|
const INDEX_BASENAME$1 = /^index\.[cm]?[tj]sx?$/;
|
|
@@ -485,6 +502,76 @@ const noArrayIsArrayMutation = createRule$7({
|
|
|
485
502
|
}
|
|
486
503
|
});
|
|
487
504
|
//#endregion
|
|
505
|
+
//#region src/rules/no-control-flow.ts
|
|
506
|
+
const noControlFlow = {
|
|
507
|
+
meta: {
|
|
508
|
+
type: "problem",
|
|
509
|
+
schema: [],
|
|
510
|
+
messages: {
|
|
511
|
+
ifStatement: "Use a lookup table, not an if/else branch.",
|
|
512
|
+
switchStatement: "Use a lookup table, not a switch branch.",
|
|
513
|
+
forStatement: "Use a lookup table or a declarative array method, not a for loop.",
|
|
514
|
+
forInStatement: "Use a lookup table or Object.entries, not a for-in loop.",
|
|
515
|
+
forOfStatement: "Use a declarative array method, not a for-of loop.",
|
|
516
|
+
whileStatement: "Use a lookup table or a declarative array method, not a while loop.",
|
|
517
|
+
doWhileStatement: "Use a lookup table or a declarative array method, not a do-while loop.",
|
|
518
|
+
conditionalExpression: "Use a lookup table, not a ternary."
|
|
519
|
+
}
|
|
520
|
+
},
|
|
521
|
+
create(context) {
|
|
522
|
+
return {
|
|
523
|
+
IfStatement(node) {
|
|
524
|
+
context.report({
|
|
525
|
+
node,
|
|
526
|
+
messageId: "ifStatement"
|
|
527
|
+
});
|
|
528
|
+
},
|
|
529
|
+
SwitchStatement(node) {
|
|
530
|
+
context.report({
|
|
531
|
+
node,
|
|
532
|
+
messageId: "switchStatement"
|
|
533
|
+
});
|
|
534
|
+
},
|
|
535
|
+
ForStatement(node) {
|
|
536
|
+
context.report({
|
|
537
|
+
node,
|
|
538
|
+
messageId: "forStatement"
|
|
539
|
+
});
|
|
540
|
+
},
|
|
541
|
+
ForInStatement(node) {
|
|
542
|
+
context.report({
|
|
543
|
+
node,
|
|
544
|
+
messageId: "forInStatement"
|
|
545
|
+
});
|
|
546
|
+
},
|
|
547
|
+
ForOfStatement(node) {
|
|
548
|
+
context.report({
|
|
549
|
+
node,
|
|
550
|
+
messageId: "forOfStatement"
|
|
551
|
+
});
|
|
552
|
+
},
|
|
553
|
+
WhileStatement(node) {
|
|
554
|
+
context.report({
|
|
555
|
+
node,
|
|
556
|
+
messageId: "whileStatement"
|
|
557
|
+
});
|
|
558
|
+
},
|
|
559
|
+
DoWhileStatement(node) {
|
|
560
|
+
context.report({
|
|
561
|
+
node,
|
|
562
|
+
messageId: "doWhileStatement"
|
|
563
|
+
});
|
|
564
|
+
},
|
|
565
|
+
ConditionalExpression(node) {
|
|
566
|
+
context.report({
|
|
567
|
+
node,
|
|
568
|
+
messageId: "conditionalExpression"
|
|
569
|
+
});
|
|
570
|
+
}
|
|
571
|
+
};
|
|
572
|
+
}
|
|
573
|
+
};
|
|
574
|
+
//#endregion
|
|
488
575
|
//#region src/rules/no-enum-number-widening.ts
|
|
489
576
|
const noEnumNumberWidening = ESLintUtils.RuleCreator((name) => `https://github.com/ExaDev/eslint-config/blob/main/src/rules/${name}.ts`)({
|
|
490
577
|
name: "no-enum-number-widening",
|
|
@@ -1325,6 +1412,7 @@ const plugin = {
|
|
|
1325
1412
|
"barrel-direct-siblings-only": barrelDirectSiblingsOnly,
|
|
1326
1413
|
"barrel-policy": barrelPolicy,
|
|
1327
1414
|
"no-array-isarray-mutation": noArrayIsArrayMutation,
|
|
1415
|
+
"no-control-flow": noControlFlow,
|
|
1328
1416
|
"no-enum-number-widening": noEnumNumberWidening,
|
|
1329
1417
|
"no-enum-reverse-lookup-widening": noEnumReverseLookupWidening,
|
|
1330
1418
|
"no-index-files": noIndexFiles,
|
|
@@ -1460,6 +1548,7 @@ function exadevConfig(options = {}, ...userConfigs) {
|
|
|
1460
1548
|
return toPublicConfigArray([
|
|
1461
1549
|
...recommendedTypeChecked,
|
|
1462
1550
|
...jsdocAndTsdoc,
|
|
1551
|
+
...jsonCanonicalConfig,
|
|
1463
1552
|
...buildReactConfig({ enabled: options.react }),
|
|
1464
1553
|
...buildNextjsConfig({ enabled: options.nextjs }),
|
|
1465
1554
|
...userConfigs
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "exadev-eslint-config",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.14.0",
|
|
4
4
|
"description": "Shared custom ESLint rules and plugin for ExaDev projects",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -88,6 +88,7 @@
|
|
|
88
88
|
"@eslint/js": "^10.0.1",
|
|
89
89
|
"@typescript-eslint/utils": "8.67.0",
|
|
90
90
|
"eslint-plugin-jsdoc": "64.3.6",
|
|
91
|
+
"eslint-plugin-json-canonical": "1.0.0",
|
|
91
92
|
"eslint-plugin-tsdoc": "^0.5.2",
|
|
92
93
|
"ts-api-utils": "^2.5.0"
|
|
93
94
|
},
|