eslint-plugin-sonarjs 3.0.6 → 4.0.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.
Files changed (102) hide show
  1. package/README.md +4 -4
  2. package/cjs/S125/rule.js +33 -20
  3. package/cjs/S1607/rule.js +2 -3
  4. package/cjs/S2068/generated-meta.js +1 -1
  5. package/cjs/S2077/rule.js +29 -21
  6. package/cjs/S2234/rule.js +13 -4
  7. package/cjs/S2301/rule.js +2 -1
  8. package/cjs/S2310/rule.js +47 -16
  9. package/cjs/S4328/rule.js +1 -2
  10. package/cjs/S4335/rule.js +92 -30
  11. package/cjs/S4423/rule.aws.js +14 -4
  12. package/cjs/S5247/rule.js +3 -0
  13. package/cjs/S5256/rule.js +14 -0
  14. package/cjs/S5850/rule.js +12 -2
  15. package/cjs/S5973/rule.js +1 -2
  16. package/cjs/S6324/rule.js +79 -2
  17. package/cjs/S6418/generated-meta.js +1 -1
  18. package/cjs/S6418/rule.js +3 -0
  19. package/cjs/S6437/rule.js +9 -0
  20. package/cjs/S7790/rule.js +21 -1
  21. package/cjs/{S3854 → S8441}/generated-meta.js +5 -5
  22. package/cjs/{S3854 → S8441}/meta.js +4 -2
  23. package/cjs/S8441/rule.js +132 -0
  24. package/cjs/helpers/ancestor.js +11 -0
  25. package/cjs/helpers/files.js +89 -12
  26. package/cjs/helpers/find-up/all-in-parent-dirs.js +1 -2
  27. package/cjs/helpers/find-up/closest.js +1 -2
  28. package/cjs/helpers/find-up/find-minimatch.js +2 -1
  29. package/cjs/helpers/module-ts.js +11 -2
  30. package/cjs/helpers/package-jsons/all-in-parent-dirs.js +7 -3
  31. package/cjs/helpers/package-jsons/closest.js +4 -4
  32. package/cjs/helpers/package-jsons/dependencies.js +29 -5
  33. package/cjs/helpers/recognizers/detectors/ContainsDetector.js +3 -7
  34. package/cjs/helpers/recognizers/detectors/EndWithDetector.js +2 -4
  35. package/cjs/helpers/validate-version.js +1 -1
  36. package/cjs/plugin-rules.js +450 -454
  37. package/docs/arrow-function-convention.md +4 -4
  38. package/docs/class-name.md +3 -3
  39. package/docs/comment-regex.md +5 -5
  40. package/docs/content-length.md +4 -4
  41. package/docs/cyclomatic-complexity.md +3 -3
  42. package/docs/expression-complexity.md +3 -3
  43. package/docs/file-header.md +4 -4
  44. package/docs/function-name.md +3 -3
  45. package/docs/max-lines-per-function.md +3 -3
  46. package/docs/max-lines.md +3 -3
  47. package/docs/max-union-size.md +3 -3
  48. package/docs/nested-control-flow.md +3 -3
  49. package/docs/new-operator-misuse.md +3 -3
  50. package/docs/no-duplicate-string.md +4 -4
  51. package/docs/no-hardcoded-passwords.md +4 -4
  52. package/docs/no-hardcoded-secrets.md +5 -5
  53. package/docs/no-implicit-dependencies.md +3 -3
  54. package/docs/no-intrusive-permissions.md +3 -3
  55. package/docs/no-nested-functions.md +3 -3
  56. package/docs/{code-eval.md → no-session-cookies-on-static-assets.md} +2 -2
  57. package/docs/regex-complexity.md +3 -3
  58. package/docs/variable-name.md +3 -3
  59. package/package.json +1 -37
  60. package/types/S125/rule.d.ts +1 -1
  61. package/types/{S1523 → S8441}/generated-meta.d.ts +2 -2
  62. package/types/S8441/meta.d.ts +3 -0
  63. package/types/helpers/ancestor.d.ts +2 -0
  64. package/types/helpers/files.d.ts +65 -5
  65. package/types/helpers/find-up/all-in-parent-dirs.d.ts +2 -2
  66. package/types/helpers/find-up/closest.d.ts +2 -2
  67. package/types/helpers/find-up/find-minimatch.d.ts +2 -2
  68. package/types/helpers/location.d.ts +14 -2
  69. package/types/helpers/package-jsons/all-in-parent-dirs.d.ts +4 -1
  70. package/types/helpers/package-jsons/closest.d.ts +2 -1
  71. package/types/helpers/package-jsons/dependencies.d.ts +12 -2
  72. package/types/helpers/package-jsons/index.d.ts +3 -3
  73. package/types/helpers/recognizers/detectors/ContainsDetector.d.ts +1 -1
  74. package/types/helpers/validate-version.d.ts +2 -1
  75. package/types/plugin-rules.d.ts +1 -6
  76. package/cjs/S1523/generated-meta.js +0 -51
  77. package/cjs/S1523/meta.js +0 -21
  78. package/cjs/S1523/rule.js +0 -105
  79. package/cjs/S3723/config.js +0 -25
  80. package/cjs/S3723/generated-meta.js +0 -51
  81. package/cjs/S3723/index.js +0 -21
  82. package/cjs/S3723/meta.js +0 -37
  83. package/cjs/S3723/rule.js +0 -64
  84. package/cjs/S3854/index.js +0 -21
  85. package/cjs/S3854/rule.js +0 -68
  86. package/cjs/external/core.js +0 -23
  87. package/docs/enforce-trailing-comma.md +0 -25
  88. package/docs/super-invocation.md +0 -7
  89. package/types/S1523/meta.d.ts +0 -2
  90. package/types/S3723/config.d.ts +0 -3
  91. package/types/S3723/generated-meta.d.ts +0 -17
  92. package/types/S3723/index.d.ts +0 -1
  93. package/types/S3723/meta.d.ts +0 -4
  94. package/types/S3723/rule.d.ts +0 -8
  95. package/types/S3854/generated-meta.d.ts +0 -17
  96. package/types/S3854/index.d.ts +0 -1
  97. package/types/S3854/meta.d.ts +0 -2
  98. package/types/S3854/rule.d.ts +0 -2
  99. package/types/external/core.d.ts +0 -1
  100. /package/cjs/{S1523 → S8441}/index.js +0 -0
  101. /package/types/{S1523 → S8441}/index.d.ts +0 -0
  102. /package/types/{S1523 → S8441}/rule.d.ts +0 -0
