eslint-plugin-no-mistakes 0.93.0 → 0.95.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 (109) hide show
  1. package/README.md +7 -0
  2. package/package.json +3 -3
  3. package/src/component-functions.js +39 -7
  4. package/src/exported-component-definitions.js +32 -0
  5. package/src/exported-components.js +22 -22
  6. package/src/fetch-call.js +102 -0
  7. package/src/helpers.js +3 -34
  8. package/src/index.js +5 -0
  9. package/src/rule-suppression.js +1 -0
  10. package/src/rules/argv-main-values.js +47 -14
  11. package/src/rules/async-ast.js +23 -0
  12. package/src/rules/async-call-disposition.js +7 -5
  13. package/src/rules/async-target-bindings.js +24 -0
  14. package/src/rules/async-targets.js +36 -12
  15. package/src/rules/await-array-methods.js +21 -24
  16. package/src/rules/gh-api-argument-safety.js +3 -2
  17. package/src/rules/imported-call-options.js +6 -2
  18. package/src/rules/module-mock-boundary.js +6 -1
  19. package/src/rules/module-mock-call-apply.js +18 -12
  20. package/src/rules/module-mock-framework.js +61 -9
  21. package/src/rules/module-mock-helpers.js +69 -8
  22. package/src/rules/module-mock-preserve-aliases.js +77 -22
  23. package/src/rules/module-mock-preserve-exports.js +21 -1
  24. package/src/rules/nextjs-static-fetch-method.js +4 -3
  25. package/src/rules/nextjs-static-fetch-url.js +4 -3
  26. package/src/rules/no-global-fetch-outside-helper-bindings.js +9 -0
  27. package/src/rules/no-global-fetch-outside-helper-helpers.js +2 -2
  28. package/src/rules/no-global-fetch-outside-helper.js +5 -2
  29. package/src/rules/no-import-only-test-files.js +69 -32
  30. package/src/rules/no-inline-noop-promise-catch-helpers.js +30 -13
  31. package/src/rules/no-object-mutation-ast.js +52 -0
  32. package/src/rules/no-object-mutation-bind.js +177 -0
  33. package/src/rules/no-object-mutation-build.js +117 -0
  34. package/src/rules/no-object-mutation-call.js +174 -0
  35. package/src/rules/no-object-mutation-class.js +134 -0
  36. package/src/rules/no-object-mutation-expr.js +137 -0
  37. package/src/rules/no-object-mutation-fn.js +199 -0
  38. package/src/rules/no-object-mutation-forward.js +146 -0
  39. package/src/rules/no-object-mutation-invoke.js +116 -0
  40. package/src/rules/no-object-mutation-join.js +76 -0
  41. package/src/rules/no-object-mutation-loop.js +116 -0
  42. package/src/rules/no-object-mutation-model.js +9 -0
  43. package/src/rules/no-object-mutation-namespace.js +91 -0
  44. package/src/rules/no-object-mutation-read.js +158 -0
  45. package/src/rules/no-object-mutation-runtime.js +7 -0
  46. package/src/rules/no-object-mutation-scan.js +173 -0
  47. package/src/rules/no-object-mutation-seed.js +109 -0
  48. package/src/rules/no-object-mutation-syntax.js +64 -0
  49. package/src/rules/no-object-mutation-values.js +134 -0
  50. package/src/rules/no-object-mutation-write.js +109 -0
  51. package/src/rules/no-object-mutation.js +59 -0
  52. package/src/rules/no-vitest-sequential.js +165 -36
  53. package/src/rules/nullable-option-defaults-helpers.js +18 -4
  54. package/src/rules/playwright-assertion-timeout-cap.js +79 -14
  55. package/src/rules/playwright-interactive-names.js +125 -0
  56. package/src/rules/playwright-literals.js +8 -3
  57. package/src/rules/playwright-no-hoisted-unique-token.js +28 -20
  58. package/src/rules/playwright-no-raw-scroll-pagination.js +25 -55
  59. package/src/rules/playwright-prefer-get-by-test-id.js +14 -5
  60. package/src/rules/playwright-require-exported-component-attribute.js +8 -2
  61. package/src/rules/playwright-require-interactive-test-id.js +30 -65
  62. package/src/rules/playwright-scroll-call.js +53 -0
  63. package/src/rules/playwright-selector-priority.js +8 -5
  64. package/src/rules/playwright-static-call.js +209 -0
  65. package/src/rules/playwright-test-timeout-bindings.js +0 -1
  66. package/src/rules/playwright-timeout-carriers.js +0 -1
  67. package/src/rules/postgres-correlated-completion.js +89 -0
  68. package/src/rules/postgres-no-async-sql-builder-array-map.js +51 -0
  69. package/src/rules/postgres-no-async-sql-builder-bindings.js +99 -0
  70. package/src/rules/postgres-no-async-sql-builder-callable.js +73 -0
  71. package/src/rules/postgres-no-async-sql-builder-calls.js +189 -0
  72. package/src/rules/postgres-no-async-sql-builder-completion.js +43 -0
  73. package/src/rules/postgres-no-async-sql-builder-constants.js +189 -0
  74. package/src/rules/postgres-no-async-sql-builder-import.js +60 -0
  75. package/src/rules/postgres-no-async-sql-builder-match.js +189 -0
  76. package/src/rules/postgres-no-async-sql-builder-returns.js +198 -0
  77. package/src/rules/postgres-no-async-sql-builder-types.js +211 -0
  78. package/src/rules/postgres-no-async-sql-builder.js +117 -0
  79. package/src/rules/postgres-no-correlated-sql-builder-append.js +161 -0
  80. package/src/rules/postgres-no-correlated-sql-builder.js +129 -0
  81. package/src/rules/postgres-no-executor-adapter-binding.js +108 -0
  82. package/src/rules/postgres-no-executor-adapter-forward.js +182 -0
  83. package/src/rules/postgres-no-executor-adapter-logic.js +172 -0
  84. package/src/rules/postgres-no-executor-adapter-member.js +152 -0
  85. package/src/rules/postgres-no-executor-adapter-passed.js +110 -0
  86. package/src/rules/postgres-no-executor-adapter-ref.js +145 -0
  87. package/src/rules/postgres-no-executor-adapter.js +170 -0
  88. package/src/rules/postgres-no-nonliteral-sql-registry-key.js +156 -0
  89. package/src/rules/postgres-runtime-helpers.js +35 -5
  90. package/src/rules/react-no-iife-in-jsx.js +46 -17
  91. package/src/rules/react-no-nullish-react-node.js +10 -4
  92. package/src/rules/react-no-use-promise-resolve.js +126 -65
  93. package/src/rules/require-options-on-imported-call.js +38 -15
  94. package/src/rules/required-call-selectors.js +12 -5
  95. package/src/rules/server-require-nullable-fetch-wrapper.js +63 -6
  96. package/src/rules/test-message-expect.js +152 -0
  97. package/src/rules/test-no-delayed-rejects-chains.js +5 -2
  98. package/src/rules/test-no-delayed-rejects-flow.js +7 -2
  99. package/src/rules/test-no-delayed-rejects.js +24 -32
  100. package/src/rules/test-no-error-message-matching.js +36 -43
  101. package/src/rules/test-no-shared-state-aliases.js +121 -1
  102. package/src/rules/test-no-shared-state-analysis.js +10 -2
  103. package/src/rules/test-no-shared-state-callees.js +64 -16
  104. package/src/rules/test-no-shared-state-mutations.js +3 -1
  105. package/src/rules/test-no-shared-state-rule-helpers.js +34 -5
  106. package/src/rules/test-no-shared-state.js +39 -50
  107. package/src/rules/test-no-skips-bindings.js +87 -31
  108. package/src/rules/test-no-skips.js +142 -1
  109. package/src/rules/vitest-mock-test-file-naming.js +95 -38
