eslint-plugin-no-mistakes 0.90.0 → 0.91.1
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 +1 -1
- package/src/rules/playwright-timeout-carriers.js +41 -20
- package/src/rules/playwright-timeout-meta.js +1 -0
- package/src/rules/vitest-timeout-array-values.js +42 -0
- package/src/rules/vitest-timeout-bindings.js +5 -1
- package/src/rules/vitest-timeout-cap.js +14 -10
- package/src/rules/vitest-timeout-carriers.js +23 -9
- package/src/rules/vitest-timeout-values.js +13 -17
package/package.json
CHANGED
|
@@ -2,28 +2,38 @@
|
|
|
2
2
|
const { unwrapExpression, isFunction } = require("./async-ast");
|
|
3
3
|
const { resolveVariable, isReassigned } = require("./async-target-bindings");
|
|
4
4
|
function createCarriers(context, evaluate, check, max, fixtureMax) {
|
|
5
|
-
|
|
5
|
+
const option = context.options[0] ?? {};
|
|
6
|
+
function configCheck(value, ...args) {
|
|
7
|
+
const unresolved = value.kind !== "number" || value.runtimeFunction;
|
|
8
|
+
if (unresolved && option.configUnknownValues === "defer-to-runner") return;
|
|
9
|
+
check(value, ...args);
|
|
10
|
+
}
|
|
11
|
+
function field(value, node, name = "timeout", cap = max, inspect = check) {
|
|
6
12
|
if (value.kind !== "object") {
|
|
7
|
-
|
|
13
|
+
inspect({ kind: "unknown" }, node, name, cap);
|
|
8
14
|
return undefined;
|
|
9
15
|
}
|
|
10
16
|
const entry = value.properties.get("timeout");
|
|
11
|
-
if (entry)
|
|
12
|
-
else if (value.unknown)
|
|
17
|
+
if (entry) inspect(entry.value, entry.origin, name, cap);
|
|
18
|
+
else if (value.unknown) inspect({ kind: "unknown" }, node, name, cap);
|
|
13
19
|
return entry?.value;
|
|
14
20
|
}
|
|
15
21
|
function config(value, node) {
|
|
16
22
|
if (value.kind !== "object") {
|
|
17
|
-
|
|
23
|
+
configCheck({ kind: "unknown" }, node, "config carrier");
|
|
18
24
|
return;
|
|
19
25
|
}
|
|
20
|
-
field(value, node, "project timeout");
|
|
26
|
+
field(value, node, "project timeout", max, configCheck);
|
|
21
27
|
const projects = value.properties.get("projects");
|
|
22
28
|
if (projects) {
|
|
23
29
|
if (projects.value.kind !== "array")
|
|
24
|
-
|
|
25
|
-
else
|
|
26
|
-
|
|
30
|
+
configCheck({ kind: "unknown" }, projects.origin, "projects carrier");
|
|
31
|
+
else {
|
|
32
|
+
if (!projects.value.lengthKnown)
|
|
33
|
+
configCheck({ kind: "unknown" }, projects.origin, "projects carrier");
|
|
34
|
+
for (const entry of projects.value.entries) config(entry.value, entry.origin);
|
|
35
|
+
}
|
|
36
|
+
} else if (value.unknown) configCheck({ kind: "unknown" }, node, "projects carrier");
|
|
27
37
|
}
|
|
28
38
|
function fixtures(node) {
|
|
29
39
|
const value = evaluate(node);
|
|
@@ -33,15 +43,26 @@ function createCarriers(context, evaluate, check, max, fixtureMax) {
|
|
|
33
43
|
}
|
|
34
44
|
if (value.unknown) check({ kind: "unknown" }, node, "fixture carrier", fixtureMax);
|
|
35
45
|
for (const entry of value.properties.values()) {
|
|
36
|
-
//
|
|
37
|
-
if (entry.value.kind === "array" && entry.value.
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
46
|
+
// Playwright inspects runtime index 1, irrespective of extra tail elements.
|
|
47
|
+
if (entry.value.kind === "array" && !entry.value.runtimeFunction) {
|
|
48
|
+
const tuple = entry.value;
|
|
49
|
+
if (tuple.knownPrefixLength < 2) {
|
|
50
|
+
if (tuple.entries.length >= 2 || !tuple.lengthKnown)
|
|
51
|
+
check({ kind: "unknown" }, entry.origin, "fixture tuple position", fixtureMax);
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
const options = tuple.entries[1];
|
|
55
|
+
if (options.value.runtimeFunction) continue;
|
|
56
|
+
if (
|
|
57
|
+
options.value.kind === "number" ||
|
|
58
|
+
["string", "boolean", "bigint", "undefined"].includes(options.value.runtimeType)
|
|
59
|
+
)
|
|
60
|
+
continue;
|
|
61
|
+
field(options.value, options.origin, "fixture timeout", fixtureMax);
|
|
62
|
+
} else if (
|
|
63
|
+
entry.value.kind === "unknown" &&
|
|
64
|
+
!isFunction(unwrapExpression(entry.origin.value))
|
|
65
|
+
)
|
|
45
66
|
check({ kind: "unknown" }, entry.origin, "fixture entry carrier", fixtureMax);
|
|
46
67
|
}
|
|
47
68
|
}
|
|
@@ -78,7 +99,7 @@ function createCarriers(context, evaluate, check, max, fixtureMax) {
|
|
|
78
99
|
if (api?.kind === "defineConfig") {
|
|
79
100
|
checkedConfigs.add(node);
|
|
80
101
|
if (node.arguments.length === 1) config(evaluate(node.arguments[0]), node.arguments[0]);
|
|
81
|
-
else
|
|
102
|
+
else configCheck({ kind: "unknown" }, node, "merged config carrier");
|
|
82
103
|
} else if (
|
|
83
104
|
(option.configFiles ?? []).some(
|
|
84
105
|
(file) =>
|
|
@@ -91,7 +112,7 @@ function createCarriers(context, evaluate, check, max, fixtureMax) {
|
|
|
91
112
|
if (checkedConfigs.has(node) || bindings.resolve(node.callee)?.kind !== "defineConfig")
|
|
92
113
|
continue;
|
|
93
114
|
if (node.arguments.length === 1) config(evaluate(node.arguments[0]), node.arguments[0]);
|
|
94
|
-
else
|
|
115
|
+
else configCheck({ kind: "unknown" }, node, "merged config carrier");
|
|
95
116
|
}
|
|
96
117
|
}
|
|
97
118
|
return { field, configRoots, fixtures, slowCondition };
|
|
@@ -9,6 +9,7 @@ module.exports = {
|
|
|
9
9
|
max: { type: "number", minimum: 0, exclusiveMinimum: true },
|
|
10
10
|
fixtureMax: { type: "number", minimum: 0, exclusiveMinimum: true },
|
|
11
11
|
unknownValues: { enum: ["ignore", "finding"] },
|
|
12
|
+
configUnknownValues: { enum: ["inherit", "defer-to-runner"] },
|
|
12
13
|
registrationPackages: { type: "array", items: { type: "string" }, uniqueItems: true },
|
|
13
14
|
exportRoles: {
|
|
14
15
|
type: "object",
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
function arrayValue(node, evaluate) {
|
|
4
|
+
const entries = [];
|
|
5
|
+
let knownPrefixLength = 0;
|
|
6
|
+
let positionsKnown = true;
|
|
7
|
+
for (const element of node.elements) {
|
|
8
|
+
if (!element) {
|
|
9
|
+
entries.push({ value: { kind: "unknown", runtimeType: "undefined" }, origin: node });
|
|
10
|
+
if (positionsKnown) knownPrefixLength++;
|
|
11
|
+
} else if (element.type === "SpreadElement") {
|
|
12
|
+
const spread = evaluate(element.argument);
|
|
13
|
+
if (spread.kind === "array" && !spread.runtimeFunction) {
|
|
14
|
+
entries.push(...spread.entries.map((entry) => ({ ...entry, origin: element })));
|
|
15
|
+
if (positionsKnown) {
|
|
16
|
+
knownPrefixLength += spread.knownPrefixLength;
|
|
17
|
+
positionsKnown = spread.lengthKnown;
|
|
18
|
+
}
|
|
19
|
+
} else {
|
|
20
|
+
entries.push({ value: { kind: "unknown" }, origin: element });
|
|
21
|
+
positionsKnown = false;
|
|
22
|
+
}
|
|
23
|
+
} else {
|
|
24
|
+
entries.push({ value: evaluate(element), origin: element });
|
|
25
|
+
if (positionsKnown) knownPrefixLength++;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return { kind: "array", entries, knownPrefixLength, lengthKnown: positionsKnown };
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function concatenateArrays(left, right) {
|
|
32
|
+
return {
|
|
33
|
+
kind: "array",
|
|
34
|
+
entries: [...left.entries, ...right.entries],
|
|
35
|
+
knownPrefixLength: left.lengthKnown
|
|
36
|
+
? left.entries.length + right.knownPrefixLength
|
|
37
|
+
: left.knownPrefixLength,
|
|
38
|
+
lengthKnown: left.lengthKnown && right.lengthKnown,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
module.exports = { arrayValue, concatenateArrays };
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
const { createBindingResolver } = require("./vitest-timeout-binding-resolver");
|
|
3
|
-
//
|
|
3
|
+
// Hosts can reuse a rule context across files. Each create starts a fresh program.
|
|
4
4
|
const resolvers = new WeakMap();
|
|
5
|
+
function resetBindingResolver(context) {
|
|
6
|
+
resolvers.delete(context);
|
|
7
|
+
}
|
|
5
8
|
function resolver(context) {
|
|
6
9
|
if (!resolvers.has(context)) resolvers.set(context, createBindingResolver(context));
|
|
7
10
|
return resolvers.get(context);
|
|
@@ -40,6 +43,7 @@ function opaqueCallback(node, context) {
|
|
|
40
43
|
return resolver(context).opaqueCallback(node);
|
|
41
44
|
}
|
|
42
45
|
module.exports = {
|
|
46
|
+
resetBindingResolver,
|
|
43
47
|
configCall,
|
|
44
48
|
overrideCall,
|
|
45
49
|
staticPropertyName,
|
|
@@ -4,6 +4,7 @@ const { isFunction, unwrapExpression } = require("./async-ast");
|
|
|
4
4
|
const { resolveVariable, isReassigned } = require("./async-target-bindings");
|
|
5
5
|
const { ruleSuppression } = require("../rule-suppression");
|
|
6
6
|
const {
|
|
7
|
+
resetBindingResolver,
|
|
7
8
|
configCall,
|
|
8
9
|
overrideCall,
|
|
9
10
|
recordCall,
|
|
@@ -27,6 +28,7 @@ module.exports = rule(
|
|
|
27
28
|
defaultMax: { type: "number", minimum: 0, exclusiveMinimum: true },
|
|
28
29
|
overrideMax: { type: "number", minimum: 0, exclusiveMinimum: true },
|
|
29
30
|
unknownValues: { enum: ["ignore", "finding"] },
|
|
31
|
+
configUnknownValues: { enum: ["inherit", "defer-to-runner"] },
|
|
30
32
|
configRoot: { type: "boolean" },
|
|
31
33
|
},
|
|
32
34
|
additionalProperties: false,
|
|
@@ -42,6 +44,7 @@ module.exports = rule(
|
|
|
42
44
|
},
|
|
43
45
|
},
|
|
44
46
|
(context) => {
|
|
47
|
+
resetBindingResolver(context);
|
|
45
48
|
const option = options(context);
|
|
46
49
|
for (const name of ["defaultMax", "overrideMax"]) {
|
|
47
50
|
if (option[name] !== undefined && (!Number.isFinite(option[name]) || option[name] <= 0))
|
|
@@ -109,10 +112,11 @@ module.exports = rule(
|
|
|
109
112
|
for (const original of overrides) {
|
|
110
113
|
const kind = overrideCall(original, context);
|
|
111
114
|
if (!kind) continue;
|
|
112
|
-
const node =
|
|
115
|
+
const node = original;
|
|
116
|
+
const callArguments = invocationArguments(original, context);
|
|
113
117
|
const max = option.overrideMax ?? 30000;
|
|
114
118
|
if (kind === "test" || kind === "hook")
|
|
115
|
-
for (const argument of
|
|
119
|
+
for (const argument of callArguments)
|
|
116
120
|
if (opaqueCallback(argument, context))
|
|
117
121
|
check(
|
|
118
122
|
{ value: { kind: "unknown" }, origin: argument },
|
|
@@ -126,14 +130,14 @@ module.exports = rule(
|
|
|
126
130
|
}
|
|
127
131
|
if (kind === "runtime") {
|
|
128
132
|
properties(
|
|
129
|
-
evaluate(
|
|
133
|
+
evaluate(callArguments[0]),
|
|
130
134
|
["testTimeout", "hookTimeout"],
|
|
131
135
|
max,
|
|
132
|
-
|
|
136
|
+
callArguments[0] || node,
|
|
133
137
|
);
|
|
134
138
|
continue;
|
|
135
139
|
}
|
|
136
|
-
const args = kind === "hook" ?
|
|
140
|
+
const args = kind === "hook" ? callArguments.slice(1) : callArguments.slice(1, 3);
|
|
137
141
|
for (const argument of args) {
|
|
138
142
|
if (callback(argument)) continue;
|
|
139
143
|
const value = evaluate(argument);
|
|
@@ -148,17 +152,17 @@ module.exports = rule(
|
|
|
148
152
|
);
|
|
149
153
|
else if (
|
|
150
154
|
kind === "test" &&
|
|
151
|
-
|
|
152
|
-
argument !==
|
|
155
|
+
callArguments.length >= 3 &&
|
|
156
|
+
argument !== callArguments.at(-1) &&
|
|
153
157
|
value.kind === "unknown"
|
|
154
158
|
)
|
|
155
159
|
properties(value, ["timeout"], max, argument);
|
|
156
160
|
}
|
|
157
|
-
const last =
|
|
161
|
+
const last = callArguments.at(-1);
|
|
158
162
|
const minimum = kind === "hook" ? 2 : 3;
|
|
159
|
-
if (!last ||
|
|
163
|
+
if (!last || callArguments.length < minimum || callback(last)) continue;
|
|
160
164
|
// An options object followed by a callback is not a trailing timeout.
|
|
161
|
-
if (evaluate(
|
|
165
|
+
if (evaluate(callArguments.at(-2)).kind === "object") continue;
|
|
162
166
|
const value = evaluate(last);
|
|
163
167
|
if (value.kind !== "object") check({ value, origin: last }, "timeout", max);
|
|
164
168
|
}
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
function createCarriers(option, check) {
|
|
3
|
+
function configCheck(entry, ...args) {
|
|
4
|
+
const unresolved = entry.value.kind !== "number" || entry.value.runtimeFunction;
|
|
5
|
+
if (unresolved && option.configUnknownValues === "defer-to-runner") return;
|
|
6
|
+
check(entry, ...args);
|
|
7
|
+
}
|
|
3
8
|
function properties(
|
|
4
9
|
value,
|
|
5
10
|
names,
|
|
@@ -7,30 +12,31 @@ function createCarriers(option, check) {
|
|
|
7
12
|
origin,
|
|
8
13
|
carrier = "timeout options",
|
|
9
14
|
carrierOrigin = origin,
|
|
15
|
+
inspect = check,
|
|
10
16
|
) {
|
|
11
17
|
if (value.kind !== "object") {
|
|
12
|
-
|
|
18
|
+
inspect({ value: { kind: "unknown" }, origin }, carrier, max, origin);
|
|
13
19
|
return;
|
|
14
20
|
}
|
|
15
21
|
let unresolvedKeys = false;
|
|
16
22
|
for (const name of names) {
|
|
17
23
|
const entry = value.properties.get(name);
|
|
18
|
-
if (entry)
|
|
24
|
+
if (entry) inspect(entry, name, max, origin);
|
|
19
25
|
else if (value.unknown) unresolvedKeys = true;
|
|
20
26
|
}
|
|
21
27
|
if (unresolvedKeys)
|
|
22
|
-
|
|
28
|
+
inspect({ value: { kind: "unknown" }, origin: carrierOrigin }, carrier, max, carrierOrigin);
|
|
23
29
|
}
|
|
24
30
|
function defaults(value, project, origin = project) {
|
|
25
31
|
const max = option.defaultMax ?? 5000;
|
|
26
32
|
if (value.kind !== "object") {
|
|
27
|
-
|
|
33
|
+
configCheck({ value: { kind: "unknown" }, origin }, "config carrier", max, origin);
|
|
28
34
|
return;
|
|
29
35
|
}
|
|
30
36
|
const testEntry = value.properties.get("test");
|
|
31
37
|
if (!testEntry) {
|
|
32
38
|
if (value.unknown)
|
|
33
|
-
|
|
39
|
+
configCheck({ value: { kind: "unknown" }, origin }, "config carrier", max, origin);
|
|
34
40
|
return;
|
|
35
41
|
}
|
|
36
42
|
const test = testEntry.value;
|
|
@@ -42,22 +48,30 @@ function createCarriers(option, check) {
|
|
|
42
48
|
test.kind === "object" ? project : testOrigin,
|
|
43
49
|
"test options carrier",
|
|
44
50
|
testOrigin,
|
|
51
|
+
configCheck,
|
|
45
52
|
);
|
|
46
53
|
if (test.kind !== "object") return;
|
|
47
54
|
const projects = test.properties.get("projects");
|
|
48
55
|
if (projects) {
|
|
49
|
-
if (projects.value.kind === "array")
|
|
56
|
+
if (projects.value.kind === "array") {
|
|
57
|
+
if (!projects.value.lengthKnown)
|
|
58
|
+
configCheck(
|
|
59
|
+
{ value: { kind: "unknown" }, origin: projects.origin },
|
|
60
|
+
"projects carrier",
|
|
61
|
+
max,
|
|
62
|
+
projects.origin,
|
|
63
|
+
);
|
|
50
64
|
for (const entry of projects.value.entries)
|
|
51
65
|
defaults(entry.value, entry.origin, entry.origin);
|
|
52
|
-
else
|
|
53
|
-
|
|
66
|
+
} else
|
|
67
|
+
configCheck(
|
|
54
68
|
{ value: { kind: "unknown" }, origin: projects.origin },
|
|
55
69
|
"projects carrier",
|
|
56
70
|
max,
|
|
57
71
|
projects.origin,
|
|
58
72
|
);
|
|
59
73
|
} else if (test.unknown && !project)
|
|
60
|
-
|
|
74
|
+
configCheck(
|
|
61
75
|
{ value: { kind: "unknown" }, origin: testOrigin },
|
|
62
76
|
"projects carrier",
|
|
63
77
|
max,
|
|
@@ -5,6 +5,7 @@ const { createPrimitiveMemberRead } = require("./vitest-timeout-primitive-reads"
|
|
|
5
5
|
const { createValueStability } = require("./vitest-timeout-value-stability");
|
|
6
6
|
const { createScalarCopyWrite } = require("./vitest-timeout-copy-scalars");
|
|
7
7
|
const { configCall, staticPropertyName } = require("./vitest-timeout-bindings");
|
|
8
|
+
const { arrayValue, concatenateArrays } = require("./vitest-timeout-array-values");
|
|
8
9
|
const UNKNOWN = { kind: "unknown" };
|
|
9
10
|
function taint(value) {
|
|
10
11
|
if (value.kind === "object")
|
|
@@ -22,6 +23,8 @@ function taint(value) {
|
|
|
22
23
|
return {
|
|
23
24
|
...value,
|
|
24
25
|
entries: value.entries.map((entry) => ({ ...entry, value: taint(entry.value) })),
|
|
26
|
+
knownPrefixLength: 0,
|
|
27
|
+
lengthKnown: false,
|
|
25
28
|
};
|
|
26
29
|
return UNKNOWN;
|
|
27
30
|
}
|
|
@@ -38,7 +41,7 @@ function merge(left, right) {
|
|
|
38
41
|
if (previous?.value.kind === "object" && value.kind === "object")
|
|
39
42
|
value = merge(previous.value, value);
|
|
40
43
|
if (previous?.value.kind === "array" && value.kind === "array")
|
|
41
|
-
value =
|
|
44
|
+
value = concatenateArrays(previous.value, value);
|
|
42
45
|
properties.set(name, { ...entry, value });
|
|
43
46
|
}
|
|
44
47
|
return { kind: "object", properties, unknown: Boolean(left.unknown || right.unknown) };
|
|
@@ -64,8 +67,14 @@ function createEvaluator(context, consumed) {
|
|
|
64
67
|
}
|
|
65
68
|
function resolve(node) {
|
|
66
69
|
if (node.type === "Literal" && node.value === null) return { kind: "null" };
|
|
67
|
-
if (node.type === "Literal")
|
|
68
|
-
|
|
70
|
+
if (node.type === "Literal") {
|
|
71
|
+
if (typeof node.value === "number") return { kind: "number", value: node.value };
|
|
72
|
+
return ["string", "boolean", "bigint"].includes(typeof node.value)
|
|
73
|
+
? { kind: "unknown", runtimeType: typeof node.value }
|
|
74
|
+
: UNKNOWN;
|
|
75
|
+
}
|
|
76
|
+
if (node.type === "UnaryExpression" && node.operator === "void")
|
|
77
|
+
return { kind: "unknown", runtimeType: "undefined" };
|
|
69
78
|
if (node.type === "UnaryExpression" && (node.operator === "-" || node.operator === "+")) {
|
|
70
79
|
const value = evaluate(node.argument);
|
|
71
80
|
return value.kind === "number"
|
|
@@ -154,20 +163,7 @@ function createEvaluator(context, consumed) {
|
|
|
154
163
|
}
|
|
155
164
|
return { kind: "object", properties, unknown };
|
|
156
165
|
}
|
|
157
|
-
if (node.type === "ArrayExpression")
|
|
158
|
-
return {
|
|
159
|
-
kind: "array",
|
|
160
|
-
entries: node.elements.flatMap((origin) => {
|
|
161
|
-
if (!origin) return [];
|
|
162
|
-
if (origin.type === "SpreadElement") {
|
|
163
|
-
const value = evaluate(origin.argument);
|
|
164
|
-
return value.kind === "array"
|
|
165
|
-
? value.entries.map((entry) => ({ ...entry, origin }))
|
|
166
|
-
: [{ value: UNKNOWN, origin }];
|
|
167
|
-
}
|
|
168
|
-
return [{ value: evaluate(origin), origin }];
|
|
169
|
-
}),
|
|
170
|
-
};
|
|
166
|
+
if (node.type === "ArrayExpression") return arrayValue(node, evaluate);
|
|
171
167
|
if (node.type === "ArrowFunctionExpression" || node.type === "FunctionExpression") {
|
|
172
168
|
const statements = node.body.type === "BlockStatement" ? node.body.body : null;
|
|
173
169
|
const value = statements
|