@glubean/scanner 0.2.2 → 0.2.4
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/contract-extraction.d.ts +201 -4
- package/dist/contract-extraction.d.ts.map +1 -1
- package/dist/contract-extraction.js +266 -22
- package/dist/contract-extraction.js.map +1 -1
- package/dist/extractor-static.d.ts +12 -0
- package/dist/extractor-static.d.ts.map +1 -1
- package/dist/extractor-static.js +14 -1
- package/dist/extractor-static.js.map +1 -1
- package/dist/index.d.ts +6 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -3
- package/dist/index.js.map +1 -1
- package/dist/scanner.d.ts.map +1 -1
- package/dist/scanner.js +10 -9
- package/dist/scanner.js.map +1 -1
- package/dist/template-sentinel.d.ts +93 -0
- package/dist/template-sentinel.d.ts.map +1 -0
- package/dist/template-sentinel.js +149 -0
- package/dist/template-sentinel.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Template-id sentinel helpers — match runtime row ids back to the static
|
|
3
|
+
* template id emitted by `extractFromSource` for `test.each` / `test.pick`.
|
|
4
|
+
*
|
|
5
|
+
* ## Background
|
|
6
|
+
*
|
|
7
|
+
* Static parsing produces ONE `ExportMeta` per data-driven export, with an
|
|
8
|
+
* id that contains `$placeholder` markers — e.g. `test.each(rows)({ id:
|
|
9
|
+
* "case-$id" })` is extracted as id `"case-$id"`. At runtime, the harness
|
|
10
|
+
* substitutes the placeholders against each row, emitting concrete ids
|
|
11
|
+
* like `"case-101"`, `"case-202"`. Downstream consumers (CLI run output,
|
|
12
|
+
* MCP discover/run, etc.) need to map the concrete event ids back to the
|
|
13
|
+
* static meta — that's what these helpers do.
|
|
14
|
+
*
|
|
15
|
+
* ## Placeholder syntax
|
|
16
|
+
*
|
|
17
|
+
* - Marker is `$<word>` — a `$` followed by a JS identifier-start character
|
|
18
|
+
* followed by zero or more identifier characters (letters, digits, `_`).
|
|
19
|
+
* - Each placeholder matches `.*` (greedy) at runtime — there is no
|
|
20
|
+
* typed/numeric variant. Multi-placeholder ids (`case-$a-$b`) match
|
|
21
|
+
* greedily left-to-right; ambiguous splits are not resolved.
|
|
22
|
+
* - Matching is **case-insensitive** (the runner's harness id substitution
|
|
23
|
+
* may lowercase row keys).
|
|
24
|
+
*
|
|
25
|
+
* ## Variant prefix
|
|
26
|
+
*
|
|
27
|
+
* VSCode prefixes ids with `each:` / `pick:` for routing. These helpers
|
|
28
|
+
* strip the prefix before matching so a `each:case-$id` template still
|
|
29
|
+
* matches a runtime `case-101`.
|
|
30
|
+
*
|
|
31
|
+
* ## Limitations
|
|
32
|
+
*
|
|
33
|
+
* - Two static templates whose runtime ids could collide (e.g.
|
|
34
|
+
* `case-$x` and `case-$y` in the same file) get first-match-wins
|
|
35
|
+
* semantics from `findTemplateMatch` — there's no warning.
|
|
36
|
+
* - The shape `case-$a-$b` matching `case-x-y-z` has multiple valid
|
|
37
|
+
* splits; this helper returns *some* match without disambiguating.
|
|
38
|
+
* Document your runtime ids to avoid placeholder ambiguity.
|
|
39
|
+
*/
|
|
40
|
+
const TEMPLATE_RE = /\$[A-Za-z_]\w*/g;
|
|
41
|
+
const VARIANT_PREFIX_RE = /^(?:each|pick):/;
|
|
42
|
+
/**
|
|
43
|
+
* Strip the VSCode variant prefix (`each:` / `pick:`) from an id, leaving
|
|
44
|
+
* the bare template / concrete id. Returns the input unchanged if no
|
|
45
|
+
* prefix is present.
|
|
46
|
+
*/
|
|
47
|
+
export function stripVariantPrefix(id) {
|
|
48
|
+
return id.replace(VARIANT_PREFIX_RE, "");
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Returns `true` if the id contains at least one `$placeholder` marker
|
|
52
|
+
* (after stripping any variant prefix). Use this to detect "this is a
|
|
53
|
+
* template, not a concrete id" — concrete runtime ids never contain `$`.
|
|
54
|
+
*/
|
|
55
|
+
export function hasTemplatePlaceholders(id) {
|
|
56
|
+
TEMPLATE_RE.lastIndex = 0;
|
|
57
|
+
return TEMPLATE_RE.test(stripVariantPrefix(id));
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Build a regex that matches concrete runtime ids against a template id.
|
|
61
|
+
* Internal — exposed via `matchesTemplateId` and `findTemplateMatch`. If
|
|
62
|
+
* the input has no placeholders, returns `undefined` (caller should do an
|
|
63
|
+
* exact-string compare instead).
|
|
64
|
+
*
|
|
65
|
+
* Each `$word` placeholder becomes `.*` (greedy). The regex is anchored
|
|
66
|
+
* (`^...$`) and case-insensitive (`/i`).
|
|
67
|
+
*/
|
|
68
|
+
function templateIdToRegExp(id) {
|
|
69
|
+
const normalized = stripVariantPrefix(id);
|
|
70
|
+
TEMPLATE_RE.lastIndex = 0;
|
|
71
|
+
if (!TEMPLATE_RE.test(normalized))
|
|
72
|
+
return undefined;
|
|
73
|
+
TEMPLATE_RE.lastIndex = 0;
|
|
74
|
+
let lastIndex = 0;
|
|
75
|
+
let pattern = "^";
|
|
76
|
+
let match;
|
|
77
|
+
while ((match = TEMPLATE_RE.exec(normalized)) !== null) {
|
|
78
|
+
pattern += escapeRegExp(normalized.slice(lastIndex, match.index));
|
|
79
|
+
pattern += ".*";
|
|
80
|
+
lastIndex = match.index + match[0].length;
|
|
81
|
+
}
|
|
82
|
+
pattern += escapeRegExp(normalized.slice(lastIndex));
|
|
83
|
+
pattern += "$";
|
|
84
|
+
return new RegExp(pattern, "i");
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Returns `true` if `concreteId` is a substituted instance of `templateId`.
|
|
88
|
+
*
|
|
89
|
+
* Both inputs are normalised (variant prefix stripped, lowercased before
|
|
90
|
+
* exact compare). For `test.each([{id: "alpha"}])({ id: "case-$id" })`
|
|
91
|
+
* the runtime emits `"case-alpha"` — `matchesTemplateId("case-$id",
|
|
92
|
+
* "case-alpha")` returns `true`.
|
|
93
|
+
*
|
|
94
|
+
* Exact-id ties pass through unchanged: `matchesTemplateId("health",
|
|
95
|
+
* "health")` is `true` even though `"health"` has no placeholders.
|
|
96
|
+
*/
|
|
97
|
+
export function matchesTemplateId(templateId, concreteId) {
|
|
98
|
+
const normalizedTemplate = stripVariantPrefix(templateId).toLowerCase();
|
|
99
|
+
const normalizedConcrete = stripVariantPrefix(concreteId).toLowerCase();
|
|
100
|
+
if (normalizedTemplate === normalizedConcrete)
|
|
101
|
+
return true;
|
|
102
|
+
return templateIdToRegExp(templateId)?.test(stripVariantPrefix(concreteId)) ?? false;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Permissive filter match — used by CLI / MCP `--filter <id>` so the user
|
|
106
|
+
* can target a row by its concrete id while only the template appears in
|
|
107
|
+
* static meta. Acceptance order:
|
|
108
|
+
* 1. Empty filter → match (caller wants everything).
|
|
109
|
+
* 2. Filter substring of the template (template `case-$id` matches filter
|
|
110
|
+
* `case`).
|
|
111
|
+
* 3. Filter is a substituted instance (template `case-$id`, filter
|
|
112
|
+
* `case-101`).
|
|
113
|
+
* 4. Filter starts with the template's literal prefix (template
|
|
114
|
+
* `case-$id`, filter `case-101-extra`).
|
|
115
|
+
*
|
|
116
|
+
* Slightly more permissive than `matchesTemplateId` — designed for human
|
|
117
|
+
* `--filter` input, not strict harness event matching.
|
|
118
|
+
*/
|
|
119
|
+
export function matchesTemplateFilter(templateId, filter) {
|
|
120
|
+
const normalizedFilter = stripVariantPrefix(filter).toLowerCase().trim();
|
|
121
|
+
if (!normalizedFilter)
|
|
122
|
+
return true;
|
|
123
|
+
const normalizedTemplate = stripVariantPrefix(templateId).toLowerCase();
|
|
124
|
+
if (normalizedTemplate.includes(normalizedFilter))
|
|
125
|
+
return true;
|
|
126
|
+
if (matchesTemplateId(templateId, normalizedFilter))
|
|
127
|
+
return true;
|
|
128
|
+
const prefix = normalizedTemplate.split(TEMPLATE_RE)[0] ?? "";
|
|
129
|
+
return prefix.length > 0 && normalizedFilter.startsWith(prefix);
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Find the static meta entry whose id matches `concreteId`. Prefers exact
|
|
133
|
+
* matches over template matches — so if both `case-$id` and `case-101`
|
|
134
|
+
* exist statically (rare but possible), the concrete row event for
|
|
135
|
+
* `case-101` resolves to the concrete entry, not the template.
|
|
136
|
+
*
|
|
137
|
+
* If multiple template entries could match (e.g. two overlapping
|
|
138
|
+
* placeholders), returns the first one in array order — no warning. Avoid
|
|
139
|
+
* collisions by namespacing template ids per file.
|
|
140
|
+
*/
|
|
141
|
+
export function findTemplateMatch(items, concreteId) {
|
|
142
|
+
const normalizedConcrete = stripVariantPrefix(concreteId).toLowerCase();
|
|
143
|
+
const exact = items.find((item) => stripVariantPrefix(item.id).toLowerCase() === normalizedConcrete);
|
|
144
|
+
return exact ?? items.find((item) => matchesTemplateId(item.id, concreteId));
|
|
145
|
+
}
|
|
146
|
+
function escapeRegExp(value) {
|
|
147
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
148
|
+
}
|
|
149
|
+
//# sourceMappingURL=template-sentinel.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"template-sentinel.js","sourceRoot":"","sources":["../src/template-sentinel.ts"],"names":[],"mappings":"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"}
|