eslint-plugin-no-mistakes 0.61.11 → 0.63.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/package.json +6 -6
- package/src/exported-components.js +1 -0
- package/src/rules/module-mock-integration.js +1 -0
- package/src/rules/nextjs-metadata-exports-location.js +42 -39
- package/src/rules/nextjs-no-manual-script-tags.js +37 -34
- package/src/rules/no-banned-import-outside-allowed-paths-aliases.js +2 -0
- package/src/rules/no-banned-import-outside-allowed-paths-scopes.js +1 -1
- package/src/rules/no-global-fetch-outside-helper.js +1 -1
- package/src/rules/no-placeholder-never-type-exports.js +42 -39
- package/src/rules/no-vitest-sequential.js +27 -24
- package/src/rules/playwright-assertion-timeout-cap.js +40 -38
- package/src/rules/playwright-no-raw-scroll-pagination.js +0 -3
- package/src/rules/playwright-require-interactive-test-id.js +2 -0
- package/src/rules/react-no-nullish-react-node.js +3 -3
- package/src/rules/test-no-delayed-rejects-loop-jumps.js +1 -1
- package/src/rules/test-no-error-message-matching.js +2 -0
- package/src/rules/ts-no-export-renaming.js +45 -42
- package/src/rules/ts-no-function-aliases.js +27 -31
- package/src/rules/ts-preserve-null-option-defaults.js +2 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "eslint-plugin-no-mistakes",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.63.0",
|
|
4
4
|
"description": "ESLint and Oxlint rules that keep code static enough for no-mistakes analyzers and coding agents",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -21,11 +21,11 @@
|
|
|
21
21
|
"test": "vitest run --coverage"
|
|
22
22
|
},
|
|
23
23
|
"devDependencies": {
|
|
24
|
-
"@typescript-eslint/parser": "^8.
|
|
25
|
-
"@vitest/coverage-v8": "^
|
|
26
|
-
"eslint": "^10.
|
|
27
|
-
"oxlint": "^1.
|
|
28
|
-
"vitest": "^
|
|
24
|
+
"@typescript-eslint/parser": "^8.69.0",
|
|
25
|
+
"@vitest/coverage-v8": "^5.0.0",
|
|
26
|
+
"eslint": "^10.10.0",
|
|
27
|
+
"oxlint": "^1.82.0",
|
|
28
|
+
"vitest": "^5.0.0"
|
|
29
29
|
},
|
|
30
30
|
"peerDependencies": {
|
|
31
31
|
"eslint": ">=9"
|
|
@@ -48,49 +48,52 @@ function collectPatternNames(node, names = new Set()) {
|
|
|
48
48
|
return names;
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
-
module.exports =
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
51
|
+
module.exports = Object.assign(
|
|
52
|
+
rule(
|
|
53
|
+
{
|
|
54
|
+
type: "problem",
|
|
55
|
+
docs: {
|
|
56
|
+
description: "restrict Next.js metadata exports to route segment files",
|
|
57
|
+
recommended: false,
|
|
58
|
+
},
|
|
59
|
+
schema: [],
|
|
60
|
+
messages: {
|
|
61
|
+
location:
|
|
62
|
+
"metadata and generateMetadata exports are only allowed in Next.js route segment files.",
|
|
63
|
+
},
|
|
57
64
|
},
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
65
|
+
(context) => ({
|
|
66
|
+
ExportNamedDeclaration(node) {
|
|
67
|
+
if (!isNextFile(context.filename)) return;
|
|
68
|
+
if (isAllowedFile(context.filename)) return;
|
|
69
|
+
if (node.declaration?.type === "VariableDeclaration") {
|
|
70
|
+
if (
|
|
71
|
+
node.declaration.declarations.some((declaration) =>
|
|
72
|
+
[...collectPatternNames(declaration.id), declarationName(declaration)].some((name) =>
|
|
73
|
+
METADATA_EXPORTS.has(name),
|
|
74
|
+
),
|
|
75
|
+
)
|
|
76
|
+
) {
|
|
77
|
+
context.report({ node, messageId: "location" });
|
|
78
|
+
}
|
|
79
|
+
}
|
|
69
80
|
if (
|
|
70
|
-
node.declaration
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
81
|
+
node.declaration?.type === "FunctionDeclaration" &&
|
|
82
|
+
METADATA_EXPORTS.has(declarationName(node.declaration))
|
|
83
|
+
) {
|
|
84
|
+
context.report({ node, messageId: "location" });
|
|
85
|
+
}
|
|
86
|
+
if (
|
|
87
|
+
node.exportKind !== "type" &&
|
|
88
|
+
node.specifiers?.some(
|
|
89
|
+
(specifier) =>
|
|
90
|
+
specifier.exportKind !== "type" && METADATA_EXPORTS.has(specifierName(specifier)),
|
|
74
91
|
)
|
|
75
92
|
) {
|
|
76
93
|
context.report({ node, messageId: "location" });
|
|
77
94
|
}
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
) {
|
|
83
|
-
context.report({ node, messageId: "location" });
|
|
84
|
-
}
|
|
85
|
-
if (
|
|
86
|
-
node.exportKind !== "type" &&
|
|
87
|
-
node.specifiers?.some(
|
|
88
|
-
(specifier) =>
|
|
89
|
-
specifier.exportKind !== "type" && METADATA_EXPORTS.has(specifierName(specifier)),
|
|
90
|
-
)
|
|
91
|
-
) {
|
|
92
|
-
context.report({ node, messageId: "location" });
|
|
93
|
-
}
|
|
94
|
-
},
|
|
95
|
-
}),
|
|
95
|
+
},
|
|
96
|
+
}),
|
|
97
|
+
),
|
|
98
|
+
{ __test: { specifierName, collectPatternNames } },
|
|
96
99
|
);
|
|
@@ -65,39 +65,42 @@ function isAllowedInlineScript(node, options, patterns) {
|
|
|
65
65
|
);
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
-
module.exports =
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
68
|
+
module.exports = Object.assign(
|
|
69
|
+
rule(
|
|
70
|
+
{
|
|
71
|
+
type: "problem",
|
|
72
|
+
docs: { description: "prefer next/script over raw script JSX tags", recommended: false },
|
|
73
|
+
schema: [
|
|
74
|
+
{
|
|
75
|
+
type: "object",
|
|
76
|
+
properties: {
|
|
77
|
+
allowInlineScriptIds: { type: "array", items: { type: "string" } },
|
|
78
|
+
allowInlineScriptIdPatterns: { type: "array", items: { type: "string" } },
|
|
79
|
+
},
|
|
80
|
+
additionalProperties: false,
|
|
78
81
|
},
|
|
79
|
-
|
|
80
|
-
},
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
},
|
|
82
|
+
],
|
|
83
|
+
messages: { script: "Use next/script instead of a raw <script> tag." },
|
|
84
|
+
},
|
|
85
|
+
(context) => {
|
|
86
|
+
const options = context.options[0] || {};
|
|
87
|
+
const patterns = allowedIdPatterns(options);
|
|
88
|
+
let isNextFile = isNextPath(context.filename);
|
|
89
|
+
return {
|
|
90
|
+
ImportDeclaration(node) {
|
|
91
|
+
if (typeof node.source.value === "string" && node.source.value.startsWith("next/")) {
|
|
92
|
+
isNextFile = true;
|
|
93
|
+
}
|
|
94
|
+
},
|
|
95
|
+
JSXOpeningElement(node) {
|
|
96
|
+
if (!isNextFile) return;
|
|
97
|
+
if (node.name.type !== "JSXIdentifier" || node.name.name !== "script") return;
|
|
98
|
+
if (isJsonLdScript(node)) return;
|
|
99
|
+
if (isAllowedInlineScript(node, options, patterns)) return;
|
|
100
|
+
context.report({ node, messageId: "script" });
|
|
101
|
+
},
|
|
102
|
+
};
|
|
103
|
+
},
|
|
104
|
+
),
|
|
105
|
+
{ __test: { attributeValue } },
|
|
103
106
|
);
|
|
@@ -6,46 +6,49 @@ function specifierName(specifier) {
|
|
|
6
6
|
return specifier.local?.name || specifier.exported?.name || specifier.exported?.value;
|
|
7
7
|
}
|
|
8
8
|
|
|
9
|
-
module.exports =
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
},
|
|
19
|
-
(context) => {
|
|
20
|
-
const neverTypes = new Set();
|
|
21
|
-
const exportSpecifiers = [];
|
|
22
|
-
return {
|
|
23
|
-
"Program > TSTypeAliasDeclaration"(node) {
|
|
24
|
-
if (node.typeAnnotation?.type === "TSNeverKeyword") neverTypes.add(node.id.name);
|
|
9
|
+
module.exports = Object.assign(
|
|
10
|
+
rule(
|
|
11
|
+
{
|
|
12
|
+
type: "problem",
|
|
13
|
+
docs: { description: "disallow exported never placeholder type aliases", recommended: false },
|
|
14
|
+
schema: [],
|
|
15
|
+
messages: {
|
|
16
|
+
placeholder:
|
|
17
|
+
"Do not export placeholder never types. Define the real type or remove the export.",
|
|
25
18
|
},
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
19
|
+
},
|
|
20
|
+
(context) => {
|
|
21
|
+
const neverTypes = new Set();
|
|
22
|
+
const exportSpecifiers = [];
|
|
23
|
+
return {
|
|
24
|
+
"Program > TSTypeAliasDeclaration"(node) {
|
|
25
|
+
if (node.typeAnnotation?.type === "TSNeverKeyword") neverTypes.add(node.id.name);
|
|
26
|
+
},
|
|
27
|
+
"Program:exit"() {
|
|
28
|
+
for (const node of exportSpecifiers) {
|
|
29
|
+
if (node.specifiers.some((specifier) => neverTypes.has(specifierName(specifier)))) {
|
|
30
|
+
context.report({ node, messageId: "placeholder" });
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
ExportNamedDeclaration(node) {
|
|
35
|
+
const declaration = node.declaration;
|
|
36
|
+
if (declaration?.type === "TSTypeAliasDeclaration") {
|
|
37
|
+
if (declaration.typeAnnotation?.type !== "TSNeverKeyword") return;
|
|
29
38
|
context.report({ node, messageId: "placeholder" });
|
|
39
|
+
return;
|
|
30
40
|
}
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
)
|
|
45
|
-
) {
|
|
46
|
-
exportSpecifiers.push(node);
|
|
47
|
-
}
|
|
48
|
-
},
|
|
49
|
-
};
|
|
50
|
-
},
|
|
41
|
+
if (
|
|
42
|
+
!node.source &&
|
|
43
|
+
node.specifiers?.some(
|
|
44
|
+
(specifier) => node.exportKind === "type" || specifier.exportKind === "type",
|
|
45
|
+
)
|
|
46
|
+
) {
|
|
47
|
+
exportSpecifiers.push(node);
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
};
|
|
51
|
+
},
|
|
52
|
+
),
|
|
53
|
+
{ __test: { specifierName } },
|
|
51
54
|
);
|
|
@@ -49,29 +49,32 @@ function hasSequentialOption(node) {
|
|
|
49
49
|
);
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
-
module.exports =
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
52
|
+
module.exports = Object.assign(
|
|
53
|
+
rule(
|
|
54
|
+
{
|
|
55
|
+
type: "problem",
|
|
56
|
+
docs: { description: "disallow Vitest sequential test modifiers", recommended: false },
|
|
57
|
+
schema: [],
|
|
58
|
+
messages: { sequential: "Use parallel tests instead of .sequential." },
|
|
59
|
+
},
|
|
60
|
+
(context) => {
|
|
61
|
+
let usesVitestImport = false;
|
|
62
|
+
return {
|
|
63
|
+
ImportDeclaration(node) {
|
|
64
|
+
if (node.source.value === "vitest") usesVitestImport = true;
|
|
65
|
+
},
|
|
66
|
+
CallExpression(node) {
|
|
67
|
+
if (!isTestFile(context.filename) && !usesVitestImport) return;
|
|
68
|
+
if (!TEST_NAMES.has(rootTestName(node.callee))) return;
|
|
69
|
+
if (node.callee.type === "MemberExpression" && hasSequentialMember(node.callee)) {
|
|
70
|
+
context.report({ node: node.callee, messageId: "sequential" });
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
if (!hasSequentialOption(node)) return;
|
|
69
74
|
context.report({ node: node.callee, messageId: "sequential" });
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
};
|
|
76
|
-
},
|
|
75
|
+
},
|
|
76
|
+
};
|
|
77
|
+
},
|
|
78
|
+
),
|
|
79
|
+
{ __test: { hasSequentialMember } },
|
|
77
80
|
);
|
|
@@ -52,48 +52,50 @@ function numericLiteral(node) {
|
|
|
52
52
|
}
|
|
53
53
|
|
|
54
54
|
function propertyName(node) {
|
|
55
|
-
if (!node) return null;
|
|
56
55
|
if (node.type === "Identifier") return node.name;
|
|
57
56
|
return node.type === "Literal" ? String(node.value) : null;
|
|
58
57
|
}
|
|
59
58
|
|
|
60
|
-
module.exports =
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
59
|
+
module.exports = Object.assign(
|
|
60
|
+
rule(
|
|
61
|
+
{
|
|
62
|
+
type: "problem",
|
|
63
|
+
docs: { description: "cap Playwright assertion timeouts", recommended: false },
|
|
64
|
+
schema: [
|
|
65
|
+
{
|
|
66
|
+
type: "object",
|
|
67
|
+
properties: { max: { type: "number" } },
|
|
68
|
+
additionalProperties: false,
|
|
69
|
+
},
|
|
70
|
+
],
|
|
71
|
+
messages: {
|
|
72
|
+
timeout:
|
|
73
|
+
"Assertion timeout must not exceed {{max}} ms. Increase the test timeout or fix the slow condition instead.",
|
|
69
74
|
},
|
|
70
|
-
],
|
|
71
|
-
messages: {
|
|
72
|
-
timeout:
|
|
73
|
-
"Assertion timeout must not exceed {{max}} ms. Increase the test timeout or fix the slow condition instead.",
|
|
74
75
|
},
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
|
|
76
|
+
(context) => {
|
|
77
|
+
const max = context.options[0]?.max ?? DEFAULT_MAX_TIMEOUT_MS;
|
|
78
|
+
let isPlaywrightFile = isPlaywrightPath(context.filename);
|
|
79
|
+
return {
|
|
80
|
+
ImportDeclaration(node) {
|
|
81
|
+
if (node.source.value === "@playwright/test") isPlaywrightFile = true;
|
|
82
|
+
},
|
|
83
|
+
CallExpression(node) {
|
|
84
|
+
if (!isPlaywrightFile) return;
|
|
85
|
+
if (node.callee.type !== "MemberExpression" || !isExpectChain(node.callee)) return;
|
|
86
|
+
const method = propertyName(node.callee.property);
|
|
87
|
+
if (!TIMEOUT_MATCHERS.has(method)) return;
|
|
88
|
+
const options = node.arguments.at(-1);
|
|
89
|
+
if (options?.type !== "ObjectExpression") return;
|
|
90
|
+
const timeout = options.properties.find(
|
|
91
|
+
(property) => property.type === "Property" && propertyName(property.key) === "timeout",
|
|
92
|
+
);
|
|
93
|
+
if (numericLiteral(timeout?.value) > max) {
|
|
94
|
+
context.report({ node, messageId: "timeout", data: { max } });
|
|
95
|
+
}
|
|
96
|
+
},
|
|
97
|
+
};
|
|
98
|
+
},
|
|
99
|
+
),
|
|
100
|
+
{ __test: { propertyName } },
|
|
99
101
|
);
|
|
@@ -28,7 +28,6 @@ function isPlaywrightPath(filename) {
|
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
function propertyName(node) {
|
|
31
|
-
if (!node) return null;
|
|
32
31
|
return node.type === "Literal" ? String(node.value) : node.name;
|
|
33
32
|
}
|
|
34
33
|
|
|
@@ -132,7 +131,6 @@ function hasQueryParamBoundary(literal, param, isRegex) {
|
|
|
132
131
|
// runtime and must not be mistaken for an actual cursor-param check. The value-wrapper expressions
|
|
133
132
|
// above recurse into their runtime `.expression` only, never a type operand.
|
|
134
133
|
function collectLiteralStrings(node, results) {
|
|
135
|
-
if (!node) return;
|
|
136
134
|
if (node.type.startsWith("TS")) {
|
|
137
135
|
if (TS_VALUE_WRAPPER_TYPES.has(node.type)) collectLiteralStrings(node.expression, results);
|
|
138
136
|
return;
|
|
@@ -158,7 +156,6 @@ function staticStringValue(node) {
|
|
|
158
156
|
}
|
|
159
157
|
|
|
160
158
|
function collectSearchParamsAccessorArgs(node, results) {
|
|
161
|
-
if (!node) return;
|
|
162
159
|
if (node.type === "CallExpression" && isSearchParamsAccessorCall(node)) {
|
|
163
160
|
const value = staticStringValue(node.arguments[0]);
|
|
164
161
|
if (value !== null) results.push(value);
|
|
@@ -13,7 +13,7 @@ function isIdentifier(node) {
|
|
|
13
13
|
|
|
14
14
|
function definePattern(pattern, props, defineBinding, defineReactNode) {
|
|
15
15
|
if (!isObjectPattern(pattern)) return;
|
|
16
|
-
for (const property of pattern.properties
|
|
16
|
+
for (const property of pattern.properties) {
|
|
17
17
|
if (property.type !== "Property") continue;
|
|
18
18
|
const name = keyName(property.key);
|
|
19
19
|
if (isIdentifier(property.value)) {
|
|
@@ -63,7 +63,7 @@ module.exports = rule(
|
|
|
63
63
|
function propsForType(type) {
|
|
64
64
|
if (type && type.type === "TSTypeLiteral") {
|
|
65
65
|
const props = new Set();
|
|
66
|
-
for (const member of type.members
|
|
66
|
+
for (const member of type.members) {
|
|
67
67
|
if (member.type !== "TSPropertySignature" || !isReactNodeType(typeAnnotation(member))) {
|
|
68
68
|
continue;
|
|
69
69
|
}
|
|
@@ -161,7 +161,7 @@ module.exports = rule(
|
|
|
161
161
|
|
|
162
162
|
function enterFunction(node) {
|
|
163
163
|
pushScope("function");
|
|
164
|
-
for (const param of node.params
|
|
164
|
+
for (const param of node.params) {
|
|
165
165
|
defineParam(param);
|
|
166
166
|
}
|
|
167
167
|
}
|
|
@@ -41,48 +41,51 @@ function isDefaultReExportAlias(node, specifier) {
|
|
|
41
41
|
return exportName(specifier.local) === "default" && Boolean(node.source);
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
-
module.exports =
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
messages: {
|
|
52
|
-
renamed:
|
|
53
|
-
"Do not rename value exports. Export the original name or rename the declaration itself so agents can trace symbols directly.",
|
|
54
|
-
},
|
|
55
|
-
schema: [
|
|
56
|
-
{
|
|
57
|
-
type: "object",
|
|
58
|
-
properties: {
|
|
59
|
-
allowDefaultReExports: { type: "boolean" },
|
|
60
|
-
includePathPatterns: { type: "array", items: { type: "string" } },
|
|
61
|
-
},
|
|
62
|
-
additionalProperties: false,
|
|
44
|
+
module.exports = Object.assign(
|
|
45
|
+
rule(
|
|
46
|
+
{
|
|
47
|
+
type: "problem",
|
|
48
|
+
docs: {
|
|
49
|
+
description: "disallow value export renaming",
|
|
50
|
+
recommended: true,
|
|
63
51
|
},
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
const options = context.options[0] || {};
|
|
68
|
-
const patterns = pathPatterns(options);
|
|
69
|
-
if (!shouldCheckFile(context, options, patterns)) return {};
|
|
70
|
-
return {
|
|
71
|
-
ExportNamedDeclaration(node) {
|
|
72
|
-
for (const specifier of node.specifiers || []) {
|
|
73
|
-
if (specifier.type !== "ExportSpecifier" || isTypeExport(node, specifier)) {
|
|
74
|
-
continue;
|
|
75
|
-
}
|
|
76
|
-
if (options.allowDefaultReExports && isDefaultReExportAlias(node, specifier)) {
|
|
77
|
-
continue;
|
|
78
|
-
}
|
|
79
|
-
const local = exportName(specifier.local);
|
|
80
|
-
const exported = exportName(specifier.exported);
|
|
81
|
-
if (local && exported && local !== exported) {
|
|
82
|
-
context.report({ node: specifier, messageId: "renamed" });
|
|
83
|
-
}
|
|
84
|
-
}
|
|
52
|
+
messages: {
|
|
53
|
+
renamed:
|
|
54
|
+
"Do not rename value exports. Export the original name or rename the declaration itself so agents can trace symbols directly.",
|
|
85
55
|
},
|
|
86
|
-
|
|
87
|
-
|
|
56
|
+
schema: [
|
|
57
|
+
{
|
|
58
|
+
type: "object",
|
|
59
|
+
properties: {
|
|
60
|
+
allowDefaultReExports: { type: "boolean" },
|
|
61
|
+
includePathPatterns: { type: "array", items: { type: "string" } },
|
|
62
|
+
},
|
|
63
|
+
additionalProperties: false,
|
|
64
|
+
},
|
|
65
|
+
],
|
|
66
|
+
},
|
|
67
|
+
(context) => {
|
|
68
|
+
const options = context.options[0] || {};
|
|
69
|
+
const patterns = pathPatterns(options);
|
|
70
|
+
if (!shouldCheckFile(context, options, patterns)) return {};
|
|
71
|
+
return {
|
|
72
|
+
ExportNamedDeclaration(node) {
|
|
73
|
+
for (const specifier of node.specifiers) {
|
|
74
|
+
if (specifier.type !== "ExportSpecifier" || isTypeExport(node, specifier)) {
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
if (options.allowDefaultReExports && isDefaultReExportAlias(node, specifier)) {
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
const local = exportName(specifier.local);
|
|
81
|
+
const exported = exportName(specifier.exported);
|
|
82
|
+
if (local && exported && local !== exported) {
|
|
83
|
+
context.report({ node: specifier, messageId: "renamed" });
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
},
|
|
87
|
+
};
|
|
88
|
+
},
|
|
89
|
+
),
|
|
90
|
+
{ __test: { exportName } },
|
|
88
91
|
);
|
|
@@ -63,14 +63,7 @@ function isSelfCall(node, call) {
|
|
|
63
63
|
if (!callee) return false;
|
|
64
64
|
const wrapper = variableWrapperName(node);
|
|
65
65
|
if (wrapper) return wrapper === callee;
|
|
66
|
-
|
|
67
|
-
const parent = node.parent;
|
|
68
|
-
return (
|
|
69
|
-
parent &&
|
|
70
|
-
parent.type === "VariableDeclarator" &&
|
|
71
|
-
parent.id.type === "Identifier" &&
|
|
72
|
-
parent.id.name === callee
|
|
73
|
-
);
|
|
66
|
+
return Boolean(node.id && node.id.name === callee);
|
|
74
67
|
}
|
|
75
68
|
|
|
76
69
|
function assignmentWrapperName(node) {
|
|
@@ -112,28 +105,31 @@ function reportIfAlias(node, context) {
|
|
|
112
105
|
}
|
|
113
106
|
}
|
|
114
107
|
|
|
115
|
-
module.exports =
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
108
|
+
module.exports = Object.assign(
|
|
109
|
+
rule(
|
|
110
|
+
{
|
|
111
|
+
type: "problem",
|
|
112
|
+
docs: {
|
|
113
|
+
description: "disallow function wrappers that only alias another function",
|
|
114
|
+
recommended: true,
|
|
115
|
+
},
|
|
116
|
+
schema: [],
|
|
117
|
+
messages: {
|
|
118
|
+
alias:
|
|
119
|
+
"Do not create a function that only aliases another function call. Export or call the original function name directly so agents can trace behavior.",
|
|
120
|
+
},
|
|
121
121
|
},
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
FunctionExpression(node) {
|
|
136
|
-
reportIfAlias(node, context);
|
|
137
|
-
},
|
|
138
|
-
}),
|
|
122
|
+
(context) => ({
|
|
123
|
+
ArrowFunctionExpression(node) {
|
|
124
|
+
reportIfAlias(node, context);
|
|
125
|
+
},
|
|
126
|
+
FunctionDeclaration(node) {
|
|
127
|
+
reportIfAlias(node, context);
|
|
128
|
+
},
|
|
129
|
+
FunctionExpression(node) {
|
|
130
|
+
reportIfAlias(node, context);
|
|
131
|
+
},
|
|
132
|
+
}),
|
|
133
|
+
),
|
|
134
|
+
{ __test: { onlyCallExpression, isSelfCall } },
|
|
139
135
|
);
|
|
@@ -58,7 +58,7 @@ module.exports = Object.assign(
|
|
|
58
58
|
const popScope = () => scopes.pop();
|
|
59
59
|
function enterFunction(node) {
|
|
60
60
|
scopes.push(createScope("function"));
|
|
61
|
-
for (const param of node.params
|
|
61
|
+
for (const param of node.params) defineParam(param);
|
|
62
62
|
}
|
|
63
63
|
function defineBinding(name, scope = currentScope()) {
|
|
64
64
|
scope.bindings.add(name);
|
|
@@ -82,7 +82,7 @@ module.exports = Object.assign(
|
|
|
82
82
|
}
|
|
83
83
|
}
|
|
84
84
|
function definePatternBindings(pattern, props, scope = currentScope(), useExisting = false) {
|
|
85
|
-
for (const property of pattern.properties
|
|
85
|
+
for (const property of pattern.properties) {
|
|
86
86
|
if (property.type === "RestElement" && isIdentifier(property.argument)) {
|
|
87
87
|
defineObject(property.argument.name, props, scope);
|
|
88
88
|
continue;
|