eslint-plugin-no-mistakes 0.92.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.
- package/README.md +7 -0
- package/package.json +3 -3
- package/src/index.js +3 -0
- package/src/rules/forbidden-member-chains-destructure.js +15 -7
- package/src/rules/forbidden-member-chains-schema.js +58 -30
- package/src/rules/forbidden-member-chains.js +2 -1
- package/src/rules/imported-call-options.js +6 -2
- package/src/rules/no-object-mutation-bind.js +167 -0
- package/src/rules/no-object-mutation-build.js +113 -0
- package/src/rules/no-object-mutation-call.js +121 -0
- package/src/rules/no-object-mutation-expr.js +134 -0
- package/src/rules/no-object-mutation-fn.js +185 -0
- package/src/rules/no-object-mutation-join.js +76 -0
- package/src/rules/no-object-mutation-loop.js +116 -0
- package/src/rules/no-object-mutation-model.js +9 -0
- package/src/rules/no-object-mutation-read.js +110 -0
- package/src/rules/no-object-mutation-runtime.js +7 -0
- package/src/rules/no-object-mutation-scan.js +174 -0
- package/src/rules/no-object-mutation-seed.js +109 -0
- package/src/rules/no-object-mutation-syntax.js +149 -0
- package/src/rules/no-object-mutation-values.js +127 -0
- package/src/rules/no-object-mutation-write.js +104 -0
- package/src/rules/no-object-mutation.js +59 -0
- package/src/rules/postgres-no-async-sql-builder-bindings.js +99 -0
- package/src/rules/postgres-no-async-sql-builder-callable.js +73 -0
- package/src/rules/postgres-no-async-sql-builder-calls.js +152 -0
- package/src/rules/postgres-no-async-sql-builder-completion.js +43 -0
- package/src/rules/postgres-no-async-sql-builder-constants.js +101 -0
- package/src/rules/postgres-no-async-sql-builder-match.js +203 -0
- package/src/rules/postgres-no-async-sql-builder-returns.js +198 -0
- package/src/rules/postgres-no-async-sql-builder-types.js +210 -0
- package/src/rules/postgres-no-async-sql-builder.js +117 -0
- package/src/rules/postgres-no-executor-adapter-binding.js +108 -0
- package/src/rules/postgres-no-executor-adapter-forward.js +166 -0
- package/src/rules/postgres-no-executor-adapter-logic.js +168 -0
- package/src/rules/postgres-no-executor-adapter-member.js +50 -0
- package/src/rules/postgres-no-executor-adapter-passed.js +110 -0
- package/src/rules/postgres-no-executor-adapter-ref.js +141 -0
- package/src/rules/postgres-no-executor-adapter.js +171 -0
- package/src/rules/require-options-on-imported-call.js +19 -4
- 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.
|
|
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.
|
|
25
|
+
"@vitest/coverage-v8": "^5.0.4",
|
|
26
26
|
"eslint": "^10.12.0",
|
|
27
27
|
"oxlint": "^1.87.0",
|
|
28
|
-
"vitest": "^5.0.
|
|
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"),
|
|
@@ -2,12 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
const { matches, expressionInfo } = require("./forbidden-member-chains-match");
|
|
4
4
|
|
|
5
|
-
const PARAM_PARENTS = new Set([
|
|
6
|
-
"FunctionDeclaration",
|
|
7
|
-
"FunctionExpression",
|
|
8
|
-
"ArrowFunctionExpression",
|
|
9
|
-
]);
|
|
10
|
-
|
|
11
5
|
function patternKey(prop) {
|
|
12
6
|
const key = prop.key;
|
|
13
7
|
if (key.type === "Identifier") return key.name;
|
|
@@ -24,8 +18,19 @@ function objectPattern(node) {
|
|
|
24
18
|
}
|
|
25
19
|
|
|
26
20
|
function destructureVisitors({ compiled, context, suppressed, info }) {
|
|
21
|
+
// The outer pattern and a default can reach the same property and chain.
|
|
22
|
+
// `const { panel: { open } = other.panel } = other` reports that pair once.
|
|
23
|
+
const reported = new WeakMap();
|
|
24
|
+
|
|
27
25
|
function report(node, chain) {
|
|
28
26
|
if (suppressed(node)) return;
|
|
27
|
+
let seen = reported.get(node);
|
|
28
|
+
if (seen?.has(chain)) return;
|
|
29
|
+
if (!seen) {
|
|
30
|
+
seen = new Set();
|
|
31
|
+
reported.set(node, seen);
|
|
32
|
+
}
|
|
33
|
+
seen.add(chain);
|
|
29
34
|
context.report({ node, messageId: "forbiddenChain", data: { message: chain.message } });
|
|
30
35
|
}
|
|
31
36
|
|
|
@@ -63,8 +68,11 @@ function destructureVisitors({ compiled, context, suppressed, info }) {
|
|
|
63
68
|
const pattern = objectPattern(node.left);
|
|
64
69
|
if (pattern) check(pattern, node.right);
|
|
65
70
|
},
|
|
71
|
+
// A default is a receiver at any depth, not only on a function parameter.
|
|
72
|
+
// The parser only allows one in a parameter, property, or array element.
|
|
73
|
+
// `const { x: { open } = other.panel } = {}` reports `*.panel.open`.
|
|
74
|
+
// An array element is too: `const [{ open } = other.panel] = []`.
|
|
66
75
|
AssignmentPattern(node) {
|
|
67
|
-
if (!PARAM_PARENTS.has(node.parent?.type)) return;
|
|
68
76
|
const pattern = objectPattern(node.left);
|
|
69
77
|
if (pattern) check(pattern, node.right);
|
|
70
78
|
},
|
|
@@ -17,29 +17,19 @@ function nonStar() {
|
|
|
17
17
|
return text(128, "^(?!\\*$)[\\s\\S]+$");
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
items: [{ enum: ["*"] }],
|
|
34
|
-
additionalItems: nonStar(),
|
|
35
|
-
};
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
function pathSchema(pattern, starMin, plainMax, starMax) {
|
|
39
|
-
return {
|
|
40
|
-
anyOf: [pathString(pattern), segments(plainMax), starThen(starMin, starMax)],
|
|
41
|
-
};
|
|
42
|
-
}
|
|
20
|
+
// Segment rules run in assertPath so a bad path names the segment. A JSON Schema
|
|
21
|
+
// anyOf only says the value matched none of the alternatives.
|
|
22
|
+
const pathValue = {
|
|
23
|
+
anyOf: [
|
|
24
|
+
{ type: "string", minLength: 1, maxLength: 256 },
|
|
25
|
+
{
|
|
26
|
+
type: "array",
|
|
27
|
+
minItems: 1,
|
|
28
|
+
maxItems: 32,
|
|
29
|
+
items: { type: "string", minLength: 1, maxLength: 128 },
|
|
30
|
+
},
|
|
31
|
+
],
|
|
32
|
+
};
|
|
43
33
|
|
|
44
34
|
function chain(properties, required) {
|
|
45
35
|
return {
|
|
@@ -60,20 +50,17 @@ const schema = [
|
|
|
60
50
|
maxItems: 64,
|
|
61
51
|
items: {
|
|
62
52
|
anyOf: [
|
|
63
|
-
chain({ path:
|
|
64
|
-
"path",
|
|
65
|
-
"message",
|
|
66
|
-
]),
|
|
53
|
+
chain({ path: pathValue }, ["path", "message"]),
|
|
67
54
|
chain(
|
|
68
55
|
{
|
|
69
|
-
path:
|
|
56
|
+
path: pathValue,
|
|
70
57
|
receiver: { type: "string", enum: ["*"] },
|
|
71
58
|
},
|
|
72
59
|
["path", "message", "receiver"],
|
|
73
60
|
),
|
|
74
61
|
chain(
|
|
75
62
|
{
|
|
76
|
-
path:
|
|
63
|
+
path: pathValue,
|
|
77
64
|
receiver: nonStar(),
|
|
78
65
|
},
|
|
79
66
|
["path", "message", "receiver"],
|
|
@@ -85,7 +72,48 @@ const schema = [
|
|
|
85
72
|
},
|
|
86
73
|
];
|
|
87
74
|
|
|
75
|
+
function shownPath(path) {
|
|
76
|
+
return Array.isArray(path) ? path.join(".") : String(path);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function assertPath(path, named) {
|
|
80
|
+
const parts = Array.isArray(path) ? path : String(path).split(".");
|
|
81
|
+
const shown = shownPath(path);
|
|
82
|
+
const empty = parts.findIndex((part) => part === "");
|
|
83
|
+
if (empty !== -1) throw new Error(`empty segment at index ${empty} in '${shown}'`);
|
|
84
|
+
const star = parts.findIndex((part, index) => part === "*" && index !== 0);
|
|
85
|
+
if (star !== -1) {
|
|
86
|
+
throw new Error(`'*' segment at index ${star} in '${shown}'; '*' is only a leading segment`);
|
|
87
|
+
}
|
|
88
|
+
if (typeof path === "string") {
|
|
89
|
+
const pattern = new RegExp(named ? NAMED_PATH : OPEN_PATH);
|
|
90
|
+
if (pattern.test(path)) return;
|
|
91
|
+
const limit = named && parts[0] !== "*" ? MAX_SEGMENTS - 1 : MAX_SEGMENTS;
|
|
92
|
+
if (parts.length > limit) {
|
|
93
|
+
throw new Error(`path '${shown}' has ${parts.length} segments; the maximum is ${limit}`);
|
|
94
|
+
}
|
|
95
|
+
throw new Error(`malformed path '${shown}'`);
|
|
96
|
+
}
|
|
97
|
+
const leadingStar = parts[0] === "*";
|
|
98
|
+
const limit = leadingStar ? MAX_SEGMENTS : named ? MAX_SEGMENTS - 1 : MAX_SEGMENTS;
|
|
99
|
+
const min = leadingStar ? (named ? 1 : 2) : 1;
|
|
100
|
+
if (parts.length < min) {
|
|
101
|
+
throw new Error(`path '${shown}' has ${parts.length} segments; at least ${min} are required`);
|
|
102
|
+
}
|
|
103
|
+
if (parts.length > limit) {
|
|
104
|
+
throw new Error(`path '${shown}' has ${parts.length} segments; the maximum is ${limit}`);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function assertChains(entries) {
|
|
109
|
+
if (!Array.isArray(entries)) return;
|
|
110
|
+
for (const entry of entries) {
|
|
111
|
+
assertPath(entry.path, typeof entry.receiver === "string" && entry.receiver !== "*");
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
88
115
|
function compile(entry) {
|
|
116
|
+
assertPath(entry.path, typeof entry.receiver === "string" && entry.receiver !== "*");
|
|
89
117
|
const parts = Array.isArray(entry.path) ? entry.path.slice() : entry.path.split(".");
|
|
90
118
|
let wildcard = false;
|
|
91
119
|
let body = parts;
|
|
@@ -101,4 +129,4 @@ function compile(entry) {
|
|
|
101
129
|
return { wildcard, parts: body, message: entry.message };
|
|
102
130
|
}
|
|
103
131
|
|
|
104
|
-
module.exports = { schema, compile };
|
|
132
|
+
module.exports = { schema, compile, assertChains };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
3
|
const { ruleSuppression } = require("../rule-suppression");
|
|
4
|
-
const { schema, compile } = require("./forbidden-member-chains-schema");
|
|
4
|
+
const { schema, compile, assertChains } = require("./forbidden-member-chains-schema");
|
|
5
5
|
const {
|
|
6
6
|
matches,
|
|
7
7
|
computeChain,
|
|
@@ -20,6 +20,7 @@ module.exports = {
|
|
|
20
20
|
create(context) {
|
|
21
21
|
const compiled = [];
|
|
22
22
|
const entries = context.options[0]?.chains;
|
|
23
|
+
assertChains(entries);
|
|
23
24
|
if (Array.isArray(entries)) {
|
|
24
25
|
for (const entry of entries) compiled.push(compile(entry));
|
|
25
26
|
}
|
|
@@ -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
|
-
//
|
|
10
|
-
//
|
|
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
|
+
};
|