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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-plugin-no-mistakes",
3
- "version": "0.61.11",
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.68.0",
25
- "@vitest/coverage-v8": "^4.1.11",
26
- "eslint": "^10.9.1",
27
- "oxlint": "^1.80.0",
28
- "vitest": "^4.1.11"
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"
@@ -195,4 +195,5 @@ module.exports = {
195
195
  collectExportedComponents,
196
196
  normalizedComponentOptions,
197
197
  shouldCheckComponent,
198
+ uniqueComponents,
198
199
  };
@@ -210,4 +210,5 @@ function integrationAllows(specifier, factory, mock, context, options) {
210
210
 
211
211
  module.exports = {
212
212
  integrationAllows,
213
+ mockedExportNames,
213
214
  };
@@ -48,49 +48,52 @@ function collectPatternNames(node, names = new Set()) {
48
48
  return names;
49
49
  }
50
50
 
51
- module.exports = rule(
52
- {
53
- type: "problem",
54
- docs: {
55
- description: "restrict Next.js metadata exports to route segment files",
56
- recommended: false,
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
- schema: [],
59
- messages: {
60
- location:
61
- "metadata and generateMetadata exports are only allowed in Next.js route segment files.",
62
- },
63
- },
64
- (context) => ({
65
- ExportNamedDeclaration(node) {
66
- if (!isNextFile(context.filename)) return;
67
- if (isAllowedFile(context.filename)) return;
68
- if (node.declaration?.type === "VariableDeclaration") {
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.declarations.some((declaration) =>
71
- [...collectPatternNames(declaration.id), declarationName(declaration)].some((name) =>
72
- METADATA_EXPORTS.has(name),
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
- if (
80
- node.declaration?.type === "FunctionDeclaration" &&
81
- METADATA_EXPORTS.has(declarationName(node.declaration))
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 = rule(
69
- {
70
- type: "problem",
71
- docs: { description: "prefer next/script over raw script JSX tags", recommended: false },
72
- schema: [
73
- {
74
- type: "object",
75
- properties: {
76
- allowInlineScriptIds: { type: "array", items: { type: "string" } },
77
- allowInlineScriptIdPatterns: { type: "array", items: { type: "string" } },
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
- additionalProperties: false,
80
- },
81
- ],
82
- messages: { script: "Use next/script instead of a raw <script> tag." },
83
- },
84
- (context) => {
85
- const options = context.options[0] || {};
86
- const patterns = allowedIdPatterns(options);
87
- let isNextFile = isNextPath(context.filename);
88
- return {
89
- ImportDeclaration(node) {
90
- if (typeof node.source.value === "string" && node.source.value.startsWith("next/")) {
91
- isNextFile = true;
92
- }
93
- },
94
- JSXOpeningElement(node) {
95
- if (!isNextFile) return;
96
- if (node.name.type !== "JSXIdentifier" || node.name.name !== "script") return;
97
- if (isJsonLdScript(node)) return;
98
- if (isAllowedInlineScript(node, options, patterns)) return;
99
- context.report({ node, messageId: "script" });
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
  );
@@ -190,4 +190,6 @@ module.exports = {
190
190
  recordAssignmentTag,
191
191
  recordVariableTag,
192
192
  setOrClearTag,
193
+ collectPossibleTag,
194
+ applyObjectPatternTagAddOnly,
193
195
  };
@@ -84,7 +84,7 @@ function createAliasScopeTracker() {
84
84
  }
85
85
 
86
86
  function exitsSwitchCase(node) {
87
- return node.consequent?.some(terminatesCase) ?? false;
87
+ return node.consequent?.some(terminatesCase) === true;
88
88
  }
89
89
 
90
90
  function exitSwitchCase(node) {
@@ -112,7 +112,7 @@ module.exports = Object.assign(
112
112
  child.type === "BreakStatement" ||
113
113
  child.type === "ReturnStatement" ||
114
114
  child.type === "ThrowStatement",
115
- ) ?? false
115
+ ) === true
116
116
  );
117
117
  }
118
118
 
@@ -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 = rule(
10
- {
11
- type: "problem",
12
- docs: { description: "disallow exported never placeholder type aliases", recommended: false },
13
- schema: [],
14
- messages: {
15
- placeholder:
16
- "Do not export placeholder never types. Define the real type or remove the export.",
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
- "Program:exit"() {
27
- for (const node of exportSpecifiers) {
28
- if (node.specifiers.some((specifier) => neverTypes.has(specifierName(specifier)))) {
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
- ExportNamedDeclaration(node) {
34
- const declaration = node.declaration;
35
- if (declaration?.type === "TSTypeAliasDeclaration") {
36
- if (declaration.typeAnnotation?.type !== "TSNeverKeyword") return;
37
- context.report({ node, messageId: "placeholder" });
38
- return;
39
- }
40
- if (
41
- !node.source &&
42
- node.specifiers?.some(
43
- (specifier) => node.exportKind === "type" || specifier.exportKind === "type",
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 = rule(
53
- {
54
- type: "problem",
55
- docs: { description: "disallow Vitest sequential test modifiers", recommended: false },
56
- schema: [],
57
- messages: { sequential: "Use parallel tests instead of .sequential." },
58
- },
59
- (context) => {
60
- let usesVitestImport = false;
61
- return {
62
- ImportDeclaration(node) {
63
- if (node.source.value === "vitest") usesVitestImport = true;
64
- },
65
- CallExpression(node) {
66
- if (!isTestFile(context.filename) && !usesVitestImport) return;
67
- if (!TEST_NAMES.has(rootTestName(node.callee))) return;
68
- if (node.callee.type === "MemberExpression" && hasSequentialMember(node.callee)) {
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
- return;
71
- }
72
- if (!hasSequentialOption(node)) return;
73
- context.report({ node: node.callee, messageId: "sequential" });
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 = 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,
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
- (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
- },
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);
@@ -126,3 +126,5 @@ function matchesAny(name, matchers) {
126
126
  }),
127
127
  );
128
128
  }
129
+
130
+ module.exports.__test = { compileMatchers };
@@ -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
  }
@@ -164,4 +164,4 @@ function possibleResourceExitBeforeMatcher(node, matcher, resourceLoop) {
164
164
  return false;
165
165
  }
166
166
 
167
- module.exports = { possibleResourceExitBeforeMatcher };
167
+ module.exports = { possibleResourceExitBeforeMatcher, branchOutcome };
@@ -146,3 +146,5 @@ module.exports = rule(
146
146
  };
147
147
  },
148
148
  );
149
+
150
+ module.exports.__test = { propertyName };
@@ -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 = rule(
45
- {
46
- type: "problem",
47
- docs: {
48
- description: "disallow value export renaming",
49
- recommended: true,
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
- (context) => {
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
- if (node.id && node.id.name === callee) return true;
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 = rule(
116
- {
117
- type: "problem",
118
- docs: {
119
- description: "disallow function wrappers that only alias another function",
120
- recommended: true,
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
- schema: [],
123
- messages: {
124
- alias:
125
- "Do not create a function that only aliases another function call. Export or call the original function name directly so agents can trace behavior.",
126
- },
127
- },
128
- (context) => ({
129
- ArrowFunctionExpression(node) {
130
- reportIfAlias(node, context);
131
- },
132
- FunctionDeclaration(node) {
133
- reportIfAlias(node, context);
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 || []) defineParam(param);
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;