@contractspec/module.workspace 1.46.2 → 1.47.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.
Files changed (56) hide show
  1. package/dist/analysis/deps/graph.js.map +1 -1
  2. package/dist/analysis/deps/parse-imports.js.map +1 -1
  3. package/dist/analysis/diff/deep-diff.js.map +1 -1
  4. package/dist/analysis/diff/semantic.js.map +1 -1
  5. package/dist/analysis/example-scan.d.ts.map +1 -1
  6. package/dist/analysis/example-scan.js +2 -37
  7. package/dist/analysis/example-scan.js.map +1 -1
  8. package/dist/analysis/feature-extractor.js +203 -0
  9. package/dist/analysis/feature-extractor.js.map +1 -0
  10. package/dist/analysis/feature-scan.d.ts.map +1 -1
  11. package/dist/analysis/feature-scan.js +20 -121
  12. package/dist/analysis/feature-scan.js.map +1 -1
  13. package/dist/analysis/impact/classifier.js.map +1 -1
  14. package/dist/analysis/impact/rules.js.map +1 -1
  15. package/dist/analysis/index.js +3 -1
  16. package/dist/analysis/snapshot/normalizer.js.map +1 -1
  17. package/dist/analysis/snapshot/snapshot.js.map +1 -1
  18. package/dist/analysis/spec-parsing-utils.d.ts +26 -0
  19. package/dist/analysis/spec-parsing-utils.d.ts.map +1 -0
  20. package/dist/analysis/spec-parsing-utils.js +98 -0
  21. package/dist/analysis/spec-parsing-utils.js.map +1 -0
  22. package/dist/analysis/spec-scan.d.ts +8 -22
  23. package/dist/analysis/spec-scan.d.ts.map +1 -1
  24. package/dist/analysis/spec-scan.js +105 -337
  25. package/dist/analysis/spec-scan.js.map +1 -1
  26. package/dist/analysis/utils/matchers.js +77 -0
  27. package/dist/analysis/utils/matchers.js.map +1 -0
  28. package/dist/analysis/utils/variables.js +45 -0
  29. package/dist/analysis/utils/variables.js.map +1 -0
  30. package/dist/analysis/validate/index.js +1 -0
  31. package/dist/analysis/validate/spec-structure.d.ts.map +1 -1
  32. package/dist/analysis/validate/spec-structure.js +401 -85
  33. package/dist/analysis/validate/spec-structure.js.map +1 -1
  34. package/dist/formatter.js.map +1 -1
  35. package/dist/formatters/index.js +2 -0
  36. package/dist/formatters/spec-markdown.d.ts +4 -1
  37. package/dist/formatters/spec-markdown.d.ts.map +1 -1
  38. package/dist/formatters/spec-markdown.js +12 -4
  39. package/dist/formatters/spec-markdown.js.map +1 -1
  40. package/dist/formatters/spec-to-docblock.d.ts +3 -1
  41. package/dist/formatters/spec-to-docblock.d.ts.map +1 -1
  42. package/dist/formatters/spec-to-docblock.js +2 -2
  43. package/dist/formatters/spec-to-docblock.js.map +1 -1
  44. package/dist/index.d.ts +4 -3
  45. package/dist/index.js +4 -2
  46. package/dist/templates/integration-utils.js.map +1 -1
  47. package/dist/templates/integration.js +3 -4
  48. package/dist/templates/integration.js.map +1 -1
  49. package/dist/templates/knowledge.js.map +1 -1
  50. package/dist/templates/workflow.js.map +1 -1
  51. package/dist/types/analysis-types.d.ts +24 -3
  52. package/dist/types/analysis-types.d.ts.map +1 -1
  53. package/dist/types/generation-types.js.map +1 -1
  54. package/dist/types/llm-types.d.ts +1 -1
  55. package/dist/types/llm-types.d.ts.map +1 -1
  56. package/package.json +9 -10
@@ -1,63 +1,110 @@
1
+ import { findMatchingDelimiter, isStability, matchStringArrayField, matchStringField, matchVersionField } from "./utils/matchers.js";
2
+ import { extractArrayConstants, resolveVariablesInBlock } from "./utils/variables.js";
3
+ import { extractRefList, extractTestCoverage, extractTestRefs, extractTestTarget, parsePolicy } from "./spec-parsing-utils.js";
4
+
1
5
  //#region src/analysis/spec-scan.ts
2
6
  /**
3
- * Infer spec type from file path based on naming conventions.
4
- * Supports all contract types from @contractspec/lib.contracts.
7
+ * Scan all specs from a single source file.
5
8
  */