package/README.md CHANGED
@@ -249,8 +249,8 @@ If you have any questions, encounter any bugs, or have feature requests, please
249
249
  | [no-globals-shadowing](https://sonarsource.github.io/rspec/#/rspec/S2137/javascript) | Special identifiers should not be bound or assigned | ✅ | | | | |
250
250
  | [no-gratuitous-expressions](https://sonarsource.github.io/rspec/#/rspec/S2589/javascript) | Boolean expressions should not be gratuitous | ✅ | | | | |
251
251
  | [no-hardcoded-ip](https://sonarsource.github.io/rspec/#/rspec/S1313/javascript) | Using hardcoded IP addresses is security-sensitive | ✅ | | | | |
252
- | [no-hardcoded-passwords](https://sonarsource.github.io/rspec/#/rspec/S2068/javascript) | Hard-coded passwords are security-sensitive | ✅ | | | | |
253
- | [no-hardcoded-secrets](https://sonarsource.github.io/rspec/#/rspec/S6418/javascript) | Hard-coded secrets are security-sensitive | ✅ | | | | |
252
+ | [no-hardcoded-passwords](https://sonarsource.github.io/rspec/#/rspec/S2068/javascript) | Credentials should not be hard-coded | ✅ | | | | |
253
+ | [no-hardcoded-secrets](https://sonarsource.github.io/rspec/#/rspec/S6418/javascript) | Secrets should not be hard-coded | ✅ | | | | |
254
254
  | [no-hook-setter-in-body](https://sonarsource.github.io/rspec/#/rspec/S6442/javascript) | React's useState hook should not be used directly in the render function or body of a component | ✅ | | | | |
255
255
  | [no-identical-conditions](https://sonarsource.github.io/rspec/#/rspec/S1862/javascript) | "if/else if" chains and "switch" cases should not have the same condition | ✅ | | | | |
256
256
  | [no-identical-expressions](https://sonarsource.github.io/rspec/#/rspec/S1764/javascript) | Identical expressions should not be used on both sides of a binary operator | ✅ | | | | |
@@ -406,7 +406,6 @@ SonarJS uses some rules are not shipped in this ESLint plugin to avoid duplicati
406
406
  | [S1090](https://sonarsource.github.io/rspec/#/rspec/S1090/javascript) | [jsx-a11y/iframe-has-title](https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/HEAD/docs/rules/iframe-has-title.md) |
407
407
  | [S1117](https://sonarsource.github.io/rspec/#/rspec/S1117/javascript) | [typescript-eslint/no-shadow](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/eslint-plugin/docs/rules/no-shadow.mdx) |
408
408
  | [S1131](https://sonarsource.github.io/rspec/#/rspec/S1131/javascript) | [eslint/no-trailing-spaces](https://eslint.org/docs/latest/rules/no-trailing-spaces) |
409
- | [S1143](https://sonarsource.github.io/rspec/#/rspec/S1143/javascript) | [eslint/no-unsafe-finally](https://eslint.org/docs/latest/rules/no-unsafe-finally) |
410
409
  | [S1199](https://sonarsource.github.io/rspec/#/rspec/S1199/javascript) | [eslint/no-lone-blocks](https://eslint.org/docs/latest/rules/no-lone-blocks) |
411
410
  | [S1314](https://sonarsource.github.io/rspec/#/rspec/S1314/javascript) | [eslint/no-octal](https://eslint.org/docs/latest/rules/no-octal) |
412
411
  | [S1321](https://sonarsource.github.io/rspec/#/rspec/S1321/javascript) | [eslint/no-with](https://eslint.org/docs/latest/rules/no-with) |
@@ -557,7 +556,6 @@ SonarJS uses some rules are not shipped in this ESLint plugin to avoid duplicati
557
556
  | [S7756](https://sonarsource.github.io/rspec/#/rspec/S7756/javascript) | [unicorn/prefer-blob-reading-methods](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/HEAD/docs/rules/prefer-blob-reading-methods.md) |
558
557
  | [S7757](https://sonarsource.github.io/rspec/#/rspec/S7757/javascript) | [unicorn/prefer-class-fields](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/HEAD/docs/rules/prefer-class-fields.md) |
559
558
  | [S7758](https://sonarsource.github.io/rspec/#/rspec/S7758/javascript) | [unicorn/prefer-code-point](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/HEAD/docs/rules/prefer-code-point.md) |
560
- | [S7759](https://sonarsource.github.io/rspec/#/rspec/S7759/javascript) | [unicorn/prefer-date-now](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/HEAD/docs/rules/prefer-date-now.md) |
561
559
  | [S7760](https://sonarsource.github.io/rspec/#/rspec/S7760/javascript) | [unicorn/prefer-default-parameters](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/HEAD/docs/rules/prefer-default-parameters.md) |
562
560
  | [S7761](https://sonarsource.github.io/rspec/#/rspec/S7761/javascript) | [unicorn/prefer-dom-node-dataset](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/HEAD/docs/rules/prefer-dom-node-dataset.md) |
563
561
  | [S7762](https://sonarsource.github.io/rspec/#/rspec/S7762/javascript) | [unicorn/prefer-dom-node-remove](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/HEAD/docs/rules/prefer-dom-node-remove.md) |
@@ -603,6 +601,7 @@ The following rules are used in SonarJS but not available in this ESLint plugin.
603
601
  | [S1082](https://sonarsource.github.io/rspec/#/rspec/S1082/javascript) | [jsx-a11y/mouse-events-have-key-events](https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/HEAD/docs/rules/mouse-events-have-key-events.md)<br>[jsx-a11y/click-events-have-key-events](https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/HEAD/docs/rules/click-events-have-key-events.md) |
604
602
  | [S1105](https://sonarsource.github.io/rspec/#/rspec/S1105/javascript) | [eslint/brace-style](https://eslint.org/docs/latest/rules/brace-style) |
605
603
  | [S1116](https://sonarsource.github.io/rspec/#/rspec/S1116/javascript) | [@stylistic/eslint-plugin/no-extra-semi](https://github.com/eslint-stylistic/eslint-stylistic/blob/main/packages/eslint-plugin/rules/no-extra-semi/README.md) |
604
+ | [S1143](https://sonarsource.github.io/rspec/#/rspec/S1143/javascript) | [eslint/no-unsafe-finally](https://eslint.org/docs/latest/rules/no-unsafe-finally) |
606
605
  | [S1186](https://sonarsource.github.io/rspec/#/rspec/S1186/javascript) | [typescript-eslint/no-empty-function](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/eslint-plugin/docs/rules/no-empty-function.mdx) |
607
606
  | [S1438](https://sonarsource.github.io/rspec/#/rspec/S1438/javascript) | [@stylistic/eslint-plugin/semi](https://github.com/eslint-stylistic/eslint-stylistic/blob/main/packages/eslint-plugin/rules/semi/README.md) |
608
607
  | [S1534](https://sonarsource.github.io/rspec/#/rspec/S1534/javascript) | [eslint/no-dupe-keys](https://eslint.org/docs/latest/rules/no-dupe-keys)<br>[typescript-eslint/no-dupe-class-members](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/eslint-plugin/docs/rules/no-dupe-class-members.mdx)<br>[react/jsx-no-duplicate-props](https://github.com/jsx-eslint/eslint-plugin-react/blob/HEAD/docs/rules/jsx-no-duplicate-props.md) |
@@ -659,6 +658,7 @@ The following rules are used in SonarJS but not available in this ESLint plugin.
659
658
  | [S7727](https://sonarsource.github.io/rspec/#/rspec/S7727/javascript) | [unicorn/no-array-callback-reference](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/HEAD/docs/rules/no-array-callback-reference.md) |
660
659
  | [S7728](https://sonarsource.github.io/rspec/#/rspec/S7728/javascript) | [unicorn/no-array-for-each](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/HEAD/docs/rules/no-array-for-each.md) |
661
660
  | [S7755](https://sonarsource.github.io/rspec/#/rspec/S7755/javascript) | [unicorn/prefer-at](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/HEAD/docs/rules/prefer-at.md) |
661
+ | [S7759](https://sonarsource.github.io/rspec/#/rspec/S7759/javascript) | [unicorn/prefer-date-now](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/HEAD/docs/rules/prefer-date-now.md) |
662
662
  | [S7763](https://sonarsource.github.io/rspec/#/rspec/S7763/javascript) | [unicorn/prefer-export-from](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/HEAD/docs/rules/prefer-export-from.md) |
663
663
 
664
664
  <!--- end decorated rules -->
package/cjs/S125/rule.js CHANGED
@@ -54,12 +54,14 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
54
54
  };
55
55
  Object.defineProperty(exports, "__esModule", { value: true });
56
56
  exports.rule = void 0;
57
- const eslint_1 = require("eslint");
58
57
  const index_js_1 = require("../helpers/index.js");
59
58
  const meta = __importStar(require("./generated-meta.js"));
60
59
  const index_js_2 = require("../helpers/recognizers/index.js");
61
60
  const node_path_1 = __importDefault(require("node:path"));
62
61
  const EXCLUDED_STATEMENTS = new Set(['BreakStatement', 'LabeledStatement', 'ContinueStatement']);
62
+ // Cheap prefilter: any meaningful JS statement must contain at least one of these characters,
63
+ // or be an import/export with a string literal (side-effect imports have no punctuation)
64
+ const CODE_CHAR_PATTERN = /[;{}()=<>]|\bimport\s+['"]|\bexport\s/;
63
65
  const recognizer = new index_js_2.CodeRecognizer(0.9, new index_js_2.JavaScriptFootPrint());
64
66
  exports.rule = {
65
67
  meta: (0, index_js_1.generateMeta)(meta, {
@@ -132,30 +134,34 @@ exports.rule = {
132
134
  };
133
135
  },
134
136
  };
135
- function isExpressionExclusion(statement, code) {
137
+ function isExpressionExclusion(statement, value, program, context) {
136
138
  if (statement.type === 'ExpressionStatement') {
137
139
  const expression = statement.expression;
138
140
  if (expression.type === 'Identifier' ||
139
141
  expression.type === 'SequenceExpression' ||
140
142
  isUnaryPlusOrMinus(expression) ||
141
- isExcludedLiteral(expression) ||
142
- !code.getLastToken(statement, token => token.value === ';')) {
143
+ isExcludedLiteral(expression)) {
143
144
  return true;
144
145
  }
146
+ // Only construct SourceCode when we need getLastToken.
147
+ // Access the constructor from context to avoid a static runtime import of 'eslint'.
148
+ const SourceCodeClass = context.sourceCode.constructor;
149
+ const code = new SourceCodeClass(value, program);
150
+ return !code.getLastToken(statement, token => token.value === ';');
145
151
  }
146
152
  return false;
147
153
  }
148
- function isExclusion(parsedBody, code) {
154
+ function isExclusion(parsedBody, value, program, context) {
149
155
  if (parsedBody.length === 1) {
150
156
  const singleStatement = parsedBody[0];
151
157
  return (EXCLUDED_STATEMENTS.has(singleStatement.type) ||
152
158
  isReturnThrowExclusion(singleStatement) ||
153
- isExpressionExclusion(singleStatement, code));
159
+ isExpressionExclusion(singleStatement, value, program, context));
154
160
  }
155
161
  return false;
156
162
  }
157
163
  function containsCode(value, context) {
158
- if (!couldBeJsCode(value) || !context.languageOptions.parser) {
164
+ if (!CODE_CHAR_PATTERN.test(value) || !couldBeJsCode(value) || !context.languageOptions.parser) {
159
165
  return false;
160
166
  }
161
167
  try {
@@ -168,29 +174,36 @@ function containsCode(value, context) {
168
174
  //In case of Vue parser: we will use the JS/TS parser instead of the Vue parser
169
175
  const parser = context.languageOptions?.parserOptions?.parser ?? context.languageOptions?.parser;
170
176
  const result = 'parse' in parser ? parser.parse(value, options) : parser.parseForESLint(value, options).ast;
171
- const parseResult = new eslint_1.SourceCode(value, result);
172
- return parseResult.ast.body.length > 0 && !isExclusion(parseResult.ast.body, parseResult);
177
+ const program = result;
178
+ return program.body.length > 0 && !isExclusion(program.body, value, program, context);
173
179
  }
174
180
  catch {
175
181
  return false;
176
182
  }
177
183
  }
178
184
  function couldBeJsCode(input) {
179
- return recognizer.extractCodeLines(input.split('\n')).length > 0;
185
+ return input.split('\n').some(line => recognizer.recognition(line) >= recognizer.threshold);
180
186
  }
181
187
  function injectMissingBraces(value) {
182
- const openCurlyBraceNum = (value.match(/{/g) ?? []).length;
183
- const closeCurlyBraceNum = (value.match(/}/g) ?? []).length;
184
- const missingBraces = openCurlyBraceNum - closeCurlyBraceNum;
185
- if (missingBraces > 0) {
186
- return value + '}'.repeat(missingBraces);
187
- }
188
- else if (missingBraces < 0) {
189
- return '{'.repeat(-missingBraces) + value;
188
+ let balance = 0;
189
+ let minBalance = 0;
190
+ for (let i = 0; i < value.length; i++) {
191
+ if (value[i] === '{') {
192
+ balance++;
193
+ }
194
+ else if (value[i] === '}') {
195
+ balance--;
196
+ if (balance < minBalance) {
197
+ minBalance = balance;
198
+ }
199
+ }
190
200
  }
191
- else {
192
- return value;
201
+ const openToAdd = -minBalance;
202
+ const closeToAdd = balance - minBalance;
203
+ if (openToAdd > 0 || closeToAdd > 0) {
204
+ return '{'.repeat(openToAdd) + value + '}'.repeat(closeToAdd);
193
205
  }
206
+ return value;
194
207
  }
195
208
  function getCommentLocation(nodes) {
196
209
  return {
package/cjs/S1607/rule.js CHANGED
@@ -53,7 +53,6 @@ Object.defineProperty(exports, "__esModule", { value: true });
53
53
  exports.rule = void 0;
54
54
  const index_js_1 = require("../helpers/index.js");
55
55
  const meta = __importStar(require("./generated-meta.js"));
56
- const node_path_1 = require("node:path");
57
56
  const dependencies_js_1 = require("../helpers/package-jsons/dependencies.js");
58
57
  const all_in_parent_dirs_js_1 = require("../helpers/package-jsons/all-in-parent-dirs.js");
59
58
  exports.rule = {
@@ -63,7 +62,7 @@ exports.rule = {
63
62
  },
64
63
  }),
65
64
  create(context) {
66
- const dependencies = (0, dependencies_js_1.getDependencies)((0, node_path_1.dirname)(context.filename), context.cwd);
65
+ const dependencies = (0, dependencies_js_1.getDependenciesSanitizePaths)(context);
67
66
  switch (true) {
68
67
  case dependencies.has('jasmine'):
69
68
  return jasmineListener();
@@ -71,7 +70,7 @@ exports.rule = {
71
70
  return jestListener();
72
71
  case dependencies.has('mocha'):
73
72
  return mochaListener();
74
- case (0, all_in_parent_dirs_js_1.getManifests)((0, node_path_1.dirname)((0, index_js_1.toUnixPath)(context.filename)), context.cwd).length > 0:
73
+ case (0, all_in_parent_dirs_js_1.getManifestsSanitizePaths)(context).length > 0:
75
74
  return nodejsListener();
76
75
  default:
77
76
  return {};
@@ -36,7 +36,7 @@ __exportStar(require("./meta.js"), exports);
36
36
  exports.meta = {
37
37
  type: 'problem',
38
38
  docs: {
39
- description: 'Hard-coded passwords are security-sensitive',
39
+ description: 'Credentials should not be hard-coded',
40
40
  recommended: true,
41
41
  url: 'https://sonarsource.github.io/rspec/#/rspec/S2068/javascript',
42
42
  requiresTypeChecking: false,
package/cjs/S2077/rule.js CHANGED
@@ -53,7 +53,24 @@ Object.defineProperty(exports, "__esModule", { value: true });
53
53
  exports.rule = void 0;
54
54
  const index_js_1 = require("../helpers/index.js");
55
55
  const meta = __importStar(require("./generated-meta.js"));
56
- const dbModules = ['pg', 'mysql', 'mysql2', 'sequelize'];
56
+ const sqlQuerySignatures = new Set([
57
+ 'pg.Client.query',
58
+ 'pg.Pool.query',
59
+ 'mysql.createConnection.query',
60
+ 'mysql.createPool.query',
61
+ 'mysql.createPoolCluster.query',
62
+ 'mysql2.createConnection.query',
63
+ 'mysql2.createPool.query',
64
+ 'mysql2.createPoolCluster.query',
65
+ 'sequelize.Sequelize.query', // Sequelize is typically destructured: const { Sequelize } = require('sequelize')
66
+ 'sqlite3.Database.run',
67
+ 'sqlite3.Database.get',
68
+ 'sqlite3.Database.all',
69
+ 'sqlite3.Database.each',
70
+ 'sqlite3.Database.exec',
71
+ 'better-sqlite3.exec',
72
+ 'better-sqlite3.prepare',
73
+ ]);
57
74
  exports.rule = {
58
75
  meta: (0, index_js_1.generateMeta)(meta, {
59
76
  messages: {
@@ -61,31 +78,22 @@ exports.rule = {
61
78
  },
62
79
  }),
63
80
  create(context) {
64
- let isDbModuleImported = false;
81
+ const services = context.sourceCode.parserServices;
82
+ const hasTypeInformation = (0, index_js_1.isRequiredParserServices)(services);
65
83
  return {
66
- Program() {
67
- // init flag for each file
68
- isDbModuleImported = false;
69
- },
70
- ImportDeclaration(node) {
71
- const { source } = node;
72
- if (dbModules.includes(String(source.value))) {
73
- isDbModuleImported = true;
74
- }
75
- },
76
84
  CallExpression(node) {
77
- const call = node;
78
- const { callee, arguments: args } = call;
79
- if ((0, index_js_1.isRequireModule)(call, ...dbModules)) {
80
- isDbModuleImported = true;
81
- return;
85
+ let fqn = null;
86
+ if (hasTypeInformation) {
87
+ const tsNode = services.esTreeNodeToTSNodeMap.get(node);
88
+ fqn = (0, index_js_1.getFullyQualifiedNameTS)(services, tsNode);
89
+ }
90
+ else {
91
+ fqn = (0, index_js_1.getFullyQualifiedName)(context, node.callee);
82
92
  }
83
- if (isDbModuleImported &&
84
- (0, index_js_1.isMemberWithProperty)(callee, 'query') &&
85
- isQuestionable(args[0])) {
93
+ if (fqn && sqlQuerySignatures.has(fqn) && isQuestionable(node.arguments[0])) {
86
94
  context.report({
87
95
  messageId: 'safeQuery',
88
- node: callee,
96
+ node: node.callee,
89
97
  });
90
98
  }
91
99
  },
package/cjs/S2234/rule.js CHANGED
@@ -59,15 +59,24 @@ exports.rule = {
59
59
  const services = context.sourceCode.parserServices;
60
60
  const canResolveType = (0, index_js_1.isRequiredParserServices)(services);
61
61
  function checkArguments(functionCall) {
62
+ // Extract argument names first (cheap operation)
63
+ const argumentNames = functionCall.arguments.map(arg => {
64
+ const argument = arg;
65
+ return argument.type === 'Identifier' ? argument.name : undefined;
66
+ });
67
+ // Early exit: detecting swapped arguments requires at least 2 identifier arguments.
68
+ // Skip expensive type resolution if there aren't enough identifiers to check.
69
+ // This avoids a TypeScript performance issue where type inference for complex
70
+ // destructuring patterns with nested defaults can have exponential complexity.
71
+ const identifierCount = argumentNames.filter(name => name !== undefined).length;
72
+ if (identifierCount < 2) {
73
+ return;
74
+ }
62
75
  const resolvedFunction = resolveFunctionDeclaration(functionCall);
63
76
  if (!resolvedFunction) {
64
77
  return;
65
78
  }
66
79
  const { params: functionParameters, declaration: functionDeclaration } = resolvedFunction;
67
- const argumentNames = functionCall.arguments.map(arg => {
68
- const argument = arg;
69
- return argument.type === 'Identifier' ? argument.name : undefined;
70
- });
71
80
  for (let argumentIndex = 0; argumentIndex < argumentNames.length; argumentIndex++) {
72
81
  const argumentName = argumentNames[argumentIndex];
73
82
  if (argumentName) {
package/cjs/S2301/rule.js CHANGED
@@ -173,7 +173,8 @@ exports.rule = {
173
173
  },
174
174
  };
175
175
  function isCallbackArgument(node) {
176
- return node.parent.type === 'CallExpression' && node.parent.arguments.includes(node);
176
+ return ((node.parent.type === 'CallExpression' && node.parent.arguments.includes(node)) ||
177
+ node.parent.type === 'JSXExpressionContainer');
177
178
  }
178
179
  /**
179
180
  * Checks if an expression has side effects (function calls, new expressions, assignments).
package/cjs/S2310/rule.js CHANGED
@@ -70,6 +70,9 @@ exports.rule = {
70
70
  }
71
71
  for (const ref of variable.references) {
72
72
  if (ref.isWrite() && isUsedInsideBody(ref.identifier, block)) {
73
+ if (isIntentionalSkipAhead(ref.identifier, block)) {
74
+ continue;
75
+ }
73
76
  (0, index_js_1.report)(context, {
74
77
  node: ref.identifier,
75
78
  message: `Remove this assignment of "${counter.name}".`,
@@ -84,25 +87,12 @@ exports.rule = {
84
87
  checkLoop(forLoop.update, collectCountersFor, node);
85
88
  }
86
89
  },
87
- 'ForInStatement > BlockStatement, ForOfStatement > BlockStatement': (node) => {
88
- const { left } = (0, index_js_1.getParent)(context, node);
89
- checkLoop(left, collectCountersForX, node);
90
- },
90
+ // Note: for-of and for-in loops are not checked because reassigning
91
+ // the iterator variable does not affect loop iteration (the iterator
92
+ // protocol controls progression, not the variable value)
91
93
  };
92
94
  },
93
95
  };
94
- function collectCountersForX(updateExpression, counters) {
95
- if (updateExpression.type === 'VariableDeclaration') {
96
- for (const decl of updateExpression.declarations) {
97
- collectCountersForX(decl.id, counters);
98
- }
99
- }
100
- else {
101
- for (const id of (0, index_js_1.resolveIdentifiers)(updateExpression, true)) {
102
- counters.push(id);
103
- }
104
- }
105
- }
106
96
  function collectCountersFor(updateExpression, counters) {
107
97
  let counter = undefined;
108
98
  if (updateExpression.type === 'AssignmentExpression') {
@@ -120,7 +110,48 @@ function collectCountersFor(updateExpression, counters) {
120
110
  counters.push(counter);
121
111
  }
122
112
  }
113
+ /**
114
+ * Checks if a loop counter modification is an intentional skip-ahead pattern
115
+ * (UpdateExpression or compound assignment) rather than a simple assignment.
116
+ * Modifications in nested for-loop update clauses are not considered skip-ahead.
117
+ */
118
+ function isIntentionalSkipAhead(id, outerLoopBody) {
119
+ if (isInNestedForLoopUpdate(id, outerLoopBody)) {
120
+ return false;
121
+ }
122
+ const parent = (0, index_js_1.getNodeParent)(id);
123
+ if (parent?.type === 'UpdateExpression') {
124
+ return true;
125
+ }
126
+ if (parent?.type === 'AssignmentExpression' && parent.operator !== '=') {
127
+ return true;
128
+ }
129
+ return false;
130
+ }
123
131
  function isUsedInsideBody(id, loopBody) {
124
132
  const bodyRange = loopBody.range;
125
133
  return id.range && bodyRange && id.range[0] > bodyRange[0] && id.range[1] < bodyRange[1];
126
134
  }
135
+ function isInNestedForLoopUpdate(id, outerLoopBody) {
136
+ let node = id;
137
+ let parent = (0, index_js_1.getNodeParent)(node);
138
+ while (parent) {
139
+ // Stop if we've reached the outer loop body
140
+ if (parent === outerLoopBody) {
141
+ return false;
142
+ }
143
+ // Check if we're in a nested for-loop's update clause
144
+ if (parent.type === 'ForStatement' && parent.update) {
145
+ const updateRange = parent.update.range;
146
+ if (updateRange &&
147
+ id.range &&
148
+ id.range[0] >= updateRange[0] &&
149
+ id.range[1] <= updateRange[1]) {
150
+ return true;
151
+ }
152
+ }
153
+ node = parent;
154
+ parent = (0, index_js_1.getNodeParent)(node);
155
+ }
156
+ return false;
157
+ }
package/cjs/S4328/rule.js CHANGED
@@ -58,7 +58,6 @@ const builtin_modules_1 = __importDefault(require("builtin-modules"));
58
58
  const typescript_1 = __importDefault(require("typescript"));
59
59
  const index_js_1 = require("../helpers/index.js");
60
60
  const meta = __importStar(require("./generated-meta.js"));
61
- const node_path_1 = require("node:path");
62
61
  const dependencies_js_1 = require("../helpers/package-jsons/dependencies.js");
63
62
  const messages = {
64
63
  removeOrAddDependency: 'Either remove this import or add it as a dependency.',
@@ -67,7 +66,7 @@ exports.rule = {
67
66
  meta: (0, index_js_1.generateMeta)(meta, { messages }),
68
67
  create(context) {
69
68
  // we need to find all the npm manifests from the directory of the analyzed file to the context working directory
70
- const dependencies = (0, dependencies_js_1.getDependencies)((0, node_path_1.dirname)(context.filename), context.cwd);
69
+ const dependencies = (0, dependencies_js_1.getDependenciesSanitizePaths)(context);
71
70
  const whitelist = context.options[0]?.whitelist || [];
72
71
  const program = context.sourceCode.parserServices?.program;
73
72
  let options, host;
package/cjs/S4335/rule.js CHANGED
@@ -66,37 +66,47 @@ exports.rule = {
66
66
  }),
67
67
  create(context) {
68
68
  const services = context.sourceCode.parserServices;
69
- if ((0, index_js_1.isRequiredParserServices)(services)) {
70
- return {
71
- TSIntersectionType: (node) => {
72
- const intersection = node;
73
- const anyOrNever = intersection.types.find(typeNode => ['TSAnyKeyword', 'TSNeverKeyword'].includes(typeNode.type));
74
- if (anyOrNever) {
75
- context.report({
76
- messageId: 'simplifyIntersection',
77
- data: {
78
- type: anyOrNever.type === 'TSAnyKeyword' ? 'any' : 'never',
79
- },
80
- node,
81
- });
82
- }
83
- else {
84
- for (const typeNode of intersection.types) {
85
- const tp = services.program
86
- .getTypeChecker()
87
- .getTypeAtLocation(services.esTreeNodeToTSNodeMap.get(typeNode));
88
- if (isTypeWithoutMembers(tp)) {
89
- context.report({
90
- messageId: 'removeIntersection',
91
- node: typeNode,
92
- });
93
- }
94
- }
95
- }
96
- },
97
- };
69
+ if (!(0, index_js_1.isRequiredParserServices)(services)) {
70
+ return {};
98
71
  }
99
- return {};
72
+ function checkIntersectionType(node) {
73
+ const intersection = node;
74
+ const anyOrNever = intersection.types.find(typeNode => ['TSAnyKeyword', 'TSNeverKeyword'].includes(typeNode.type));
75
+ if (anyOrNever) {
76
+ context.report({
77
+ messageId: 'simplifyIntersection',
78
+ data: {
79
+ type: anyOrNever.type === 'TSAnyKeyword' ? 'any' : 'never',
80
+ },
81
+ node,
82
+ });
83
+ return;
84
+ }
85
+ checkTypesWithoutMembers(intersection, services);
86
+ }
87
+ function checkTypesWithoutMembers(intersection, parserServices) {
88
+ for (const typeNode of intersection.types) {
89
+ const tp = parserServices.program
90
+ .getTypeChecker()
91
+ .getTypeAtLocation(parserServices.esTreeNodeToTSNodeMap.get(typeNode));
92
+ if (!isTypeWithoutMembers(tp)) {
93
+ continue;
94
+ }
95
+ if (isLiteralUnionPattern(intersection)) {
96
+ continue;
97
+ }
98
+ if (isGenericTypePattern(intersection, typeNode, parserServices)) {
99
+ continue;
100
+ }
101
+ context.report({
102
+ messageId: 'removeIntersection',
103
+ node: typeNode,
104
+ });
105
+ }
106
+ }
107
+ return {
108
+ TSIntersectionType: checkIntersectionType,
109
+ };
100
110
  },
101
111
  };
102
112
  function isTypeWithoutMembers(tp) {
@@ -127,3 +137,55 @@ function isStandaloneInterface(typeSymbol) {
127
137
  function isInterfaceDeclaration(declaration) {
128
138
  return declaration.kind === typescript_1.default.SyntaxKind.InterfaceDeclaration;
129
139
  }
140
+ /**
141
+ * Detects the LiteralUnion pattern: `(X & {})` used within a union type to preserve
142
+ * IDE autocomplete for literal types while accepting any primitive value.
143
+ * Example: `'small' | 'medium' | 'large' | (string & {})`
144
+ */
145
+ function isLiteralUnionPattern(intersection) {
146
+ if (intersection.types.length !== 2) {
147
+ return false;
148
+ }
149
+ const parent = (0, index_js_1.getNodeParent)(intersection);
150
+ if (!parent || parent.type !== 'TSUnionType') {
151
+ return false;
152
+ }
153
+ const otherType = intersection.types.find(t => t.type !== 'TSTypeLiteral' || (t.members && t.members.length > 0));
154
+ if (!otherType) {
155
+ return false;
156
+ }
157
+ return (otherType.type === 'TSStringKeyword' ||
158
+ otherType.type === 'TSNumberKeyword' ||
159
+ otherType.type === 'TSTypeReference');
160
+ }
161
+ /**
162
+ * Detects generic type patterns where `& {}` serves a legitimate purpose:
163
+ * 1. Mapped type pattern: `{ [K in keyof T]: T[K] } & {}` - forces type flattening (Simplify/Prettify)
164
+ * 2. Generic type reference: `GenericType<T> & {}` where GenericType has type arguments
165
+ * 3. Type parameter reference: `T & {}` where T is a type parameter
166
+ *
167
+ * These patterns are used for type normalization and non-nullability constraints.
168
+ */
169
+ function isGenericTypePattern(intersection, emptyTypeNode, parserServices) {
170
+ const siblingTypes = intersection.types.filter(t => t !== emptyTypeNode);
171
+ return siblingTypes.some(siblingType => {
172
+ // Pattern 1: Mapped type (Simplify/Prettify pattern)
173
+ if (siblingType.type === 'TSMappedType') {
174
+ return true;
175
+ }
176
+ // Pattern 2: Generic type reference with type arguments
177
+ if (siblingType.type === 'TSTypeReference' && siblingType.typeArguments) {
178
+ return true;
179
+ }
180
+ // Pattern 3: Type parameter reference (e.g., T & {})
181
+ if (siblingType.type === 'TSTypeReference') {
182
+ const tp = parserServices.program
183
+ .getTypeChecker()
184
+ .getTypeAtLocation(parserServices.esTreeNodeToTSNodeMap.get(siblingType));
185
+ if (tp.isTypeParameter()) {
186
+ return true;
187
+ }
188
+ }
189
+ return false;
190
+ });
191
+ }
@@ -23,16 +23,26 @@ exports.rule = (0, cdk_js_1.AwsCdkTemplate)({
23
23
  'aws_cdk_lib.aws_apigateway.CfnDomainName': (0, cdk_js_1.AwsCdkCheckArguments)('AWSApiGateway', true, 'securityPolicy', { primitives: { valid: ['TLS_1_2'] } }),
24
24
  'aws_cdk_lib.aws_apigateway.DomainName': (0, cdk_js_1.AwsCdkCheckArguments)('AWSApiGateway', false, 'securityPolicy', { fqns: { valid: ['aws_cdk_lib.aws_apigateway.SecurityPolicy.TLS_1_2'] } }),
25
25
  'aws_cdk_lib.aws_elasticsearch.CfnDomain': (0, cdk_js_1.AwsCdkCheckArguments)(['AWSOpenElasticSearch', 'enforceTLS12'], true, ['domainEndpointOptions', 'tlsSecurityPolicy'], {
26
- primitives: { valid: ['Policy-Min-TLS-1-2-2019-07'] },
26
+ primitives: { valid: ['Policy-Min-TLS-1-2-2019-07', 'Policy-Min-TLS-1-2-PFS-2023-10'] },
27
27
  }),
28
28
  'aws_cdk_lib.aws_opensearchservice.Domain': (0, cdk_js_1.AwsCdkCheckArguments)(['AWSOpenElasticSearch', 'enforceTLS12'], true, 'tlsSecurityPolicy', {
29
- fqns: { valid: ['aws_cdk_lib.aws_opensearchservice.TLSSecurityPolicy.TLS_1_2'] },
29
+ fqns: {
30
+ valid: [
31
+ 'aws_cdk_lib.aws_opensearchservice.TLSSecurityPolicy.TLS_1_2',
32
+ 'aws_cdk_lib.aws_opensearchservice.TLSSecurityPolicy.TLS_1_2_PFS',
33
+ ],
34
+ },
30
35
  }),
31
36
  'aws_cdk_lib.aws_opensearchservice.CfnDomain': (0, cdk_js_1.AwsCdkCheckArguments)(['AWSOpenElasticSearch', 'enforceTLS12'], true, ['domainEndpointOptions', 'tlsSecurityPolicy'], {
32
- primitives: { valid: ['Policy-Min-TLS-1-2-2019-07'] },
37
+ primitives: { valid: ['Policy-Min-TLS-1-2-2019-07', 'Policy-Min-TLS-1-2-PFS-2023-10'] },
33
38
  }),
34
39
  'aws_cdk_lib.aws_elasticsearch.Domain': (0, cdk_js_1.AwsCdkCheckArguments)(['AWSOpenElasticSearch', 'enforceTLS12'], true, 'tlsSecurityPolicy', {
35
- fqns: { valid: ['aws_cdk_lib.aws_elasticsearch.TLSSecurityPolicy.TLS_1_2'] },
40
+ fqns: {
41
+ valid: [
42
+ 'aws_cdk_lib.aws_elasticsearch.TLSSecurityPolicy.TLS_1_2',
43
+ 'aws_cdk_lib.aws_elasticsearch.TLSSecurityPolicy.TLS_1_2_PFS',
44
+ ],
45
+ },
36
46
  }),
37
47
  }, {
38
48
  messages: {
package/cjs/S5247/rule.js CHANGED
@@ -82,6 +82,9 @@ exports.rule = {
82
82
  if (fqn === 'markdown-it') {
83
83
  (0, index_js_1.checkSensitiveCall)(context, callExpression, 0, 'html', true, MESSAGE);
84
84
  }
85
+ if (fqn === 'swig.setDefaults') {
86
+ (0, index_js_1.checkSensitiveCall)(context, callExpression, 0, 'autoescape', false, MESSAGE);
87
+ }
85
88
  },
86
89
  NewExpression: (node) => {
87
90
  const newExpression = node;
package/cjs/S5256/rule.js CHANGED
@@ -59,6 +59,15 @@ const { getLiteralPropValue, getProp } = jsx_ast_utils_x_1.default;
59
59
  const table_js_1 = require("../helpers/table.js");
60
60
  const index_js_1 = require("../helpers/index.js");
61
61
  const meta = __importStar(require("./generated-meta.js"));
62
+ /**
63
+ * Detects reusable table wrapper components that receive children via props spread.
64
+ * Returns true if the table has no JSX children AND has a JSXSpreadAttribute.
65
+ */
66
+ function isReusableWrapperComponent(tree) {
67
+ const hasNoChildren = tree.children.length === 0;
68
+ const hasSpreadAttribute = tree.openingElement.attributes.some(attr => attr.type === 'JSXSpreadAttribute');
69
+ return hasNoChildren && hasSpreadAttribute;
70
+ }
62
71
  exports.rule = {
63
72
  meta: (0, index_js_1.generateMeta)(meta),
64
73
  create(context) {
@@ -95,6 +104,11 @@ exports.rule = {
95
104
  if (ariaHidden && getLiteralPropValue(ariaHidden) === true) {
96
105
  return;
97
106
  }
107
+ // Skip reusable wrapper components: tables with no children and spread props
108
+ // where table structure is provided via props.children at usage sites
109
+ if (isReusableWrapperComponent(tree)) {
110
+ return;
111
+ }
98
112
  if (!checkValidTable(tree)) {
99
113
  context.report({
100
114
  node,