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.
- package/README.md +4 -4
- package/cjs/S125/rule.js +33 -20
- package/cjs/S1607/rule.js +2 -3
- package/cjs/S2068/generated-meta.js +1 -1
- package/cjs/S2077/rule.js +29 -21
- package/cjs/S2234/rule.js +13 -4
- package/cjs/S2301/rule.js +2 -1
- package/cjs/S2310/rule.js +47 -16
- package/cjs/S4328/rule.js +1 -2
- package/cjs/S4335/rule.js +92 -30
- package/cjs/S4423/rule.aws.js +14 -4
- package/cjs/S5247/rule.js +3 -0
- package/cjs/S5256/rule.js +14 -0
- package/cjs/S5850/rule.js +12 -2
- package/cjs/S5973/rule.js +1 -2
- package/cjs/S6324/rule.js +79 -2
- package/cjs/S6418/generated-meta.js +1 -1
- package/cjs/S6418/rule.js +3 -0
- package/cjs/S6437/rule.js +9 -0
- package/cjs/S7790/rule.js +21 -1
- package/cjs/{S3854 → S8441}/generated-meta.js +5 -5
- package/cjs/{S3854 → S8441}/meta.js +4 -2
- package/cjs/S8441/rule.js +132 -0
- package/cjs/helpers/ancestor.js +11 -0
- package/cjs/helpers/files.js +89 -12
- package/cjs/helpers/find-up/all-in-parent-dirs.js +1 -2
- package/cjs/helpers/find-up/closest.js +1 -2
- package/cjs/helpers/find-up/find-minimatch.js +2 -1
- package/cjs/helpers/module-ts.js +11 -2
- package/cjs/helpers/package-jsons/all-in-parent-dirs.js +7 -3
- package/cjs/helpers/package-jsons/closest.js +4 -4
- package/cjs/helpers/package-jsons/dependencies.js +29 -5
- package/cjs/helpers/recognizers/detectors/ContainsDetector.js +3 -7
- package/cjs/helpers/recognizers/detectors/EndWithDetector.js +2 -4
- package/cjs/helpers/validate-version.js +1 -1
- package/cjs/plugin-rules.js +450 -454
- package/docs/arrow-function-convention.md +4 -4
- package/docs/class-name.md +3 -3
- package/docs/comment-regex.md +5 -5
- package/docs/content-length.md +4 -4
- package/docs/cyclomatic-complexity.md +3 -3
- package/docs/expression-complexity.md +3 -3
- package/docs/file-header.md +4 -4
- package/docs/function-name.md +3 -3
- package/docs/max-lines-per-function.md +3 -3
- package/docs/max-lines.md +3 -3
- package/docs/max-union-size.md +3 -3
- package/docs/nested-control-flow.md +3 -3
- package/docs/new-operator-misuse.md +3 -3
- package/docs/no-duplicate-string.md +4 -4
- package/docs/no-hardcoded-passwords.md +4 -4
- package/docs/no-hardcoded-secrets.md +5 -5
- package/docs/no-implicit-dependencies.md +3 -3
- package/docs/no-intrusive-permissions.md +3 -3
- package/docs/no-nested-functions.md +3 -3
- package/docs/{code-eval.md → no-session-cookies-on-static-assets.md} +2 -2
- package/docs/regex-complexity.md +3 -3
- package/docs/variable-name.md +3 -3
- package/package.json +1 -37
- package/types/S125/rule.d.ts +1 -1
- package/types/{S1523 → S8441}/generated-meta.d.ts +2 -2
- package/types/S8441/meta.d.ts +3 -0
- package/types/helpers/ancestor.d.ts +2 -0
- package/types/helpers/files.d.ts +65 -5
- package/types/helpers/find-up/all-in-parent-dirs.d.ts +2 -2
- package/types/helpers/find-up/closest.d.ts +2 -2
- package/types/helpers/find-up/find-minimatch.d.ts +2 -2
- package/types/helpers/location.d.ts +14 -2
- package/types/helpers/package-jsons/all-in-parent-dirs.d.ts +4 -1
- package/types/helpers/package-jsons/closest.d.ts +2 -1
- package/types/helpers/package-jsons/dependencies.d.ts +12 -2
- package/types/helpers/package-jsons/index.d.ts +3 -3
- package/types/helpers/recognizers/detectors/ContainsDetector.d.ts +1 -1
- package/types/helpers/validate-version.d.ts +2 -1
- package/types/plugin-rules.d.ts +1 -6
- package/cjs/S1523/generated-meta.js +0 -51
- package/cjs/S1523/meta.js +0 -21
- package/cjs/S1523/rule.js +0 -105
- package/cjs/S3723/config.js +0 -25
- package/cjs/S3723/generated-meta.js +0 -51
- package/cjs/S3723/index.js +0 -21
- package/cjs/S3723/meta.js +0 -37
- package/cjs/S3723/rule.js +0 -64
- package/cjs/S3854/index.js +0 -21
- package/cjs/S3854/rule.js +0 -68
- package/cjs/external/core.js +0 -23
- package/docs/enforce-trailing-comma.md +0 -25
- package/docs/super-invocation.md +0 -7
- package/types/S1523/meta.d.ts +0 -2
- package/types/S3723/config.d.ts +0 -3
- package/types/S3723/generated-meta.d.ts +0 -17
- package/types/S3723/index.d.ts +0 -1
- package/types/S3723/meta.d.ts +0 -4
- package/types/S3723/rule.d.ts +0 -8
- package/types/S3854/generated-meta.d.ts +0 -17
- package/types/S3854/index.d.ts +0 -1
- package/types/S3854/meta.d.ts +0 -2
- package/types/S3854/rule.d.ts +0 -2
- package/types/external/core.d.ts +0 -1
- /package/cjs/{S1523 → S8441}/index.js +0 -0
- /package/types/{S1523 → S8441}/index.d.ts +0 -0
- /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) |
|
|
253
|
-
| [no-hardcoded-secrets](https://sonarsource.github.io/rspec/#/rspec/S6418/javascript) |
|
|
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,
|
|
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,
|
|
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,
|
|
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
|
|
172
|
-
return
|
|
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
|
|
185
|
+
return input.split('\n').some(line => recognizer.recognition(line) >= recognizer.threshold);
|
|
180
186
|
}
|
|
181
187
|
function injectMissingBraces(value) {
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
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
|
-
|
|
192
|
-
|
|
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.
|
|
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.
|
|
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: '
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
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 (
|
|
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
|
-
|
|
88
|
-
|
|
89
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
+
}
|
package/cjs/S4423/rule.aws.js
CHANGED
|
@@ -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: {
|
|
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: {
|
|
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,
|