exadev-eslint-config 2.12.2 → 2.13.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 CHANGED
@@ -170,6 +170,7 @@ Trailing arguments are arbitrary flat-config objects, appended in order after ev
170
170
  | `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
171
  | `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
172
  | `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). |
173
+ | `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
174
  | `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
175
  | `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
176
  | `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
@@ -39,6 +39,7 @@ let typescript = require("typescript");
39
39
  typescript = __toESM(typescript, 1);
40
40
  let ts_api_utils = require("ts-api-utils");
41
41
  //#region src/jsdoc.ts
42
+ const JS_TS_FILE_PATTERNS$1 = "**/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs}";
42
43
  const jsdocConfig = eslint_plugin_jsdoc.default.configs["flat/recommended-tsdoc-error"];
43
44
  const REQUIREMENTS_TIER_RULES = [
44
45
  "jsdoc/require-example",
@@ -61,12 +62,14 @@ const REQUIREMENTS_TIER_RULES = [
61
62
  "jsdoc/require-yields-type"
62
63
  ];
63
64
  const jsdocAndTsdoc = [{
65
+ files: [JS_TS_FILE_PATTERNS$1],
64
66
  ...jsdocConfig,
65
67
  rules: {
66
68
  ...jsdocConfig.rules,
67
69
  ...Object.fromEntries(REQUIREMENTS_TIER_RULES.map((rule) => [rule, "off"]))
68
70
  }
69
71
  }, {
72
+ files: [JS_TS_FILE_PATTERNS$1],
70
73
  plugins: { tsdoc: eslint_plugin_tsdoc.default },
71
74
  rules: { "tsdoc/syntax": "error" }
72
75
  }];
@@ -156,7 +159,7 @@ function buildReactConfig(options = {}) {
156
159
  }
157
160
  //#endregion
158
161
  //#region package.json
159
- var version = "2.12.2";
162
+ var version = "2.13.0";
160
163
  //#endregion
161
164
  //#region src/rules/barrel-helpers.ts
162
165
  const INDEX_BASENAME$1 = /^index\.[cm]?[tj]sx?$/;
@@ -513,6 +516,76 @@ const noArrayIsArrayMutation = createRule$7({
513
516
  }
514
517
  });
515
518
  //#endregion
519
+ //#region src/rules/no-control-flow.ts
520
+ const noControlFlow = {
521
+ meta: {
522
+ type: "problem",
523
+ schema: [],
524
+ messages: {
525
+ ifStatement: "Use a lookup table, not an if/else branch.",
526
+ switchStatement: "Use a lookup table, not a switch branch.",
527
+ forStatement: "Use a lookup table or a declarative array method, not a for loop.",
528
+ forInStatement: "Use a lookup table or Object.entries, not a for-in loop.",
529
+ forOfStatement: "Use a declarative array method, not a for-of loop.",
530
+ whileStatement: "Use a lookup table or a declarative array method, not a while loop.",
531
+ doWhileStatement: "Use a lookup table or a declarative array method, not a do-while loop.",
532
+ conditionalExpression: "Use a lookup table, not a ternary."
533
+ }
534
+ },
535
+ create(context) {
536
+ return {
537
+ IfStatement(node) {
538
+ context.report({
539
+ node,
540
+ messageId: "ifStatement"
541
+ });
542
+ },
543
+ SwitchStatement(node) {
544
+ context.report({
545
+ node,
546
+ messageId: "switchStatement"
547
+ });
548
+ },
549
+ ForStatement(node) {
550
+ context.report({
551
+ node,
552
+ messageId: "forStatement"
553
+ });
554
+ },
555
+ ForInStatement(node) {
556
+ context.report({
557
+ node,
558
+ messageId: "forInStatement"
559
+ });
560
+ },
561
+ ForOfStatement(node) {
562
+ context.report({
563
+ node,
564
+ messageId: "forOfStatement"
565
+ });
566
+ },
567
+ WhileStatement(node) {
568
+ context.report({
569
+ node,
570
+ messageId: "whileStatement"
571
+ });
572
+ },
573
+ DoWhileStatement(node) {
574
+ context.report({
575
+ node,
576
+ messageId: "doWhileStatement"
577
+ });
578
+ },
579
+ ConditionalExpression(node) {
580
+ context.report({
581
+ node,
582
+ messageId: "conditionalExpression"
583
+ });
584
+ }
585
+ };
586
+ }
587
+ };
588
+ //#endregion
516
589
  //#region src/rules/no-enum-number-widening.ts
517
590
  const noEnumNumberWidening = _typescript_eslint_utils.ESLintUtils.RuleCreator((name) => `https://github.com/ExaDev/eslint-config/blob/main/src/rules/${name}.ts`)({
518
591
  name: "no-enum-number-widening",