package/README.md CHANGED
@@ -47,6 +47,13 @@ See [options and examples](../../docs/eslint-rules/no-comment-phrases.md).
47
47
  `forbidden-member-chains` is an opt-in rule for configured static member-access chains.
48
48
  See [options and examples](../../docs/eslint-rules/forbidden-member-chains.md).
49
49
 
50
+ `no-object-mutation` is an opt-in rule for shared object writes and mutating calls.
51
+ See [options and examples](../../docs/eslint-rules/no-object-mutation.md).
52
+
53
+ `postgres-no-async-sql-builder` is an opt-in rule for async SQL builders that never wait.
54
+ It suggests removing `async` only when the visible contract allows a synchronous return, and it never autofixes.
55
+ See [options and examples](../../docs/eslint-rules/postgres-no-async-sql-builder.md).
56
+
50
57
  `require-options-on-imported-call` requires a static options object on configured
51
58
  imports, globals with no local binding, and terminal methods with a matching receiver shape.
52
59
  See [options and examples](../../docs/eslint-rules/require-options-on-imported-call.md).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-plugin-no-mistakes",
3
- "version": "0.93.0",
3
+ "version": "0.95.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": {
@@ -22,10 +22,10 @@
22
22
  },
23
23
  "devDependencies": {
24
24
  "@typescript-eslint/parser": "^8.71.1",
25
- "@vitest/coverage-v8": "^5.0.3",
25
+ "@vitest/coverage-v8": "^5.0.4",
26
26
  "eslint": "^10.12.0",
27
27
  "oxlint": "^1.87.0",
28
- "vitest": "^5.0.3"
28
+ "vitest": "^5.0.4"
29
29
  },
