eslint-plugin-no-mistakes 0.90.0 → 0.91.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +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-cap.js +12 -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 };
|
|
@@ -27,6 +27,7 @@ module.exports = rule(
|
|
|
27
27
|
defaultMax: { type: "number", minimum: 0, exclusiveMinimum: true },
|
|
28
28
|
overrideMax: { type: "number", minimum: 0, exclusiveMinimum: true },
|
|
29
29
|
unknownValues: { enum: ["ignore", "finding"] },
|
|
30
|
+
configUnknownValues: { enum: ["inherit", "defer-to-runner"] },
|
|
30
31
|
configRoot: { type: "boolean" },
|
|
31
32
|
},
|
|
32
33
|
additionalProperties: false,
|
|
@@ -109,10 +110,11 @@ module.exports = rule(
|
|
|
109
110
|
for (const original of overrides) {
|
|
110
111
|
const kind = overrideCall(original, context);
|
|
111
112
|
if (!kind) continue;
|
|
112
|
-
const node =
|
|
113
|
+
const node = original;
|
|
114
|
+
const callArguments = invocationArguments(original, context);
|
|
113
115
|
const max = option.overrideMax ?? 30000;
|
|
114
116
|
if (kind === "test" || kind === "hook")
|
|
115
|
-
for (const argument of
|
|
117
|
+
for (const argument of callArguments)
|
|
116
118
|
if (opaqueCallback(argument, context))
|
|
117
119
|
check(
|
|
118
120
|
{ value: { kind: "unknown" }, origin: argument },
|
|
@@ -126,14 +128,14 @@ module.exports = rule(
|
|
|
126
128
|
}
|
|
127
129
|
if (kind === "runtime") {
|
|
128
130
|
properties(
|
|
129
|
-
evaluate(
|
|
131
|
+
evaluate(callArguments[0]),
|
|
130
132
|
["testTimeout", "hookTimeout"],
|
|
131
133
|
max,
|
|
132
|
-
|
|
134
|
+
callArguments[0] || node,
|
|
133
135
|
);
|
|
134
136
|
continue;
|
|
135
137
|
}
|
|
136
|
-
const args = kind === "hook" ?
|
|
138
|
+
const args = kind === "hook" ? callArguments.slice(1) : callArguments.slice(1, 3);
|
|
137
139
|
for (const argument of args) {
|
|
138
140
|
if (callback(argument)) continue;
|
|
139
141
|
const value = evaluate(argument);
|
|
@@ -148,17 +150,17 @@ module.exports = rule(
|
|
|
148
150
|
);
|
|
149
151
|
else if (
|
|
150
152
|
kind === "test" &&
|
|
151
|
-
|
|
152
|
-
argument !==
|
|
153
|
+
callArguments.length >= 3 &&
|
|
154
|
+
argument !== callArguments.at(-1) &&
|
|
153
155
|
value.kind === "unknown"
|
|
154
156
|
)
|
|
155
157
|
properties(value, ["timeout"], max, argument);
|
|
156
158
|
}
|
|
157
|
-
const last =
|
|
159
|
+
const last = callArguments.at(-1);
|
|
158
160
|
const minimum = kind === "hook" ? 2 : 3;
|
|
159
|
-
if (!last ||
|
|
161
|
+
if (!last || callArguments.length < minimum || callback(last)) continue;
|
|
160
162
|
// An options object followed by a callback is not a trailing timeout.
|
|
161
|
-
if (evaluate(
|
|
163
|
+
if (evaluate(callArguments.at(-2)).kind === "object") continue;
|
|
162
164
|
const value = evaluate(last);
|
|
163
165
|
if (value.kind !== "object") check({ value, origin: last }, "timeout", max);
|
|
164
166
|
}
|
|
@@ -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
|