@descryy/runtime-test-runner 0.4.12 → 0.4.14
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/dist/bounded-file.d.ts +18 -0
- package/dist/bounded-file.d.ts.map +1 -0
- package/dist/bounded-file.js +77 -0
- package/dist/bounded-file.js.map +1 -0
- package/dist/child-environment.d.ts +12 -0
- package/dist/child-environment.d.ts.map +1 -0
- package/dist/child-environment.js +124 -0
- package/dist/child-environment.js.map +1 -0
- package/dist/coverage-capture.d.ts +23 -0
- package/dist/coverage-capture.d.ts.map +1 -0
- package/dist/coverage-capture.js +165 -0
- package/dist/coverage-capture.js.map +1 -0
- package/dist/executed-coverage.d.ts +45 -0
- package/dist/executed-coverage.d.ts.map +1 -0
- package/dist/executed-coverage.js +143 -0
- package/dist/executed-coverage.js.map +1 -0
- package/dist/go-cover-profile.d.ts +29 -0
- package/dist/go-cover-profile.d.ts.map +1 -0
- package/dist/go-cover-profile.js +162 -0
- package/dist/go-cover-profile.js.map +1 -0
- package/dist/go-test.d.ts +29 -2
- package/dist/go-test.d.ts.map +1 -1
- package/dist/go-test.js +149 -30
- package/dist/go-test.js.map +1 -1
- package/dist/graph-mapping.d.ts +1 -0
- package/dist/graph-mapping.d.ts.map +1 -1
- package/dist/graph-mapping.js +78 -1
- package/dist/graph-mapping.js.map +1 -1
- package/dist/index.d.ts +21 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +11 -1
- package/dist/index.js.map +1 -1
- package/dist/lexers/c-family.d.ts +4 -0
- package/dist/lexers/c-family.d.ts.map +1 -0
- package/dist/lexers/c-family.js +221 -0
- package/dist/lexers/c-family.js.map +1 -0
- package/dist/lexers/chars.d.ts +7 -0
- package/dist/lexers/chars.d.ts.map +1 -0
- package/dist/lexers/chars.js +49 -0
- package/dist/lexers/chars.js.map +1 -0
- package/dist/lexers/index.d.ts +7 -0
- package/dist/lexers/index.d.ts.map +1 -0
- package/dist/lexers/index.js +5 -0
- package/dist/lexers/index.js.map +1 -0
- package/dist/lexers/python.d.ts +3 -0
- package/dist/lexers/python.d.ts.map +1 -0
- package/dist/lexers/python.js +101 -0
- package/dist/lexers/python.js.map +1 -0
- package/dist/quality/annotations.d.ts +14 -0
- package/dist/quality/annotations.d.ts.map +1 -0
- package/dist/quality/annotations.js +147 -0
- package/dist/quality/annotations.js.map +1 -0
- package/dist/quality/assertion-kinds.d.ts +18 -0
- package/dist/quality/assertion-kinds.d.ts.map +1 -0
- package/dist/quality/assertion-kinds.js +25 -0
- package/dist/quality/assertion-kinds.js.map +1 -0
- package/dist/quality/chains.d.ts +25 -0
- package/dist/quality/chains.d.ts.map +1 -0
- package/dist/quality/chains.js +129 -0
- package/dist/quality/chains.js.map +1 -0
- package/dist/quality/common.d.ts +31 -0
- package/dist/quality/common.d.ts.map +1 -0
- package/dist/quality/common.js +211 -0
- package/dist/quality/common.js.map +1 -0
- package/dist/quality/csharp-family.d.ts +9 -0
- package/dist/quality/csharp-family.d.ts.map +1 -0
- package/dist/quality/csharp-family.js +560 -0
- package/dist/quality/csharp-family.js.map +1 -0
- package/dist/quality/detect.d.ts +3 -0
- package/dist/quality/detect.d.ts.map +1 -0
- package/dist/quality/detect.js +348 -0
- package/dist/quality/detect.js.map +1 -0
- package/dist/quality/durations.d.ts +4 -0
- package/dist/quality/durations.d.ts.map +1 -0
- package/dist/quality/durations.js +177 -0
- package/dist/quality/durations.js.map +1 -0
- package/dist/quality/expressions.d.ts +7 -0
- package/dist/quality/expressions.d.ts.map +1 -0
- package/dist/quality/expressions.js +123 -0
- package/dist/quality/expressions.js.map +1 -0
- package/dist/quality/facts.d.ts +5 -0
- package/dist/quality/facts.d.ts.map +1 -0
- package/dist/quality/facts.js +183 -0
- package/dist/quality/facts.js.map +1 -0
- package/dist/quality/failure-of.d.ts +4 -0
- package/dist/quality/failure-of.d.ts.map +1 -0
- package/dist/quality/failure-of.js +233 -0
- package/dist/quality/failure-of.js.map +1 -0
- package/dist/quality/frameworks.d.ts +5 -0
- package/dist/quality/frameworks.d.ts.map +1 -0
- package/dist/quality/frameworks.js +20 -0
- package/dist/quality/frameworks.js.map +1 -0
- package/dist/quality/go-family.d.ts +6 -0
- package/dist/quality/go-family.d.ts.map +1 -0
- package/dist/quality/go-family.js +393 -0
- package/dist/quality/go-family.js.map +1 -0
- package/dist/quality/index.d.ts +10 -0
- package/dist/quality/index.d.ts.map +1 -0
- package/dist/quality/index.js +19 -0
- package/dist/quality/index.js.map +1 -0
- package/dist/quality/java-family.d.ts +12 -0
- package/dist/quality/java-family.d.ts.map +1 -0
- package/dist/quality/java-family.js +529 -0
- package/dist/quality/java-family.js.map +1 -0
- package/dist/quality/js-assertions.d.ts +11 -0
- package/dist/quality/js-assertions.d.ts.map +1 -0
- package/dist/quality/js-assertions.js +297 -0
- package/dist/quality/js-assertions.js.map +1 -0
- package/dist/quality/js-family.d.ts +3 -0
- package/dist/quality/js-family.d.ts.map +1 -0
- package/dist/quality/js-family.js +470 -0
- package/dist/quality/js-family.js.map +1 -0
- package/dist/quality/python-family.d.ts +6 -0
- package/dist/quality/python-family.d.ts.map +1 -0
- package/dist/quality/python-family.js +786 -0
- package/dist/quality/python-family.js.map +1 -0
- package/dist/quality/scope.d.ts +26 -0
- package/dist/quality/scope.d.ts.map +1 -0
- package/dist/quality/scope.js +33 -0
- package/dist/quality/scope.js.map +1 -0
- package/dist/quality/selectors.d.ts +14 -0
- package/dist/quality/selectors.d.ts.map +1 -0
- package/dist/quality/selectors.js +137 -0
- package/dist/quality/selectors.js.map +1 -0
- package/dist/quality/statuses.d.ts +7 -0
- package/dist/quality/statuses.d.ts.map +1 -0
- package/dist/quality/statuses.js +143 -0
- package/dist/quality/statuses.js.map +1 -0
- package/dist/quality/stream.d.ts +23 -0
- package/dist/quality/stream.d.ts.map +1 -0
- package/dist/quality/stream.js +194 -0
- package/dist/quality/stream.js.map +1 -0
- package/dist/quality/types.d.ts +88 -0
- package/dist/quality/types.d.ts.map +1 -0
- package/dist/quality/types.js +2 -0
- package/dist/quality/types.js.map +1 -0
- package/dist/run-timeout.d.ts +25 -0
- package/dist/run-timeout.d.ts.map +1 -0
- package/dist/run-timeout.js +52 -0
- package/dist/run-timeout.js.map +1 -0
- package/dist/runner-wrappers.d.ts +74 -0
- package/dist/runner-wrappers.d.ts.map +1 -0
- package/dist/runner-wrappers.js +604 -0
- package/dist/runner-wrappers.js.map +1 -0
- package/dist/swift-xctest.d.ts +15 -2
- package/dist/swift-xctest.d.ts.map +1 -1
- package/dist/swift-xctest.js +143 -20
- package/dist/swift-xctest.js.map +1 -1
- package/dist/test-runner-collector.d.ts +56 -6
- package/dist/test-runner-collector.d.ts.map +1 -1
- package/dist/test-runner-collector.js +265 -113
- package/dist/test-runner-collector.js.map +1 -1
- package/dist/testgen/csharp-facts.d.ts +3 -0
- package/dist/testgen/csharp-facts.d.ts.map +1 -0
- package/dist/testgen/csharp-facts.js +306 -0
- package/dist/testgen/csharp-facts.js.map +1 -0
- package/dist/testgen/csharp-profile.d.ts +14 -0
- package/dist/testgen/csharp-profile.d.ts.map +1 -0
- package/dist/testgen/csharp-profile.js +233 -0
- package/dist/testgen/csharp-profile.js.map +1 -0
- package/dist/testgen/declarations.d.ts +10 -0
- package/dist/testgen/declarations.d.ts.map +1 -0
- package/dist/testgen/declarations.js +110 -0
- package/dist/testgen/declarations.js.map +1 -0
- package/dist/testgen/facts-common.d.ts +33 -0
- package/dist/testgen/facts-common.d.ts.map +1 -0
- package/dist/testgen/facts-common.js +76 -0
- package/dist/testgen/facts-common.js.map +1 -0
- package/dist/testgen/failure.d.ts +4 -0
- package/dist/testgen/failure.d.ts.map +1 -0
- package/dist/testgen/failure.js +106 -0
- package/dist/testgen/failure.js.map +1 -0
- package/dist/testgen/frameworks.d.ts +6 -0
- package/dist/testgen/frameworks.d.ts.map +1 -0
- package/dist/testgen/frameworks.js +516 -0
- package/dist/testgen/frameworks.js.map +1 -0
- package/dist/testgen/go-facts.d.ts +11 -0
- package/dist/testgen/go-facts.d.ts.map +1 -0
- package/dist/testgen/go-facts.js +237 -0
- package/dist/testgen/go-facts.js.map +1 -0
- package/dist/testgen/go-profile.d.ts +6 -0
- package/dist/testgen/go-profile.d.ts.map +1 -0
- package/dist/testgen/go-profile.js +155 -0
- package/dist/testgen/go-profile.js.map +1 -0
- package/dist/testgen/index.d.ts +28 -0
- package/dist/testgen/index.d.ts.map +1 -0
- package/dist/testgen/index.js +21 -0
- package/dist/testgen/index.js.map +1 -0
- package/dist/testgen/java-build.d.ts +13 -0
- package/dist/testgen/java-build.d.ts.map +1 -0
- package/dist/testgen/java-build.js +250 -0
- package/dist/testgen/java-build.js.map +1 -0
- package/dist/testgen/java-facts.d.ts +3 -0
- package/dist/testgen/java-facts.d.ts.map +1 -0
- package/dist/testgen/java-facts.js +349 -0
- package/dist/testgen/java-facts.js.map +1 -0
- package/dist/testgen/java-profile.d.ts +13 -0
- package/dist/testgen/java-profile.d.ts.map +1 -0
- package/dist/testgen/java-profile.js +194 -0
- package/dist/testgen/java-profile.js.map +1 -0
- package/dist/testgen/languages.d.ts +11 -0
- package/dist/testgen/languages.d.ts.map +1 -0
- package/dist/testgen/languages.js +63 -0
- package/dist/testgen/languages.js.map +1 -0
- package/dist/testgen/lexer.d.ts +19 -0
- package/dist/testgen/lexer.d.ts.map +1 -0
- package/dist/testgen/lexer.js +257 -0
- package/dist/testgen/lexer.js.map +1 -0
- package/dist/testgen/mutant-core.d.ts +19 -0
- package/dist/testgen/mutant-core.d.ts.map +1 -0
- package/dist/testgen/mutant-core.js +39 -0
- package/dist/testgen/mutant-core.js.map +1 -0
- package/dist/testgen/mutants-lang.d.ts +6 -0
- package/dist/testgen/mutants-lang.d.ts.map +1 -0
- package/dist/testgen/mutants-lang.js +291 -0
- package/dist/testgen/mutants-lang.js.map +1 -0
- package/dist/testgen/mutants.d.ts +9 -0
- package/dist/testgen/mutants.d.ts.map +1 -0
- package/dist/testgen/mutants.js +148 -0
- package/dist/testgen/mutants.js.map +1 -0
- package/dist/testgen/paths.d.ts +32 -0
- package/dist/testgen/paths.d.ts.map +1 -0
- package/dist/testgen/paths.js +211 -0
- package/dist/testgen/paths.js.map +1 -0
- package/dist/testgen/process.d.ts +19 -0
- package/dist/testgen/process.d.ts.map +1 -0
- package/dist/testgen/process.js +89 -0
- package/dist/testgen/process.js.map +1 -0
- package/dist/testgen/profile-failure.d.ts +7 -0
- package/dist/testgen/profile-failure.d.ts.map +1 -0
- package/dist/testgen/profile-failure.js +29 -0
- package/dist/testgen/profile-failure.js.map +1 -0
- package/dist/testgen/python-facts.d.ts +11 -0
- package/dist/testgen/python-facts.d.ts.map +1 -0
- package/dist/testgen/python-facts.js +380 -0
- package/dist/testgen/python-facts.js.map +1 -0
- package/dist/testgen/python-profile.d.ts +4 -0
- package/dist/testgen/python-profile.d.ts.map +1 -0
- package/dist/testgen/python-profile.js +344 -0
- package/dist/testgen/python-profile.js.map +1 -0
- package/dist/testgen/source-facts.d.ts +30 -0
- package/dist/testgen/source-facts.d.ts.map +1 -0
- package/dist/testgen/source-facts.js +497 -0
- package/dist/testgen/source-facts.js.map +1 -0
- package/dist/testgen/stubs.d.ts +4 -0
- package/dist/testgen/stubs.d.ts.map +1 -0
- package/dist/testgen/stubs.js +66 -0
- package/dist/testgen/stubs.js.map +1 -0
- package/dist/testgen/toolchain.d.ts +23 -0
- package/dist/testgen/toolchain.d.ts.map +1 -0
- package/dist/testgen/toolchain.js +201 -0
- package/dist/testgen/toolchain.js.map +1 -0
- package/dist/testgen/types.d.ts +67 -0
- package/dist/testgen/types.d.ts.map +1 -0
- package/dist/testgen/types.js +2 -0
- package/dist/testgen/types.js.map +1 -0
- package/dist/v8-coverage.d.ts +21 -0
- package/dist/v8-coverage.d.ts.map +1 -0
- package/dist/v8-coverage.js +217 -0
- package/dist/v8-coverage.js.map +1 -0
- package/dist/vitest-reporter.d.ts +46 -13
- package/dist/vitest-reporter.d.ts.map +1 -1
- package/dist/vitest-reporter.js +119 -26
- package/dist/vitest-reporter.js.map +1 -1
- package/dist/vitest-version.d.ts +21 -0
- package/dist/vitest-version.d.ts.map +1 -0
- package/dist/vitest-version.js +98 -0
- package/dist/vitest-version.js.map +1 -0
- package/package.json +14 -8
- package/reporters/junit/classes/DescryJUnitListener.class +0 -0
- package/reporters/junit/classes/META-INF/services/org.junit.platform.launcher.TestExecutionListener +1 -0
- package/reporters/minitest/descry_minitest_reporter.rb +150 -0
- package/reporters/phpunit/DescryPhpUnitExtension.php +209 -0
- package/reporters/pytest/descry_pytest_plugin.py +397 -0
- package/reporters/rspec/descry_rspec_formatter.rb +175 -0
- package/reporters/xunit/bin/Release/netstandard2.0/Descry.TestLogger.deps.json +203 -0
- package/reporters/xunit/bin/Release/netstandard2.0/Descry.TestLogger.dll +0 -0
- package/reporters/xunit/bin/Release/netstandard2.0/Descry.TestLogger.pdb +0 -0
|
@@ -0,0 +1,786 @@
|
|
|
1
|
+
import { argsOf, parseChain } from "./chains.js";
|
|
2
|
+
import { allChains, chainsInRange, spanSetOf } from "./common.js";
|
|
3
|
+
import { durationMs } from "./durations.js";
|
|
4
|
+
import { isNullLiteralRange } from "./expressions.js";
|
|
5
|
+
import { EXISTENCE, negate, SNAPSHOT, STRONG, STRONG_THROWS, STRONG_URL, STRONG_VALUE, strongerOf, strongStatus, WEAK, weakerOf } from "./assertion-kinds.js";
|
|
6
|
+
import { selectorFromBy, selectorFromCall } from "./selectors.js";
|
|
7
|
+
import { isStatusSubject, isUrlSubject, statusFromTokens } from "./statuses.js";
|
|
8
|
+
import { endLineOf, isIdent, isPunct, lineOf, splitTopLevel, stringValue, textAt, textOf } from "./stream.js";
|
|
9
|
+
const CHAIN_OPTIONS = { generics: false, sameLine: true };
|
|
10
|
+
function buildLayout(stream) {
|
|
11
|
+
const toks = stream.toks;
|
|
12
|
+
const stmtStart = new Int32Array(toks.length).fill(0);
|
|
13
|
+
const stmtId = new Int32Array(toks.length).fill(0);
|
|
14
|
+
const starts = [];
|
|
15
|
+
let depth = 0;
|
|
16
|
+
let current = 0;
|
|
17
|
+
for (let index = 0; index < toks.length; index += 1) {
|
|
18
|
+
const token = toks[index];
|
|
19
|
+
const previous = toks[index - 1];
|
|
20
|
+
let begins = index === 0;
|
|
21
|
+
if (!begins && depth === 0) {
|
|
22
|
+
const continued = previous.kind === "punctuator" && previous.text === "\\";
|
|
23
|
+
if (!continued && endLineOf(previous) < token.line)
|
|
24
|
+
begins = true;
|
|
25
|
+
else if (previous.kind === "punctuator" && previous.text === ";")
|
|
26
|
+
begins = true;
|
|
27
|
+
}
|
|
28
|
+
if (begins) {
|
|
29
|
+
current = index;
|
|
30
|
+
starts.push(index);
|
|
31
|
+
}
|
|
32
|
+
stmtStart[index] = current;
|
|
33
|
+
stmtId[index] = starts.length - 1;
|
|
34
|
+
if (token.kind === "punctuator") {
|
|
35
|
+
if (token.text === "(" || token.text === "[" || token.text === "{")
|
|
36
|
+
depth += 1;
|
|
37
|
+
else if (token.text === ")" || token.text === "]" || token.text === "}")
|
|
38
|
+
depth = Math.max(0, depth - 1);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return { stmtStart, starts, stmtId };
|
|
42
|
+
}
|
|
43
|
+
function statementEnd(stream, layout, start) {
|
|
44
|
+
const id = layout.stmtId[start];
|
|
45
|
+
const next = layout.starts[id + 1];
|
|
46
|
+
return next === undefined ? stream.toks.length - 1 : next - 1;
|
|
47
|
+
}
|
|
48
|
+
function blockEnd(stream, layout, start) {
|
|
49
|
+
const headerEnd = statementEnd(stream, layout, start);
|
|
50
|
+
if (textAt(stream, headerEnd) !== ":")
|
|
51
|
+
return headerEnd;
|
|
52
|
+
const indent = stream.toks[layout.stmtStart[start]].column;
|
|
53
|
+
let index = headerEnd + 1;
|
|
54
|
+
while (index < stream.toks.length) {
|
|
55
|
+
if (layout.stmtStart[index] === index && stream.toks[index].column <= indent)
|
|
56
|
+
break;
|
|
57
|
+
index += 1;
|
|
58
|
+
}
|
|
59
|
+
return index - 1;
|
|
60
|
+
}
|
|
61
|
+
function bodyIndent(stream, layout, start) {
|
|
62
|
+
const headerEnd = statementEnd(stream, layout, start);
|
|
63
|
+
if (textAt(stream, headerEnd) !== ":")
|
|
64
|
+
return null;
|
|
65
|
+
const first = stream.toks[headerEnd + 1];
|
|
66
|
+
return first === undefined ? null : first.column;
|
|
67
|
+
}
|
|
68
|
+
function decoratorsBefore(stream, layout, declarationStart) {
|
|
69
|
+
const found = [];
|
|
70
|
+
let cursor = declarationStart;
|
|
71
|
+
while (cursor > 0) {
|
|
72
|
+
const previous = cursor - 1;
|
|
73
|
+
const start = layout.stmtStart[previous];
|
|
74
|
+
if (!isPunct(stream, start, "@"))
|
|
75
|
+
break;
|
|
76
|
+
found.push({ start, end: statementEnd(stream, layout, start) });
|
|
77
|
+
cursor = start;
|
|
78
|
+
}
|
|
79
|
+
return found;
|
|
80
|
+
}
|
|
81
|
+
function chainOfDecorator(stream, decorator) {
|
|
82
|
+
if (stream.toks[decorator.start + 1]?.kind !== "identifier")
|
|
83
|
+
return null;
|
|
84
|
+
return parseChain(stream, decorator.start + 1, CHAIN_OPTIONS);
|
|
85
|
+
}
|
|
86
|
+
const SKIP_NAMES = new Set(["skip", "skipif", "skipIf", "skipUnless", "skip_if", "skip_unless", "skip_on", "skipTest"]);
|
|
87
|
+
function decoratorSkips(stream, decorators) {
|
|
88
|
+
for (const decorator of decorators) {
|
|
89
|
+
const chain = chainOfDecorator(stream, decorator);
|
|
90
|
+
if (chain === null)
|
|
91
|
+
continue;
|
|
92
|
+
if (chain.segments.some((segment) => SKIP_NAMES.has(segment.name)))
|
|
93
|
+
return true;
|
|
94
|
+
}
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
const WEAK_CALLS = new Set(["len", "bool"]);
|
|
98
|
+
const EXISTENCE_CALLS = new Set(["hasattr", "exists", "isfile", "isdir", "is_file", "is_dir", "isexists", "path_exists"]);
|
|
99
|
+
function snapshotReference(stream, from, last) {
|
|
100
|
+
for (let index = from; index <= last; index += 1) {
|
|
101
|
+
const token = stream.toks[index];
|
|
102
|
+
if (token.kind === "identifier" && (token.text === "snapshot" || token.text === "match_snapshot" || token.text === "matches_snapshot"))
|
|
103
|
+
return true;
|
|
104
|
+
}
|
|
105
|
+
return false;
|
|
106
|
+
}
|
|
107
|
+
function stripParentheses(stream, from, last) {
|
|
108
|
+
let start = from;
|
|
109
|
+
let end = last;
|
|
110
|
+
while (end > start && isPunct(stream, start, "(") && stream.match[start] === end) {
|
|
111
|
+
start += 1;
|
|
112
|
+
end -= 1;
|
|
113
|
+
}
|
|
114
|
+
return [start, end];
|
|
115
|
+
}
|
|
116
|
+
function splitOnKeyword(stream, from, last, keyword) {
|
|
117
|
+
const parts = [];
|
|
118
|
+
let start = from;
|
|
119
|
+
let index = from;
|
|
120
|
+
while (index <= last) {
|
|
121
|
+
const token = stream.toks[index];
|
|
122
|
+
if (token.kind === "punctuator" && (token.text === "(" || token.text === "[" || token.text === "{") && stream.match[index] > index) {
|
|
123
|
+
index = stream.match[index] + 1;
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
if (token.kind === "identifier" && token.text === keyword && index > start) {
|
|
127
|
+
parts.push([start, index - 1]);
|
|
128
|
+
start = index + 1;
|
|
129
|
+
}
|
|
130
|
+
index += 1;
|
|
131
|
+
}
|
|
132
|
+
if (start <= last)
|
|
133
|
+
parts.push([start, last]);
|
|
134
|
+
return parts;
|
|
135
|
+
}
|
|
136
|
+
const COMPARISON_PUNCTUATION = new Set(["==", "!=", "<", ">", "<=", ">="]);
|
|
137
|
+
function findComparison(stream, from, last) {
|
|
138
|
+
let index = from;
|
|
139
|
+
while (index <= last) {
|
|
140
|
+
const token = stream.toks[index];
|
|
141
|
+
if (token.kind === "punctuator") {
|
|
142
|
+
if ((token.text === "(" || token.text === "[" || token.text === "{") && stream.match[index] > index) {
|
|
143
|
+
index = stream.match[index] + 1;
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
146
|
+
if (COMPARISON_PUNCTUATION.has(token.text) && index > from && index < last)
|
|
147
|
+
return { operator: token.text, left: [from, index - 1], right: [index + 1, last] };
|
|
148
|
+
}
|
|
149
|
+
else if (token.kind === "identifier" && index > from) {
|
|
150
|
+
if (token.text === "in" && index < last)
|
|
151
|
+
return { operator: "in", left: [from, index - 1], right: [index + 1, last] };
|
|
152
|
+
if (token.text === "not" && isIdent(stream, index + 1, "in") && index + 1 < last)
|
|
153
|
+
return { operator: "not in", left: [from, index - 1], right: [index + 2, last] };
|
|
154
|
+
if (token.text === "is" && index < last) {
|
|
155
|
+
if (isIdent(stream, index + 1, "not") && index + 1 < last)
|
|
156
|
+
return { operator: "is not", left: [from, index - 1], right: [index + 2, last] };
|
|
157
|
+
return { operator: "is", left: [from, index - 1], right: [index + 1, last] };
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
index += 1;
|
|
161
|
+
}
|
|
162
|
+
return null;
|
|
163
|
+
}
|
|
164
|
+
function comparesWithNone(stream, from, last) {
|
|
165
|
+
const [start, end] = stripParentheses(stream, from, last);
|
|
166
|
+
const comparison = findComparison(stream, start, end);
|
|
167
|
+
if (comparison === null || (comparison.operator !== "is" && comparison.operator !== "==" && comparison.operator !== "is not" && comparison.operator !== "!="))
|
|
168
|
+
return false;
|
|
169
|
+
return isNullLiteralRange(stream, comparison.left[0], comparison.left[1]) || isNullLiteralRange(stream, comparison.right[0], comparison.right[1]);
|
|
170
|
+
}
|
|
171
|
+
function bareCallName(stream, from, last) {
|
|
172
|
+
if (stream.toks[from]?.kind !== "identifier")
|
|
173
|
+
return null;
|
|
174
|
+
const chain = parseChain(stream, from, CHAIN_OPTIONS);
|
|
175
|
+
if (chain.end !== last)
|
|
176
|
+
return null;
|
|
177
|
+
const finalSegment = chain.segments[chain.segments.length - 1];
|
|
178
|
+
return { name: finalSegment.name, wholeCall: finalSegment.open >= 0 };
|
|
179
|
+
}
|
|
180
|
+
const DEPTH_LIMIT = 48;
|
|
181
|
+
export function classifyPythonExpression(stream, fromIndex, lastIndex, depth = 0) {
|
|
182
|
+
if (depth > DEPTH_LIMIT)
|
|
183
|
+
return STRONG;
|
|
184
|
+
const [from, last] = stripParentheses(stream, fromIndex, lastIndex);
|
|
185
|
+
if (last < from)
|
|
186
|
+
return WEAK;
|
|
187
|
+
const disjuncts = splitOnKeyword(stream, from, last, "or");
|
|
188
|
+
if (disjuncts.length > 1)
|
|
189
|
+
return disjuncts.map((part) => classifyPythonExpression(stream, part[0], part[1], depth + 1)).reduce(weakerOf);
|
|
190
|
+
const conjuncts = splitOnKeyword(stream, from, last, "and");
|
|
191
|
+
if (conjuncts.length > 1)
|
|
192
|
+
return conjuncts.map((part) => classifyPythonExpression(stream, part[0], part[1], depth + 1)).reduce(strongerOf);
|
|
193
|
+
if (isIdent(stream, from, "not") && !isIdent(stream, from + 1, "in") && from < last) {
|
|
194
|
+
if (comparesWithNone(stream, from + 1, last))
|
|
195
|
+
return WEAK;
|
|
196
|
+
return negate(classifyPythonExpression(stream, from + 1, last, depth + 1));
|
|
197
|
+
}
|
|
198
|
+
const comparison = findComparison(stream, from, last);
|
|
199
|
+
if (comparison !== null) {
|
|
200
|
+
const { operator, left, right } = comparison;
|
|
201
|
+
if (operator === "==") {
|
|
202
|
+
if (snapshotReference(stream, left[0], left[1]) || snapshotReference(stream, right[0], right[1]))
|
|
203
|
+
return SNAPSHOT;
|
|
204
|
+
if (isStatusSubject(stream, left[0], left[1])) {
|
|
205
|
+
const value = statusFromTokens(stream, right[0], right[1]);
|
|
206
|
+
if (value !== null)
|
|
207
|
+
return strongStatus(value);
|
|
208
|
+
}
|
|
209
|
+
if (isStatusSubject(stream, right[0], right[1])) {
|
|
210
|
+
const value = statusFromTokens(stream, left[0], left[1]);
|
|
211
|
+
if (value !== null)
|
|
212
|
+
return strongStatus(value);
|
|
213
|
+
}
|
|
214
|
+
if (isUrlSubject(stream, left[0], left[1]) || isUrlSubject(stream, right[0], right[1]))
|
|
215
|
+
return STRONG_URL;
|
|
216
|
+
return STRONG_VALUE;
|
|
217
|
+
}
|
|
218
|
+
if (operator === "!=" || operator === "is not")
|
|
219
|
+
return isNullLiteralRange(stream, left[0], left[1]) || isNullLiteralRange(stream, right[0], right[1]) ? WEAK : STRONG;
|
|
220
|
+
if (operator === "not in")
|
|
221
|
+
return STRONG;
|
|
222
|
+
return STRONG_VALUE;
|
|
223
|
+
}
|
|
224
|
+
const call = bareCallName(stream, from, last);
|
|
225
|
+
if (call !== null) {
|
|
226
|
+
if (!call.wholeCall)
|
|
227
|
+
return WEAK;
|
|
228
|
+
if (WEAK_CALLS.has(call.name))
|
|
229
|
+
return WEAK;
|
|
230
|
+
if (EXISTENCE_CALLS.has(call.name))
|
|
231
|
+
return EXISTENCE;
|
|
232
|
+
if (call.name === "snapshot" || call.name === "match_snapshot")
|
|
233
|
+
return SNAPSHOT;
|
|
234
|
+
return STRONG;
|
|
235
|
+
}
|
|
236
|
+
if (from === last && stream.toks[from].kind === "identifier")
|
|
237
|
+
return WEAK;
|
|
238
|
+
return STRONG;
|
|
239
|
+
}
|
|
240
|
+
const UNITTEST_THROWS = /^assert(Raises|RaisesRegex|Raises_regex|Throws)$/;
|
|
241
|
+
const UNITTEST_NEGATED = /^assert(Not|IsNot)/;
|
|
242
|
+
function classifyUnittest(stream, segment) {
|
|
243
|
+
const name = segment.name;
|
|
244
|
+
const args = argsOf(stream, segment);
|
|
245
|
+
if (UNITTEST_THROWS.test(name))
|
|
246
|
+
return STRONG_THROWS;
|
|
247
|
+
if (/^assertMatch.*Snapshot$/.test(name) || name === "assertSnapshot")
|
|
248
|
+
return SNAPSHOT;
|
|
249
|
+
if (name === "assertTrue" || name === "assertFalse") {
|
|
250
|
+
if (args[0] === undefined)
|
|
251
|
+
return null;
|
|
252
|
+
if (name === "assertFalse" && comparesWithNone(stream, args[0][0], args[0][1]))
|
|
253
|
+
return WEAK;
|
|
254
|
+
const inner = classifyPythonExpression(stream, args[0][0], args[0][1]);
|
|
255
|
+
return name === "assertFalse" ? negate(inner) : inner;
|
|
256
|
+
}
|
|
257
|
+
if (name === "assertIsNotNone")
|
|
258
|
+
return WEAK;
|
|
259
|
+
if (UNITTEST_NEGATED.test(name)) {
|
|
260
|
+
const operands = args.slice(0, 2);
|
|
261
|
+
if (operands.length < 2)
|
|
262
|
+
return WEAK;
|
|
263
|
+
return operands.some((operand) => isNullLiteralRange(stream, operand[0], operand[1])) ? WEAK : STRONG;
|
|
264
|
+
}
|
|
265
|
+
if (name === "assertEqual" || name === "assertEquals" || name === "assertDictEqual" || name === "assertListEqual") {
|
|
266
|
+
if (args.length >= 2) {
|
|
267
|
+
for (const [subject, other] of [[args[0], args[1]], [args[1], args[0]]]) {
|
|
268
|
+
if (isStatusSubject(stream, subject[0], subject[1])) {
|
|
269
|
+
const value = statusFromTokens(stream, other[0], other[1]);
|
|
270
|
+
if (value !== null)
|
|
271
|
+
return strongStatus(value);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
if (isUrlSubject(stream, args[0][0], args[0][1]) || isUrlSubject(stream, args[1][0], args[1][1]))
|
|
275
|
+
return STRONG_URL;
|
|
276
|
+
}
|
|
277
|
+
return STRONG_VALUE;
|
|
278
|
+
}
|
|
279
|
+
if (name === "assertContains" || name === "assertRedirects") {
|
|
280
|
+
for (let index = segment.open + 1; index < segment.close; index += 1) {
|
|
281
|
+
if (isIdent(stream, index, "status_code") && isPunct(stream, index + 1, "=")) {
|
|
282
|
+
const value = statusFromTokens(stream, index + 2, index + 2);
|
|
283
|
+
if (value !== null)
|
|
284
|
+
return strongStatus(value);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
return STRONG_VALUE;
|
|
288
|
+
}
|
|
289
|
+
return STRONG_VALUE;
|
|
290
|
+
}
|
|
291
|
+
const MOCK_STRONG = new Set(["assert_called_with", "assert_called_once_with", "assert_any_call", "assert_has_calls", "assert_awaited_with", "assert_awaited_once_with", "assert_not_called", "assert_called_once", "assert_awaited_once", "assert_not_awaited", "assert_has_awaits", "assert_any_await"]);
|
|
292
|
+
const MOCK_WEAK = new Set(["assert_called", "assert_awaited"]);
|
|
293
|
+
function pythonCallAssertion(stream, chain) {
|
|
294
|
+
const segments = chain.segments;
|
|
295
|
+
for (let index = 0; index < segments.length; index += 1) {
|
|
296
|
+
const segment = segments[index];
|
|
297
|
+
if (segment.open < 0)
|
|
298
|
+
continue;
|
|
299
|
+
if (segment.name === "raises" && index >= 1 && segments[index - 1].name === "pytest")
|
|
300
|
+
return { nameIndex: segment.nameIndex, classified: STRONG_THROWS };
|
|
301
|
+
if ((segment.name === "warns" || segment.name === "deprecated_call") && index >= 1 && segments[index - 1].name === "pytest")
|
|
302
|
+
return { nameIndex: segment.nameIndex, classified: STRONG };
|
|
303
|
+
if (/^assert[A-Z]/.test(segment.name) && index >= 1) {
|
|
304
|
+
const classified = classifyUnittest(stream, segment);
|
|
305
|
+
if (classified !== null)
|
|
306
|
+
return { nameIndex: segment.nameIndex, classified };
|
|
307
|
+
}
|
|
308
|
+
if (segment.name.startsWith("assert_") && index >= 1) {
|
|
309
|
+
if (segment.name === "assert_match" && /snapshot/i.test(segments[index - 1].name))
|
|
310
|
+
return { nameIndex: segment.nameIndex, classified: SNAPSHOT };
|
|
311
|
+
if (MOCK_STRONG.has(segment.name))
|
|
312
|
+
return { nameIndex: segment.nameIndex, classified: STRONG };
|
|
313
|
+
if (MOCK_WEAK.has(segment.name))
|
|
314
|
+
return { nameIndex: segment.nameIndex, classified: WEAK };
|
|
315
|
+
return { nameIndex: segment.nameIndex, classified: STRONG_VALUE };
|
|
316
|
+
}
|
|
317
|
+
if ((segment.name === "assert_match_snapshot" || segment.name === "match_snapshot") && index === 0)
|
|
318
|
+
return { nameIndex: segment.nameIndex, classified: SNAPSHOT };
|
|
319
|
+
}
|
|
320
|
+
return null;
|
|
321
|
+
}
|
|
322
|
+
function stringArg(stream, segment, position) {
|
|
323
|
+
const range = argsOf(stream, segment)[position];
|
|
324
|
+
if (range === undefined || range[0] !== range[1])
|
|
325
|
+
return null;
|
|
326
|
+
return stringValue(stream.toks[range[0]]);
|
|
327
|
+
}
|
|
328
|
+
function dotted(target) {
|
|
329
|
+
const cut = target.lastIndexOf(".");
|
|
330
|
+
if (cut <= 0 || cut === target.length - 1)
|
|
331
|
+
return null;
|
|
332
|
+
return { module: target.slice(0, cut), symbol: target.slice(cut + 1) };
|
|
333
|
+
}
|
|
334
|
+
const MOCK_FACTORIES = new Set(["MagicMock", "Mock", "AsyncMock", "PropertyMock", "NonCallableMock", "NonCallableMagicMock", "create_autospec"]);
|
|
335
|
+
const NETWORK_HEADS = new Set(["responses", "requests_mock", "httpretty", "respx", "aioresponses", "vcr", "HTTPretty", "httpx_mock", "pook"]);
|
|
336
|
+
function pythonMocksOf(stream, chain) {
|
|
337
|
+
const segments = chain.segments;
|
|
338
|
+
const found = [];
|
|
339
|
+
const first = segments[0];
|
|
340
|
+
for (let index = 0; index < segments.length; index += 1) {
|
|
341
|
+
const segment = segments[index];
|
|
342
|
+
if (index === 0 && NETWORK_HEADS.has(segment.name) && segments.length >= 2 && segments[1].name !== "") {
|
|
343
|
+
found.push({ nameIndex: segments[1].nameIndex, kind: "network", module: null, symbol: null });
|
|
344
|
+
continue;
|
|
345
|
+
}
|
|
346
|
+
if (segment.open < 0 && segment.name !== "patch")
|
|
347
|
+
continue;
|
|
348
|
+
if (segment.name === "patch") {
|
|
349
|
+
const member = segments[index + 1];
|
|
350
|
+
if (member !== undefined && (member.name === "dict" || member.name === "stopall" || member.name === "stop"))
|
|
351
|
+
continue;
|
|
352
|
+
if (member !== undefined && member.name === "object" && member.open >= 0) {
|
|
353
|
+
found.push({ nameIndex: member.nameIndex, kind: "function", module: null, symbol: stringArg(stream, member, 1) });
|
|
354
|
+
continue;
|
|
355
|
+
}
|
|
356
|
+
if (member !== undefined && member.name === "multiple" && member.open >= 0) {
|
|
357
|
+
found.push({ nameIndex: member.nameIndex, kind: "function", module: null, symbol: null });
|
|
358
|
+
continue;
|
|
359
|
+
}
|
|
360
|
+
if (segment.open >= 0) {
|
|
361
|
+
const target = stringArg(stream, segment, 0);
|
|
362
|
+
const split = target === null ? null : dotted(target);
|
|
363
|
+
if (split !== null)
|
|
364
|
+
found.push({ nameIndex: segment.nameIndex, kind: "function", module: split.module, symbol: split.symbol });
|
|
365
|
+
else
|
|
366
|
+
found.push({ nameIndex: segment.nameIndex, kind: target === null ? "function" : "module", module: target, symbol: null });
|
|
367
|
+
}
|
|
368
|
+
continue;
|
|
369
|
+
}
|
|
370
|
+
if (segment.name === "setattr" && index >= 1 && /monkeypatch|^mp$/i.test(segments[index - 1].name)) {
|
|
371
|
+
const target = stringArg(stream, segment, 0);
|
|
372
|
+
const split = target === null ? null : dotted(target);
|
|
373
|
+
if (split !== null)
|
|
374
|
+
found.push({ nameIndex: segment.nameIndex, kind: "function", module: split.module, symbol: split.symbol });
|
|
375
|
+
else
|
|
376
|
+
found.push({ nameIndex: segment.nameIndex, kind: "function", module: null, symbol: stringArg(stream, segment, 1) });
|
|
377
|
+
continue;
|
|
378
|
+
}
|
|
379
|
+
if ((segment.name === "spy" || segment.name === "stub") && index >= 1 && segments[index - 1].name === "mocker") {
|
|
380
|
+
found.push({ nameIndex: segment.nameIndex, kind: "function", module: null, symbol: segment.name === "spy" ? stringArg(stream, segment, 1) : null });
|
|
381
|
+
continue;
|
|
382
|
+
}
|
|
383
|
+
if (MOCK_FACTORIES.has(segment.name) && segment.open >= 0) {
|
|
384
|
+
found.push({ nameIndex: segment.nameIndex, kind: "function", module: null, symbol: null });
|
|
385
|
+
continue;
|
|
386
|
+
}
|
|
387
|
+
if (segment.name === "freeze_time" && segment.open >= 0) {
|
|
388
|
+
found.push({ nameIndex: segment.nameIndex, kind: "timer", module: null, symbol: null });
|
|
389
|
+
continue;
|
|
390
|
+
}
|
|
391
|
+
if (segment.name === "travel" && index >= 1 && segments[index - 1].name === "time_machine" && segment.open >= 0) {
|
|
392
|
+
found.push({ nameIndex: segment.nameIndex, kind: "timer", module: null, symbol: null });
|
|
393
|
+
continue;
|
|
394
|
+
}
|
|
395
|
+
if (segment.name === "use_cassette" && segment.open >= 0)
|
|
396
|
+
found.push({ nameIndex: segment.nameIndex, kind: "network", module: null, symbol: null });
|
|
397
|
+
if (segment.name === "aioresponses" && segment.open >= 0 && index === 0)
|
|
398
|
+
found.push({ nameIndex: segment.nameIndex, kind: "network", module: null, symbol: null });
|
|
399
|
+
}
|
|
400
|
+
if (first.name === "freezegun" && segments[1]?.name === "freeze_time" && found.length === 0)
|
|
401
|
+
found.push({ nameIndex: segments[1].nameIndex, kind: "timer", module: null, symbol: null });
|
|
402
|
+
return found;
|
|
403
|
+
}
|
|
404
|
+
const SLEEP_RECEIVERS = new Set(["time", "asyncio", "trio", "anyio", "gevent", "eventlet"]);
|
|
405
|
+
function pythonSleepOf(stream, chain) {
|
|
406
|
+
const segments = chain.segments;
|
|
407
|
+
const last = segments[segments.length - 1];
|
|
408
|
+
if (last.name !== "sleep" || last.open < 0)
|
|
409
|
+
return null;
|
|
410
|
+
if (segments.length > 1 && !SLEEP_RECEIVERS.has(segments[segments.length - 2].name))
|
|
411
|
+
return null;
|
|
412
|
+
const range = argsOf(stream, last)[0];
|
|
413
|
+
return { nameIndex: last.nameIndex, ms: range === undefined ? null : durationMs(stream, range[0], range[1], 1000) };
|
|
414
|
+
}
|
|
415
|
+
function pythonSelectors(stream, chain) {
|
|
416
|
+
const found = [];
|
|
417
|
+
const segments = chain.segments;
|
|
418
|
+
const head = segments[0].name;
|
|
419
|
+
for (let index = 0; index < segments.length; index += 1) {
|
|
420
|
+
const segment = segments[index];
|
|
421
|
+
if (segment.open < 0 || segment.name === "")
|
|
422
|
+
continue;
|
|
423
|
+
const args = argsOf(stream, segment);
|
|
424
|
+
const normalised = segment.name.toLowerCase().replace(/_/g, "");
|
|
425
|
+
if ((normalised === "findelement" || normalised === "findelements") && args.length >= 2) {
|
|
426
|
+
const locator = args[0];
|
|
427
|
+
const value = args[1];
|
|
428
|
+
const byName = locator[1] - locator[0] === 2 && isIdent(stream, locator[0], "By") && isPunct(stream, locator[0] + 1, ".") ? textAt(stream, locator[1]) : null;
|
|
429
|
+
const text = value[0] === value[1] ? stringValue(stream.toks[value[0]]) : null;
|
|
430
|
+
if (byName !== null && text !== null) {
|
|
431
|
+
const candidate = selectorFromBy(byName, text);
|
|
432
|
+
if (candidate !== null)
|
|
433
|
+
found.push({ nameIndex: segment.nameIndex, text: candidate.text, stability: candidate.stability });
|
|
434
|
+
}
|
|
435
|
+
continue;
|
|
436
|
+
}
|
|
437
|
+
const first = args[0];
|
|
438
|
+
const text = first !== undefined && first[0] === first[1] ? stringValue(stream.toks[first[0]]) : null;
|
|
439
|
+
if (text === null)
|
|
440
|
+
continue;
|
|
441
|
+
const candidate = selectorFromCall(segment.name, index > 0 ? segments[index - 1].name : null, text, { head, allowFind: false });
|
|
442
|
+
if (candidate !== null)
|
|
443
|
+
found.push({ nameIndex: segment.nameIndex, text: candidate.text, stability: candidate.stability });
|
|
444
|
+
}
|
|
445
|
+
return found;
|
|
446
|
+
}
|
|
447
|
+
const HELPER_EXCLUDED = new Set([
|
|
448
|
+
"print", "len", "range", "str", "int", "float", "bool", "list", "dict", "set", "tuple", "frozenset", "sorted", "reversed", "enumerate", "zip", "map", "filter", "min", "max", "sum", "any", "all", "abs", "round", "isinstance", "issubclass", "hasattr", "getattr", "setattr", "delattr", "type", "repr", "open", "iter", "next", "id", "hash", "format", "bytes", "bytearray", "callable", "vars", "dir", "super", "object", "input", "eval", "exec", "compile", "divmod", "pow", "chr", "ord", "hex", "oct", "bin", "slice", "memoryview", "property", "staticmethod", "classmethod", "globals", "locals", "ascii", "complex", "if", "while", "for", "return", "assert", "lambda", "yield", "await", "not", "and", "or", "in", "is", "with", "print_",
|
|
449
|
+
"patch", "MagicMock", "Mock", "AsyncMock", "call", "mock_open", "sentinel", "PropertyMock", "create_autospec", "freeze_time", "sleep", "skip", "fixture", "param", "raises", "approx", "fail", "parametrize", "mark", "snapshot", "responses", "aioresponses",
|
|
450
|
+
]);
|
|
451
|
+
function pythonHelperOf(stream, chain) {
|
|
452
|
+
const segments = chain.segments;
|
|
453
|
+
if (segments.length !== 1)
|
|
454
|
+
return null;
|
|
455
|
+
const only = segments[0];
|
|
456
|
+
if (only.open < 0 || HELPER_EXCLUDED.has(only.name))
|
|
457
|
+
return null;
|
|
458
|
+
if (/^[A-Z]/.test(only.name) || /(Error|Exception|Warning)$/.test(only.name))
|
|
459
|
+
return null;
|
|
460
|
+
const previous = stream.toks[chain.head - 1];
|
|
461
|
+
if (previous !== undefined && previous.kind === "identifier" && (previous.text === "def" || previous.text === "class" || previous.text === "import" || previous.text === "from" || previous.text === "as"))
|
|
462
|
+
return null;
|
|
463
|
+
return only.name;
|
|
464
|
+
}
|
|
465
|
+
function collectDefinitions(stream, layout) {
|
|
466
|
+
const definitions = [];
|
|
467
|
+
for (const start of layout.starts) {
|
|
468
|
+
let index = start;
|
|
469
|
+
if (isIdent(stream, index, "async") && isIdent(stream, index + 1, "def"))
|
|
470
|
+
index += 1;
|
|
471
|
+
const token = stream.toks[index];
|
|
472
|
+
if (token === undefined || token.kind !== "identifier" || (token.text !== "def" && token.text !== "class"))
|
|
473
|
+
continue;
|
|
474
|
+
const name = stream.toks[index + 1];
|
|
475
|
+
if (name === undefined || name.kind !== "identifier")
|
|
476
|
+
continue;
|
|
477
|
+
definitions.push({ start, defIndex: index, nameIndex: index + 1, name: name.text, kind: token.text });
|
|
478
|
+
}
|
|
479
|
+
return definitions;
|
|
480
|
+
}
|
|
481
|
+
const HOOK_FUNCTIONS = {
|
|
482
|
+
setUp: { kind: "setup", scope: "each" },
|
|
483
|
+
asyncSetUp: { kind: "setup", scope: "each" },
|
|
484
|
+
tearDown: { kind: "teardown", scope: "each" },
|
|
485
|
+
asyncTearDown: { kind: "teardown", scope: "each" },
|
|
486
|
+
setup_method: { kind: "setup", scope: "each" },
|
|
487
|
+
teardown_method: { kind: "teardown", scope: "each" },
|
|
488
|
+
setup_function: { kind: "setup", scope: "each" },
|
|
489
|
+
teardown_function: { kind: "teardown", scope: "each" },
|
|
490
|
+
setUpClass: { kind: "setup", scope: "all" },
|
|
491
|
+
tearDownClass: { kind: "teardown", scope: "all" },
|
|
492
|
+
setUpModule: { kind: "setup", scope: "all" },
|
|
493
|
+
tearDownModule: { kind: "teardown", scope: "all" },
|
|
494
|
+
setup_class: { kind: "setup", scope: "all" },
|
|
495
|
+
teardown_class: { kind: "teardown", scope: "all" },
|
|
496
|
+
setup_module: { kind: "setup", scope: "all" },
|
|
497
|
+
teardown_module: { kind: "teardown", scope: "all" },
|
|
498
|
+
};
|
|
499
|
+
const ASSIGNMENT_TOKENS = new Set(["=", "+=", "-=", "*=", "/=", "//=", "%=", "**=", "@=", "&=", "|=", "^=", ">>=", "<<=", ":="]);
|
|
500
|
+
const MUTATORS = new Set(["append", "extend", "update", "add", "remove", "pop", "clear", "insert", "setdefault", "discard", "popitem", "sort", "reverse", "put", "put_nowait"]);
|
|
501
|
+
function fixtureInfo(stream, decorators) {
|
|
502
|
+
for (const decorator of decorators) {
|
|
503
|
+
const chain = chainOfDecorator(stream, decorator);
|
|
504
|
+
if (chain === null)
|
|
505
|
+
continue;
|
|
506
|
+
const fixture = chain.segments.find((segment) => segment.name === "fixture");
|
|
507
|
+
if (fixture === undefined)
|
|
508
|
+
continue;
|
|
509
|
+
let autouse = false;
|
|
510
|
+
let scope = "each";
|
|
511
|
+
if (fixture.open >= 0) {
|
|
512
|
+
for (let index = fixture.open + 1; index < fixture.close; index += 1) {
|
|
513
|
+
if (isIdent(stream, index, "autouse") && isPunct(stream, index + 1, "=") && isIdent(stream, index + 2, "True"))
|
|
514
|
+
autouse = true;
|
|
515
|
+
if (isIdent(stream, index, "scope") && isPunct(stream, index + 1, "=")) {
|
|
516
|
+
const value = stringValue(stream.toks[index + 2]);
|
|
517
|
+
if (value !== null && value !== "function")
|
|
518
|
+
scope = "all";
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
return { autouse, scope };
|
|
523
|
+
}
|
|
524
|
+
return null;
|
|
525
|
+
}
|
|
526
|
+
function containsYield(stream, from, to) {
|
|
527
|
+
for (let index = from; index <= to; index += 1)
|
|
528
|
+
if (isIdent(stream, index, "yield"))
|
|
529
|
+
return true;
|
|
530
|
+
return false;
|
|
531
|
+
}
|
|
532
|
+
function nestedFunctionSpans(stream, layout, definitions, first, last) {
|
|
533
|
+
const spans = [];
|
|
534
|
+
const enclosingEnds = [];
|
|
535
|
+
for (const candidate of definitions) {
|
|
536
|
+
if (candidate.kind !== "def" || candidate.defIndex < first || candidate.defIndex > last)
|
|
537
|
+
continue;
|
|
538
|
+
while (enclosingEnds.length > 0 && enclosingEnds[enclosingEnds.length - 1] < candidate.start)
|
|
539
|
+
enclosingEnds.pop();
|
|
540
|
+
const end = blockEnd(stream, layout, candidate.start);
|
|
541
|
+
if (enclosingEnds.length > 0)
|
|
542
|
+
spans.push([candidate.defIndex, end]);
|
|
543
|
+
enclosingEnds.push(end);
|
|
544
|
+
}
|
|
545
|
+
return spanSetOf(spans);
|
|
546
|
+
}
|
|
547
|
+
export function extractPython(scope) {
|
|
548
|
+
const { stream, first, last, out } = scope;
|
|
549
|
+
const layout = buildLayout(stream);
|
|
550
|
+
const definitions = collectDefinitions(stream, layout);
|
|
551
|
+
const chains = allChains(stream, CHAIN_OPTIONS);
|
|
552
|
+
const rangeChains = chainsInRange(chains, first, last);
|
|
553
|
+
let nestedFunctions = null;
|
|
554
|
+
for (let index = first; index <= last; index += 1) {
|
|
555
|
+
const token = stream.toks[index];
|
|
556
|
+
if (token.kind !== "identifier" || token.text !== "assert")
|
|
557
|
+
continue;
|
|
558
|
+
const previous = stream.toks[index - 1];
|
|
559
|
+
const startsStatement = layout.stmtStart[index] === index || (previous !== undefined && previous.kind === "punctuator" && previous.text === ":");
|
|
560
|
+
if (!startsStatement)
|
|
561
|
+
continue;
|
|
562
|
+
const end = statementEnd(stream, layout, layout.stmtStart[index]);
|
|
563
|
+
if (end <= index)
|
|
564
|
+
continue;
|
|
565
|
+
const parts = splitTopLevel(stream, index + 1, end);
|
|
566
|
+
const expression = parts[0];
|
|
567
|
+
if (expression === undefined)
|
|
568
|
+
continue;
|
|
569
|
+
const classified = classifyPythonExpression(stream, expression[0], expression[1]);
|
|
570
|
+
out.assertions.push({ line: token.line, strength: classified.strength, expects: classified.expects, text: textOf(stream, index, end) });
|
|
571
|
+
}
|
|
572
|
+
for (const chain of rangeChains) {
|
|
573
|
+
const assertion = pythonCallAssertion(stream, chain);
|
|
574
|
+
if (assertion !== null)
|
|
575
|
+
out.assertions.push({ line: lineOf(stream, assertion.nameIndex), strength: assertion.classified.strength, expects: assertion.classified.expects, text: textOf(stream, chain.head, chain.end) });
|
|
576
|
+
for (const mock of pythonMocksOf(stream, chain))
|
|
577
|
+
out.mocks.push({ line: lineOf(stream, mock.nameIndex), module: mock.module, symbol: mock.symbol, kind: mock.kind });
|
|
578
|
+
const sleep = pythonSleepOf(stream, chain);
|
|
579
|
+
if (sleep !== null) {
|
|
580
|
+
nestedFunctions ??= nestedFunctionSpans(stream, layout, definitions, first, last);
|
|
581
|
+
if (!nestedFunctions.contains(chain.head))
|
|
582
|
+
out.sleeps.push({ line: lineOf(stream, sleep.nameIndex), ms: sleep.ms });
|
|
583
|
+
}
|
|
584
|
+
for (const selector of pythonSelectors(stream, chain))
|
|
585
|
+
out.selectors.push({ line: lineOf(stream, selector.nameIndex), text: selector.text, stability: selector.stability });
|
|
586
|
+
const helper = pythonHelperOf(stream, chain);
|
|
587
|
+
if (helper !== null)
|
|
588
|
+
out.helperCalls.push({ line: lineOf(stream, chain.head), name: helper });
|
|
589
|
+
}
|
|
590
|
+
const subject = definitions.find((definition) => definition.kind === "def" && definition.start >= first && definition.start <= last);
|
|
591
|
+
const enclosingClasses = (subject === undefined ? definitions.filter((d) => d.kind === "class" && d.start >= first && d.start <= last).slice(0, 1) : definitions.filter((d) => d.kind === "class" && d.start < subject.start && blockEnd(stream, layout, d.start) >= subject.start));
|
|
592
|
+
const decorated = [];
|
|
593
|
+
if (subject !== undefined)
|
|
594
|
+
decorated.push(decoratorsBefore(stream, layout, subject.start));
|
|
595
|
+
for (const owner of enclosingClasses)
|
|
596
|
+
decorated.push(decoratorsBefore(stream, layout, owner.start));
|
|
597
|
+
for (const decorators of decorated) {
|
|
598
|
+
if (decoratorSkips(stream, decorators))
|
|
599
|
+
out.skipped = true;
|
|
600
|
+
for (const decorator of decorators) {
|
|
601
|
+
const chain = chainOfDecorator(stream, decorator);
|
|
602
|
+
if (chain === null)
|
|
603
|
+
continue;
|
|
604
|
+
for (const mock of pythonMocksOf(stream, chain))
|
|
605
|
+
out.mocks.push({ line: lineOf(stream, mock.nameIndex), module: mock.module, symbol: mock.symbol, kind: mock.kind });
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
if (subject !== undefined) {
|
|
609
|
+
const indent = bodyIndent(stream, layout, subject.start);
|
|
610
|
+
const end = blockEnd(stream, layout, subject.start);
|
|
611
|
+
for (const chain of chains) {
|
|
612
|
+
if (chain.head <= subject.defIndex || chain.head > end)
|
|
613
|
+
continue;
|
|
614
|
+
if (layout.stmtStart[chain.head] !== chain.head || stream.toks[chain.head].column !== indent)
|
|
615
|
+
continue;
|
|
616
|
+
const names = chain.segments.map((segment) => segment.name);
|
|
617
|
+
const unconditional = (names[0] === "pytest" && names[1] === "skip") || (names[0] === "self" && names[1] === "skipTest") || (names[0] === "unittest" && names[1] === "skip");
|
|
618
|
+
if (unconditional)
|
|
619
|
+
out.skipped = true;
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
for (const start of layout.starts) {
|
|
623
|
+
if (stream.toks[start].column !== 1)
|
|
624
|
+
continue;
|
|
625
|
+
if (!isIdent(stream, start, "pytestmark"))
|
|
626
|
+
continue;
|
|
627
|
+
const end = statementEnd(stream, layout, start);
|
|
628
|
+
for (let index = start; index <= end; index += 1)
|
|
629
|
+
if (SKIP_NAMES.has(textAt(stream, index)))
|
|
630
|
+
out.skipped = true;
|
|
631
|
+
}
|
|
632
|
+
for (const definition of definitions) {
|
|
633
|
+
if (definition.kind !== "def")
|
|
634
|
+
continue;
|
|
635
|
+
const hook = HOOK_FUNCTIONS[definition.name];
|
|
636
|
+
if (hook !== undefined) {
|
|
637
|
+
out.hooks.push({ line: lineOf(stream, definition.defIndex), kind: hook.kind, scope: hook.scope });
|
|
638
|
+
continue;
|
|
639
|
+
}
|
|
640
|
+
const decorators = decoratorsBefore(stream, layout, definition.start);
|
|
641
|
+
const fixture = fixtureInfo(stream, decorators);
|
|
642
|
+
if (fixture === null)
|
|
643
|
+
continue;
|
|
644
|
+
const end = blockEnd(stream, layout, definition.start);
|
|
645
|
+
const line = lineOf(stream, definition.defIndex);
|
|
646
|
+
if (containsYield(stream, definition.defIndex, end)) {
|
|
647
|
+
out.hooks.push({ line, kind: "setup", scope: fixture.scope });
|
|
648
|
+
out.hooks.push({ line, kind: "teardown", scope: fixture.scope });
|
|
649
|
+
}
|
|
650
|
+
else if (fixture.autouse)
|
|
651
|
+
out.hooks.push({ line, kind: "setup", scope: fixture.scope });
|
|
652
|
+
}
|
|
653
|
+
for (const chain of chains) {
|
|
654
|
+
const lastSegment = chain.segments[chain.segments.length - 1];
|
|
655
|
+
if (lastSegment.open < 0)
|
|
656
|
+
continue;
|
|
657
|
+
if (lastSegment.name === "addCleanup" || lastSegment.name === "addfinalizer" || lastSegment.name === "addAsyncCleanup")
|
|
658
|
+
out.hooks.push({ line: lineOf(stream, chain.head), kind: "teardown", scope: "each" });
|
|
659
|
+
if (lastSegment.name === "addClassCleanup")
|
|
660
|
+
out.hooks.push({ line: lineOf(stream, chain.head), kind: "teardown", scope: "all" });
|
|
661
|
+
}
|
|
662
|
+
collectPythonSharedState(scope, layout, definitions);
|
|
663
|
+
}
|
|
664
|
+
function collectPythonSharedState(scope, layout, definitions) {
|
|
665
|
+
const { stream, first, last, out } = scope;
|
|
666
|
+
if (first > last)
|
|
667
|
+
return;
|
|
668
|
+
const moduleNames = new Set();
|
|
669
|
+
const classNames = new Set();
|
|
670
|
+
const classAttributes = new Set();
|
|
671
|
+
const assignmentTarget = (start) => {
|
|
672
|
+
const token = stream.toks[start];
|
|
673
|
+
if (token === undefined || token.kind !== "identifier")
|
|
674
|
+
return null;
|
|
675
|
+
const next = stream.toks[start + 1];
|
|
676
|
+
if (next === undefined || next.kind !== "punctuator")
|
|
677
|
+
return null;
|
|
678
|
+
if (next.text === "=" || (next.text === ":" && isPunct(stream, start + 3, "=")) || (next.text === ":" && stream.toks[start + 2]?.kind === "identifier" && isPunct(stream, start + 3, "=")))
|
|
679
|
+
return token.text;
|
|
680
|
+
return null;
|
|
681
|
+
};
|
|
682
|
+
for (const start of layout.starts) {
|
|
683
|
+
if (stream.toks[start].column !== 1)
|
|
684
|
+
continue;
|
|
685
|
+
const name = assignmentTarget(start);
|
|
686
|
+
if (name !== null)
|
|
687
|
+
moduleNames.add(name);
|
|
688
|
+
}
|
|
689
|
+
for (const definition of definitions) {
|
|
690
|
+
if (definition.kind !== "class")
|
|
691
|
+
continue;
|
|
692
|
+
classNames.add(definition.name);
|
|
693
|
+
const indent = bodyIndent(stream, layout, definition.start);
|
|
694
|
+
const end = blockEnd(stream, layout, definition.start);
|
|
695
|
+
if (indent === null)
|
|
696
|
+
continue;
|
|
697
|
+
for (const start of layout.starts) {
|
|
698
|
+
if (start <= definition.defIndex || start > end || stream.toks[start].column !== indent)
|
|
699
|
+
continue;
|
|
700
|
+
const name = assignmentTarget(start);
|
|
701
|
+
if (name !== null)
|
|
702
|
+
classAttributes.add(name);
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
if (moduleNames.size === 0 && classAttributes.size === 0)
|
|
706
|
+
return;
|
|
707
|
+
const globals = new Set();
|
|
708
|
+
const parameters = new Set();
|
|
709
|
+
const locals = new Set();
|
|
710
|
+
for (let index = first; index <= last; index += 1) {
|
|
711
|
+
const token = stream.toks[index];
|
|
712
|
+
if (token.kind === "identifier" && token.text === "global" && layout.stmtStart[index] === index) {
|
|
713
|
+
const end = statementEnd(stream, layout, index);
|
|
714
|
+
for (let cursor = index + 1; cursor <= end; cursor += 1)
|
|
715
|
+
if (stream.toks[cursor].kind === "identifier")
|
|
716
|
+
globals.add(stream.toks[cursor].text);
|
|
717
|
+
}
|
|
718
|
+
if (token.kind === "identifier" && token.text === "def" && isPunct(stream, index + 2, "(")) {
|
|
719
|
+
const close = stream.match[index + 2];
|
|
720
|
+
for (let cursor = index + 3; cursor < close; cursor += 1) {
|
|
721
|
+
const parameter = stream.toks[cursor];
|
|
722
|
+
const before = stream.toks[cursor - 1];
|
|
723
|
+
if (parameter.kind === "identifier" && before.kind === "punctuator" && (before.text === "(" || before.text === "," || before.text === "*" || before.text === "**"))
|
|
724
|
+
parameters.add(parameter.text);
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
for (let index = first; index <= last; index += 1) {
|
|
729
|
+
const token = stream.toks[index];
|
|
730
|
+
if (token.kind !== "identifier")
|
|
731
|
+
continue;
|
|
732
|
+
if (layout.stmtStart[index] === index && assignmentTarget(index) === token.text && !globals.has(token.text))
|
|
733
|
+
locals.add(token.text);
|
|
734
|
+
if (isIdent(stream, index - 1, "for") || isIdent(stream, index - 1, "as"))
|
|
735
|
+
locals.add(token.text);
|
|
736
|
+
}
|
|
737
|
+
const qualifierNames = new Set([...classNames, "cls"]);
|
|
738
|
+
const record = (index, name) => {
|
|
739
|
+
out.sharedState.push({ line: lineOf(stream, index), name });
|
|
740
|
+
};
|
|
741
|
+
const isAssignment = (index) => {
|
|
742
|
+
const token = stream.toks[index];
|
|
743
|
+
return token !== undefined && token.kind === "punctuator" && ASSIGNMENT_TOKENS.has(token.text);
|
|
744
|
+
};
|
|
745
|
+
for (let index = first; index <= last; index += 1) {
|
|
746
|
+
const token = stream.toks[index];
|
|
747
|
+
if (token.kind !== "identifier")
|
|
748
|
+
continue;
|
|
749
|
+
const previous = stream.toks[index - 1];
|
|
750
|
+
const owner = previous !== undefined && previous.kind === "punctuator" && previous.text === "." ? stream.toks[index - 2] : undefined;
|
|
751
|
+
if (owner === undefined) {
|
|
752
|
+
if (!moduleNames.has(token.text) || parameters.has(token.text))
|
|
753
|
+
continue;
|
|
754
|
+
const chain = parseChain(stream, index, CHAIN_OPTIONS);
|
|
755
|
+
if (chain.segments[0].open >= 0)
|
|
756
|
+
continue;
|
|
757
|
+
const lastSegment = chain.segments[chain.segments.length - 1];
|
|
758
|
+
const bare = chain.end === index;
|
|
759
|
+
const assigned = isAssignment(chain.end + 1);
|
|
760
|
+
const mutatorCall = chain.segments.length > 1 && lastSegment.open >= 0 && MUTATORS.has(lastSegment.name);
|
|
761
|
+
if (bare && assigned) {
|
|
762
|
+
if (globals.has(token.text))
|
|
763
|
+
record(index, token.text);
|
|
764
|
+
}
|
|
765
|
+
else if ((mutatorCall || (!bare && assigned)) && !locals.has(token.text))
|
|
766
|
+
record(index, token.text);
|
|
767
|
+
continue;
|
|
768
|
+
}
|
|
769
|
+
if (owner.kind !== "identifier")
|
|
770
|
+
continue;
|
|
771
|
+
const viaSelf = owner.text === "self";
|
|
772
|
+
if (!viaSelf && !qualifierNames.has(owner.text))
|
|
773
|
+
continue;
|
|
774
|
+
if (!classAttributes.has(token.text))
|
|
775
|
+
continue;
|
|
776
|
+
const chain = parseChain(stream, index, CHAIN_OPTIONS);
|
|
777
|
+
if (chain.segments[0].open >= 0)
|
|
778
|
+
continue;
|
|
779
|
+
const lastSegment = chain.segments[chain.segments.length - 1];
|
|
780
|
+
const assigned = isAssignment(chain.end + 1);
|
|
781
|
+
const mutatorCall = chain.segments.length > 1 && lastSegment.open >= 0 && MUTATORS.has(lastSegment.name);
|
|
782
|
+
if (viaSelf ? mutatorCall || (chain.end > index && assigned) : mutatorCall || assigned)
|
|
783
|
+
record(index, token.text);
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
//# sourceMappingURL=python-family.js.map
|