30
30
  "peerDependencies": {
31
31
  "eslint": ">=9"
@@ -1,5 +1,7 @@
1
1
  "use strict";
2
2
 
3
+ const { literalString } = require("./helpers");
4
+
3
5
  function nonEmptyStrings(value, fallback) {
4
6
  if (!Array.isArray(value)) {
5
7
  return fallback;
@@ -23,7 +25,7 @@ function unwrapExpression(node, opts) {
23
25
  if (node.type !== "CallExpression" || !isConfiguredWrapper(node.callee, opts)) {
24
26
  return node;
25
27
  }
26
- for (const arg of node.arguments) {
28
+ for (const arg of wrapperArguments(node)) {
27
29
  const unwrapped = unwrapExpression(arg, opts);
28
30
  if (isFunctionNode(unwrapped)) {
29
31
  return unwrapped;
@@ -32,19 +34,49 @@ function unwrapExpression(node, opts) {
32
34
  return node;
33
35
  }
34
36
 
37
+ function wrapperArguments(node) {
38
+ const args = node.arguments || [];
39
+ const name = staticCalleeName(node.callee);
40
+ if (name !== "call" && name !== "apply") return args;
41
+ if (args.some((arg) => arg?.type === "SpreadElement")) return [];
42
+ if (name === "call") return args.slice(1);
43
+ const list = args[1];
44
+ if (
45
+ list?.type !== "ArrayExpression" ||
46
+ list.elements.some((element) => !element || element.type === "SpreadElement")
47
+ ) {
48
+ return [];
49
+ }
50
+ return list.elements;
51
+ }
52
+
35
53
  function isConfiguredWrapper(callee, opts) {
36
54
  const name = calleeName(callee);
37
55
  return Boolean(name && opts.wrappers.has(name));
38
56
  }
39
57
 
58
+ function staticCalleeName(node) {
59
+ if (!node || node.type !== "MemberExpression") return null;
60
+ if (node.computed && node.property?.type === "Identifier") return null;
61
+ if (!node.computed && typeof node.property?.name === "string") return node.property.name;
62
+ return literalString(node.property);
63
+ }
64
+
40
65
  function calleeName(node) {
41
- if (node.type === "Identifier") {
42
- return node.name;
43
- }
44
- if (node.type === "MemberExpression" && !node.computed) {
45
- return node.property.name;
66
+ let current = node;
67
+ for (let depth = 0; depth < 4; depth += 1) {
68
+ while (current?.type === "ChainExpression") current = current.expression;
69
+ if (current?.type === "MemberExpression") {
70
+ const name = staticCalleeName(current);
71
+ if (name === "call" || name === "apply") {
72
+ current = current.object;
73
+ continue;
74
+ }
75
+ return name;
76
+ }
77
+ break;
46
78
  }
47
- return null;
79
+ return current?.type === "Identifier" ? current.name : null;
48
80
  }
49
81
 
50
82
  function isFunctionNode(node) {
@@ -0,0 +1,32 @@
1
+ "use strict";
2
+
3
+ const { functionFromExpression } = require("./component-functions");
4
+
5
+ function collectTopLevelDefinition(statement, definitions, opts, aliases, exported) {
6
+ if (statement.type === "FunctionDeclaration" && statement.id) {
7
+ definitions.set(statement.id.name, { name: statement.id.name, fn: statement });
8
+ return;
9
+ }
10
+ if (statement.type !== "VariableDeclaration") {
11
+ return;
12
+ }
13
+ for (const declaration of statement.declarations) {
14
+ if (declaration.id.type !== "Identifier") {
15
+ continue;
16
+ }
17
+ const fn = functionFromExpression(declaration.init, opts);
18
+ if (fn) {
19
+ definitions.set(declaration.id.name, { name: declaration.id.name, fn });
20
+ continue;
21
+ }
22
+ if (declaration.init?.type === "Identifier" && declaration.parent?.kind === "const") {
23
+ aliases.push({
24
+ name: declaration.id.name,
25
+ target: declaration.init.name,
26
+ exported,
27
+ });
28
+ }
29
+ }
30
+ }
31
+
32
+ module.exports = { collectTopLevelDefinition };
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
 
3
3
  const { functionFromExpression, nonEmptyStrings } = require("./component-functions");
4
+ const { collectTopLevelDefinition } = require("./exported-component-definitions");
4
5
 
5
6
  const DEFAULT_COMPONENT_NAME_PATTERN = "^[A-Z]";
6
7
  const DEFAULT_WRAPPERS = ["memo", "forwardRef", "observer"];
@@ -66,17 +67,34 @@ function collectExportedComponents(program, opts) {
66
67
  const namedExports = new Set();
67
68
  const defaultExports = new Set();
68
69
  const components = [];
70
+ const aliases = [];
69
71
 
70
72
  for (const statement of program.body) {
71
- collectTopLevelDefinition(statement, definitions, opts);
73
+ collectTopLevelDefinition(statement, definitions, opts, aliases, false);
72
74
  if (statement.type === "ExportNamedDeclaration") {
73
- collectNamedExport(statement, definitions, namedExports, defaultExports, components, opts);
75
+ collectNamedExport(
76
+ statement,
77
+ definitions,
78
+ namedExports,
79
+ defaultExports,
80
+ components,
81
+ opts,
82
+ aliases,
83
+ );
74
84
  }
75
85
  if (statement.type === "ExportDefaultDeclaration") {
76
86
  collectDefaultExport(statement, definitions, defaultExports, components, opts);
77
87
  }
78
88
  }
79
89
 
90
+ for (const alias of aliases) {
91
+ const target = definitions.get(alias.target);
92
+ if (!target) continue;
93
+ const definition = { name: alias.name, fn: target.fn };
94
+ definitions.set(alias.name, definition);
95
+ if (alias.exported && opts.exportTypes.has("named")) components.push(definition);
96
+ }
97
+
80
98
  pushExportedDefinitions(namedExports, definitions, components, opts.exportTypes.has("named"));
81
99
  pushExportedDefinitions(defaultExports, definitions, components, opts.exportTypes.has("default"));
82
100
  return uniqueComponents(components);
@@ -94,25 +112,6 @@ function pushExportedDefinitions(names, definitions, components, enabled) {
94
112
  }
95
113
  }
96
114
 
97
- function collectTopLevelDefinition(statement, definitions, opts) {
98
- if (statement.type === "FunctionDeclaration" && statement.id) {
99
- definitions.set(statement.id.name, { name: statement.id.name, fn: statement });
100
- return;
101
- }
102
- if (statement.type !== "VariableDeclaration") {
103
- return;
104
- }
105
- for (const declaration of statement.declarations) {
106
- if (declaration.id.type !== "Identifier") {
107
- continue;
108
- }
109
- const fn = functionFromExpression(declaration.init, opts);
110
- if (fn) {
111
- definitions.set(declaration.id.name, { name: declaration.id.name, fn });
112
- }
113
- }
114
- }
115
-
116
115
  function collectNamedExport(
117
116
  statement,
118
117
  definitions,
@@ -120,6 +119,7 @@ function collectNamedExport(
120
119
  defaultExports,
121
120
  components,
122
121
  opts,
122
+ aliases,
123
123
  ) {
124
124
  if (statement.source) {
125
125
  return;
@@ -133,7 +133,7 @@ function collectNamedExport(
133
133
  return;
134
134
  }
135
135
  if (statement.declaration?.type === "VariableDeclaration") {
136
- collectTopLevelDefinition(statement.declaration, definitions, opts);
136
+ collectTopLevelDefinition(statement.declaration, definitions, opts, aliases, true);
137
137
  for (const declaration of statement.declaration.declarations) {
138
138
  const definition = definitions.get(declaration.id.name);
139
139
  if (declaration.id.type === "Identifier" && definition && opts.exportTypes.has("named")) {
@@ -0,0 +1,102 @@
1
+ "use strict";
2
+
3
+ const { unwrapExpression } = require("./rules/async-ast");
4
+ const { logicalCall, memberPropertyName, propertyName } = require("./rules/module-mock-helpers");
5
+
6
+ const LOCAL_BINDING_TYPES = new Set([
7
+ "Variable",
8
+ "Parameter",
9
+ "CatchClause",
10
+ "FunctionName",
11
+ "ImportBinding",
12
+ "ClassName",
13
+ ]);
14
+ const FETCH_ROOTS = new Set(["globalThis", "window", "self", "global"]);
15
+
16
+ function isFetchShadowed(scope) {
17
+ while (scope) {
18
+ const get = scope.set?.get;
19
+ const variable = typeof get === "function" ? get.call(scope.set, "fetch") : null;
20
+ if (variable) return variable.defs.some((def) => LOCAL_BINDING_TYPES.has(def.type));
21
+ if (!scope.set || typeof get !== "function") {
22
+ const fallback = scope.variables?.find((item) => item.name === "fetch");
23
+ if (fallback) return fallback.defs.some((def) => LOCAL_BINDING_TYPES.has(def.type));
24
+ }
25
+ scope = scope.upper;
26
+ }
27
+ return false;
28
+ }
29
+
30
+ function unboundRoot(node, context) {
31
+ if (node?.type !== "Identifier" || !FETCH_ROOTS.has(node.name)) return false;
32
+ let scope = context.sourceCode.getScope(node);
33
+ while (scope) {
34
+ const variable = scope.variables?.find((item) => item.name === node.name);
35
+ if (variable) return !variable.defs?.some((def) => LOCAL_BINDING_TYPES.has(def.type));
36
+ scope = scope.upper;
37
+ }
38
+ return true;
39
+ }
40
+
41
+ function bindingVariable(node, context) {
42
+ if (!context?.sourceCode?.getScope) return null;
43
+ let scope = context.sourceCode.getScope(node);
44
+ while (scope) {
45
+ const variable = scope.variables?.find((item) => item.name === node.name);
46
+ if (variable) return variable;
47
+ scope = scope.upper;
48
+ }
49
+ return null;
50
+ }
51
+
52
+ function fetchCallee(node, context, seen) {
53
+ node = unwrapExpression(node);
54
+ if (!node) return false;
55
+ const visiting = seen || new Set();
56
+ if (node.type === "MemberExpression") {
57
+ if (memberPropertyName(node) !== "fetch") return false;
58
+ return unboundRoot(unwrapExpression(node.object), context);
59
+ }
60
+ if (node.type === "Identifier") {
61
+ if (node.name === "fetch" && !isFetchShadowed(context?.sourceCode?.getScope?.(node)))
62
+ return true;
63
+ const variable = bindingVariable(node, context);
64
+ if (!variable || visiting.has(variable) || variable.defs.length !== 1) return false;
65
+ visiting.add(variable);
66
+ const def = variable.defs[0];
67
+ if (def.type !== "Variable" || def.parent?.kind !== "const") return false;
68
+ if (def.node.id.type === "Identifier") {
69
+ if (!def.node.init) return false;
70
+ return fetchCallee(def.node.init, context, visiting);
71
+ }
72
+ if (def.node.id.type !== "ObjectPattern") return false;
73
+ for (const item of def.node.id.properties) {
74
+ if (item.type === "Property") {
75
+ const value = item.value.type === "AssignmentPattern" ? item.value.left : item.value;
76
+ // `const { [name]: schedule }` names an expression, not the fetch property.
77
+ if (item.computed && item.key?.type === "Identifier") continue;
78
+ if (
79
+ value.type === "Identifier" &&
80
+ value.name === node.name &&
81
+ propertyName(item.key) === "fetch"
82
+ ) {
83
+ return unboundRoot(unwrapExpression(def.node.init), context);
84
+ }
85
+ }
86
+ }
87
+ return false;
88
+ }
89
+ return false;
90
+ }
91
+
92
+ function fetchInvocation(node, context) {
93
+ const invoked = logicalCall(node);
94
+ if (!invoked?.callee || !fetchCallee(invoked.callee, context)) return null;
95
+ return invoked.arguments;
96
+ }
97
+
98
+ function isFetchCall(node, context) {
99
+ return fetchInvocation(node, context) !== null;
100
+ }
101
+
102
+ module.exports = { fetchInvocation, isFetchCall };
package/src/helpers.js CHANGED
@@ -1,5 +1,7 @@
1
1
  "use strict";
2
2
 
3
+ const { fetchInvocation, isFetchCall } = require("./fetch-call");
4
+
3
5
  const DEFAULT_SELECTOR_ATTRIBUTES = ["data-testid", "data-pw"];
4
6
  const DEFAULT_NAMING_PATTERN = "^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$";
5
7
  const INTERACTIVE_ELEMENTS = new Set(["button", "input", "select", "textarea"]);
@@ -134,40 +136,6 @@ function selectorValueNode(attribute) {
134
136
  return expression && expression.type !== "JSXEmptyExpression" ? expression : null;
135
137
  }
136
138
 
137
- const LOCAL_BINDING_TYPES = new Set([
138
- "Variable",
139
- "Parameter",
140
- "CatchClause",
141
- "FunctionName",
142
- "ImportBinding",
143
- "ClassName",
144
- ]);
145
-
146
- function isFetchShadowed(scope) {
147
- while (scope) {
148
- const get = scope.set?.get;
149
- const variable = typeof get === "function" ? get.call(scope.set, "fetch") : null;
150
- if (variable) {
151
- return variable.defs.some((def) => LOCAL_BINDING_TYPES.has(def.type));
152
- }
153
- if (!scope.set || typeof get !== "function") {
154
- const fallback = scope.variables?.find((item) => item.name === "fetch");
155
- if (fallback) {
156
- return fallback.defs.some((def) => LOCAL_BINDING_TYPES.has(def.type));
157
- }
158
- }
159
- scope = scope.upper;
160
- }
161
- return false;
162
- }
163
-
164
- function isFetchCall(node, context) {
165
- if (node.callee.type !== "Identifier" || node.callee.name !== "fetch") {
166
- return false;
167
- }
168
- return !isFetchShadowed(context.sourceCode.getScope(node));
169
- }
170
-
171
139
  function rule(meta, create) {
172
140
  return { meta, create };
173
141
  }
@@ -179,6 +147,7 @@ module.exports = {
179
147
  canonicalAttribute,
180
148
  cssSelectorValues,
181
149
  INTERACTIVE_ELEMENTS,
150
+ fetchInvocation,
182
151
  isFetchCall,
183
152
  isSelectorAttribute,
184
153
  isSelectorCall,
package/src/index.js CHANGED
@@ -17,6 +17,7 @@ const rules = {
17
17
  "no-banned-import-outside-allowed-paths": require("./rules/no-banned-import-outside-allowed-paths"),
18
18
  "no-global-fetch-outside-helper": require("./rules/no-global-fetch-outside-helper"),
19
19
  "no-delete-property": require("./rules/no-delete-property"),
20
+ "no-object-mutation": require("./rules/no-object-mutation"),
20
21
  "no-import-only-test-files": require("./rules/no-import-only-test-files"),
21
22
  "no-inline-noop-promise-catch": require("./rules/no-inline-noop-promise-catch"),
22
23
  "no-placeholder-never-type-exports": require("./rules/no-placeholder-never-type-exports"),
@@ -37,7 +38,11 @@ const rules = {
37
38
  "react-no-iife-in-jsx": require("./rules/react-no-iife-in-jsx"),
38
39
  "playwright-unique": require("./rules/playwright-unique"),
39
40
  "postgres-cursor-call-contract": require("./rules/postgres-cursor-call-contract"),
41
+ "postgres-no-async-sql-builder": require("./rules/postgres-no-async-sql-builder"),
42
+ "postgres-no-correlated-sql-builder": require("./rules/postgres-no-correlated-sql-builder"),
43
+ "postgres-no-executor-adapter": require("./rules/postgres-no-executor-adapter"),
40
44
  "postgres-no-manual-transaction": require("./rules/postgres-no-manual-transaction"),
45
+ "postgres-no-nonliteral-sql-registry-key": require("./rules/postgres-no-nonliteral-sql-registry-key"),
41
46
  "postgres-no-unbounded-query-fanout": require("./rules/postgres-no-unbounded-query-fanout"),
42
47
  "react-no-nullish-react-node": require("./rules/react-no-nullish-react-node"),
43
48
  "react-no-use-promise-resolve": require("./rules/react-no-use-promise-resolve"),
@@ -6,6 +6,7 @@ function ruleSuppression(context, ruleId, suppressionOptions) {
6
6
  const lines = new Set();
7
7
  let file = false;
8
8
  const source = context.sourceCode;
9
+ if (!source?.ast?.tokens || !source.getAllComments) return () => false;
9
10
  const first = source.ast.tokens[0];
10
11
  const requireReason = suppressionOptions?.requireReason === true;
11
12
  for (const comment of source.getAllComments()) {
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
  const { unwrapExpression } = require("./async-ast");
3
3
  const { resolveVariable, memberPropertyName, isLocalRequire } = require("./async-target-bindings");
4
- const { literalString } = require("./module-mock-helpers");
4
+ const { literalString, logicalCall } = require("./module-mock-helpers");
5
5
 
6
6
  function globalName(node, name, context) {
7
7
  if (node?.type !== "Identifier" || node.name !== name) return false;
@@ -51,6 +51,25 @@ function createClassifier(context, wrappers) {
51
51
  if (nodeProcess(node, context)) return PROCESS;
52
52
  if (globalName(node, "__filename", context)) return MAIN;
53
53
  const def = variable.defs.length === 1 ? variable.defs[0] : null;
54
+ if (def?.type === "ImportBinding" && def.node?.type === "ImportSpecifier") {
55
+ const imported = def.node.imported;
56
+ const importedName = imported?.type === "Literal" ? String(imported.value) : imported?.name;
57
+ if (
58
+ importedName === "argv" &&
59
+ ["process", "node:process"].includes(def.parent?.source?.value)
60
+ ) {
61
+ return ARGUMENTS;
62
+ }
63
+ }
64
+ if (
65
+ def?.type === "Variable" &&
66
+ def.parent?.kind === "const" &&
67
+ def.node?.id?.type === "ObjectPattern"
68
+ ) {
69
+ const key = objectPatternKey(def.node.id, node.name);
70
+ if (key === "argv" && classify(def.node.init, depth + 1) === PROCESS) return ARGUMENTS;
71
+ return 0;
72
+ }
54
73
  if (
55
74
  def?.type !== "Variable" ||
56
75
  def.parent.kind !== "const" ||
@@ -75,13 +94,7 @@ function createClassifier(context, wrappers) {
75
94
  return MAIN;
76
95
  const object = classify(node.object, depth + 1);
77
96
  if (object === PROCESS && name === "argv") return ARGUMENTS;
78
- if (
79
- object === ARGUMENTS &&
80
- node.computed &&
81
- node.property.type === "Literal" &&
82
- node.property.value === 1
83
- )
84
- return ARGV;
97
+ if (object === ARGUMENTS && node.computed && staticIndexOne(node.property)) return ARGV;
85
98
  // A URL conversion's href preserves the same module/argv identity.
86
99
  if (
87
100
  name === "href" &&
@@ -91,19 +104,20 @@ function createClassifier(context, wrappers) {
91
104
  return object;
92
105
  }
93
106
  if (node.type === "CallExpression") {
94
- const callee = unwrapExpression(node.callee);
107
+ const invoked = logicalCall(node);
108
+ if (!invoked) return 0;
109
+ const callee = unwrapExpression(invoked.callee);
95
110
  if (
96
111
  callee?.type === "MemberExpression" &&
97
112
  memberPropertyName(callee) === "at" &&
98
113
  classify(callee.object, depth + 1) === ARGUMENTS &&
99
- node.arguments.length === 1 &&
100
- node.arguments[0].type === "Literal" &&
101
- node.arguments[0].value === 1
114
+ invoked.arguments.length === 1 &&
115
+ staticIndexOne(invoked.arguments[0])
102
116
  )
103
117
  return ARGV;
104
- if (node.arguments.length !== 1 || node.arguments[0].type === "SpreadElement") return 0;
118
+ if (invoked.arguments.length !== 1 || invoked.arguments[0].type === "SpreadElement") return 0;
105
119
  if (globalName(callee, "Boolean", context) || wrappers.resolveCallTarget(node)) {
106
- const value = classify(node.arguments[0], depth + 1);
120
+ const value = classify(invoked.arguments[0], depth + 1);
107
121
  return value === ARGV || value === MAIN ? value : 0;
108
122
  }
109
123
  }
@@ -111,4 +125,23 @@ function createClassifier(context, wrappers) {
111
125
  }
112
126
  return (node) => classify(node);
113
127
  }
128
+ function staticIndexOne(node) {
129
+ if (!node) return false;
130
+ if (node.type === "Literal") return node.value === 1 || node.value === "1";
131
+ return literalString(node) === "1";
132
+ }
133
+
134
+ function objectPatternKey(pattern, name) {
135
+ for (const property of pattern.properties) {
136
+ if (property.type !== "Property") continue;
137
+ const value =
138
+ property.value.type === "AssignmentPattern" ? property.value.left : property.value;
139
+ if (value.type !== "Identifier" || value.name !== name) continue;
140
+ if (property.key.type === "Identifier" && !property.computed) return property.key.name;
141
+ if (property.key.type === "Literal") return String(property.key.value);
142
+ return literalString(property.key);
143
+ }
144
+ return null;
145
+ }
146
+
114
147
  module.exports = { createClassifier, ARGV, MAIN };
@@ -6,6 +6,7 @@ const transparentExpressionTypes = new Set([
6
6
  "TSAsExpression",
7
7
  "TSTypeAssertion",
8
8
  "TSSatisfiesExpression",
9
+ "TSInstantiationExpression",
9
10
  ]);
10
11
 
11
12
  function isFunction(node) {
@@ -24,6 +25,18 @@ function findContainingFunction(node) {
24
25
  }
25
26
  }
26
27
 
28
+ function findContainingRoot(node) {
29
+ return findContainingFunction(node) ?? findProgram(node);
30
+ }
31
+
32
+ function findProgram(node) {
33
+ let current = node;
34
+ while (current) {
35
+ if (current.type === "Program") return current;
36
+ current = current.parent;
37
+ }
38
+ }
39
+
27
40
  function isTransparentExpression(node) {
28
41
  return transparentExpressionTypes.has(node?.type);
29
42
  }
@@ -34,6 +47,14 @@ function unwrapExpression(node) {
34
47
  return current;
35
48
  }
36
49
 
50
+ function unwrapSequence(node) {
51
+ let current = unwrapExpression(node);
52
+ while (current?.type === "SequenceExpression") {
53
+ current = unwrapExpression(current.expressions.at(-1));
54
+ }
55
+ return current;
56
+ }
57
+
37
58
  function unwrapTransparentParent(node) {
38
59
  let current = node;
39
60
  while (isTransparentExpression(current.parent)) current = current.parent;
@@ -81,10 +102,12 @@ function isUnconditionalBeforeReturn(node, block) {
81
102
 
82
103
  module.exports = {
83
104
  findContainingFunction,
105
+ findContainingRoot,
84
106
  isFunction,
85
107
  isTransparentExpression,
86
108
  isUnconditionalBeforeReturn,
87
109
  traverse,
88
110
  unwrapExpression,
111
+ unwrapSequence,
89
112
  unwrapTransparentParent,
90
113
  };
@@ -5,19 +5,21 @@ const {
5
5
  findContainingFunction,
6
6
  isFunction,
7
7
  isTransparentExpression,
8
+ unwrapExpression,
8
9
  unwrapTransparentParent,
9
10
  } = require("./async-ast");
10
11
  const { targetOptionsSchema } = require("./async-schema");
11
12
  const { createTargetMatcher, memberPropertyName } = require("./async-targets");
12
13
 
13
14
  function isPromiseAllCall(node) {
15
+ const callee = unwrapExpression(node?.callee);
16
+ const object = unwrapExpression(callee?.object);
14
17
  return (
15
18
  node?.type === "CallExpression" &&
16
- node.callee.type === "MemberExpression" &&
17
- !node.callee.computed &&
18
- node.callee.object.type === "Identifier" &&
19
- node.callee.object.name === "Promise" &&
20
- memberPropertyName(node.callee) === "all"
19
+ callee?.type === "MemberExpression" &&
20
+ object?.type === "Identifier" &&
21
+ object.name === "Promise" &&
22
+ memberPropertyName(callee) === "all"
21
23
  );
22
24
  }
23
25
 
@@ -64,6 +64,29 @@ function recordObjectPatternBindings(pattern, source, recordDirect) {
64
64
  }
65
65
  }
66
66
 
67
+ function stableConstInitializer(node, context, seen) {
68
+ if (node?.type !== "Identifier") return null;
69
+ const variable = resolveVariable(node, context);
70
+ if (
71
+ !variable ||
72
+ seen.has(variable) ||
73
+ variable.defs.length !== 1 ||
74
+ isReassigned(node, context)
75
+ ) {
76
+ return null;
77
+ }
78
+ const def = variable.defs[0];
79
+ if (
80
+ def.type !== "Variable" ||
81
+ def.parent?.kind !== "const" ||
82
+ def.node?.id?.type !== "Identifier"
83
+ ) {
84
+ return null;
85
+ }
86
+ seen.add(variable);
87
+ return def.node.init;
88
+ }
89
+
67
90
  function namespaceSourceFromInit(init, context, namespaceBindings) {
68
91
  const expression = unwrapExpression(init);
69
92
  if (expression?.type !== "Identifier") return null;
@@ -77,4 +100,5 @@ module.exports = {
77
100
  namespaceSourceFromInit,
78
101
  recordObjectPatternBindings,
79
102
  resolveVariable,
103
+ stableConstInitializer,
80
104
  };