test-inventory 0.6.0 → 0.7.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/CHANGELOG.md +126 -0
- package/dist/case-titles.d.ts +14 -0
- package/dist/case-titles.js +227 -0
- package/dist/cli.d.ts +1 -1
- package/dist/cli.js +3 -0
- package/dist/config-runs.d.ts +31 -5
- package/dist/config-runs.js +181 -41
- package/dist/config.js +65 -3
- package/dist/dependency-hash.d.ts +12 -4
- package/dist/dependency-hash.js +85 -10
- package/dist/diagnostics.js +1 -0
- package/dist/extract.d.ts +19 -1
- package/dist/extract.js +350 -31
- package/dist/filter.js +1 -0
- package/dist/framework-version.d.ts +4 -2
- package/dist/framework-version.js +17 -3
- package/dist/frameworks/common.d.ts +6 -0
- package/dist/frameworks/common.js +20 -0
- package/dist/frameworks/playwright.d.ts +3 -1
- package/dist/frameworks/playwright.js +14 -5
- package/dist/frameworks/vitest.d.ts +12 -2
- package/dist/frameworks/vitest.js +101 -16
- package/dist/imported-values.d.ts +22 -0
- package/dist/imported-values.js +135 -0
- package/dist/model.d.ts +19 -2
- package/dist/options.d.ts +11 -0
- package/dist/options.js +7 -0
- package/dist/parse.d.ts +6 -0
- package/dist/parse.js +204 -0
- package/dist/records.d.ts +2 -0
- package/dist/records.js +17 -6
- package/dist/resolve.js +28 -8
- package/dist/scan-file.d.ts +5 -0
- package/dist/scan-file.js +8 -5
- package/dist/scan-source.js +3 -1
- package/dist/scan.js +10 -4
- package/dist/types.d.ts +15 -3
- package/dist/values.d.ts +39 -1
- package/dist/values.js +425 -18
- package/package.json +1 -1
- package/schema/v1.json +28 -1
package/dist/dependency-hash.js
CHANGED
|
@@ -4,6 +4,7 @@ import { childNodes, isFunctionNode, propertyKey, unwrap } from "./ast.js";
|
|
|
4
4
|
import { exportName, parseModule } from "./frameworks/bindings.js";
|
|
5
5
|
import { destructuredNames, problem, takesWhole } from "./frameworks/common.js";
|
|
6
6
|
import { shortHash, hashNodes } from "./hash.js";
|
|
7
|
+
import { parseFile } from "./parse.js";
|
|
7
8
|
import { snippet } from "./source.js";
|
|
8
9
|
import { bindingNames, isScopeNode, Values } from "./values.js";
|
|
9
10
|
/** Statements that only declare something, unless code in them runs while they are evaluated. */
|
|
@@ -157,6 +158,50 @@ function isAutoOption(property) {
|
|
|
157
158
|
property.value.value === true);
|
|
158
159
|
}
|
|
159
160
|
const CODE_FILE = /\.[cm]?[jt]sx?$/;
|
|
161
|
+
/** A Vue or Svelte single-file component: markup with its code in `<script>` blocks. */
|
|
162
|
+
const COMPONENT_FILE = /\.(?:vue|svelte)$/;
|
|
163
|
+
/**
|
|
164
|
+
* A comment, which may hold a `<script>` that isn't one, or a script block with its attributes and code. An attribute
|
|
165
|
+
* may hold `>`, as Svelte's `generics="T extends Record<string, number>"` does. Both end where HTML ends them: a
|
|
166
|
+
* comment also at `--!>`, a script at `</script` with anything before its `>`.
|
|
167
|
+
*/
|
|
168
|
+
const COMPONENT_BLOCK = /<!--[\s\S]*?--!?>|<script\b((?:[^>"']|"[^"]*"|'[^']*')*)>([\s\S]*?)<\/script(?:[\s/][^>]*)?>/gi;
|
|
169
|
+
/**
|
|
170
|
+
* A file's bytes with line endings as `\n`, as git may check out a text file with either; one git takes for binary, with
|
|
171
|
+
* a NUL byte in its first 8,000, stays as it is.
|
|
172
|
+
*/
|
|
173
|
+
function withLineFeeds(bytes) {
|
|
174
|
+
if (bytes.subarray(0, 8000).includes(0))
|
|
175
|
+
return bytes;
|
|
176
|
+
// Latin-1 maps each byte to one character and back, so only the line endings change.
|
|
177
|
+
return Buffer.from(Buffer.from(bytes).toString('latin1').replace(/\r\n?/g, '\n'), 'latin1');
|
|
178
|
+
}
|
|
179
|
+
/** Whether a script block holds code: one with a `type` other than a JavaScript one, as JSON-LD, holds data. */
|
|
180
|
+
function isScriptCode(attributes) {
|
|
181
|
+
const type = /(?:^|\s)type\s*=\s*["']?([^"'\s>]*)/i.exec(attributes)?.[1] ?? '';
|
|
182
|
+
return /^(?:module|(?:text|application)\/(?:java|ecma)script)?$/i.test(type);
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* A component's script blocks as one module, the rest of the file blanked so that offsets stay those of the file, and
|
|
186
|
+
* a file name for the language they are in: `lang="ts"` is TypeScript, no `lang` JavaScript.
|
|
187
|
+
*/
|
|
188
|
+
function componentScript(code) {
|
|
189
|
+
// A byte order mark stays, for the parser to leave out as it does from any file.
|
|
190
|
+
const blank = (text) => text.replace(/[^\r\n\uFEFF]/g, ' ');
|
|
191
|
+
let script = '';
|
|
192
|
+
let end = 0;
|
|
193
|
+
let lang = 'js';
|
|
194
|
+
for (const match of code.matchAll(COMPONENT_BLOCK)) {
|
|
195
|
+
const [whole, attributes, body] = match;
|
|
196
|
+
if (whole.startsWith('<!--') || !isScriptCode(attributes))
|
|
197
|
+
continue;
|
|
198
|
+
const start = match.index + '<script'.length + attributes.length + 1;
|
|
199
|
+
script += blank(code.slice(end, start)) + body;
|
|
200
|
+
end = start + body.length;
|
|
201
|
+
lang = /(?:^|\s)lang\s*=\s*["']?(\w+)/.exec(attributes)?.[1] ?? lang;
|
|
202
|
+
}
|
|
203
|
+
return { fileName: `component.${lang}`, code: script + blank(code.slice(end)) };
|
|
204
|
+
}
|
|
160
205
|
const DECLARATION_FILE = /\.d\.[cm]?ts$/;
|
|
161
206
|
/**
|
|
162
207
|
* What an import being looked up stands for while the lookup leads back to it, through re-exports: nothing found
|
|
@@ -469,7 +514,8 @@ export class DependencyGraph {
|
|
|
469
514
|
hashOf(unit) {
|
|
470
515
|
if (unit.hash === undefined) {
|
|
471
516
|
if (unit.module.text !== null) {
|
|
472
|
-
unit
|
|
517
|
+
// Counted once, through the unit of all of it, however many of a component's units a test reaches.
|
|
518
|
+
unit.hash = unit.module.whole.includes(unit) ? shortHash(unit.module.text) : null;
|
|
473
519
|
}
|
|
474
520
|
else {
|
|
475
521
|
const cut = [...unit.cut].map(([start, end]) => ({ start, end }));
|
|
@@ -490,7 +536,12 @@ export class DependencyGraph {
|
|
|
490
536
|
this.modules.set(parsed, module);
|
|
491
537
|
}
|
|
492
538
|
else {
|
|
493
|
-
|
|
539
|
+
const text = source.bytes ? withLineFeeds(source.bytes) : source.code.replace(/\r\n?/g, '\n');
|
|
540
|
+
// A component's markup and styles shape what it renders, so it is taken by its text, and its scripts are
|
|
541
|
+
// followed for the modules they import.
|
|
542
|
+
const script = COMPONENT_FILE.test(source.relativeFilePath) ? componentScript(source.code) : null;
|
|
543
|
+
const parsed = script && parseFile(script.fileName, script.code);
|
|
544
|
+
module = this.newModule(source.relativeFilePath, parsed, text, new Values(), parsed && source.readModule);
|
|
494
545
|
}
|
|
495
546
|
this.modules.set(source, module);
|
|
496
547
|
}
|
|
@@ -572,7 +623,7 @@ export class DependencyGraph {
|
|
|
572
623
|
importUnit(module, statement, specifier) {
|
|
573
624
|
let unit = this.imports.get(specifier);
|
|
574
625
|
if (!unit) {
|
|
575
|
-
const roots = [statement.source, specifier].map((node) => ({ node, scopes: [] }));
|
|
626
|
+
const roots = [...new Set([statement.source, specifier])].map((node) => ({ node, scopes: [] }));
|
|
576
627
|
unit = this.newUnit(module, roots, new Map(), false);
|
|
577
628
|
unit.edges = new Set();
|
|
578
629
|
this.imports.set(specifier, unit);
|
|
@@ -682,8 +733,12 @@ export class DependencyGraph {
|
|
|
682
733
|
];
|
|
683
734
|
}
|
|
684
735
|
if (module.parsed.error) {
|
|
685
|
-
|
|
736
|
+
const place = this.placeUnit(from, at, `'${specifier}' could not be parsed, so dependencyHash doesn't cover it.`);
|
|
737
|
+
return module.text === null ? [place] : [this.textUnit(module), place];
|
|
686
738
|
}
|
|
739
|
+
// A component's markup may use anything its scripts declare or import, its types included.
|
|
740
|
+
if (module.text !== null)
|
|
741
|
+
return [this.wholeUnit(module, false)];
|
|
687
742
|
return name === '' ? [this.loadUnit(module, types)] : this.exported(module, name, types, new Set());
|
|
688
743
|
}
|
|
689
744
|
/**
|
|
@@ -711,7 +766,7 @@ export class DependencyGraph {
|
|
|
711
766
|
: [this.declarationUnit(module, declaration, types)];
|
|
712
767
|
}
|
|
713
768
|
if (statement.type === 'ExportAllDeclaration' && statement.exported && exportName(statement.exported) === name) {
|
|
714
|
-
return this.
|
|
769
|
+
return this.passedOn(module, statement, statement.source, statement.exported, '*', types);
|
|
715
770
|
}
|
|
716
771
|
if (statement.type !== 'ExportNamedDeclaration')
|
|
717
772
|
continue;
|
|
@@ -735,7 +790,7 @@ export class DependencyGraph {
|
|
|
735
790
|
continue;
|
|
736
791
|
const localName = exportName(specifier.local);
|
|
737
792
|
return statement.source
|
|
738
|
-
? this.
|
|
793
|
+
? this.passedOn(module, statement, statement.source, specifier, localName, types)
|
|
739
794
|
: local(localName);
|
|
740
795
|
}
|
|
741
796
|
// `export *` passes on every name but `default`, and only when the module doesn't export the name itself.
|
|
@@ -745,11 +800,19 @@ export class DependencyGraph {
|
|
|
745
800
|
continue;
|
|
746
801
|
const found = this.reexported(module, statement.source.value, name, types, searched);
|
|
747
802
|
if (found)
|
|
748
|
-
return found;
|
|
803
|
+
return [...found, this.importUnit(module, statement, statement.source)];
|
|
749
804
|
}
|
|
750
805
|
}
|
|
751
806
|
return [this.wholeUnit(module, types)];
|
|
752
807
|
}
|
|
808
|
+
/**
|
|
809
|
+
* A name a module passes on from another, as `export { quote } from './nasdaq'` does, with the re-export itself: which
|
|
810
|
+
* module the name comes from is code too, so the barrel is among the files a change to which affects the test.
|
|
811
|
+
*/
|
|
812
|
+
passedOn(module, statement, source, specifier, name, types) {
|
|
813
|
+
const units = this.imported(module, source.value, name, types, statement);
|
|
814
|
+
return units === IN_PROGRESS ? units : [...units, this.importUnit(module, { source }, specifier)];
|
|
815
|
+
}
|
|
753
816
|
/** What `export * from source` gives for a name; null when the module behind it doesn't export it. */
|
|
754
817
|
reexported(from, source, name, types, searched) {
|
|
755
818
|
const read = this.read(from, source);
|
|
@@ -1161,10 +1224,22 @@ export class DependencyGraph {
|
|
|
1161
1224
|
}
|
|
1162
1225
|
break;
|
|
1163
1226
|
}
|
|
1164
|
-
case 'ImportDeclaration':
|
|
1165
|
-
if (
|
|
1166
|
-
|
|
1227
|
+
case 'ImportDeclaration': {
|
|
1228
|
+
if (module.text === null) {
|
|
1229
|
+
if (node.importKind !== 'type' || types)
|
|
1230
|
+
add(this.imported(module, node.source.value, '', types, node));
|
|
1231
|
+
return;
|
|
1232
|
+
}
|
|
1233
|
+
// A component's markup uses what its scripts import, where the scan doesn't look, and Vue builds the props
|
|
1234
|
+
// of `defineProps<Props>()` from the types, so every import counts, types with their types.
|
|
1235
|
+
const isType = node.importKind === 'type';
|
|
1236
|
+
add(this.imported(module, node.source.value, '', isType, node));
|
|
1237
|
+
for (const specifier of node.specifiers) {
|
|
1238
|
+
const isTypeName = isType || (specifier.type === 'ImportSpecifier' && specifier.importKind === 'type');
|
|
1239
|
+
add(this.toDeclaration(module, specifier, specifier.local.name, null, isTypeName));
|
|
1240
|
+
}
|
|
1167
1241
|
return;
|
|
1242
|
+
}
|
|
1168
1243
|
case 'TSImportEqualsDeclaration':
|
|
1169
1244
|
if (node.moduleReference.type !== 'TSExternalModuleReference')
|
|
1170
1245
|
break;
|
package/dist/diagnostics.js
CHANGED
package/dist/extract.d.ts
CHANGED
|
@@ -48,6 +48,8 @@ export interface PendingSuite {
|
|
|
48
48
|
/** Declared under a condition, or in a function: it may not run while the file loads. */
|
|
49
49
|
isInCondition: boolean;
|
|
50
50
|
isInFunction: boolean;
|
|
51
|
+
/** With `expandCases`, the index of the case of a table or pass of a loop this describe is; null otherwise. */
|
|
52
|
+
caseIndex: number | null;
|
|
51
53
|
}
|
|
52
54
|
export interface PendingTest {
|
|
53
55
|
scope: Scope;
|
|
@@ -66,8 +68,17 @@ export interface PendingTest {
|
|
|
66
68
|
states: StateSetting[];
|
|
67
69
|
annotations: Annotation[];
|
|
68
70
|
isInLoop: boolean;
|
|
71
|
+
/**
|
|
72
|
+
* How many times each loop around the test registers it, outermost first; null for a loop the scan can't count, or
|
|
73
|
+
* one that registers it under a condition.
|
|
74
|
+
*/
|
|
75
|
+
loops: (number | null)[];
|
|
76
|
+
/** How many passes each loop around the test makes, whether or not it registers the test on each. */
|
|
77
|
+
passes: (number | null)[];
|
|
69
78
|
isInCondition: boolean;
|
|
70
79
|
isInFunction: boolean;
|
|
80
|
+
/** With `expandCases`, the index of the case of a table or pass of a loop this test is; null otherwise. */
|
|
81
|
+
caseIndex: number | null;
|
|
71
82
|
comments: string[];
|
|
72
83
|
/** Run settings from calls in the body, which win over everything around the test. */
|
|
73
84
|
run: RunSettings;
|
|
@@ -85,6 +96,13 @@ export interface Frame {
|
|
|
85
96
|
readModule: ModuleReader | null;
|
|
86
97
|
relativeFilePath: string;
|
|
87
98
|
}
|
|
99
|
+
/** How the walk lists tables and loops. */
|
|
100
|
+
export interface ExtractOptions {
|
|
101
|
+
/** One test or describe per case of a table and per pass of a loop whose values the scan can work out. */
|
|
102
|
+
expandCases: boolean;
|
|
103
|
+
/** Vitest's `[major, minor]`, whose rules give each case its title; null for the newest rules. */
|
|
104
|
+
version: readonly [number, number] | null;
|
|
105
|
+
}
|
|
88
106
|
/** Reads a module a helper is imported from as a frame; null when it can't be parsed. */
|
|
89
107
|
export type OpenModule = (module: ModuleSource) => Frame | null;
|
|
90
108
|
export interface Extraction {
|
|
@@ -112,4 +130,4 @@ export declare function anySlow(slows: NonNullable<RunSettings['slow']>[]): NonN
|
|
|
112
130
|
export declare function eitherCondition(parts: string[]): string;
|
|
113
131
|
export declare function noHooks(): HookCounts;
|
|
114
132
|
/** Walks the scanned file; `openModule` reads the modules of imported helpers that the file calls. */
|
|
115
|
-
export declare function extract(main: Frame, openModule: OpenModule): Extraction;
|
|
133
|
+
export declare function extract(main: Frame, openModule: OpenModule, options?: ExtractOptions): Extraction;
|