@@ -1353,6 +1426,7 @@ const plugin = {
1353
1426
  "barrel-direct-siblings-only": barrelDirectSiblingsOnly,
1354
1427
  "barrel-policy": barrelPolicy,
1355
1428
  "no-array-isarray-mutation": noArrayIsArrayMutation,
1429
+ "no-control-flow": noControlFlow,
1356
1430
  "no-enum-number-widening": noEnumNumberWidening,
1357
1431
  "no-enum-reverse-lookup-widening": noEnumReverseLookupWidening,
1358
1432
  "no-index-files": noIndexFiles,
@@ -1399,11 +1473,22 @@ const plugin = {
1399
1473
  //#endregion
1400
1474
  //#region src/recommended-type-checked.ts
1401
1475
  const TEST_FILE_PATTERNS = "**/*.{test,spec}.{ts,tsx,mts,cts,js,jsx,mjs,cjs}";
1476
+ const JS_TS_FILE_PATTERNS = "**/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs}";
1477
+ function scopeToJsTs(configs) {
1478
+ return configs.map((config) => config.files ? config : {
1479
+ ...config,
1480
+ files: [JS_TS_FILE_PATTERNS]
1481
+ });
1482
+ }
1402
1483
  const recommendedTypeChecked = [
1403
- _eslint_js.default.configs.recommended,
1404
- ...typescript_eslint.default.configs.strictTypeChecked,
1405
- ...typescript_eslint.default.configs.stylisticTypeChecked,
1406
1484
  {
1485
+ ..._eslint_js.default.configs.recommended,
1486
+ files: [JS_TS_FILE_PATTERNS]
1487
+ },
1488
+ ...scopeToJsTs(typescript_eslint.default.configs.strictTypeChecked),
1489
+ ...scopeToJsTs(typescript_eslint.default.configs.stylisticTypeChecked),
1490
+ {
1491
+ files: [JS_TS_FILE_PATTERNS],
1407
1492
  plugins: { exadev: plugin },
1408
1493
  linterOptions: { noInlineConfig: true },
1409
1494
  rules: {
package/dist/index.js CHANGED
@@ -8,6 +8,7 @@ import { AST_NODE_TYPES, ESLintUtils, TSESLint } from "@typescript-eslint/utils"
8
8
  import * as ts from "typescript";
9
9
  import { isPropertyReadonlyInType, isTypeReference } from "ts-api-utils";
10
10
  //#region src/jsdoc.ts
11
+ const JS_TS_FILE_PATTERNS$1 = "**/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs}";
11
12
  const jsdocConfig = jsdoc.configs["flat/recommended-tsdoc-error"];
12
13
  const REQUIREMENTS_TIER_RULES = [
13
14
  "jsdoc/require-example",
@@ -30,12 +31,14 @@ const REQUIREMENTS_TIER_RULES = [
30
31
  "jsdoc/require-yields-type"
31
32
  ];
32
33
  const jsdocAndTsdoc = [{
34
+ files: [JS_TS_FILE_PATTERNS$1],
33
35
  ...jsdocConfig,
34
36
  rules: {
35
37
  ...jsdocConfig.rules,
36
38
  ...Object.fromEntries(REQUIREMENTS_TIER_RULES.map((rule) => [rule, "off"]))
37
39
  }
38
40
  }, {
41
+ files: [JS_TS_FILE_PATTERNS$1],
39
42
  plugins: { tsdoc },
40
43
  rules: { "tsdoc/syntax": "error" }
41
44
  }];
@@ -125,7 +128,7 @@ function buildReactConfig(options = {}) {
125
128
  }
126
129
  //#endregion
127
130
  //#region package.json
128
- var version = "2.12.2";
131
+ var version = "2.13.0";
129
132
  //#endregion
130
133
  //#region src/rules/barrel-helpers.ts
131
134
  const INDEX_BASENAME$1 = /^index\.[cm]?[tj]sx?$/;
@@ -482,6 +485,76 @@ const noArrayIsArrayMutation = createRule$7({
482
485
  }
483
486
  });
484
487
  //#endregion
488
+ //#region src/rules/no-control-flow.ts
489
+ const noControlFlow = {
490
+ meta: {
491
+ type: "problem",
492
+ schema: [],
493
+ messages: {
494
+ ifStatement: "Use a lookup table, not an if/else branch.",
495
+ switchStatement: "Use a lookup table, not a switch branch.",
496
+ forStatement: "Use a lookup table or a declarative array method, not a for loop.",
497
+ forInStatement: "Use a lookup table or Object.entries, not a for-in loop.",
498
+ forOfStatement: "Use a declarative array method, not a for-of loop.",
499
+ whileStatement: "Use a lookup table or a declarative array method, not a while loop.",
500
+ doWhileStatement: "Use a lookup table or a declarative array method, not a do-while loop.",
501
+ conditionalExpression: "Use a lookup table, not a ternary."
502
+ }
503
+ },
504
+ create(context) {
505
+ return {
506
+ IfStatement(node) {
507
+ context.report({
508
+ node,
509
+ messageId: "ifStatement"
510
+ });
511
+ },
512
+ SwitchStatement(node) {
513
+ context.report({
514
+ node,
515
+ messageId: "switchStatement"
516
+ });
517
+ },
518
+ ForStatement(node) {
519
+ context.report({
520
+ node,
521
+ messageId: "forStatement"
522
+ });
523
+ },
524
+ ForInStatement(node) {
525
+ context.report({
526
+ node,
527
+ messageId: "forInStatement"
528
+ });
529
+ },
530
+ ForOfStatement(node) {
531
+ context.report({
532
+ node,
533
+ messageId: "forOfStatement"
534
+ });
535
+ },
536
+ WhileStatement(node) {
537
+ context.report({
538
+ node,
539
+ messageId: "whileStatement"
540
+ });
541
+ },
542
+ DoWhileStatement(node) {
543
+ context.report({
544
+ node,
545
+ messageId: "doWhileStatement"
546
+ });
547
+ },
548
+ ConditionalExpression(node) {
549
+ context.report({
550
+ node,
551
+ messageId: "conditionalExpression"
552
+ });
553
+ }
554
+ };
555
+ }
556
+ };
557
+ //#endregion
485
558
  //#region src/rules/no-enum-number-widening.ts
486
559
  const noEnumNumberWidening = ESLintUtils.RuleCreator((name) => `https://github.com/ExaDev/eslint-config/blob/main/src/rules/${name}.ts`)({
487
560
  name: "no-enum-number-widening",
@@ -1322,6 +1395,7 @@ const plugin = {
1322
1395
  "barrel-direct-siblings-only": barrelDirectSiblingsOnly,
1323
1396
  "barrel-policy": barrelPolicy,
1324
1397
  "no-array-isarray-mutation": noArrayIsArrayMutation,
1398
+ "no-control-flow": noControlFlow,
1325
1399
  "no-enum-number-widening": noEnumNumberWidening,
1326
1400
  "no-enum-reverse-lookup-widening": noEnumReverseLookupWidening,
1327
1401
  "no-index-files": noIndexFiles,
@@ -1368,11 +1442,22 @@ const plugin = {
1368
1442
  //#endregion
1369
1443
  //#region src/recommended-type-checked.ts
1370
1444
  const TEST_FILE_PATTERNS = "**/*.{test,spec}.{ts,tsx,mts,cts,js,jsx,mjs,cjs}";
1445
+ const JS_TS_FILE_PATTERNS = "**/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs}";
1446
+ function scopeToJsTs(configs) {
1447
+ return configs.map((config) => config.files ? config : {
1448
+ ...config,
1449
+ files: [JS_TS_FILE_PATTERNS]
1450
+ });
1451
+ }
1371
1452
  const recommendedTypeChecked = [
1372
- js.configs.recommended,
1373
- ...tseslint.configs.strictTypeChecked,
1374
- ...tseslint.configs.stylisticTypeChecked,
1375
1453
  {
1454
+ ...js.configs.recommended,
1455
+ files: [JS_TS_FILE_PATTERNS]
1456
+ },
1457
+ ...scopeToJsTs(tseslint.configs.strictTypeChecked),
1458
+ ...scopeToJsTs(tseslint.configs.stylisticTypeChecked),
1459
+ {
1460
+ files: [JS_TS_FILE_PATTERNS],
1376
1461
  plugins: { exadev: plugin },
1377
1462
  linterOptions: { noInlineConfig: true },
1378
1463
  rules: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "exadev-eslint-config",
3
- "version": "2.12.2",
3
+ "version": "2.13.0",
4
4
  "description": "Shared custom ESLint rules and plugin for ExaDev projects",
5
5
  "type": "module",
6
6
  "sideEffects": false,
@@ -63,6 +63,7 @@
63
63
  "@arethetypeswrong/cli": "^0.18.5",
64
64
  "@commitlint/cli": "^21.2.1",
65
65
  "@commitlint/config-conventional": "^21.2.0",
66
+ "@eslint/json": "2.1.0",
66
67
  "@next/eslint-plugin-next": "^16.3.2",
67
68
  "@semantic-release/changelog": "^7.0.0",
68
69
  "@semantic-release/git": "^11.0.1",
@@ -85,7 +86,7 @@
85
86
  },
86
87
  "dependencies": {
87
88
  "@eslint/js": "^10.0.1",
88
- "@typescript-eslint/utils": "^8.67.0",
89
+ "@typescript-eslint/utils": "8.67.0",
89
90
  "eslint-plugin-jsdoc": "64.3.6",
90
91
  "eslint-plugin-tsdoc": "^0.5.2",
91
92
  "ts-api-utils": "^2.5.0"