@glubean/scanner 0.1.2
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/extractor-static.d.ts +85 -0
- package/dist/extractor-static.d.ts.map +1 -0
- package/dist/extractor-static.js +433 -0
- package/dist/extractor-static.js.map +1 -0
- package/dist/fs.d.ts +13 -0
- package/dist/fs.d.ts.map +1 -0
- package/dist/fs.js +70 -0
- package/dist/fs.js.map +1 -0
- package/dist/index.d.ts +84 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +98 -0
- package/dist/index.js.map +1 -0
- package/dist/scanner.d.ts +64 -0
- package/dist/scanner.d.ts.map +1 -0
- package/dist/scanner.js +192 -0
- package/dist/scanner.js.map +1 -0
- package/dist/spec.d.ts +31 -0
- package/dist/spec.d.ts.map +1 -0
- package/dist/spec.js +33 -0
- package/dist/spec.js.map +1 -0
- package/dist/static.d.ts +24 -0
- package/dist/static.d.ts.map +1 -0
- package/dist/static.js +23 -0
- package/dist/static.js.map +1 -0
- package/dist/types.d.ts +128 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +5 -0
- package/dist/types.js.map +1 -0
- package/package.json +25 -0
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Static analysis extractor for Glubean test files.
|
|
3
|
+
*
|
|
4
|
+
* Uses regex patterns to extract test metadata WITHOUT importing files.
|
|
5
|
+
* This is useful for:
|
|
6
|
+
* - Build systems that scan code without execution
|
|
7
|
+
* - CI/CD pipelines
|
|
8
|
+
* - IDE extensions (VSCode)
|
|
9
|
+
*
|
|
10
|
+
* Note: Static analysis may miss dynamically computed metadata.
|
|
11
|
+
*
|
|
12
|
+
* **Limitations:**
|
|
13
|
+
* - Template variables (`$id`, `$_pick`) in IDs are preserved as-is, not resolved.
|
|
14
|
+
* - Dynamically computed IDs or tags are not detected.
|
|
15
|
+
* - `test.each()` / `test.pick()` produce one ExportMeta with the template ID,
|
|
16
|
+
* not one per data row (row count is unknown statically).
|
|
17
|
+
* - Deeply nested or multi-line object literals with complex expressions may
|
|
18
|
+
* not be fully parsed.
|
|
19
|
+
*/
|
|
20
|
+
import type { ExportMeta } from "./types.js";
|
|
21
|
+
/**
|
|
22
|
+
* Check if a file's content looks like a Glubean test/task file.
|
|
23
|
+
*
|
|
24
|
+
* Useful as a fast guard before running the more expensive `extractFromSource`.
|
|
25
|
+
*
|
|
26
|
+
* Detection layers (any match → true):
|
|
27
|
+
* 1. Direct SDK module import (`jsr:@glubean/sdk`, `@glubean/sdk`)
|
|
28
|
+
* 2. Named import of a known function name (auto-detected aliases or convention)
|
|
29
|
+
*
|
|
30
|
+
* @param content - TypeScript source code
|
|
31
|
+
* @param customFns - Additional function names discovered via `extractAliasesFromSource`.
|
|
32
|
+
* When provided, these are checked in imports alongside the base names.
|
|
33
|
+
* When omitted, falls back to `*Test` / `*Task` convention matching.
|
|
34
|
+
* @returns `true` if the source looks like a Glubean file
|
|
35
|
+
*/
|
|
36
|
+
export declare function isGlubeanFile(content: string, customFns?: string[]): boolean;
|
|
37
|
+
/**
|
|
38
|
+
* Extract custom function names created by `.extend()` calls.
|
|
39
|
+
*
|
|
40
|
+
* Scans source for patterns like:
|
|
41
|
+
* - `const browserTest = test.extend({...})`
|
|
42
|
+
* - `export const screenshotTest = browserTest.extend({...})`
|
|
43
|
+
*
|
|
44
|
+
* Returns the variable names (e.g. `["browserTest", "screenshotTest"]`).
|
|
45
|
+
* These can then be passed to `extractFromSource()` and `isGlubeanFile()`
|
|
46
|
+
* so they recognize `export const x = browserTest(...)` in other files.
|
|
47
|
+
*
|
|
48
|
+
* @param content - TypeScript source code
|
|
49
|
+
* @returns Array of discovered alias names
|
|
50
|
+
*/
|
|
51
|
+
export declare function extractAliasesFromSource(content: string): string[];
|
|
52
|
+
/**
|
|
53
|
+
* Extract test metadata from TypeScript source using static analysis (regex).
|
|
54
|
+
*
|
|
55
|
+
* Recognizes the following patterns:
|
|
56
|
+
* - `export const x = test("id", fn)` — simple test with string ID
|
|
57
|
+
* - `export const x = test({ id, name, tags }, fn)` — simple test with meta
|
|
58
|
+
* - `export const x = test("id").step(...)` — builder with steps
|
|
59
|
+
* - `export const x = test.each(data)("id-$key", fn)` — data-driven
|
|
60
|
+
* - `export const x = test.pick(examples)("id-$_pick", fn)` — example selection
|
|
61
|
+
*
|
|
62
|
+
* This is a pure function — no file system or runtime access needed.
|
|
63
|
+
*
|
|
64
|
+
* @param content - TypeScript source code
|
|
65
|
+
* @param customFns - Additional function names discovered via `extractAliasesFromSource`.
|
|
66
|
+
* When provided, these names are matched alongside `test` and `task`.
|
|
67
|
+
* When omitted, falls back to `*Test` / `*Task` convention matching.
|
|
68
|
+
* @returns Array of extracted export metadata
|
|
69
|
+
*/
|
|
70
|
+
export declare function extractFromSource(content: string, customFns?: string[]): ExportMeta[];
|
|
71
|
+
/**
|
|
72
|
+
* Create a static metadata extractor that uses file system to read content.
|
|
73
|
+
*
|
|
74
|
+
* Aliases can be supplied at two levels:
|
|
75
|
+
* - `customFns` (construction-time): baked-in aliases known upfront.
|
|
76
|
+
* - `runtimeFns` (call-time): aliases discovered during a Scanner two-phase
|
|
77
|
+
* scan. These are merged with `customFns` so the extractor benefits from
|
|
78
|
+
* aliases discovered after construction.
|
|
79
|
+
*
|
|
80
|
+
* @param readFile - Function to read file content as string
|
|
81
|
+
* @param customFns - Additional function names (from alias discovery)
|
|
82
|
+
* @returns MetadataExtractor function
|
|
83
|
+
*/
|
|
84
|
+
export declare function createStaticExtractor(readFile: (path: string) => Promise<string>, customFns?: string[]): (filePath: string, runtimeFns?: string[]) => Promise<ExportMeta[]>;
|
|
85
|
+
//# sourceMappingURL=extractor-static.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"extractor-static.d.ts","sourceRoot":"","sources":["../src/extractor-static.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAoC7C;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,EAAE,GAAG,OAAO,CAU5E;AAgSD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,EAAE,CAUlE;AAMD;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,EAAE,GAAG,UAAU,EAAE,CAkCrF;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,qBAAqB,CACnC,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,EAC3C,SAAS,CAAC,EAAE,MAAM,EAAE,GACnB,CAAC,QAAQ,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,EAAE,KAAK,OAAO,CAAC,UAAU,EAAE,CAAC,CAOpE"}
|
|
@@ -0,0 +1,433 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Static analysis extractor for Glubean test files.
|
|
3
|
+
*
|
|
4
|
+
* Uses regex patterns to extract test metadata WITHOUT importing files.
|
|
5
|
+
* This is useful for:
|
|
6
|
+
* - Build systems that scan code without execution
|
|
7
|
+
* - CI/CD pipelines
|
|
8
|
+
* - IDE extensions (VSCode)
|
|
9
|
+
*
|
|
10
|
+
* Note: Static analysis may miss dynamically computed metadata.
|
|
11
|
+
*
|
|
12
|
+
* **Limitations:**
|
|
13
|
+
* - Template variables (`$id`, `$_pick`) in IDs are preserved as-is, not resolved.
|
|
14
|
+
* - Dynamically computed IDs or tags are not detected.
|
|
15
|
+
* - `test.each()` / `test.pick()` produce one ExportMeta with the template ID,
|
|
16
|
+
* not one per data row (row count is unknown statically).
|
|
17
|
+
* - Deeply nested or multi-line object literals with complex expressions may
|
|
18
|
+
* not be fully parsed.
|
|
19
|
+
*/
|
|
20
|
+
// ---------------------------------------------------------------------------
|
|
21
|
+
// SDK import detection
|
|
22
|
+
// ---------------------------------------------------------------------------
|
|
23
|
+
/** Base function names that are always recognized. */
|
|
24
|
+
const BASE_FNS = ["test", "task"];
|
|
25
|
+
/** Direct SDK module import patterns. */
|
|
26
|
+
const SDK_MODULE_PATTERNS = [
|
|
27
|
+
// jsr:@glubean/sdk or jsr:@glubean/sdk@0.5.0 (with optional version)
|
|
28
|
+
/import\s+.*from\s+["']jsr:@glubean\/sdk(?:@[^"']*)?["']/,
|
|
29
|
+
// @glubean/sdk (bare specifier via import map or package.json)
|
|
30
|
+
/import\s+.*from\s+["']@glubean\/sdk(?:\/[^"']*)?["']/,
|
|
31
|
+
];
|
|
32
|
+
/** Escape special regex chars in a string. */
|
|
33
|
+
function escapeRegExp(s) {
|
|
34
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Build a regex alternation from function names: `"test|task|browserTest"`.
|
|
38
|
+
* When no custom names are provided, falls back to a convention pattern
|
|
39
|
+
* that matches `test`, `task`, `*Test`, and `*Task`.
|
|
40
|
+
*/
|
|
41
|
+
function buildFnAlternation(customFns) {
|
|
42
|
+
if (customFns && customFns.length > 0) {
|
|
43
|
+
const all = [...new Set([...BASE_FNS, ...customFns])];
|
|
44
|
+
return all.map(escapeRegExp).join("|");
|
|
45
|
+
}
|
|
46
|
+
// Convention fallback: test | task | *Test | *Task
|
|
47
|
+
return "\\w*(?:Test|Task)|test|task";
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Check if a file's content looks like a Glubean test/task file.
|
|
51
|
+
*
|
|
52
|
+
* Useful as a fast guard before running the more expensive `extractFromSource`.
|
|
53
|
+
*
|
|
54
|
+
* Detection layers (any match → true):
|
|
55
|
+
* 1. Direct SDK module import (`jsr:@glubean/sdk`, `@glubean/sdk`)
|
|
56
|
+
* 2. Named import of a known function name (auto-detected aliases or convention)
|
|
57
|
+
*
|
|
58
|
+
* @param content - TypeScript source code
|
|
59
|
+
* @param customFns - Additional function names discovered via `extractAliasesFromSource`.
|
|
60
|
+
* When provided, these are checked in imports alongside the base names.
|
|
61
|
+
* When omitted, falls back to `*Test` / `*Task` convention matching.
|
|
62
|
+
* @returns `true` if the source looks like a Glubean file
|
|
63
|
+
*/
|
|
64
|
+
export function isGlubeanFile(content, customFns) {
|
|
65
|
+
// Layer 1: Direct SDK module import
|
|
66
|
+
if (SDK_MODULE_PATTERNS.some((p) => p.test(content)))
|
|
67
|
+
return true;
|
|
68
|
+
// Layer 2: Named import of a known function name
|
|
69
|
+
const alt = buildFnAlternation(customFns);
|
|
70
|
+
const importPattern = new RegExp(`import\\s+.*\\{[^}]*\\b(${alt})\\b[^}]*\\}`);
|
|
71
|
+
return importPattern.test(content);
|
|
72
|
+
}
|
|
73
|
+
// ---------------------------------------------------------------------------
|
|
74
|
+
// Comment stripping
|
|
75
|
+
// ---------------------------------------------------------------------------
|
|
76
|
+
/**
|
|
77
|
+
* Remove comments from source while preserving line positions.
|
|
78
|
+
* Block comments are replaced with spaces (newlines kept); line comments are
|
|
79
|
+
* replaced with spaces up to the newline. String literals are skipped so that
|
|
80
|
+
* `//` or `/*` inside strings are not treated as comments.
|
|
81
|
+
*/
|
|
82
|
+
function stripComments(source) {
|
|
83
|
+
let result = "";
|
|
84
|
+
let i = 0;
|
|
85
|
+
const len = source.length;
|
|
86
|
+
while (i < len) {
|
|
87
|
+
const ch = source[i];
|
|
88
|
+
// String literals — pass through unchanged
|
|
89
|
+
if (ch === '"' || ch === "'") {
|
|
90
|
+
const quote = ch;
|
|
91
|
+
result += source[i++];
|
|
92
|
+
while (i < len && source[i] !== quote) {
|
|
93
|
+
if (source[i] === "\\" && i + 1 < len)
|
|
94
|
+
result += source[i++];
|
|
95
|
+
if (i < len)
|
|
96
|
+
result += source[i++];
|
|
97
|
+
}
|
|
98
|
+
if (i < len)
|
|
99
|
+
result += source[i++]; // closing quote
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
// Template literal — simplified (no nested template tracking)
|
|
103
|
+
if (ch === "`") {
|
|
104
|
+
result += source[i++];
|
|
105
|
+
while (i < len && source[i] !== "`") {
|
|
106
|
+
if (source[i] === "\\" && i + 1 < len)
|
|
107
|
+
result += source[i++];
|
|
108
|
+
if (i < len)
|
|
109
|
+
result += source[i++];
|
|
110
|
+
}
|
|
111
|
+
if (i < len)
|
|
112
|
+
result += source[i++]; // closing backtick
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
// Block comment — replace with spaces, keep newlines for line numbers
|
|
116
|
+
if (ch === "/" && i + 1 < len && source[i + 1] === "*") {
|
|
117
|
+
i += 2;
|
|
118
|
+
result += " ";
|
|
119
|
+
while (i < len && !(source[i] === "*" && i + 1 < len && source[i + 1] === "/")) {
|
|
120
|
+
result += source[i] === "\n" ? "\n" : " ";
|
|
121
|
+
i++;
|
|
122
|
+
}
|
|
123
|
+
if (i < len) {
|
|
124
|
+
result += " ";
|
|
125
|
+
i += 2;
|
|
126
|
+
}
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
// Line comment — replace with spaces until newline
|
|
130
|
+
if (ch === "/" && i + 1 < len && source[i + 1] === "/") {
|
|
131
|
+
i += 2;
|
|
132
|
+
while (i < len && source[i] !== "\n") {
|
|
133
|
+
result += " ";
|
|
134
|
+
i++;
|
|
135
|
+
}
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
result += source[i++];
|
|
139
|
+
}
|
|
140
|
+
return result;
|
|
141
|
+
}
|
|
142
|
+
// ---------------------------------------------------------------------------
|
|
143
|
+
// Parsing helpers
|
|
144
|
+
// ---------------------------------------------------------------------------
|
|
145
|
+
/** Count newlines before `offset` to compute 1-based line number. */
|
|
146
|
+
function getLineNumber(content, offset) {
|
|
147
|
+
let line = 1;
|
|
148
|
+
for (let i = 0; i < offset && i < content.length; i++) {
|
|
149
|
+
if (content[i] === "\n")
|
|
150
|
+
line++;
|
|
151
|
+
}
|
|
152
|
+
return line;
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Find the index of the matching closing bracket starting from `startIndex`
|
|
156
|
+
* (which must point to the opening bracket). Respects string boundaries.
|
|
157
|
+
* Returns -1 if no match is found.
|
|
158
|
+
*/
|
|
159
|
+
function findMatching(source, startIndex, open, close) {
|
|
160
|
+
let depth = 0;
|
|
161
|
+
let inString = false;
|
|
162
|
+
let stringChar = "";
|
|
163
|
+
for (let i = startIndex; i < source.length; i++) {
|
|
164
|
+
const ch = source[i];
|
|
165
|
+
if (inString) {
|
|
166
|
+
if (ch === "\\" && i + 1 < source.length) {
|
|
167
|
+
i++; // skip escaped char
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
170
|
+
if (ch === stringChar)
|
|
171
|
+
inString = false;
|
|
172
|
+
continue;
|
|
173
|
+
}
|
|
174
|
+
if (ch === '"' || ch === "'" || ch === "`") {
|
|
175
|
+
inString = true;
|
|
176
|
+
stringChar = ch;
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
179
|
+
if (ch === open)
|
|
180
|
+
depth++;
|
|
181
|
+
if (ch === close) {
|
|
182
|
+
depth--;
|
|
183
|
+
if (depth === 0)
|
|
184
|
+
return i;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
return -1;
|
|
188
|
+
}
|
|
189
|
+
/** Shorthand: find closing `)` for an opening `(`. */
|
|
190
|
+
function findCloseParen(source, openIndex) {
|
|
191
|
+
return findMatching(source, openIndex, "(", ")");
|
|
192
|
+
}
|
|
193
|
+
/** Shorthand: find closing `}` for an opening `{`. */
|
|
194
|
+
function findCloseBrace(source, openIndex) {
|
|
195
|
+
return findMatching(source, openIndex, "{", "}");
|
|
196
|
+
}
|
|
197
|
+
// ---------------------------------------------------------------------------
|
|
198
|
+
// Metadata extraction from object literals
|
|
199
|
+
// ---------------------------------------------------------------------------
|
|
200
|
+
/**
|
|
201
|
+
* Parse `id`, `name`, `tags`, and `timeout` from a TestMeta-like object literal string.
|
|
202
|
+
* Handles both `tags: ["a", "b"]` and `tags: "a"` forms, with single or double quotes.
|
|
203
|
+
*/
|
|
204
|
+
function parseMetaObject(source) {
|
|
205
|
+
const result = {};
|
|
206
|
+
const idMatch = source.match(/id:\s*(['"])([^'"]+)\1/);
|
|
207
|
+
if (idMatch)
|
|
208
|
+
result.id = idMatch[2];
|
|
209
|
+
const nameMatch = source.match(/name:\s*(['"])([^'"]+)\1/);
|
|
210
|
+
if (nameMatch)
|
|
211
|
+
result.name = nameMatch[2];
|
|
212
|
+
// Tags as array: tags: ["smoke", "auth"] or tags: ['smoke', 'auth']
|
|
213
|
+
const tagsArrayMatch = source.match(/tags:\s*\[([^\]]*)\]/);
|
|
214
|
+
if (tagsArrayMatch) {
|
|
215
|
+
result.tags = [...tagsArrayMatch[1].matchAll(/(['"])([^'"]+)\1/g)].map((m) => m[2]);
|
|
216
|
+
}
|
|
217
|
+
else {
|
|
218
|
+
// Tags as single string: tags: "smoke" or tags: 'smoke'
|
|
219
|
+
const tagsStringMatch = source.match(/tags:\s*(['"])([^'"]+)\1/);
|
|
220
|
+
if (tagsStringMatch)
|
|
221
|
+
result.tags = [tagsStringMatch[2]];
|
|
222
|
+
}
|
|
223
|
+
const timeoutMatch = source.match(/timeout:\s*(\d+)/);
|
|
224
|
+
if (timeoutMatch)
|
|
225
|
+
result.timeout = Number(timeoutMatch[1]);
|
|
226
|
+
return result;
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* Extract `name` and `tags` from a `.meta({...})` builder call within `scope`.
|
|
230
|
+
*/
|
|
231
|
+
function extractBuilderMeta(scope) {
|
|
232
|
+
const match = scope.match(/\.meta\(\s*\{/);
|
|
233
|
+
if (!match || match.index === undefined)
|
|
234
|
+
return {};
|
|
235
|
+
const braceStart = scope.indexOf("{", match.index);
|
|
236
|
+
const braceEnd = findCloseBrace(scope, braceStart);
|
|
237
|
+
if (braceEnd === -1)
|
|
238
|
+
return {};
|
|
239
|
+
const obj = scope.substring(braceStart, braceEnd + 1);
|
|
240
|
+
return parseMetaObject(obj);
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* Extract step names from `.step("name", ...)` or `.step('name', ...)` chains within `scope`.
|
|
244
|
+
*/
|
|
245
|
+
function extractSteps(scope) {
|
|
246
|
+
const steps = [];
|
|
247
|
+
const stepPattern = /\.step\(\s*(['"])([^'"]+)\1/g;
|
|
248
|
+
let m;
|
|
249
|
+
while ((m = stepPattern.exec(scope)) !== null) {
|
|
250
|
+
steps.push({ name: m[2] });
|
|
251
|
+
}
|
|
252
|
+
return steps;
|
|
253
|
+
}
|
|
254
|
+
// ---------------------------------------------------------------------------
|
|
255
|
+
// Declaration parser
|
|
256
|
+
// ---------------------------------------------------------------------------
|
|
257
|
+
/**
|
|
258
|
+
* Parse a single test declaration from the text that follows `test` in
|
|
259
|
+
* `export const NAME = test<scope>`. Returns null if the pattern is not
|
|
260
|
+
* recognized.
|
|
261
|
+
*/
|
|
262
|
+
function parseTestDeclaration(scope, exportName, line) {
|
|
263
|
+
let rest = scope;
|
|
264
|
+
let variant;
|
|
265
|
+
// Check for .each() or .pick() — may appear on same line or next line
|
|
266
|
+
const dataMatch = rest.match(/^\s*\.\s*(each|pick)\s*\(/);
|
|
267
|
+
if (dataMatch) {
|
|
268
|
+
variant = dataMatch[1];
|
|
269
|
+
const openIndex = rest.indexOf("(", dataMatch.index);
|
|
270
|
+
const closeIndex = findCloseParen(rest, openIndex);
|
|
271
|
+
if (closeIndex === -1)
|
|
272
|
+
return null;
|
|
273
|
+
rest = rest.substring(closeIndex + 1);
|
|
274
|
+
}
|
|
275
|
+
// Expect opening paren of the test call: test( or test.each(...)( or <generic>test<T>(
|
|
276
|
+
const callMatch = rest.match(/^\s*(?:<[^>]*>)?\s*\(/);
|
|
277
|
+
if (!callMatch)
|
|
278
|
+
return null;
|
|
279
|
+
const callOpenIndex = rest.indexOf("(", callMatch.index);
|
|
280
|
+
const afterOpen = rest.substring(callOpenIndex + 1).trimStart();
|
|
281
|
+
let id;
|
|
282
|
+
let name;
|
|
283
|
+
let tags;
|
|
284
|
+
let timeout;
|
|
285
|
+
if (afterOpen.startsWith('"') || afterOpen.startsWith("'")) {
|
|
286
|
+
// String ID
|
|
287
|
+
const quote = afterOpen[0];
|
|
288
|
+
const endQuote = afterOpen.indexOf(quote, 1);
|
|
289
|
+
if (endQuote === -1)
|
|
290
|
+
return null;
|
|
291
|
+
id = afterOpen.substring(1, endQuote);
|
|
292
|
+
}
|
|
293
|
+
else if (afterOpen.startsWith("{")) {
|
|
294
|
+
// TestMeta object
|
|
295
|
+
const braceEnd = findCloseBrace(afterOpen, 0);
|
|
296
|
+
if (braceEnd === -1)
|
|
297
|
+
return null;
|
|
298
|
+
const objStr = afterOpen.substring(0, braceEnd + 1);
|
|
299
|
+
const parsed = parseMetaObject(objStr);
|
|
300
|
+
id = parsed.id;
|
|
301
|
+
name = parsed.name;
|
|
302
|
+
tags = parsed.tags;
|
|
303
|
+
timeout = parsed.timeout;
|
|
304
|
+
}
|
|
305
|
+
if (!id)
|
|
306
|
+
return null;
|
|
307
|
+
// Extract builder .meta({...}) from the full scope
|
|
308
|
+
const builderMeta = extractBuilderMeta(scope);
|
|
309
|
+
if (!name && builderMeta.name)
|
|
310
|
+
name = builderMeta.name;
|
|
311
|
+
if (!tags && builderMeta.tags)
|
|
312
|
+
tags = builderMeta.tags;
|
|
313
|
+
if (timeout === undefined && builderMeta.timeout !== undefined) {
|
|
314
|
+
timeout = builderMeta.timeout;
|
|
315
|
+
}
|
|
316
|
+
// Extract .step("name", ...) chains from the full scope
|
|
317
|
+
const steps = extractSteps(scope);
|
|
318
|
+
const result = {
|
|
319
|
+
type: "test",
|
|
320
|
+
id,
|
|
321
|
+
exportName,
|
|
322
|
+
location: { line, col: 1 },
|
|
323
|
+
};
|
|
324
|
+
if (name)
|
|
325
|
+
result.name = name;
|
|
326
|
+
if (tags && tags.length > 0)
|
|
327
|
+
result.tags = tags;
|
|
328
|
+
if (timeout !== undefined)
|
|
329
|
+
result.timeout = timeout;
|
|
330
|
+
if (variant)
|
|
331
|
+
result.variant = variant;
|
|
332
|
+
if (steps.length > 0)
|
|
333
|
+
result.steps = steps;
|
|
334
|
+
return result;
|
|
335
|
+
}
|
|
336
|
+
// ---------------------------------------------------------------------------
|
|
337
|
+
// Alias discovery (auto-detect test.extend / task.extend)
|
|
338
|
+
// ---------------------------------------------------------------------------
|
|
339
|
+
/**
|
|
340
|
+
* Extract custom function names created by `.extend()` calls.
|
|
341
|
+
*
|
|
342
|
+
* Scans source for patterns like:
|
|
343
|
+
* - `const browserTest = test.extend({...})`
|
|
344
|
+
* - `export const screenshotTest = browserTest.extend({...})`
|
|
345
|
+
*
|
|
346
|
+
* Returns the variable names (e.g. `["browserTest", "screenshotTest"]`).
|
|
347
|
+
* These can then be passed to `extractFromSource()` and `isGlubeanFile()`
|
|
348
|
+
* so they recognize `export const x = browserTest(...)` in other files.
|
|
349
|
+
*
|
|
350
|
+
* @param content - TypeScript source code
|
|
351
|
+
* @returns Array of discovered alias names
|
|
352
|
+
*/
|
|
353
|
+
export function extractAliasesFromSource(content) {
|
|
354
|
+
const stripped = stripComments(content);
|
|
355
|
+
// Match: [export] const NAME = SOMETHING.extend(
|
|
356
|
+
const pattern = /(?:export\s+)?const\s+(\w+)\s*=\s*\w+\.extend\s*\(/g;
|
|
357
|
+
const aliases = [];
|
|
358
|
+
let m;
|
|
359
|
+
while ((m = pattern.exec(stripped)) !== null) {
|
|
360
|
+
aliases.push(m[1]);
|
|
361
|
+
}
|
|
362
|
+
return aliases;
|
|
363
|
+
}
|
|
364
|
+
// ---------------------------------------------------------------------------
|
|
365
|
+
// Public API
|
|
366
|
+
// ---------------------------------------------------------------------------
|
|
367
|
+
/**
|
|
368
|
+
* Extract test metadata from TypeScript source using static analysis (regex).
|
|
369
|
+
*
|
|
370
|
+
* Recognizes the following patterns:
|
|
371
|
+
* - `export const x = test("id", fn)` — simple test with string ID
|
|
372
|
+
* - `export const x = test({ id, name, tags }, fn)` — simple test with meta
|
|
373
|
+
* - `export const x = test("id").step(...)` — builder with steps
|
|
374
|
+
* - `export const x = test.each(data)("id-$key", fn)` — data-driven
|
|
375
|
+
* - `export const x = test.pick(examples)("id-$_pick", fn)` — example selection
|
|
376
|
+
*
|
|
377
|
+
* This is a pure function — no file system or runtime access needed.
|
|
378
|
+
*
|
|
379
|
+
* @param content - TypeScript source code
|
|
380
|
+
* @param customFns - Additional function names discovered via `extractAliasesFromSource`.
|
|
381
|
+
* When provided, these names are matched alongside `test` and `task`.
|
|
382
|
+
* When omitted, falls back to `*Test` / `*Task` convention matching.
|
|
383
|
+
* @returns Array of extracted export metadata
|
|
384
|
+
*/
|
|
385
|
+
export function extractFromSource(content, customFns) {
|
|
386
|
+
const results = [];
|
|
387
|
+
const stripped = stripComments(content);
|
|
388
|
+
// Build the function-name alternation — either explicit aliases or convention fallback
|
|
389
|
+
const alt = buildFnAlternation(customFns);
|
|
390
|
+
const exportPattern = new RegExp(`export\\s+const\\s+(\\w+)\\s*=\\s*(${alt})\\b`, "g");
|
|
391
|
+
const matches = [];
|
|
392
|
+
let m;
|
|
393
|
+
while ((m = exportPattern.exec(stripped)) !== null) {
|
|
394
|
+
matches.push({
|
|
395
|
+
exportName: m[1],
|
|
396
|
+
offset: m.index,
|
|
397
|
+
afterTest: m.index + m[0].length,
|
|
398
|
+
});
|
|
399
|
+
}
|
|
400
|
+
for (let i = 0; i < matches.length; i++) {
|
|
401
|
+
const { exportName, offset, afterTest } = matches[i];
|
|
402
|
+
// Scope from right after the function name to the start of the next export (or EOF)
|
|
403
|
+
const endOffset = i + 1 < matches.length ? matches[i + 1].offset : stripped.length;
|
|
404
|
+
const scope = stripped.substring(afterTest, endOffset);
|
|
405
|
+
const line = getLineNumber(stripped, offset);
|
|
406
|
+
const meta = parseTestDeclaration(scope, exportName, line);
|
|
407
|
+
if (meta)
|
|
408
|
+
results.push(meta);
|
|
409
|
+
}
|
|
410
|
+
return results;
|
|
411
|
+
}
|
|
412
|
+
/**
|
|
413
|
+
* Create a static metadata extractor that uses file system to read content.
|
|
414
|
+
*
|
|
415
|
+
* Aliases can be supplied at two levels:
|
|
416
|
+
* - `customFns` (construction-time): baked-in aliases known upfront.
|
|
417
|
+
* - `runtimeFns` (call-time): aliases discovered during a Scanner two-phase
|
|
418
|
+
* scan. These are merged with `customFns` so the extractor benefits from
|
|
419
|
+
* aliases discovered after construction.
|
|
420
|
+
*
|
|
421
|
+
* @param readFile - Function to read file content as string
|
|
422
|
+
* @param customFns - Additional function names (from alias discovery)
|
|
423
|
+
* @returns MetadataExtractor function
|
|
424
|
+
*/
|
|
425
|
+
export function createStaticExtractor(readFile, customFns) {
|
|
426
|
+
return async (filePath, runtimeFns) => {
|
|
427
|
+
const content = await readFile(filePath);
|
|
428
|
+
// Merge construction-time and call-time aliases
|
|
429
|
+
const merged = customFns || runtimeFns ? [...new Set([...(customFns ?? []), ...(runtimeFns ?? [])])] : undefined;
|
|
430
|
+
return extractFromSource(content, merged);
|
|
431
|
+
};
|
|
432
|
+
}
|
|
433
|
+
//# sourceMappingURL=extractor-static.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"extractor-static.js","sourceRoot":"","sources":["../src/extractor-static.ts"],"names":[],"mappings":"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"}
|
package/dist/fs.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Node.js file system and hasher implementations for the scanner.
|
|
3
|
+
*/
|
|
4
|
+
import type { FileSystem, Hasher } from "./scanner.js";
|
|
5
|
+
/**
|
|
6
|
+
* Node.js file system implementation.
|
|
7
|
+
*/
|
|
8
|
+
export declare const nodeFs: FileSystem;
|
|
9
|
+
/**
|
|
10
|
+
* Node.js hasher implementation using crypto module.
|
|
11
|
+
*/
|
|
12
|
+
export declare const nodeHasher: Hasher;
|
|
13
|
+
//# sourceMappingURL=fs.d.ts.map
|
package/dist/fs.d.ts.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fs.d.ts","sourceRoot":"","sources":["../src/fs.ts"],"names":[],"mappings":"AAAA;;GAEG;AAKH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAEvD;;GAEG;AACH,eAAO,MAAM,MAAM,EAAE,UA6DpB,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,UAAU,EAAE,MAKxB,CAAC"}
|
package/dist/fs.js
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Node.js file system and hasher implementations for the scanner.
|
|
3
|
+
*/
|
|
4
|
+
import * as fs from "node:fs/promises";
|
|
5
|
+
import * as path from "node:path";
|
|
6
|
+
import { createHash } from "node:crypto";
|
|
7
|
+
/**
|
|
8
|
+
* Node.js file system implementation.
|
|
9
|
+
*/
|
|
10
|
+
export const nodeFs = {
|
|
11
|
+
async exists(p) {
|
|
12
|
+
try {
|
|
13
|
+
await fs.stat(p);
|
|
14
|
+
return true;
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
return false;
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
async readText(p) {
|
|
21
|
+
return await fs.readFile(p, "utf-8");
|
|
22
|
+
},
|
|
23
|
+
async readBytes(p) {
|
|
24
|
+
const buffer = await fs.readFile(p);
|
|
25
|
+
return new Uint8Array(buffer);
|
|
26
|
+
},
|
|
27
|
+
async *walk(dir, options) {
|
|
28
|
+
const skipSet = new Set(options.skipDirs);
|
|
29
|
+
const extSet = new Set(options.extensions);
|
|
30
|
+
async function* walkDir(currentDir) {
|
|
31
|
+
const entries = await fs.readdir(currentDir, { withFileTypes: true });
|
|
32
|
+
for (const entry of entries) {
|
|
33
|
+
const fullPath = path.join(currentDir, entry.name);
|
|
34
|
+
if (entry.isDirectory()) {
|
|
35
|
+
// Skip excluded directories
|
|
36
|
+
if (!skipSet.has(entry.name)) {
|
|
37
|
+
yield* walkDir(fullPath);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
else if (entry.isFile()) {
|
|
41
|
+
// Check file extension
|
|
42
|
+
const ext = path.extname(entry.name);
|
|
43
|
+
if (extSet.has(ext)) {
|
|
44
|
+
yield fullPath;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
yield* walkDir(dir);
|
|
50
|
+
},
|
|
51
|
+
join(...segments) {
|
|
52
|
+
return path.join(...segments);
|
|
53
|
+
},
|
|
54
|
+
relative(base, target) {
|
|
55
|
+
return path.relative(base, target);
|
|
56
|
+
},
|
|
57
|
+
resolve(p) {
|
|
58
|
+
return path.resolve(p);
|
|
59
|
+
},
|
|
60
|
+
};
|
|
61
|
+
/**
|
|
62
|
+
* Node.js hasher implementation using crypto module.
|
|
63
|
+
*/
|
|
64
|
+
export const nodeHasher = {
|
|
65
|
+
sha256(content) {
|
|
66
|
+
const hash = createHash("sha256").update(content).digest("hex");
|
|
67
|
+
return Promise.resolve("sha256-" + hash);
|
|
68
|
+
},
|
|
69
|
+
};
|
|
70
|
+
//# sourceMappingURL=fs.js.map
|
package/dist/fs.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fs.js","sourceRoot":"","sources":["../src/fs.ts"],"names":[],"mappings":"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"}
|