6
- function inferSpecTypeFromFilePath(filePath) {
7
- if (filePath.includes(".operations.") || filePath.includes("/operations/") || filePath.includes(".operation.") || filePath.includes("/operation/")) return "operation";
8
- if (filePath.includes(".event.") || filePath.includes("/events/") || filePath.endsWith("/events.ts")) return "event";
9
- if (filePath.includes(".presentation.") || filePath.includes("/presentations/") || filePath.endsWith("/presentations.ts")) return "presentation";
10
- if (filePath.includes(".feature.")) return "feature";
11
- if (filePath.includes(".capability.")) return "capability";
12
- if (filePath.includes(".data-view.")) return "data-view";
13
- if (filePath.includes(".form.")) return "form";
14
- if (filePath.includes(".migration.")) return "migration";
15
- if (filePath.includes(".workflow.")) return "workflow";
16
- if (filePath.includes(".experiment.")) return "experiment";
17
- if (filePath.includes(".integration.")) return "integration";
18
- if (filePath.includes(".knowledge.")) return "knowledge";
19
- if (filePath.includes(".telemetry.")) return "telemetry";
20
- if (filePath.includes(".app-config.")) return "app-config";
21
- if (filePath.includes(".policy.")) return "policy";
22
- if (filePath.includes(".test-spec.")) return "test-spec";
23
- return "unknown";
9
+ function scanAllSpecsFromSource(code, filePath) {
10
+ const results = [];
11
+ const variables = extractArrayConstants(code);
12
+ const definitionRegex = /export\s+const\s+(\w+)\s*=\s*define(Command|Query|Event|Presentation|Capability|Policy|Type|Example|AppConfig|Integration|Workflow|TestSpec|Feature)\s*\(/g;
13
+ let match;
14
+ while ((match = definitionRegex.exec(code)) !== null) {
15
+ const start = match.index;
16
+ const end = findMatchingDelimiter(code, start + match[0].length - 1, "(", ")");
17
+ if (end === -1) continue;
18
+ let finalEnd = end;
19
+ if (code[finalEnd + 1] === ";") finalEnd++;
20
+ const resolvedBlock = resolveVariablesInBlock(code.substring(start, finalEnd + 1), variables);
21
+ const result = scanSpecSource(resolvedBlock, filePath);
22
+ if (result) results.push({
23
+ ...result,
24
+ sourceBlock: resolvedBlock
25
+ });
26
+ }
27
+ if (results.length === 0 && filePath.includes(".spec.")) {
28
+ if (scanSpecSource(code, filePath).key !== "unknown") {
29
+ const result = scanSpecSource(resolveVariablesInBlock(code, variables), filePath);
30
+ results.push(result);
31
+ }
32
+ }
33
+ return results;
24
34
  }
25
35
  /**
26
- * Scan spec source code to extract metadata without executing it.
36
+ * Scan a single spec source string.
27
37
  */
28
38
  function scanSpecSource(code, filePath) {
29
- const specType = inferSpecTypeFromFilePath(filePath);
30
- const key = matchStringField(code, "key");
31
- const description = matchStringField(code, "description");
32
- const goal = matchStringField(code, "goal");
33
- const context = matchStringField(code, "context");
39
+ const key = (code.match(/key\s*:\s*['"]([^'"]+)['"]/) ?? code.match(/export\s+const\s+(\w+)\s*=/))?.[1] ?? "unknown";
40
+ const version = matchVersionField(code, "version");
41
+ const description = matchStringField(code, "description") ?? void 0;
42
+ const goal = matchStringField(code, "goal") ?? void 0;
43
+ const context = matchStringField(code, "context") ?? void 0;
34
44
  const stabilityRaw = matchStringField(code, "stability");
35
45
  const stability = isStability(stabilityRaw) ? stabilityRaw : void 0;
36
46
  const owners = matchStringArrayField(code, "owners");
37
47
  const tags = matchStringArrayField(code, "tags");
38
- const version = matchVersionField(code, "version");
39
- const kind = inferOperationKind(code);
40
- const hasMeta = /meta\s*:\s*{/.test(code);
41
- const hasIo = /\bio\s*:\s*{/.test(code);
42
- const hasPolicy = /\bpolicy\s*:\s*{/.test(code);
43
- const hasPayload = /\bpayload\s*:\s*{/.test(code);
44
- const hasContent = /\bcontent\s*:\s*{/.test(code);
45
- const hasDefinition = /\bdefinition\s*:\s*{/.test(code);
46
- const emittedEvents = specType === "operation" ? extractEmittedEvents(code) : void 0;
47
- const policyRefs = specType === "operation" ? extractPolicyRefs(code) : void 0;
48
+ let specType = "unknown";
49
+ let kind;
50
+ if (code.includes("defineCommand")) {
51
+ specType = "operation";
52
+ kind = "command";
53
+ } else if (code.includes("defineQuery")) {
54
+ specType = "operation";
55
+ kind = "query";
56
+ } else if (code.includes("defineEvent")) {
57
+ specType = "event";
58
+ kind = "event";
59
+ } else if (code.includes("definePresentation")) {
60
+ specType = "presentation";
61
+ kind = "presentation";
62
+ } else if (code.includes("definePolicy")) {
63
+ specType = "policy";
64
+ kind = "policy";
65
+ } else if (code.includes("defineCapability")) {
66
+ specType = "capability";
67
+ kind = "capability";
68
+ } else if (code.includes("defineExample")) {
69
+ specType = "example";
70
+ kind = "example";
71
+ } else if (code.includes("defineAppConfig")) {
72
+ specType = "app-config";
73
+ kind = "app-config";
74
+ } else if (code.includes("defineIntegration")) {
75
+ specType = "integration";
76
+ kind = "integration";
77
+ } else if (code.includes("defineWorkflow")) {
78
+ specType = "workflow";
79
+ kind = "workflow";
80
+ } else if (code.includes("defineTestSpec")) {
81
+ specType = "test-spec";
82
+ kind = "test-spec";
83
+ } else if (code.includes("defineFeature")) {
84
+ specType = "feature";
85
+ kind = "feature";
86
+ }
87
+ const hasMeta = /meta\s*:\s*\{/.test(code);
88
+ const hasIo = /io\s*:\s*\{/.test(code);
89
+ const hasPolicy = /policy\s*:\s*\{/.test(code);
90
+ const hasPayload = /payload\s*:\s*\{/.test(code);
91
+ const hasContent = /content\s*:\s*\{/.test(code);
92
+ const hasDefinition = /definition\s*:\s*\{/.test(code);
93
+ const emittedEvents = extractRefList(code, "emits") ?? extractRefList(code, "emittedEvents");
48
94
  const testRefs = extractTestRefs(code);
95
+ const policyRefs = hasPolicy ? parsePolicy(code) : void 0;
49
96
  return {
50
97
  filePath,
98
+ key,
99
+ version,
51
100
  specType,
52
- key: key ?? void 0,
53
- description: description ?? void 0,
54
- goal: goal ?? void 0,
55
- context: context ?? void 0,
101
+ kind,
102
+ description,
103
+ goal,
104
+ context,
56
105
  stability,
57
106
  owners,
58
107
  tags,
59
- version: version ?? void 0,
60
- kind,
61
108
  hasMeta,
62
109
  hasIo,
63
110
  hasPolicy,
@@ -67,307 +114,28 @@ function scanSpecSource(code, filePath) {
67
114
  emittedEvents,
68
115
  policyRefs,
69
116
  testRefs,
117
+ testTarget: extractTestTarget(code),
118
+ testCoverage: extractTestCoverage(code),
70
119
  sourceBlock: code
71
120
  };
72
121
  }
73
122
  /**
74
- * Extract emitted event refs from operation spec source.
75
- * Looks for sideEffects.emits array entries.
123
+ * Infer spec type from file path convention.
76
124
  */
77
- function extractEmittedEvents(code) {
78
- const events = [];
79
- const inlinePattern = /\{\s*key:\s*['"]([^'"]+)['"]\s*,\s*version:\s*(?:['"]([^'"]+)['"]|(\d+(?:\.\d+)*))/g;
80
- let match;
81
- while ((match = inlinePattern.exec(code)) !== null) {
82
- const key = match[1];
83
- const version = match[2] || match[3];
84
- if (key && version) events.push({
85
- key,
86
- version
87
- });
88
- }
89
- const refPattern = /\{\s*ref:\s*(\w+)/g;
90
- while ((match = refPattern.exec(code)) !== null) if (match[1] && !match[1].startsWith("when")) {}
91
- return events.length > 0 ? events : void 0;
92
- }
93
- /**
94
- * Extract policy refs from operation spec source.
95
- */
96
- function extractPolicyRefs(code) {
97
- const policies = [];
98
- const policyPattern = /\{\s*key:\s*['"]([^'"]+)['"]\s*,\s*version:\s*(?:['"]([^'"]+)['"]|(\d+(?:\.\d+)*))/g;
99
- const policySectionMatch = code.match(/policies\s*:\s*\[([\s\S]*?)\]/);
100
- if (policySectionMatch?.[1]) {
101
- let match;
102
- while ((match = policyPattern.exec(policySectionMatch[1])) !== null) {
103
- const key = match[1];
104
- const version = match[2] || match[3];
105
- if (key && version) policies.push({
106
- key,
107
- version
108
- });
109
- }
110
- }
111
- return policies.length > 0 ? policies : void 0;
112
- }
113
- /**
114
- * Extract test spec refs.
115
- */
116
- function extractTestRefs(code) {
117
- const tests = [];
118
- const testsSectionMatch = code.match(/tests\s*:\s*\[([\s\S]*?)\]/);
119
- if (testsSectionMatch?.[1]) {
120
- const refPattern = /\{\s*key:\s*['"]([^'"]+)['"]\s*,\s*version:\s*(?:['"]([^'"]+)['"]|(\d+(?:\.\d+)*))/g;
121
- let match;
122
- while ((match = refPattern.exec(testsSectionMatch[1])) !== null) {
123
- const key = match[1];
124
- const version = match[2] || match[3];
125
- if (key && version) tests.push({
126
- key,
127
- version
128
- });
129
- }
130
- }
131
- return tests.length > 0 ? tests : void 0;
132
- }
133
- function matchStringField(code, field) {
134
- const regex = /* @__PURE__ */ new RegExp(`${escapeRegex(field)}\\s*:\\s*['"]([^'"]+)['"]`);
135
- return code.match(regex)?.[1] ?? null;
136
- }
137
- function matchVersionField(code, field) {
138
- const regex = /* @__PURE__ */ new RegExp(`${escapeRegex(field)}\\s*:\\s*(?:['"]([^'"]+)['"]|(\\d+(?:\\.\\d+)*))`);
139
- const match = code.match(regex);
140
- if (match?.[1]) return match[1];
141
- if (match?.[2]) return match[2];
142
- }
143
- function matchStringArrayField(code, field) {
144
- const regex = /* @__PURE__ */ new RegExp(`${escapeRegex(field)}\\s*:\\s*\\[([\\s\\S]*?)\\]`);
145
- const match = code.match(regex);
146
- if (!match?.[1]) return void 0;
147
- const inner = match[1];
148
- const items = Array.from(inner.matchAll(/['"]([^'"]+)['"]/g)).map((m) => m[1]).filter((value) => typeof value === "string" && value.length > 0);
149
- return items.length > 0 ? items : void 0;
150
- }
151
- function isStability(value) {
152
- return value === "experimental" || value === "beta" || value === "stable" || value === "deprecated";
153
- }
154
- function escapeRegex(value) {
155
- return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
156
- }
157
- /**
158
- * Infer operation kind from source code.
159
- * First checks for defineCommand/defineQuery usage (which set kind automatically),
160
- * then falls back to explicit kind field.
161
- */
162
- function inferOperationKind(code) {
163
- if (/defineCommand\s*\(/.test(code)) return "command";
164
- if (/defineQuery\s*\(/.test(code)) return "query";
165
- const kindRaw = matchStringField(code, "kind");
166
- return kindRaw === "command" || kindRaw === "query" ? kindRaw : "unknown";
167
- }
168
- /**
169
- * Infer operation kind from a specific code block.
170
- */
171
- function inferOperationKindFromBlock(block) {
172
- if (/defineCommand\s*\(/.test(block)) return "command";
173
- if (/defineQuery\s*\(/.test(block)) return "query";
174
- const kindRaw = matchStringField(block, "kind");
175
- return kindRaw === "command" || kindRaw === "query" ? kindRaw : "unknown";
176
- }
177
- /**
178
- * Extract key and version from a meta block.
179
- */
180
- function extractMetaFromBlock(block) {
181
- const key = matchStringField(block, "key");
182
- const version = matchVersionField(block, "version");
183
- if (key && version !== void 0) return {
184
- key,
185
- version
186
- };
187
- return null;
188
- }
189
- /**
190
- * Define function patterns for all spec types.
191
- */
192
- const DEFINE_FUNCTION_PATTERNS = [
193
- {
194
- pattern: /defineCommand\s*\(\s*\{/g,
195
- type: "operation"
196
- },
197
- {
198
- pattern: /defineQuery\s*\(\s*\{/g,
199
- type: "operation"
200
- },
201
- {
202
- pattern: /defineEvent\s*\(\s*\{/g,
203
- type: "event"
204
- },
205
- {
206
- pattern: /:\s*PresentationSpec\s*=\s*\{/g,
207
- type: "presentation"
208
- },
209
- {
210
- pattern: /:\s*PresentationSpec\s*=\s*\{/g,
211
- type: "presentation"
212
- },
213
- {
214
- pattern: /:\s*PresentationDescriptor\s*=\s*\{/g,
215
- type: "presentation"
216
- },
217
- {
218
- pattern: /definePresentation\s*\(\s*\{/g,
219
- type: "presentation"
220
- },
221
- {
222
- pattern: /defineCapability\s*\(\s*\{/g,
223
- type: "capability"
224
- },
225
- {
226
- pattern: /defineWorkflow\s*\(\s*\{/g,
227
- type: "workflow"
228
- },
229
- {
230
- pattern: /defineExperiment\s*\(\s*\{/g,
231
- type: "experiment"
232
- },
233
- {
234
- pattern: /defineIntegration\s*\(\s*\{/g,
235
- type: "integration"
236
- },
237
- {
238
- pattern: /defineKnowledge\s*\(\s*\{/g,
239
- type: "knowledge"
240
- },
241
- {
242
- pattern: /defineTelemetry\s*\(\s*\{/g,
243
- type: "telemetry"
244
- },
245
- {
246
- pattern: /defineAppConfig\s*\(\s*\{/g,
247
- type: "app-config"
248
- },
249
- {
250
- pattern: /definePolicy\s*\(\s*\{/g,
251
- type: "policy"
252
- },
253
- {
254
- pattern: /defineTestSpec\s*\(\s*\{/g,
255
- type: "test-spec"
256
- },
257
- {
258
- pattern: /defineDataView\s*\(\s*\{/g,
259
- type: "data-view"
260
- },
261
- {
262
- pattern: /defineForm\s*\(\s*\{/g,
263
- type: "form"
264
- },
265
- {
266
- pattern: /defineMigration\s*\(\s*\{/g,
267
- type: "migration"
268
- }
269
- ];
270
- /**
271
- * Find matching closing brace for an opening brace.
272
- * Returns the index of the closing brace or -1 if not found.
273
- */
274
- function findMatchingBrace(code, startIndex) {
275
- let depth = 0;
276
- let inString = false;
277
- let stringChar = "";
278
- for (let i = startIndex; i < code.length; i++) {
279
- const char = code[i];
280
- const prevChar = i > 0 ? code[i - 1] : "";
281
- if ((char === "\"" || char === "'" || char === "`") && prevChar !== "\\") {
282
- if (!inString) {
283
- inString = true;
284
- stringChar = char;
285
- } else if (char === stringChar) inString = false;
286
- continue;
287
- }
288
- if (inString) continue;
289
- if (char === "{") depth++;
290
- else if (char === "}") {
291
- depth--;
292
- if (depth === 0) return i;
293
- }
294
- }
295
- return -1;
296
- }
297
- /**
298
- * Scan spec source code to extract ALL specs from a file.
299
- * This function finds multiple spec definitions in a single file.
300
- */
301
- function scanAllSpecsFromSource(code, filePath) {
302
- const results = [];
303
- const baseSpecType = inferSpecTypeFromFilePath(filePath);
304
- const processedPositions = /* @__PURE__ */ new Set();
305
- for (const { pattern, type } of DEFINE_FUNCTION_PATTERNS) {
306
- pattern.lastIndex = 0;
307
- let match;
308
- while ((match = pattern.exec(code)) !== null) {
309
- const startPos = match.index;
310
- if (processedPositions.has(startPos)) continue;
311
- processedPositions.add(startPos);
312
- const openBracePos = code.indexOf("{", startPos);
313
- if (openBracePos === -1) continue;
314
- const closeBracePos = findMatchingBrace(code, openBracePos);
315
- if (closeBracePos === -1) continue;
316
- const block = code.slice(openBracePos, closeBracePos + 1);
317
- const meta = extractMetaFromBlock(block);
318
- if (!meta) continue;
319
- const description = matchStringField(block, "description");
320
- const goal = matchStringField(block, "goal");
321
- const context = matchStringField(block, "context");
322
- const stabilityRaw = matchStringField(block, "stability");
323
- const stability = isStability(stabilityRaw) ? stabilityRaw : void 0;
324
- const owners = matchStringArrayField(block, "owners");
325
- const tags = matchStringArrayField(block, "tags");
326
- const hasMeta = /meta\s*:\s*{/.test(block);
327
- const hasIo = /\bio\s*:\s*{/.test(block);
328
- const hasPolicy = /\bpolicy\s*:\s*{/.test(block);
329
- const hasPayload = /\bpayload\s*:\s*{/.test(block);
330
- const hasContent = /\bcontent\s*:\s*{/.test(block);
331
- const hasDefinition = /\bdefinition\s*:\s*{/.test(block);
332
- const kind = type === "operation" ? inferOperationKindFromBlock(block) : "unknown";
333
- const emittedEvents = type === "operation" ? extractEmittedEvents(block) : void 0;
334
- const policyRefs = type === "operation" ? extractPolicyRefs(block) : void 0;
335
- const testRefs = extractTestRefs(block);
336
- results.push({
337
- filePath,
338
- specType: type,
339
- key: meta.key,
340
- version: meta.version,
341
- description: description ?? void 0,
342
- goal: goal ?? void 0,
343
- context: context ?? void 0,
344
- stability,
345
- owners,
346
- tags,
347
- kind,
348
- hasMeta,
349
- hasIo,
350
- hasPolicy,
351
- hasPayload,
352
- hasContent,
353
- hasDefinition,
354
- emittedEvents,
355
- policyRefs,
356
- testRefs,
357
- sourceBlock: block
358
- });
359
- }
360
- }
361
- if (results.length === 0 && baseSpecType !== "unknown") {
362
- const fallback = scanSpecSource(code, filePath);
363
- if (fallback.key && fallback.version !== void 0) results.push({
364
- ...fallback,
365
- sourceBlock: code
366
- });
367
- }
368
- return results;
125
+ function inferSpecTypeFromFilePath(filePath) {
126
+ if (filePath.includes(".contracts.") || /\/operations?\//.test(filePath)) return "operation";
127
+ if (filePath.includes(".event.") || /\/events?\//.test(filePath)) return "event";
128
+ if (filePath.includes(".presentation.") || /\/presentations?\//.test(filePath)) return "presentation";
129
+ if (filePath.includes(".policy.") || /\/policies?\//.test(filePath)) return "policy";
130
+ if (filePath.includes(".feature.") || /\/features?\//.test(filePath)) return "feature";
131
+ if (filePath.includes(".type.") || /\/types?\//.test(filePath)) return "type";
132
+ if (filePath.includes(".example.") || /\/examples?\//.test(filePath)) return "example";
133
+ if (filePath.includes(".app-config.")) return "app-config";
134
+ if (filePath.includes(".workflow.") || /\/workflows?\//.test(filePath)) return "workflow";
135
+ if (filePath.includes(".integration.") || /\/integrations?\//.test(filePath)) return "integration";
136
+ return "unknown";
369
137
  }
370
138
 
371
139
  //#endregion
372
- export { extractEmittedEvents, extractPolicyRefs, extractTestRefs, inferSpecTypeFromFilePath, scanAllSpecsFromSource, scanSpecSource };
140
+ export { inferSpecTypeFromFilePath, scanAllSpecsFromSource, scanSpecSource };
373
141
  //# sourceMappingURL=spec-scan.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"spec-scan.js","names":["events: RefInfo[]","policies: RefInfo[]","tests: RefInfo[]","results: SpecScanResult[]"],"sources":["../../src/analysis/spec-scan.ts"],"sourcesContent":["/**\n * Spec source scanning utilities.\n * Extracted from cli-contractspec/src/utils/spec-scan.ts\n */\n\nimport type {\n AnalyzedOperationKind,\n AnalyzedSpecType,\n RefInfo,\n SpecScanResult,\n} from '../types/analysis-types';\nimport type { Stability } from '../types/spec-types';\n\n/**\n * Infer spec type from file path based on naming conventions.\n * Supports all contract types from @contractspec/lib.contracts.\n */\nexport function inferSpecTypeFromFilePath(filePath: string): AnalyzedSpecType {\n // Check more specific patterns first\n // Operation patterns: .contracts. OR /contracts/ directory\n if (\n filePath.includes('.operations.') ||\n filePath.includes('/operations/') ||\n filePath.includes('.operation.') ||\n filePath.includes('/operation/')\n )\n return 'operation';\n\n // Event patterns: .event. OR /events/ OR /events.ts\n if (\n filePath.includes('.event.') ||\n filePath.includes('/events/') ||\n filePath.endsWith('/events.ts')\n )\n return 'event';\n\n // Presentation patterns: .presentation. OR /presentations/ OR /presentations.ts\n if (\n filePath.includes('.presentation.') ||\n filePath.includes('/presentations/') ||\n filePath.endsWith('/presentations.ts')\n )\n return 'presentation';\n\n if (filePath.includes('.feature.')) return 'feature';\n if (filePath.includes('.capability.')) return 'capability';\n if (filePath.includes('.data-view.')) return 'data-view';\n if (filePath.includes('.form.')) return 'form';\n if (filePath.includes('.migration.')) return 'migration';\n if (filePath.includes('.workflow.')) return 'workflow';\n if (filePath.includes('.experiment.')) return 'experiment';\n if (filePath.includes('.integration.')) return 'integration';\n if (filePath.includes('.knowledge.')) return 'knowledge';\n if (filePath.includes('.telemetry.')) return 'telemetry';\n if (filePath.includes('.app-config.')) return 'app-config';\n if (filePath.includes('.policy.')) return 'policy';\n if (filePath.includes('.test-spec.')) return 'test-spec';\n return 'unknown';\n}\n\n/**\n * Scan spec source code to extract metadata without executing it.\n */\nexport function scanSpecSource(code: string, filePath: string): SpecScanResult {\n const specType = inferSpecTypeFromFilePath(filePath);\n\n const key = matchStringField(code, 'key');\n const description = matchStringField(code, 'description');\n const goal = matchStringField(code, 'goal');\n const context = matchStringField(code, 'context');\n const stabilityRaw = matchStringField(code, 'stability');\n const stability = isStability(stabilityRaw) ? stabilityRaw : undefined;\n const owners = matchStringArrayField(code, 'owners');\n const tags = matchStringArrayField(code, 'tags');\n\n const version = matchVersionField(code, 'version');\n const kind = inferOperationKind(code);\n\n const hasMeta = /meta\\s*:\\s*{/.test(code);\n const hasIo = /\\bio\\s*:\\s*{/.test(code);\n const hasPolicy = /\\bpolicy\\s*:\\s*{/.test(code);\n const hasPayload = /\\bpayload\\s*:\\s*{/.test(code);\n const hasContent = /\\bcontent\\s*:\\s*{/.test(code);\n const hasDefinition = /\\bdefinition\\s*:\\s*{/.test(code);\n\n // Extract references from operations\n const emittedEvents =\n specType === 'operation' ? extractEmittedEvents(code) : undefined;\n const policyRefs =\n specType === 'operation' ? extractPolicyRefs(code) : undefined;\n const testRefs = extractTestRefs(code);\n\n return {\n filePath,\n specType,\n key: key ?? undefined,\n description: description ?? undefined,\n goal: goal ?? undefined,\n context: context ?? undefined,\n stability,\n owners,\n tags,\n version: version ?? undefined,\n kind,\n hasMeta,\n hasIo,\n hasPolicy,\n hasPayload,\n hasContent,\n hasDefinition,\n emittedEvents,\n policyRefs,\n testRefs,\n sourceBlock: code,\n };\n}\n\n/**\n * Extract emitted event refs from operation spec source.\n * Looks for sideEffects.emits array entries.\n */\nexport function extractEmittedEvents(code: string): RefInfo[] | undefined {\n const events: RefInfo[] = [];\n\n // Match inline emit declarations: { key: 'x', version: '1' or 1, ... }\n const inlinePattern =\n /\\{\\s*key:\\s*['\"]([^'\"]+)['\"]\\s*,\\s*version:\\s*(?:['\"]([^'\"]+)['\"]|(\\d+(?:\\.\\d+)*))/g;\n let match;\n while ((match = inlinePattern.exec(code)) !== null) {\n const key = match[1];\n const version = match[2] || match[3];\n if (key && version) {\n events.push({ key, version });\n }\n }\n\n // Match ref pattern: { ref: SomeEventSpec, ... }\n // We can't fully resolve these without execution, but we can note they exist\n const refPattern = /\\{\\s*ref:\\s*(\\w+)/g;\n while ((match = refPattern.exec(code)) !== null) {\n // Store a placeholder - actual resolution needs runtime\n if (match[1] && !match[1].startsWith('when')) {\n // We can't extract key/version from a variable ref statically\n // This is noted for completeness but won't be fully resolvable\n }\n }\n\n return events.length > 0 ? events : undefined;\n}\n\n/**\n * Extract policy refs from operation spec source.\n */\nexport function extractPolicyRefs(code: string): RefInfo[] | undefined {\n const policies: RefInfo[] = [];\n\n // Match policy ref pattern in policies array\n const policyPattern =\n /\\{\\s*key:\\s*['\"]([^'\"]+)['\"]\\s*,\\s*version:\\s*(?:['\"]([^'\"]+)['\"]|(\\d+(?:\\.\\d+)*))/g;\n\n // Only look within policy section\n const policySectionMatch = code.match(/policies\\s*:\\s*\\[([\\s\\S]*?)\\]/);\n if (policySectionMatch?.[1]) {\n let match;\n while ((match = policyPattern.exec(policySectionMatch[1])) !== null) {\n const key = match[1];\n const version = match[2] || match[3];\n if (key && version) {\n policies.push({ key, version });\n }\n }\n }\n\n return policies.length > 0 ? policies : undefined;\n}\n\n/**\n * Extract test spec refs.\n */\nexport function extractTestRefs(code: string): RefInfo[] | undefined {\n const tests: RefInfo[] = [];\n\n // Look for tests array\n const testsSectionMatch = code.match(/tests\\s*:\\s*\\[([\\s\\S]*?)\\]/);\n if (testsSectionMatch?.[1]) {\n const refPattern =\n /\\{\\s*key:\\s*['\"]([^'\"]+)['\"]\\s*,\\s*version:\\s*(?:['\"]([^'\"]+)['\"]|(\\d+(?:\\.\\d+)*))/g;\n let match;\n while ((match = refPattern.exec(testsSectionMatch[1])) !== null) {\n const key = match[1];\n const version = match[2] || match[3];\n if (key && version) {\n tests.push({ key, version });\n }\n }\n }\n\n return tests.length > 0 ? tests : undefined;\n}\n\nfunction matchStringField(code: string, field: string): string | null {\n const regex = new RegExp(`${escapeRegex(field)}\\\\s*:\\\\s*['\"]([^'\"]+)['\"]`);\n const match = code.match(regex);\n return match?.[1] ?? null;\n}\n\nfunction matchVersionField(code: string, field: string): string | undefined {\n const regex = new RegExp(\n `${escapeRegex(field)}\\\\s*:\\\\s*(?:['\"]([^'\"]+)['\"]|(\\\\d+(?:\\\\.\\\\d+)*))`\n );\n const match = code.match(regex);\n if (match?.[1]) return match[1];\n if (match?.[2]) return match[2];\n return undefined;\n}\n\nfunction matchStringArrayField(\n code: string,\n field: string\n): string[] | undefined {\n const regex = new RegExp(`${escapeRegex(field)}\\\\s*:\\\\s*\\\\[([\\\\s\\\\S]*?)\\\\]`);\n const match = code.match(regex);\n if (!match?.[1]) return undefined;\n\n const inner = match[1];\n const items = Array.from(inner.matchAll(/['\"]([^'\"]+)['\"]/g))\n .map((m) => m[1])\n .filter(\n (value): value is string => typeof value === 'string' && value.length > 0\n );\n\n return items.length > 0 ? items : undefined;\n}\n\nfunction isStability(value: string | null): value is Stability {\n return (\n value === 'experimental' ||\n value === 'beta' ||\n value === 'stable' ||\n value === 'deprecated'\n );\n}\n\nfunction escapeRegex(value: string): string {\n return value.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n}\n\n/**\n * Infer operation kind from source code.\n * First checks for defineCommand/defineQuery usage (which set kind automatically),\n * then falls back to explicit kind field.\n */\nfunction inferOperationKind(code: string): AnalyzedOperationKind {\n // Check for defineCommand/defineQuery usage first (they set kind automatically)\n if (/defineCommand\\s*\\(/.test(code)) return 'command';\n if (/defineQuery\\s*\\(/.test(code)) return 'query';\n // Fall back to explicit kind field\n const kindRaw = matchStringField(code, 'kind');\n return kindRaw === 'command' || kindRaw === 'query' ? kindRaw : 'unknown';\n}\n\n/**\n * Infer operation kind from a specific code block.\n */\nfunction inferOperationKindFromBlock(block: string): AnalyzedOperationKind {\n if (/defineCommand\\s*\\(/.test(block)) return 'command';\n if (/defineQuery\\s*\\(/.test(block)) return 'query';\n const kindRaw = matchStringField(block, 'kind');\n return kindRaw === 'command' || kindRaw === 'query' ? kindRaw : 'unknown';\n}\n\n/**\n * Extract key and version from a meta block.\n */\nfunction extractMetaFromBlock(\n block: string\n): { key: string; version: string } | null {\n const key = matchStringField(block, 'key');\n const version = matchVersionField(block, 'version');\n if (key && version !== undefined) {\n return { key, version };\n }\n return null;\n}\n\n/**\n * Define function patterns for all spec types.\n */\nconst DEFINE_FUNCTION_PATTERNS = [\n // Operations\n { pattern: /defineCommand\\s*\\(\\s*\\{/g, type: 'operation' as const },\n { pattern: /defineQuery\\s*\\(\\s*\\{/g, type: 'operation' as const },\n // Events\n { pattern: /defineEvent\\s*\\(\\s*\\{/g, type: 'event' as const },\n // Presentations (both v1 and v2 patterns)\n {\n pattern: /:\\s*PresentationSpec\\s*=\\s*\\{/g,\n type: 'presentation' as const,\n },\n {\n pattern: /:\\s*PresentationSpec\\s*=\\s*\\{/g,\n type: 'presentation' as const,\n },\n {\n pattern: /:\\s*PresentationDescriptor\\s*=\\s*\\{/g,\n type: 'presentation' as const,\n },\n { pattern: /definePresentation\\s*\\(\\s*\\{/g, type: 'presentation' as const },\n // Capabilities\n { pattern: /defineCapability\\s*\\(\\s*\\{/g, type: 'capability' as const },\n // Workflows\n { pattern: /defineWorkflow\\s*\\(\\s*\\{/g, type: 'workflow' as const },\n // Experiments\n { pattern: /defineExperiment\\s*\\(\\s*\\{/g, type: 'experiment' as const },\n // Integrations\n { pattern: /defineIntegration\\s*\\(\\s*\\{/g, type: 'integration' as const },\n // Knowledge\n { pattern: /defineKnowledge\\s*\\(\\s*\\{/g, type: 'knowledge' as const },\n // Telemetry\n { pattern: /defineTelemetry\\s*\\(\\s*\\{/g, type: 'telemetry' as const },\n // App config\n { pattern: /defineAppConfig\\s*\\(\\s*\\{/g, type: 'app-config' as const },\n // Policy\n { pattern: /definePolicy\\s*\\(\\s*\\{/g, type: 'policy' as const },\n // Test spec\n { pattern: /defineTestSpec\\s*\\(\\s*\\{/g, type: 'test-spec' as const },\n // Data view\n { pattern: /defineDataView\\s*\\(\\s*\\{/g, type: 'data-view' as const },\n // Form\n { pattern: /defineForm\\s*\\(\\s*\\{/g, type: 'form' as const },\n // Migration\n { pattern: /defineMigration\\s*\\(\\s*\\{/g, type: 'migration' as const },\n];\n\n/**\n * Find matching closing brace for an opening brace.\n * Returns the index of the closing brace or -1 if not found.\n */\nfunction findMatchingBrace(code: string, startIndex: number): number {\n let depth = 0;\n let inString = false;\n let stringChar = '';\n\n for (let i = startIndex; i < code.length; i++) {\n const char = code[i];\n const prevChar = i > 0 ? code[i - 1] : '';\n\n // Handle string literals\n if ((char === '\"' || char === \"'\" || char === '`') && prevChar !== '\\\\') {\n if (!inString) {\n inString = true;\n stringChar = char;\n } else if (char === stringChar) {\n inString = false;\n }\n continue;\n }\n\n if (inString) continue;\n\n if (char === '{') {\n depth++;\n } else if (char === '}') {\n depth--;\n if (depth === 0) {\n return i;\n }\n }\n }\n\n return -1;\n}\n\n/**\n * Scan spec source code to extract ALL specs from a file.\n * This function finds multiple spec definitions in a single file.\n */\nexport function scanAllSpecsFromSource(\n code: string,\n filePath: string\n): SpecScanResult[] {\n const results: SpecScanResult[] = [];\n const baseSpecType = inferSpecTypeFromFilePath(filePath);\n\n // Track positions we've already processed to avoid duplicates\n const processedPositions = new Set<number>();\n\n for (const { pattern, type } of DEFINE_FUNCTION_PATTERNS) {\n // Reset the regex lastIndex\n pattern.lastIndex = 0;\n\n let match;\n while ((match = pattern.exec(code)) !== null) {\n const startPos = match.index;\n\n // Skip if we've already processed this position\n if (processedPositions.has(startPos)) continue;\n processedPositions.add(startPos);\n\n // Find the opening brace position\n const openBracePos = code.indexOf('{', startPos);\n if (openBracePos === -1) continue;\n\n // Find the matching closing brace\n const closeBracePos = findMatchingBrace(code, openBracePos);\n if (closeBracePos === -1) continue;\n\n // Extract the block content\n const block = code.slice(openBracePos, closeBracePos + 1);\n\n // Extract meta information\n const meta = extractMetaFromBlock(block);\n if (!meta) continue;\n\n // Extract additional metadata\n const description = matchStringField(block, 'description');\n const goal = matchStringField(block, 'goal');\n const context = matchStringField(block, 'context');\n const stabilityRaw = matchStringField(block, 'stability');\n const stability = isStability(stabilityRaw) ? stabilityRaw : undefined;\n const owners = matchStringArrayField(block, 'owners');\n const tags = matchStringArrayField(block, 'tags');\n\n const hasMeta = /meta\\s*:\\s*{/.test(block);\n const hasIo = /\\bio\\s*:\\s*{/.test(block);\n const hasPolicy = /\\bpolicy\\s*:\\s*{/.test(block);\n const hasPayload = /\\bpayload\\s*:\\s*{/.test(block);\n const hasContent = /\\bcontent\\s*:\\s*{/.test(block);\n const hasDefinition = /\\bdefinition\\s*:\\s*{/.test(block);\n\n // Infer kind for operations\n const kind =\n type === 'operation' ? inferOperationKindFromBlock(block) : 'unknown';\n\n // Extract references from operations\n const emittedEvents =\n type === 'operation' ? extractEmittedEvents(block) : undefined;\n const policyRefs =\n type === 'operation' ? extractPolicyRefs(block) : undefined;\n const testRefs = extractTestRefs(block);\n\n results.push({\n filePath,\n specType: type,\n key: meta.key,\n version: meta.version,\n description: description ?? undefined,\n goal: goal ?? undefined,\n context: context ?? undefined,\n stability,\n owners,\n tags,\n kind,\n hasMeta,\n hasIo,\n hasPolicy,\n hasPayload,\n hasContent,\n hasDefinition,\n emittedEvents,\n policyRefs,\n testRefs,\n sourceBlock: block,\n });\n }\n }\n\n // If no specs found via patterns, fall back to file-type based scanning\n // This handles cases where the file uses non-standard patterns\n if (results.length === 0 && baseSpecType !== 'unknown') {\n const fallback = scanSpecSource(code, filePath);\n if (fallback.key && fallback.version !== undefined) {\n results.push({ ...fallback, sourceBlock: code });\n }\n }\n\n return results;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"spec-scan.js","names":[],"sources":["../../src/analysis/spec-scan.ts"],"sourcesContent":["/**\n * Spec scanning utilities.\n *\n * Scans source code to extract spec definitions and metadata without execution.\n */\n\nimport type { SpecScanResult } from '../types/analysis-types';\nimport {\n extractArrayConstants,\n resolveVariablesInBlock,\n} from './utils/variables';\n\nimport {\n findMatchingDelimiter,\n isStability,\n matchStringArrayField,\n matchStringField,\n matchVersionField,\n} from './utils/matchers';\n\nimport {\n parsePolicy,\n extractRefList,\n extractTestRefs,\n extractTestCoverage,\n extractTestTarget,\n} from './spec-parsing-utils';\n\nexport { extractTestTarget, extractTestCoverage } from './spec-parsing-utils';\n\n/**\n * Scan all specs from a single source file.\n */\nexport function scanAllSpecsFromSource(\n code: string,\n filePath: string\n): SpecScanResult[] {\n const results: SpecScanResult[] = [];\n\n // Pre-scan for variables available in file scope\n const variables = extractArrayConstants(code);\n\n // Match export definitions: export const X = defineXXX calls\n const definitionRegex =\n /export\\s+const\\s+(\\w+)\\s*=\\s*define(Command|Query|Event|Presentation|Capability|Policy|Type|Example|AppConfig|Integration|Workflow|TestSpec|Feature)\\s*\\(/g;\n let match;\n\n while ((match = definitionRegex.exec(code)) !== null) {\n const start = match.index;\n const openParenIndex = start + match[0].length - 1;\n const end = findMatchingDelimiter(code, openParenIndex, '(', ')');\n if (end === -1) continue;\n\n // Optional: include trailing semicolon\n let finalEnd = end;\n if (code[finalEnd + 1] === ';') {\n finalEnd++;\n }\n\n const block = code.substring(start, finalEnd + 1);\n\n // Resolve variables in the block\n const resolvedBlock = resolveVariablesInBlock(block, variables);\n\n const result = scanSpecSource(resolvedBlock, filePath);\n if (result) {\n results.push({\n ...result,\n sourceBlock: resolvedBlock, // Ensure the result has the resolved block\n });\n }\n }\n\n // Fallback: legacy file scan if no explicit exports found\n if (results.length === 0 && filePath.includes('.spec.')) {\n const result = scanSpecSource(code, filePath);\n if (result.key !== 'unknown') {\n // Try to resolve globally even for fallback (though scope is harder)\n const resolvedBlock = resolveVariablesInBlock(code, variables);\n const result = scanSpecSource(resolvedBlock, filePath);\n results.push(result);\n }\n }\n\n return results;\n}\n\n/**\n * Scan a single spec source string.\n */\nexport function scanSpecSource(code: string, filePath: string): SpecScanResult {\n const keyMatch =\n code.match(/key\\s*:\\s*['\"]([^'\"]+)['\"]/) ??\n code.match(/export\\s+const\\s+(\\w+)\\s*=/);\n const key = keyMatch?.[1] ?? 'unknown';\n\n const version = matchVersionField(code, 'version');\n const description = matchStringField(code, 'description') ?? undefined;\n const goal = matchStringField(code, 'goal') ?? undefined;\n const context = matchStringField(code, 'context') ?? undefined;\n const stabilityRaw = matchStringField(code, 'stability');\n const stability = isStability(stabilityRaw) ? stabilityRaw : undefined;\n const owners = matchStringArrayField(code, 'owners');\n const tags = matchStringArrayField(code, 'tags');\n\n // Determine type\n let specType: SpecScanResult['specType'] = 'unknown';\n let kind: string | undefined;\n\n if (code.includes('defineCommand')) {\n specType = 'operation';\n kind = 'command';\n } else if (code.includes('defineQuery')) {\n specType = 'operation';\n kind = 'query';\n } else if (code.includes('defineEvent')) {\n specType = 'event';\n kind = 'event';\n } else if (code.includes('definePresentation')) {\n specType = 'presentation';\n kind = 'presentation';\n } else if (code.includes('definePolicy')) {\n specType = 'policy';\n kind = 'policy';\n } else if (code.includes('defineCapability')) {\n specType = 'capability';\n kind = 'capability';\n } else if (code.includes('defineExample')) {\n specType = 'example';\n kind = 'example';\n } else if (code.includes('defineAppConfig')) {\n specType = 'app-config';\n kind = 'app-config';\n } else if (code.includes('defineIntegration')) {\n specType = 'integration';\n kind = 'integration';\n } else if (code.includes('defineWorkflow')) {\n specType = 'workflow';\n kind = 'workflow';\n } else if (code.includes('defineTestSpec')) {\n specType = 'test-spec';\n kind = 'test-spec';\n } else if (code.includes('defineFeature')) {\n specType = 'feature';\n kind = 'feature';\n }\n\n // Check feature flags/sections\n const hasMeta = /meta\\s*:\\s*\\{/.test(code);\n const hasIo = /io\\s*:\\s*\\{/.test(code);\n const hasPolicy = /policy\\s*:\\s*\\{/.test(code);\n const hasPayload = /payload\\s*:\\s*\\{/.test(code);\n const hasContent = /content\\s*:\\s*\\{/.test(code);\n const hasDefinition = /definition\\s*:\\s*\\{/.test(code);\n\n // References\n const emittedEvents =\n extractRefList(code, 'emits') ?? extractRefList(code, 'emittedEvents');\n const testRefs = extractTestRefs(code);\n\n const policyRefs = hasPolicy ? parsePolicy(code) : undefined;\n\n return {\n filePath,\n key,\n version,\n specType,\n kind: kind as SpecScanResult['kind'],\n description,\n goal,\n context,\n stability,\n owners,\n tags,\n hasMeta,\n hasIo,\n hasPolicy,\n hasPayload,\n hasContent,\n hasDefinition,\n emittedEvents,\n policyRefs,\n testRefs,\n testTarget: extractTestTarget(code),\n testCoverage: extractTestCoverage(code),\n sourceBlock: code,\n };\n}\n\n/**\n * Infer spec type from file path convention.\n */\nexport function inferSpecTypeFromFilePath(\n filePath: string\n): SpecScanResult['specType'] | 'feature' | 'unknown' {\n if (filePath.includes('.contracts.') || /\\/operations?\\//.test(filePath)) {\n return 'operation';\n }\n if (filePath.includes('.event.') || /\\/events?\\//.test(filePath)) {\n return 'event';\n }\n if (\n filePath.includes('.presentation.') ||\n /\\/presentations?\\//.test(filePath)\n ) {\n return 'presentation';\n }\n if (filePath.includes('.policy.') || /\\/policies?\\//.test(filePath)) {\n return 'policy';\n }\n if (filePath.includes('.feature.') || /\\/features?\\//.test(filePath)) {\n return 'feature';\n }\n if (filePath.includes('.type.') || /\\/types?\\//.test(filePath)) {\n return 'type';\n }\n if (filePath.includes('.example.') || /\\/examples?\\//.test(filePath)) {\n return 'example';\n }\n if (filePath.includes('.app-config.')) {\n return 'app-config';\n }\n if (filePath.includes('.workflow.') || /\\/workflows?\\//.test(filePath)) {\n return 'workflow';\n }\n if (\n filePath.includes('.integration.') ||\n /\\/integrations?\\//.test(filePath)\n ) {\n return 'integration';\n }\n return 'unknown';\n}\n"],"mappings":"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"}
@@ -0,0 +1,77 @@
1
+ //#region src/analysis/utils/matchers.ts
2
+ /**
3
+ * Escape regex special characters.
4
+ */
5
+ function escapeRegex(value) {
6
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
7
+ }
8
+ /**
9
+ * Match a string field in source code.
10
+ */
11
+ function matchStringField(code, field) {
12
+ const regex = /* @__PURE__ */ new RegExp(`${escapeRegex(field)}\\s*:\\s*['"]([^'"]+)['"]`);
13
+ return code.match(regex)?.[1] ?? null;
14
+ }
15
+ /**
16
+ * Match a string field within a limited scope.
17
+ */
18
+ function matchStringFieldIn(code, field) {
19
+ return matchStringField(code, field);
20
+ }
21
+ /**
22
+ * Match a version field which can be a string or number.
23
+ */
24
+ function matchVersionField(code, field) {
25
+ const regex = /* @__PURE__ */ new RegExp(`${escapeRegex(field)}\\s*:\\s*(?:['"]([^'"]+)['"]|(\\d+(?:\\.\\d+)*))`);
26
+ const match = code.match(regex);
27
+ if (match?.[1]) return match[1];
28
+ if (match?.[2]) return match[2];
29
+ }
30
+ /**
31
+ * Match a string array field in source code.
32
+ */
33
+ function matchStringArrayField(code, field) {
34
+ const regex = /* @__PURE__ */ new RegExp(`${escapeRegex(field)}\\s*:\\s*\\[([\\s\\S]*?)\\]`);
35
+ const match = code.match(regex);
36
+ if (!match?.[1]) return void 0;
37
+ const inner = match[1];
38
+ const items = Array.from(inner.matchAll(/['"]([^'"]+)['"]/g)).map((m) => m[1]).filter((value) => typeof value === "string" && value.length > 0);
39
+ return items.length > 0 ? items : void 0;
40
+ }
41
+ /**
42
+ * Check if a value is a valid stability.
43
+ */
44
+ function isStability(value) {
45
+ return value === "experimental" || value === "beta" || value === "stable" || value === "deprecated";
46
+ }
47
+ /**
48
+ * Find matching closing delimiter for an opening delimiter.
49
+ * Returns the index of the closing delimiter or -1 if not found.
50
+ */
51
+ function findMatchingDelimiter(code, startIndex, openChar, closeChar) {
52
+ let depth = 0;
53
+ let inString = false;
54
+ let stringChar = "";
55
+ for (let i = startIndex; i < code.length; i++) {
56
+ const char = code[i];
57
+ const prevChar = i > 0 ? code[i - 1] : "";
58
+ if ((char === "\"" || char === "'" || char === "`") && prevChar !== "\\") {
59
+ if (!inString) {
60
+ inString = true;
61
+ stringChar = char;
62
+ } else if (char === stringChar) inString = false;
63
+ continue;
64
+ }
65
+ if (inString) continue;
66
+ if (char === openChar) depth++;
67
+ else if (char === closeChar) {
68
+ depth--;
69
+ if (depth === 0) return i;
70
+ }
71
+ }
72
+ return -1;
73
+ }
74
+
75
+ //#endregion
76
+ export { escapeRegex, findMatchingDelimiter, isStability, matchStringArrayField, matchStringField, matchStringFieldIn, matchVersionField };
77
+ //# sourceMappingURL=matchers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"matchers.js","names":[],"sources":["../../../src/analysis/utils/matchers.ts"],"sourcesContent":["/**\n * Shared regex matchers and parsing utilities for spec scanning.\n */\n\nimport type { Stability } from '../../types/spec-types';\n\n/**\n * Escape regex special characters.\n */\nexport function escapeRegex(value: string): string {\n return value.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n}\n\n/**\n * Match a string field in source code.\n */\nexport function matchStringField(code: string, field: string): string | null {\n const regex = new RegExp(`${escapeRegex(field)}\\\\s*:\\\\s*['\"]([^'\"]+)['\"]`);\n const match = code.match(regex);\n return match?.[1] ?? null;\n}\n\n/**\n * Match a string field within a limited scope.\n */\nexport function matchStringFieldIn(code: string, field: string): string | null {\n return matchStringField(code, field);\n}\n\n/**\n * Match a version field which can be a string or number.\n */\nexport function matchVersionField(\n code: string,\n field: string\n): string | undefined {\n const regex = new RegExp(\n `${escapeRegex(field)}\\\\s*:\\\\s*(?:['\"]([^'\"]+)['\"]|(\\\\d+(?:\\\\.\\\\d+)*))`\n );\n const match = code.match(regex);\n if (match?.[1]) return match[1];\n if (match?.[2]) return match[2];\n return undefined;\n}\n\n/**\n * Match a string array field in source code.\n */\nexport function matchStringArrayField(\n code: string,\n field: string\n): string[] | undefined {\n const regex = new RegExp(`${escapeRegex(field)}\\\\s*:\\\\s*\\\\[([\\\\s\\\\S]*?)\\\\]`);\n const match = code.match(regex);\n if (!match?.[1]) return undefined;\n\n const inner = match[1];\n const items = Array.from(inner.matchAll(/['\"]([^'\"]+)['\"]/g))\n .map((m) => m[1])\n .filter(\n (value): value is string => typeof value === 'string' && value.length > 0\n );\n\n return items.length > 0 ? items : undefined;\n}\n\n/**\n * Check if a value is a valid stability.\n */\nexport function isStability(value: string | null): value is Stability {\n return (\n value === 'experimental' ||\n value === 'beta' ||\n value === 'stable' ||\n value === 'deprecated'\n );\n}\n\n/**\n * Find matching closing delimiter for an opening delimiter.\n * Returns the index of the closing delimiter or -1 if not found.\n */\nexport function findMatchingDelimiter(\n code: string,\n startIndex: number,\n openChar: string,\n closeChar: string\n): number {\n let depth = 0;\n let inString = false;\n let stringChar = '';\n\n for (let i = startIndex; i < code.length; i++) {\n const char = code[i];\n const prevChar = i > 0 ? code[i - 1] : '';\n\n // Handle string literals\n if ((char === '\"' || char === \"'\" || char === '`') && prevChar !== '\\\\') {\n if (!inString) {\n inString = true;\n stringChar = char;\n } else if (char === stringChar) {\n inString = false;\n }\n continue;\n }\n\n if (inString) continue;\n\n if (char === openChar) {\n depth++;\n } else if (char === closeChar) {\n depth--;\n if (depth === 0) {\n return i;\n }\n }\n }\n\n return -1;\n}\n\n/**\n * Find matching closing brace for an opening brace.\n * Returns the index of the closing brace or -1 if not found.\n */\nexport function findMatchingBrace(code: string, startIndex: number): number {\n return findMatchingDelimiter(code, startIndex, '{', '}');\n}\n"],"mappings":";;;;AASA,SAAgB,YAAY,OAAuB;AACjD,QAAO,MAAM,QAAQ,uBAAuB,OAAO;;;;;AAMrD,SAAgB,iBAAiB,MAAc,OAA8B;CAC3E,MAAM,wBAAQ,IAAI,OAAO,GAAG,YAAY,MAAM,CAAC,2BAA2B;AAE1E,QADc,KAAK,MAAM,MAAM,GAChB,MAAM;;;;;AAMvB,SAAgB,mBAAmB,MAAc,OAA8B;AAC7E,QAAO,iBAAiB,MAAM,MAAM;;;;;AAMtC,SAAgB,kBACd,MACA,OACoB;CACpB,MAAM,wBAAQ,IAAI,OAChB,GAAG,YAAY,MAAM,CAAC,kDACvB;CACD,MAAM,QAAQ,KAAK,MAAM,MAAM;AAC/B,KAAI,QAAQ,GAAI,QAAO,MAAM;AAC7B,KAAI,QAAQ,GAAI,QAAO,MAAM;;;;;AAO/B,SAAgB,sBACd,MACA,OACsB;CACtB,MAAM,wBAAQ,IAAI,OAAO,GAAG,YAAY,MAAM,CAAC,6BAA6B;CAC5E,MAAM,QAAQ,KAAK,MAAM,MAAM;AAC/B,KAAI,CAAC,QAAQ,GAAI,QAAO;CAExB,MAAM,QAAQ,MAAM;CACpB,MAAM,QAAQ,MAAM,KAAK,MAAM,SAAS,oBAAoB,CAAC,CAC1D,KAAK,MAAM,EAAE,GAAG,CAChB,QACE,UAA2B,OAAO,UAAU,YAAY,MAAM,SAAS,EACzE;AAEH,QAAO,MAAM,SAAS,IAAI,QAAQ;;;;;AAMpC,SAAgB,YAAY,OAA0C;AACpE,QACE,UAAU,kBACV,UAAU,UACV,UAAU,YACV,UAAU;;;;;;AAQd,SAAgB,sBACd,MACA,YACA,UACA,WACQ;CACR,IAAI,QAAQ;CACZ,IAAI,WAAW;CACf,IAAI,aAAa;AAEjB,MAAK,IAAI,IAAI,YAAY,IAAI,KAAK,QAAQ,KAAK;EAC7C,MAAM,OAAO,KAAK;EAClB,MAAM,WAAW,IAAI,IAAI,KAAK,IAAI,KAAK;AAGvC,OAAK,SAAS,QAAO,SAAS,OAAO,SAAS,QAAQ,aAAa,MAAM;AACvE,OAAI,CAAC,UAAU;AACb,eAAW;AACX,iBAAa;cACJ,SAAS,WAClB,YAAW;AAEb;;AAGF,MAAI,SAAU;AAEd,MAAI,SAAS,SACX;WACS,SAAS,WAAW;AAC7B;AACA,OAAI,UAAU,EACZ,QAAO;;;AAKb,QAAO"}