eslint-plugin-no-mistakes 0.93.0 → 0.94.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 (38) hide show
  1. package/README.md +7 -0
  2. package/package.json +3 -3
  3. package/src/index.js +3 -0
  4. package/src/rules/imported-call-options.js +6 -2
  5. package/src/rules/no-object-mutation-bind.js +167 -0
  6. package/src/rules/no-object-mutation-build.js +113 -0
  7. package/src/rules/no-object-mutation-call.js +121 -0
  8. package/src/rules/no-object-mutation-expr.js +134 -0
  9. package/src/rules/no-object-mutation-fn.js +185 -0
  10. package/src/rules/no-object-mutation-join.js +76 -0
  11. package/src/rules/no-object-mutation-loop.js +116 -0
  12. package/src/rules/no-object-mutation-model.js +9 -0
  13. package/src/rules/no-object-mutation-read.js +110 -0
  14. package/src/rules/no-object-mutation-runtime.js +7 -0
  15. package/src/rules/no-object-mutation-scan.js +174 -0
  16. package/src/rules/no-object-mutation-seed.js +109 -0
  17. package/src/rules/no-object-mutation-syntax.js +149 -0
  18. package/src/rules/no-object-mutation-values.js +127 -0
  19. package/src/rules/no-object-mutation-write.js +104 -0
  20. package/src/rules/no-object-mutation.js +59 -0
  21. package/src/rules/postgres-no-async-sql-builder-bindings.js +99 -0
  22. package/src/rules/postgres-no-async-sql-builder-callable.js +73 -0
  23. package/src/rules/postgres-no-async-sql-builder-calls.js +152 -0
  24. package/src/rules/postgres-no-async-sql-builder-completion.js +43 -0
  25. package/src/rules/postgres-no-async-sql-builder-constants.js +101 -0
  26. package/src/rules/postgres-no-async-sql-builder-match.js +203 -0
  27. package/src/rules/postgres-no-async-sql-builder-returns.js +198 -0
  28. package/src/rules/postgres-no-async-sql-builder-types.js +210 -0
  29. package/src/rules/postgres-no-async-sql-builder.js +117 -0
  30. package/src/rules/postgres-no-executor-adapter-binding.js +108 -0
  31. package/src/rules/postgres-no-executor-adapter-forward.js +166 -0
  32. package/src/rules/postgres-no-executor-adapter-logic.js +168 -0
  33. package/src/rules/postgres-no-executor-adapter-member.js +50 -0
  34. package/src/rules/postgres-no-executor-adapter-passed.js +110 -0
  35. package/src/rules/postgres-no-executor-adapter-ref.js +141 -0
  36. package/src/rules/postgres-no-executor-adapter.js +171 -0
  37. package/src/rules/require-options-on-imported-call.js +19 -4
  38. package/src/rules/test-no-skips-bindings.js +26 -12
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.94.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"
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,6 +38,8 @@ 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-executor-adapter": require("./rules/postgres-no-executor-adapter"),
40
43
  "postgres-no-manual-transaction": require("./rules/postgres-no-manual-transaction"),
41
44
  "postgres-no-unbounded-query-fanout": require("./rules/postgres-no-unbounded-query-fanout"),
42
45
  "react-no-nullish-react-node": require("./rules/react-no-nullish-react-node"),
@@ -6,8 +6,12 @@ const { isDefinitelyUndefinedValue } = require("./imported-call-undefined");
6
6
  const { propertyName, literalString } = require("./module-mock-helpers");
7
7
 
8
8
  const pathPatternList = { type: "array", items: { type: "string" } };
9
- // Slash-wrapped values are regexes. An unclosed class such as `/[/` must fail
10
- // here, before the rule can drop the target and match nothing.
9
+ // A leading `/` is a wrapped regex, not a glob. The body cannot contain an
10
+ // unescaped `/`, so `/^@acme/http$/` and `/abs/**` fail here instead of
11
+ // matching a package path or an absolute glob. Escape slashes
12
+ // (`/^@acme\/http$/`) or write a glob that does not start with `/`.
13
+ // An unclosed class such as `/[/` must also fail before the rule can drop
14
+ // the target and match nothing.
11
15
  const selectorPattern = {
12
16
  type: "string",
13
17
  minLength: 1,
@@ -0,0 +1,167 @@
1
+ "use strict";
2
+
3
+ const api = require("./no-object-mutation-runtime");
4
+ const { considerWrite } = require("./no-object-mutation-write");
5
+ const { literalKey, memberKey, staticKey } = require("./no-object-mutation-syntax");
6
+ const { elementTag, freshCopy, readMember, shared } = require("./no-object-mutation-values");
7
+
8
+ function bindName(name, tag, scope, kind) {
9
+ const target = kind === "var" ? scope.functionEnv : scope;
10
+ if (target.isModule) {
11
+ target.cells.set(name, {
12
+ kind: "module",
13
+ tag: tag?.type === "mapset" ? tag : shared("module"),
14
+ });
15
+ return;
16
+ }
17
+ target.cells.set(name, { kind, tag });
18
+ }
19
+
20
+ function bindingScope(scope, name) {
21
+ for (let cursor = scope; cursor; cursor = cursor.outer) {
22
+ if (cursor.cells.has(name)) {
23
+ return cursor;
24
+ }
25
+ }
26
+ return null;
27
+ }
28
+
29
+ function bindingEscapes(scope, name) {
30
+ const owner = bindingScope(scope, name);
31
+ if (!owner) {
32
+ return false;
33
+ }
34
+ const cell = owner.cells.get(name);
35
+ if (cell.kind === "import") {
36
+ return false;
37
+ }
38
+ // A local `let` in this function can still be a builder. A cell in the
39
+ // module or another function can be read after this write.
40
+ return owner.isModule || owner.functionEnv !== scope.functionEnv;
41
+ }
42
+
43
+ function assignBinding(state, name, tag, scope) {
44
+ const owner = bindingScope(scope, name);
45
+ const cell = owner?.cells.get(name);
46
+ if (!cell || cell.kind === "import") {
47
+ return;
48
+ }
49
+ if (bindingEscapes(scope, name)) {
50
+ api.releaseEscaping(state, tag);
51
+ }
52
+ if (cell.kind === "module") {
53
+ cell.tag = tag?.type === "mapset" ? tag : shared("module");
54
+ return;
55
+ }
56
+ cell.tag = tag;
57
+ }
58
+
59
+ function assignPattern(state, pattern, tag, scope) {
60
+ if (pattern.type === "MemberExpression") {
61
+ considerWrite(state, api.visit(state, pattern.object, scope, "read"), pattern, {
62
+ viaCall: false,
63
+ objectNode: pattern,
64
+ key: memberKey(pattern),
65
+ rhs: tag,
66
+ scope,
67
+ });
68
+ return;
69
+ }
70
+ if (pattern.type === "Identifier") {
71
+ assignBinding(state, pattern.name, tag, scope);
72
+ return;
73
+ }
74
+ if (pattern.type === "AssignmentPattern") {
75
+ api.visit(state, pattern.right, scope, "read");
76
+ assignPattern(state, pattern.left, tag, scope);
77
+ return;
78
+ }
79
+ if (pattern.type === "RestElement") {
80
+ assignPattern(state, pattern.argument, freshCopy(tag, scope), scope);
81
+ return;
82
+ }
83
+ if (pattern.type === "ObjectPattern") {
84
+ for (const property of pattern.properties) {
85
+ if (property.type === "RestElement") {
86
+ assignPattern(state, property.argument, freshCopy(tag, scope), scope);
87
+ continue;
88
+ }
89
+ const key = property.computed ? literalKey(property.key) : staticKey(property.key);
90
+ if (property.computed) {
91
+ api.visit(state, property.key, scope, "read");
92
+ }
93
+ assignPattern(state, property.value, key ? readMember(tag, key) : tag, scope);
94
+ }
95
+ return;
96
+ }
97
+ if (pattern.type === "ArrayPattern") {
98
+ for (const element of pattern.elements) {
99
+ if (element === null) {
100
+ continue;
101
+ }
102
+ assignPattern(state, element, elementTag(tag), scope);
103
+ }
104
+ }
105
+ }
106
+
107
+ function bindPattern(state, pattern, tag, scope, kind) {
108
+ if (pattern.type === "TSParameterProperty") {
109
+ bindPattern(
110
+ state,
111
+ pattern.parameter,
112
+ kind === "param" ? shared("parameter") : tag,
113
+ scope,
114
+ kind,
115
+ );
116
+ return;
117
+ }
118
+ if (pattern.type === "Identifier") {
119
+ bindName(pattern.name, kind === "param" ? shared("parameter") : tag, scope, kind);
120
+ return;
121
+ }
122
+ if (pattern.type === "AssignmentPattern") {
123
+ api.visit(state, pattern.right, scope, "read");
124
+ bindPattern(state, pattern.left, kind === "param" ? shared("parameter") : tag, scope, kind);
125
+ return;
126
+ }
127
+ if (pattern.type === "RestElement") {
128
+ const next = kind === "param" ? shared("parameter") : freshCopy(tag, scope);
129
+ bindPattern(state, pattern.argument, next, scope, kind);
130
+ return;
131
+ }
132
+ if (pattern.type === "ObjectPattern") {
133
+ for (const property of pattern.properties) {
134
+ if (property.type === "RestElement") {
135
+ const next = kind === "param" ? shared("parameter") : freshCopy(tag, scope);
136
+ bindPattern(state, property.argument, next, scope, kind);
137
+ continue;
138
+ }
139
+ let key = null;
140
+ if (property.computed) {
141
+ api.visit(state, property.key, scope, "read");
142
+ key = literalKey(property.key);
143
+ } else {
144
+ key = staticKey(property.key);
145
+ }
146
+ const field = kind === "param" ? shared("parameter") : key ? readMember(tag, key) : tag;
147
+ bindPattern(state, property.value, field, scope, kind);
148
+ }
149
+ return;
150
+ }
151
+ for (const element of pattern.elements) {
152
+ // The else is required. An if/continue here makes v8 record a negative branch.
153
+ if (element === null) {
154
+ continue;
155
+ } else {
156
+ const next = kind === "param" ? shared("parameter") : elementTag(tag);
157
+ bindPattern(state, element, next, scope, kind);
158
+ }
159
+ }
160
+ }
161
+
162
+ module.exports = {
163
+ assignPattern,
164
+ bindingEscapes,
165
+ bindName,
166
+ bindPattern,
167
+ };
@@ -0,0 +1,113 @@
1
+ "use strict";
2
+
3
+ const api = require("./no-object-mutation-runtime");
4
+ const { assignPattern, bindingEscapes } = require("./no-object-mutation-bind");
5
+ const { readOnlyFresh } = require("./no-object-mutation-join");
6
+ const { considerWrite, markEscaped } = require("./no-object-mutation-write");
7
+ const { literalKey, memberKey, staticKey } = require("./no-object-mutation-syntax");
8
+ const { createFresh, remember, unknown } = require("./no-object-mutation-values");
9
+
10
+ const SKIP_KEYS = new Set([
11
+ "decorators",
12
+ "end",
13
+ "loc",
14
+ "parent",
15
+ "range",
16
+ "returnType",
17
+ "start",
18
+ "tokens",
19
+ "typeAnnotation",
20
+ "typeParameters",
21
+ ]);
22
+
23
+ function visitAssignment(state, node, scope) {
24
+ if (node.left.type === "MemberExpression") {
25
+ const target = api.visit(state, node.left.object, scope, "read");
26
+ const rhs = api.visit(state, node.right, scope, readOnlyFresh(target) ? "read" : "escape");
27
+ considerWrite(state, target, node, {
28
+ viaCall: false,
29
+ objectNode: node.left,
30
+ key: memberKey(node.left),
31
+ rhs,
32
+ scope,
33
+ });
34
+ return rhs;
35
+ }
36
+ const escapes = node.left.type === "Identifier" && bindingEscapes(scope, node.left.name);
37
+ const rhs = api.visit(state, node.right, scope, escapes ? "escape" : "read");
38
+ assignPattern(state, node.left, rhs, scope);
39
+ return rhs;
40
+ }
41
+
42
+ function visitObject(state, node, scope, role) {
43
+ const fresh = createFresh(scope);
44
+ for (const property of node.properties) {
45
+ if (property.type === "SpreadElement") {
46
+ remember(fresh, api.visit(state, property.argument, scope, "read"));
47
+ continue;
48
+ }
49
+ if (property.computed) {
50
+ api.visit(state, property.key, scope, "read");
51
+ }
52
+ const value = api.visit(state, property.value, scope, "read");
53
+ if (property.kind !== "init" || property.method) {
54
+ // Methods are not data fields, but they escape with the object.
55
+ fresh.items.push(value);
56
+ remember(fresh, unknown());
57
+ continue;
58
+ }
59
+ const key = property.computed ? literalKey(property.key) : staticKey(property.key);
60
+ if (key) {
61
+ fresh.fields.set(key, value);
62
+ } else {
63
+ remember(fresh, value);
64
+ }
65
+ }
66
+ if (role === "escape") {
67
+ markEscaped(state, fresh);
68
+ }
69
+ return fresh;
70
+ }
71
+
72
+ function visitArray(state, node, scope, role) {
73
+ const fresh = createFresh(scope);
74
+ for (const element of node.elements) {
75
+ if (!element) {
76
+ continue;
77
+ }
78
+ remember(
79
+ fresh,
80
+ element.type === "SpreadElement"
81
+ ? api.visit(state, element.argument, scope, "read")
82
+ : api.visit(state, element, scope, "read"),
83
+ );
84
+ }
85
+ if (role === "escape") {
86
+ markEscaped(state, fresh);
87
+ }
88
+ return fresh;
89
+ }
90
+
91
+ function walkChildren(state, node, scope) {
92
+ for (const key of Object.keys(node)) {
93
+ if (SKIP_KEYS.has(key)) {
94
+ continue;
95
+ }
96
+ const value = node[key];
97
+ if (Array.isArray(value)) {
98
+ // visit ignores a hole and a value that is not a syntax node.
99
+ for (const childNode of value) {
100
+ api.visit(state, childNode, scope, "read");
101
+ }
102
+ } else if (value && typeof value.type === "string") {
103
+ api.visit(state, value, scope, "read");
104
+ }
105
+ }
106
+ }
107
+
108
+ module.exports = {
109
+ visitArray,
110
+ visitAssignment,
111
+ visitObject,
112
+ walkChildren,
113
+ };
@@ -0,0 +1,121 @@
1
+ "use strict";
2
+
3
+ const api = require("./no-object-mutation-runtime");
4
+ const { callFunction, escapeFunction } = require("./no-object-mutation-fn");
5
+ const { mutationCall } = require("./no-object-mutation-syntax");
6
+ const { readOnlyFresh } = require("./no-object-mutation-join");
7
+ const { callable, lookup, unknown, unwrap } = require("./no-object-mutation-model");
8
+ const { considerWrite } = require("./no-object-mutation-write");
9
+
10
+ function visitArgument(state, argument, scope, role) {
11
+ if (argument.type === "SpreadElement") {
12
+ return api.visit(state, argument.argument, scope, role);
13
+ }
14
+ return api.visit(state, argument, scope, role);
15
+ }
16
+
17
+ function visitNew(state, node, scope) {
18
+ const callee = unwrap(node.callee);
19
+ let tag = unknown();
20
+ if (
21
+ callee?.type === "Identifier" &&
22
+ (callee.name === "Map" || callee.name === "Set") &&
23
+ !lookup(scope, callee.name)
24
+ ) {
25
+ tag = { type: "mapset" };
26
+ } else {
27
+ api.visit(state, node.callee, scope, "read");
28
+ }
29
+ for (const argument of node.arguments) {
30
+ visitArgument(state, argument, scope, "escape");
31
+ }
32
+ return tag;
33
+ }
34
+
35
+ function visitCall(state, node, scope) {
36
+ const mutation = mutationCall(node, scope);
37
+ if (mutation) {
38
+ const target = api.visit(state, mutation.targetNode, scope, "read");
39
+ const argRole = readOnlyFresh(target) ? "read" : "escape";
40
+ const remembered = [];
41
+ for (const argument of mutation.otherArgs) {
42
+ remembered.push(visitArgument(state, argument, scope, argRole));
43
+ }
44
+ considerWrite(state, target, node, {
45
+ viaCall: true,
46
+ objectNode: mutation.targetNode,
47
+ scope,
48
+ remembered,
49
+ });
50
+ if (readOnlyFresh(target)) {
51
+ return target;
52
+ }
53
+ return unknown();
54
+ }
55
+ const calleeTag = api.visit(state, node.callee, scope, "read");
56
+ for (const argument of node.arguments) {
57
+ visitArgument(state, argument, scope, "escape");
58
+ }
59
+ invokeCallable(state, calleeTag);
60
+ return unknown();
61
+ }
62
+
63
+ function invokeCallable(state, tag) {
64
+ if (tag.type === "union") {
65
+ for (const member of tag.members) {
66
+ invokeCallable(state, member);
67
+ }
68
+ return;
69
+ }
70
+ const fn = callable(tag);
71
+ if (fn) {
72
+ callFunction(state, fn);
73
+ } else {
74
+ return;
75
+ }
76
+ }
77
+
78
+ function captureOuter(scope, tag, seen) {
79
+ if (!tag || !scope.fnTag) {
80
+ return;
81
+ }
82
+ const local = seen || new Set();
83
+ if (local.has(tag)) {
84
+ return;
85
+ }
86
+ local.add(tag);
87
+ if (tag.type === "union") {
88
+ for (const member of tag.members) {
89
+ captureOuter(scope, member, local);
90
+ }
91
+ return;
92
+ }
93
+ if (tag.type === "fresh" && tag.ownerEnv !== scope.ownerEnv) {
94
+ scope.fnTag.captured.push(tag);
95
+ }
96
+ }
97
+
98
+ function visitIdentifier(state, node, scope, role) {
99
+ const tag = lookup(scope, node.name)?.tag;
100
+ if (role === "escape") {
101
+ api.releaseEscaping(state, tag);
102
+ }
103
+ captureOuter(scope, tag);
104
+ return tag || unknown();
105
+ }
106
+
107
+ function escapeExport(state, local, scope) {
108
+ const fn = callable(lookup(scope, local.name)?.tag);
109
+ if (fn) {
110
+ escapeFunction(state, fn);
111
+ } else {
112
+ return;
113
+ }
114
+ }
115
+
116
+ module.exports = {
117
+ escapeExport,
118
+ visitCall,
119
+ visitIdentifier,
120
+ visitNew,
121
+ };
@@ -0,0 +1,134 @@
1
+ "use strict";
2
+
3
+ const api = require("./no-object-mutation-runtime");
4
+ const {
5
+ visitArray,
6
+ visitAssignment,
7
+ visitObject,
8
+ walkChildren,
9
+ } = require("./no-object-mutation-build");
10
+ const { visitCall, visitIdentifier, visitNew } = require("./no-object-mutation-call");
11
+ const { memberKey, unwrap } = require("./no-object-mutation-syntax");
12
+ const { joinValues, readMember, shared, unknown } = require("./no-object-mutation-values");
13
+ const { considerWrite } = require("./no-object-mutation-write");
14
+
15
+ function visitExpression(state, node, scope, role) {
16
+ switch (node.type) {
17
+ case "Identifier":
18
+ return visitIdentifier(state, node, scope, role);
19
+ case "ThisExpression":
20
+ return { type: "this", inClass: Boolean(scope.inClass) };
21
+ case "Super":
22
+ case "Literal":
23
+ case "TemplateElement":
24
+ case "JSXText":
25
+ case "JSXIdentifier":
26
+ case "JSXEmptyExpression":
27
+ return unknown();
28
+ case "TemplateLiteral":
29
+ for (const expression of node.expressions) {
30
+ api.visit(state, expression, scope, "read");
31
+ }
32
+ return unknown();
33
+ case "TaggedTemplateExpression":
34
+ api.visit(state, node.tag, scope, "read");
35
+ for (const expression of node.quasi.expressions) {
36
+ api.visit(state, expression, scope, "escape");
37
+ }
38
+ return unknown();
39
+ case "YieldExpression":
40
+ if (node.argument) {
41
+ api.visit(state, node.argument, scope, "escape");
42
+ }
43
+ return unknown();
44
+ case "AwaitExpression":
45
+ return api.visit(state, node.argument, scope, "read");
46
+ case "UnaryExpression":
47
+ if (node.operator === "delete") {
48
+ api.visit(state, node.argument, scope, "read");
49
+ return unknown();
50
+ }
51
+ return api.visit(state, node.argument, scope, "read");
52
+ case "UpdateExpression": {
53
+ const member = unwrap(node.argument);
54
+ if (member?.type === "MemberExpression") {
55
+ if (member.computed) {
56
+ api.visit(state, member.property, scope, "read");
57
+ }
58
+ considerWrite(state, api.visit(state, member.object, scope, "read"), node, {
59
+ viaCall: false,
60
+ objectNode: member,
61
+ key: memberKey(member),
62
+ scope,
63
+ });
64
+ } else {
65
+ api.visit(state, node.argument, scope, "read");
66
+ }
67
+ return unknown();
68
+ }
69
+ case "BinaryExpression":
70
+ api.visit(state, node.left, scope, "read");
71
+ api.visit(state, node.right, scope, "read");
72
+ return unknown();
73
+ case "LogicalExpression":
74
+ // && yields the left only when that side is falsy, so it cannot be the object.
75
+ if (node.operator === "&&") {
76
+ api.visit(state, node.left, scope, "read");
77
+ return api.visit(state, node.right, scope, role);
78
+ }
79
+ return joinValues(
80
+ api.visit(state, node.left, scope, role),
81
+ api.visit(state, node.right, scope, role),
82
+ );
83
+ case "ConditionalExpression":
84
+ api.visit(state, node.test, scope, "read");
85
+ return joinValues(
86
+ api.visit(state, node.consequent, scope, role),
87
+ api.visit(state, node.alternate, scope, role),
88
+ );
89
+ case "SequenceExpression": {
90
+ let tag = unknown();
91
+ for (const expression of node.expressions) {
92
+ tag = api.visit(state, expression, scope, role);
93
+ }
94
+ return tag;
95
+ }
96
+ case "AssignmentExpression":
97
+ return visitAssignment(state, node, scope);
98
+ case "MemberExpression":
99
+ return readMember(api.visit(state, node.object, scope, "read"), memberKey(node));
100
+ case "ObjectExpression":
101
+ return visitObject(state, node, scope, role);
102
+ case "ArrayExpression":
103
+ return visitArray(state, node, scope, role);
104
+ case "NewExpression":
105
+ return visitNew(state, node, scope);
106
+ case "CallExpression":
107
+ return visitCall(state, node, scope);
108
+ case "ImportExpression":
109
+ api.visit(state, node.source, scope, "read");
110
+ return unknown();
111
+ case "MetaProperty":
112
+ if (node.meta?.name === "import" && node.property?.name === "meta") {
113
+ return shared("module");
114
+ }
115
+ return unknown();
116
+ case "JSXElement":
117
+ case "JSXFragment":
118
+ case "JSXAttribute":
119
+ case "JSXOpeningElement":
120
+ case "JSXClosingElement":
121
+ walkChildren(state, node, scope);
122
+ return unknown();
123
+ case "JSXSpreadAttribute":
124
+ return api.visit(state, node.argument, scope, "escape");
125
+ case "JSXSpreadChild":
126
+ return api.visit(state, node.expression, scope, "escape");
127
+ case "JSXExpressionContainer":
128
+ return api.visit(state, node.expression, scope, "escape");
129
+ default:
130
+ return undefined;
131
+ }
132
+ }
133
+
134
+ module.exports = { visitExpression };