umple-lsp-server 0.4.2 → 1.0.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 (72) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/README.md +35 -0
  3. package/completions.scm +9 -3
  4. package/definitions.scm +5 -0
  5. package/highlights.scm +487 -0
  6. package/out/codeActions.d.ts +31 -0
  7. package/out/codeActions.js +361 -0
  8. package/out/codeActions.js.map +1 -0
  9. package/out/completionAnalysis.d.ts +9 -1
  10. package/out/completionAnalysis.js +1211 -64
  11. package/out/completionAnalysis.js.map +1 -1
  12. package/out/completionBuilder.d.ts +1 -1
  13. package/out/completionBuilder.js +463 -319
  14. package/out/completionBuilder.js.map +1 -1
  15. package/out/completionTriggers.d.ts +20 -0
  16. package/out/completionTriggers.js +69 -0
  17. package/out/completionTriggers.js.map +1 -0
  18. package/out/diagnosticSources.d.ts +3 -0
  19. package/out/diagnosticSources.js +11 -0
  20. package/out/diagnosticSources.js.map +1 -0
  21. package/out/diagramNavigation.js +3 -3
  22. package/out/diagramNavigation.js.map +1 -1
  23. package/out/documentSymbolBuilder.js +2 -37
  24. package/out/documentSymbolBuilder.js.map +1 -1
  25. package/out/formatter.d.ts +13 -1
  26. package/out/formatter.js +303 -10
  27. package/out/formatter.js.map +1 -1
  28. package/out/hoverBuilder.js +90 -23
  29. package/out/hoverBuilder.js.map +1 -1
  30. package/out/inlayHints.d.ts +21 -0
  31. package/out/inlayHints.js +98 -0
  32. package/out/inlayHints.js.map +1 -0
  33. package/out/referenceSearch.d.ts +1 -1
  34. package/out/referenceSearch.js +134 -7
  35. package/out/referenceSearch.js.map +1 -1
  36. package/out/resolver.js +82 -3
  37. package/out/resolver.js.map +1 -1
  38. package/out/semanticTokens.d.ts +32 -0
  39. package/out/semanticTokens.js +228 -0
  40. package/out/semanticTokens.js.map +1 -0
  41. package/out/server.js +216 -36
  42. package/out/server.js.map +1 -1
  43. package/out/snippets.d.ts +39 -0
  44. package/out/snippets.js +328 -0
  45. package/out/snippets.js.map +1 -0
  46. package/out/symbolIndex.d.ts +50 -0
  47. package/out/symbolIndex.js +170 -7
  48. package/out/symbolIndex.js.map +1 -1
  49. package/out/symbolPresentation.d.ts +3 -0
  50. package/out/symbolPresentation.js +45 -0
  51. package/out/symbolPresentation.js.map +1 -0
  52. package/out/symbolTypes.d.ts +1 -0
  53. package/out/tokenAnalysis.js +77 -4
  54. package/out/tokenAnalysis.js.map +1 -1
  55. package/out/tokenTypes.d.ts +8 -1
  56. package/out/tokenTypes.js +2 -0
  57. package/out/tokenTypes.js.map +1 -1
  58. package/out/treeUtils.js +17 -4
  59. package/out/treeUtils.js.map +1 -1
  60. package/out/workspaceSymbolBuilder.d.ts +3 -0
  61. package/out/workspaceSymbolBuilder.js +117 -0
  62. package/out/workspaceSymbolBuilder.js.map +1 -0
  63. package/package.json +5 -2
  64. package/references.scm +31 -3
  65. package/tree-sitter-umple.wasm +0 -0
  66. package/out/bin.d.ts +0 -2
  67. package/out/bin.js +0 -5
  68. package/out/bin.js.map +0 -1
  69. package/out/log.d.ts +0 -7
  70. package/out/log.js +0 -22
  71. package/out/log.js.map +0 -1
  72. package/out/tsconfig.tsbuildinfo +0 -1
@@ -15,7 +15,23 @@ exports.symbolKindToCompletionKind = symbolKindToCompletionKind;
15
15
  exports.buildSemanticCompletionItems = buildSemanticCompletionItems;
16
16
  const node_1 = require("vscode-languageserver/node");
17
17
  const keywords_1 = require("./keywords");
18
+ const snippets_1 = require("./snippets");
18
19
  const path = require("path");
20
+ /**
21
+ * Topic 054 — emit snippet completion items for the given scope when the
22
+ * client advertised snippet support. No-op for typed-prefix / suppress /
23
+ * symbol-only scopes (they have no snippet entries registered).
24
+ */
25
+ function appendSnippetsForScope(items, seen, scope, snippetSupport) {
26
+ if (!snippetSupport)
27
+ return;
28
+ for (const entry of (0, snippets_1.getSnippetsForScope)(scope)) {
29
+ if (seen.has(entry.label))
30
+ continue;
31
+ seen.add(entry.label);
32
+ items.push((0, snippets_1.snippetEntryToItem)(entry));
33
+ }
34
+ }
19
35
  // ── Curated top-level construct keywords ───────────────────────────────────
20
36
  // Derived from the grammar's _definition rule (grammar.js lines 66-92).
21
37
  // Raw parser lookahead is NOT surfaced at this scope.
@@ -57,7 +73,7 @@ const CLASS_BODY_KEYWORDS = [
57
73
  // Trace
58
74
  "trace", "tracecase",
59
75
  // Nested definitions
60
- "class", "enum", "mixset",
76
+ "inner", "class", "enum", "mixset",
61
77
  // Class-level declarations
62
78
  "abstract", "singleton",
63
79
  // Attribute modifiers
@@ -196,40 +212,18 @@ const ASSOCIATION_MULTIPLICITY_STARTERS = [
196
212
  "1..*",
197
213
  "0..*",
198
214
  ];
199
- // ── Constraint keyword blocklist ────────────────────────────────────────────
200
- const CONSTRAINT_BLOCKLIST = new Set([
201
- "ERROR",
202
- // Top-level definition keywords
203
- "namespace", "class", "interface", "trait", "abstract",
204
- "association", "associationClass", "statemachine",
205
- "enum", "external", "mixset",
206
- // Generate statement + all targets
207
- "generate", "Java", "Nothing", "Php", "RTCpp", "SimpleCpp",
208
- "Ruby", "Python", "Cpp", "Json", "StructureDiagram", "Yuml",
209
- "Violet", "Umlet", "Simulate", "TextUml", "Scxml",
210
- "GvStateDiagram", "GvClassDiagram", "GvFeatureDiagram",
211
- "GvClassTraitDiagram", "GvEntityRelationshipDiagram",
212
- "Alloy", "NuSMV", "NuSMVOptimizer", "Papyrus", "Ecore", "Xmi",
213
- "Xtext", "Sql", "StateTables", "EventSequence", "InstanceDiagram",
214
- "Umple", "UmpleSelf", "USE", "Test", "SimpleMetrics",
215
- "PlainRequirementsDoc", "Uigu2", "ExternalGrammar", "Mermaid",
216
- // Other top-level directives
217
- "use", "strictness",
218
- // Class-level keywords invalid inside constraints
219
- "isA", "req", "require", "subfeature", "isFeature",
220
- "depend", "singleton", "displayColor", "displayColour",
221
- "implementsReq", "filter", "includeFilter",
222
- // Trace/SM/attribute keywords that can't appear in expressions
223
- "trace", "tracecase", "activate", "deactivate",
224
- "onAllObjects", "onThisThreadOnly", "onThisObject",
225
- "where", "until", "giving", "record",
226
- "queued", "pooled", "as",
227
- "key", "immutable", "unique", "lazy", "settable",
228
- "internal", "defaulted", "autounique",
229
- "entry", "exit", "do",
230
- "emit", "around", "custom", "generated",
231
- "include", "hops", "super", "sub",
232
- ]);
215
+ // All seven Umple association arrow operators, mirroring the grammar rule
216
+ // arrow: choice("--", "->", "<-", "<@>-", "-<@>", ">->", "<-<")
217
+ // Offered after the left multiplicity (`1 |`) before the arrow is committed.
218
+ const ASSOCIATION_ARROW_STARTERS = [
219
+ "--",
220
+ "->",
221
+ "<-",
222
+ "<@>-",
223
+ "-<@>",
224
+ ">->",
225
+ "<-<",
226
+ ];
233
227
  // ── Kind → CompletionItemKind mapping ───────────────────────────────────────
234
228
  function symbolKindToCompletionKind(kind) {
235
229
  switch (kind) {
@@ -245,8 +239,12 @@ function symbolKindToCompletionKind(kind) {
245
239
  return node_1.CompletionItemKind.EnumMember;
246
240
  case "statemachine":
247
241
  return node_1.CompletionItemKind.Enum;
242
+ case "event":
243
+ return node_1.CompletionItemKind.Event;
248
244
  case "attribute":
249
245
  return node_1.CompletionItemKind.Field;
246
+ case "port":
247
+ return node_1.CompletionItemKind.Property;
250
248
  case "const":
251
249
  return node_1.CompletionItemKind.Constant;
252
250
  case "method":
@@ -263,6 +261,69 @@ function symbolKindToCompletionKind(kind) {
263
261
  return node_1.CompletionItemKind.Text;
264
262
  }
265
263
  }
264
+ /**
265
+ * Append symbols of the given kinds into `items`/`seen`. Each kind is looked
266
+ * up either globally (no `container`) or container-scoped with optional
267
+ * inheritance. Dedup is by label against the shared `seen` set, so callers
268
+ * can interleave this with curated keyword / built-in emission.
269
+ *
270
+ * Order: kinds iterated in declared order; within a kind, whatever order
271
+ * `symbolIndex.getSymbols` returns.
272
+ */
273
+ function appendSymbolsOfKinds(items, seen, symbolIndex, reachableFiles, opts) {
274
+ for (const symKind of opts.kinds) {
275
+ const query = opts.container !== undefined
276
+ ? { container: opts.container, kind: symKind, inherited: opts.inherited }
277
+ : { kind: symKind };
278
+ const symbols = symbolIndex
279
+ .getSymbols(query)
280
+ .filter((s) => reachableFiles.has(path.normalize(s.file)));
281
+ for (const sym of symbols) {
282
+ if (!seen.has(sym.name)) {
283
+ seen.add(sym.name);
284
+ items.push({
285
+ label: sym.name,
286
+ kind: symbolKindToCompletionKind(symKind),
287
+ detail: symKind,
288
+ });
289
+ }
290
+ }
291
+ }
292
+ }
293
+ /**
294
+ * Assemble a symbol-only completion list: optional built-ins followed by
295
+ * named symbols across the given kinds. A single dedup Set shares across
296
+ * built-ins and symbols so a user-defined type that shadows a built-in name
297
+ * (rare) doesn't appear twice.
298
+ *
299
+ * Used by every typed-prefix scope and by `association_type`. Preserves the
300
+ * exact emission ordering the pre-refactor branches relied on:
301
+ * 1. built-ins in BUILTIN_TYPES order (optionally skipping `void`)
302
+ * 2. symbols per-kind, kinds iterated in declared order
303
+ * 3. within each kind, whatever order `SymbolIndex.getSymbols` returns
304
+ */
305
+ function buildTypeCompletionItems(symbolIndex, reachableFiles, opts) {
306
+ const items = [];
307
+ const seen = new Set();
308
+ if (opts.includeBuiltins) {
309
+ for (const typ of keywords_1.BUILTIN_TYPES) {
310
+ if (opts.excludeVoid && typ === "void")
311
+ continue;
312
+ if (!seen.has(typ)) {
313
+ seen.add(typ);
314
+ items.push({
315
+ label: typ,
316
+ kind: node_1.CompletionItemKind.TypeParameter,
317
+ detail: "built-in",
318
+ });
319
+ }
320
+ }
321
+ }
322
+ appendSymbolsOfKinds(items, seen, symbolIndex, reachableFiles, {
323
+ kinds: opts.kinds,
324
+ });
325
+ return items;
326
+ }
266
327
  // ── Main builder ────────────────────────────────────────────────────────────
267
328
  /**
268
329
  * Build semantic completion items from a CompletionInfo + SymbolIndex.
@@ -277,15 +338,16 @@ function symbolKindToCompletionKind(kind) {
277
338
  * @param symbolKinds - the normalized symbolKinds (after use_path → mixset conversion).
278
339
  * Caller should not pass "suppress", "use_path", isComment, or isDefinitionName states.
279
340
  */
280
- function buildSemanticCompletionItems(info, symbolKinds, symbolIndex, reachableFiles) {
281
- const items = [];
282
- const seen = new Set();
341
+ function buildSemanticCompletionItems(info, symbolKinds, symbolIndex, reachableFiles, snippetSupport = false) {
283
342
  // Top-level scope: curated keywords only, no raw lookahead or symbols.
284
343
  if (symbolKinds === "top_level") {
285
- return TOP_LEVEL_KEYWORDS.map((kw) => ({
344
+ const tlItems = TOP_LEVEL_KEYWORDS.map((kw) => ({
286
345
  label: kw,
287
346
  kind: node_1.CompletionItemKind.Keyword,
288
347
  }));
348
+ const tlSeen = new Set(TOP_LEVEL_KEYWORDS);
349
+ appendSnippetsForScope(tlItems, tlSeen, "top_level", snippetSupport);
350
+ return tlItems;
289
351
  }
290
352
  // Class-body scope: curated keywords + built-in types + type symbols.
291
353
  if (symbolKinds === "class_body") {
@@ -301,21 +363,10 @@ function buildSemanticCompletionItems(info, symbolKinds, symbolIndex, reachableF
301
363
  cbItems.push({ label: typ, kind: node_1.CompletionItemKind.TypeParameter, detail: "type" });
302
364
  }
303
365
  }
304
- for (const symKind of ["class", "interface", "trait", "enum"]) {
305
- const symbols = symbolIndex
306
- .getSymbols({ kind: symKind })
307
- .filter((s) => reachableFiles.has(path.normalize(s.file)));
308
- for (const sym of symbols) {
309
- if (!cbSeen.has(sym.name)) {
310
- cbSeen.add(sym.name);
311
- cbItems.push({
312
- label: sym.name,
313
- kind: symbolKindToCompletionKind(symKind),
314
- detail: symKind,
315
- });
316
- }
317
- }
318
- }
366
+ appendSymbolsOfKinds(cbItems, cbSeen, symbolIndex, reachableFiles, {
367
+ kinds: ["class", "interface", "trait", "enum"],
368
+ });
369
+ appendSnippetsForScope(cbItems, cbSeen, "class_body", snippetSupport);
319
370
  return cbItems;
320
371
  }
321
372
  // Trait-body scope: curated keywords + built-in types + type symbols.
@@ -332,21 +383,10 @@ function buildSemanticCompletionItems(info, symbolKinds, symbolIndex, reachableF
332
383
  tbItems.push({ label: typ, kind: node_1.CompletionItemKind.TypeParameter, detail: "type" });
333
384
  }
334
385
  }
335
- for (const symKind of ["class", "interface", "trait", "enum"]) {
336
- const symbols = symbolIndex
337
- .getSymbols({ kind: symKind })
338
- .filter((s) => reachableFiles.has(path.normalize(s.file)));
339
- for (const sym of symbols) {
340
- if (!tbSeen.has(sym.name)) {
341
- tbSeen.add(sym.name);
342
- tbItems.push({
343
- label: sym.name,
344
- kind: symbolKindToCompletionKind(symKind),
345
- detail: symKind,
346
- });
347
- }
348
- }
349
- }
386
+ appendSymbolsOfKinds(tbItems, tbSeen, symbolIndex, reachableFiles, {
387
+ kinds: ["class", "interface", "trait", "enum"],
388
+ });
389
+ appendSnippetsForScope(tbItems, tbSeen, "trait_body", snippetSupport);
350
390
  return tbItems;
351
391
  }
352
392
  // Interface-body scope: curated keywords + built-in types + type symbols.
@@ -363,21 +403,10 @@ function buildSemanticCompletionItems(info, symbolKinds, symbolIndex, reachableF
363
403
  ibItems.push({ label: typ, kind: node_1.CompletionItemKind.TypeParameter, detail: "type" });
364
404
  }
365
405
  }
366
- for (const symKind of ["class", "interface", "trait", "enum"]) {
367
- const symbols = symbolIndex
368
- .getSymbols({ kind: symKind })
369
- .filter((s) => reachableFiles.has(path.normalize(s.file)));
370
- for (const sym of symbols) {
371
- if (!ibSeen.has(sym.name)) {
372
- ibSeen.add(sym.name);
373
- ibItems.push({
374
- label: sym.name,
375
- kind: symbolKindToCompletionKind(symKind),
376
- detail: symKind,
377
- });
378
- }
379
- }
380
- }
406
+ appendSymbolsOfKinds(ibItems, ibSeen, symbolIndex, reachableFiles, {
407
+ kinds: ["class", "interface", "trait", "enum"],
408
+ });
409
+ appendSnippetsForScope(ibItems, ibSeen, "interface_body", snippetSupport);
381
410
  return ibItems;
382
411
  }
383
412
  // Association-class-body scope: same as class body (shares _class_content).
@@ -394,49 +423,31 @@ function buildSemanticCompletionItems(info, symbolKinds, symbolIndex, reachableF
394
423
  abItems.push({ label: typ, kind: node_1.CompletionItemKind.TypeParameter, detail: "type" });
395
424
  }
396
425
  }
397
- for (const symKind of ["class", "interface", "trait", "enum"]) {
398
- const symbols = symbolIndex
399
- .getSymbols({ kind: symKind })
400
- .filter((s) => reachableFiles.has(path.normalize(s.file)));
401
- for (const sym of symbols) {
402
- if (!abSeen.has(sym.name)) {
403
- abSeen.add(sym.name);
404
- abItems.push({
405
- label: sym.name,
406
- kind: symbolKindToCompletionKind(symKind),
407
- detail: symKind,
408
- });
409
- }
410
- }
411
- }
426
+ appendSymbolsOfKinds(abItems, abSeen, symbolIndex, reachableFiles, {
427
+ kinds: ["class", "interface", "trait", "enum"],
428
+ });
429
+ appendSnippetsForScope(abItems, abSeen, "assoc_class_body", snippetSupport);
412
430
  return abItems;
413
431
  }
414
432
  // Filter-body scope: curated filter-statement starters only.
415
433
  if (symbolKinds === "filter_body") {
416
- return FILTER_BODY_KEYWORDS.map((kw) => ({
434
+ const fbItems = FILTER_BODY_KEYWORDS.map((kw) => ({
417
435
  label: kw,
418
436
  kind: node_1.CompletionItemKind.Keyword,
419
437
  }));
438
+ const fbSeen = new Set(FILTER_BODY_KEYWORDS);
439
+ appendSnippetsForScope(fbItems, fbSeen, "filter_body", snippetSupport);
440
+ return fbItems;
420
441
  }
421
442
  // Trace entity scope: scoped attrs/methods only, no raw keywords.
422
443
  if (symbolKinds === "trace_attribute_method" && info.enclosingClass) {
423
444
  const trItems = [];
424
445
  const trSeen = new Set();
425
- for (const kind of ["attribute", "method"]) {
426
- const symbols = symbolIndex
427
- .getSymbols({ container: info.enclosingClass, kind, inherited: true })
428
- .filter((s) => reachableFiles.has(path.normalize(s.file)));
429
- for (const sym of symbols) {
430
- if (!trSeen.has(sym.name)) {
431
- trSeen.add(sym.name);
432
- trItems.push({
433
- label: sym.name,
434
- kind: symbolKindToCompletionKind(kind),
435
- detail: kind,
436
- });
437
- }
438
- }
439
- }
446
+ appendSymbolsOfKinds(trItems, trSeen, symbolIndex, reachableFiles, {
447
+ kinds: ["attribute", "method"],
448
+ container: info.enclosingClass,
449
+ inherited: true,
450
+ });
440
451
  return trItems;
441
452
  }
442
453
  // Guard scope: scoped attrs/methods + boolean literals only, no raw keywords.
@@ -449,23 +460,36 @@ function buildSemanticCompletionItems(info, symbolKinds, symbolIndex, reachableF
449
460
  gItems.push({ label: lit, kind: node_1.CompletionItemKind.Keyword });
450
461
  }
451
462
  // Scoped attrs + methods from enclosing class
452
- for (const kind of ["attribute", "method"]) {
453
- const symbols = symbolIndex
454
- .getSymbols({ container: info.enclosingClass, kind, inherited: true })
455
- .filter((s) => reachableFiles.has(path.normalize(s.file)));
456
- for (const sym of symbols) {
457
- if (!gSeen.has(sym.name)) {
458
- gSeen.add(sym.name);
459
- gItems.push({
460
- label: sym.name,
461
- kind: symbolKindToCompletionKind(kind),
462
- detail: kind,
463
- });
464
- }
465
- }
466
- }
463
+ appendSymbolsOfKinds(gItems, gSeen, symbolIndex, reachableFiles, {
464
+ kinds: ["attribute", "method"],
465
+ container: info.enclosingClass,
466
+ inherited: true,
467
+ });
467
468
  return gItems;
468
469
  }
470
+ // Constraint scope `[...]`: own attributes only (Umple E28 — no inherited
471
+ // attrs in constraints). Symbol-only; no raw lookahead, no operators.
472
+ if (symbolKinds === "own_attribute" && info.enclosingClass) {
473
+ const oaItems = [];
474
+ const oaSeen = new Set();
475
+ appendSymbolsOfKinds(oaItems, oaSeen, symbolIndex, reachableFiles, {
476
+ kinds: ["attribute"],
477
+ container: info.enclosingClass,
478
+ });
479
+ return oaItems;
480
+ }
481
+ // Sorted-key scope `sorted{...}`: attributes of the owner class with
482
+ // inheritance. Symbol-only; no raw lookahead, no operators.
483
+ if (symbolKinds === "sorted_attribute" && info.sortedKeyOwner) {
484
+ const skItems = [];
485
+ const skSeen = new Set();
486
+ appendSymbolsOfKinds(skItems, skSeen, symbolIndex, reachableFiles, {
487
+ kinds: ["attribute"],
488
+ container: info.sortedKeyOwner,
489
+ inherited: true,
490
+ });
491
+ return skItems;
492
+ }
469
493
  // Transition-target scope: state symbols only, no keywords.
470
494
  if (symbolKinds === "transition_target") {
471
495
  const ttItems = [];
@@ -482,19 +506,10 @@ function buildSemanticCompletionItems(info, symbolKinds, symbolIndex, reachableF
482
506
  }
483
507
  }
484
508
  else {
485
- const states = symbolIndex
486
- .getSymbols({ kind: "state", container: info.enclosingStateMachine })
487
- .filter((s) => reachableFiles.has(path.normalize(s.file)));
488
- for (const sym of states) {
489
- if (!ttSeen.has(sym.name)) {
490
- ttSeen.add(sym.name);
491
- ttItems.push({
492
- label: sym.name,
493
- kind: symbolKindToCompletionKind("state"),
494
- detail: "state",
495
- });
496
- }
497
- }
509
+ appendSymbolsOfKinds(ttItems, ttSeen, symbolIndex, reachableFiles, {
510
+ kinds: ["state"],
511
+ container: info.enclosingStateMachine,
512
+ });
498
513
  }
499
514
  }
500
515
  return ttItems;
@@ -513,21 +528,9 @@ function buildSemanticCompletionItems(info, symbolKinds, symbolIndex, reachableF
513
528
  mbItems.push({ label: typ, kind: node_1.CompletionItemKind.TypeParameter, detail: "type" });
514
529
  }
515
530
  }
516
- for (const symKind of ["class", "interface", "trait", "enum"]) {
517
- const symbols = symbolIndex
518
- .getSymbols({ kind: symKind })
519
- .filter((s) => reachableFiles.has(path.normalize(s.file)));
520
- for (const sym of symbols) {
521
- if (!mbSeen.has(sym.name)) {
522
- mbSeen.add(sym.name);
523
- mbItems.push({
524
- label: sym.name,
525
- kind: symbolKindToCompletionKind(symKind),
526
- detail: symKind,
527
- });
528
- }
529
- }
530
- }
531
+ appendSymbolsOfKinds(mbItems, mbSeen, symbolIndex, reachableFiles, {
532
+ kinds: ["class", "interface", "trait", "enum"],
533
+ });
531
534
  return mbItems;
532
535
  }
533
536
  // Statemachine-body scope: curated keywords + state symbols from enclosing SM.
@@ -540,20 +543,12 @@ function buildSemanticCompletionItems(info, symbolKinds, symbolIndex, reachableF
540
543
  }
541
544
  // Offer existing state names from the enclosing SM
542
545
  if (info.enclosingStateMachine) {
543
- const states = symbolIndex
544
- .getSymbols({ kind: "state", container: info.enclosingStateMachine })
545
- .filter((s) => reachableFiles.has(path.normalize(s.file)));
546
- for (const sym of states) {
547
- if (!smSeen.has(sym.name)) {
548
- smSeen.add(sym.name);
549
- smItems.push({
550
- label: sym.name,
551
- kind: symbolKindToCompletionKind("state"),
552
- detail: "state",
553
- });
554
- }
555
- }
546
+ appendSymbolsOfKinds(smItems, smSeen, symbolIndex, reachableFiles, {
547
+ kinds: ["state"],
548
+ container: info.enclosingStateMachine,
549
+ });
556
550
  }
551
+ appendSnippetsForScope(smItems, smSeen, "statemachine_body", snippetSupport);
557
552
  return smItems;
558
553
  }
559
554
  // State-body scope: curated keywords + state symbols + built-in types for methods.
@@ -573,20 +568,12 @@ function buildSemanticCompletionItems(info, symbolKinds, symbolIndex, reachableF
573
568
  }
574
569
  // Offer state names from enclosing SM (for nested state references)
575
570
  if (info.enclosingStateMachine) {
576
- const states = symbolIndex
577
- .getSymbols({ kind: "state", container: info.enclosingStateMachine })
578
- .filter((s) => reachableFiles.has(path.normalize(s.file)));
579
- for (const sym of states) {
580
- if (!sbSeen.has(sym.name)) {
581
- sbSeen.add(sym.name);
582
- sbItems.push({
583
- label: sym.name,
584
- kind: symbolKindToCompletionKind("state"),
585
- detail: "state",
586
- });
587
- }
588
- }
571
+ appendSymbolsOfKinds(sbItems, sbSeen, symbolIndex, reachableFiles, {
572
+ kinds: ["state"],
573
+ container: info.enclosingStateMachine,
574
+ });
589
575
  }
576
+ appendSnippetsForScope(sbItems, sbSeen, "state_body", snippetSupport);
590
577
  return sbItems;
591
578
  }
592
579
  // Trait SM op contexts are symbol-only — no keywords or operators.
@@ -617,21 +604,209 @@ function buildSemanticCompletionItems(info, symbolKinds, symbolIndex, reachableF
617
604
  if (symbolKinds === "trace_method" && info.enclosingClass) {
618
605
  const items = [];
619
606
  const seen = new Set();
620
- const symbols = symbolIndex
621
- .getSymbols({ kind: "method", container: info.enclosingClass, inherited: true })
607
+ appendSymbolsOfKinds(items, seen, symbolIndex, reachableFiles, {
608
+ kinds: ["method"],
609
+ container: info.enclosingClass,
610
+ inherited: true,
611
+ });
612
+ return items;
613
+ }
614
+ if (symbolKinds === "trace_event" && info.enclosingClass) {
615
+ const items = [];
616
+ const seen = new Set();
617
+ appendSymbolsOfKinds(items, seen, symbolIndex, reachableFiles, {
618
+ kinds: ["event"],
619
+ container: info.enclosingClass,
620
+ inherited: true,
621
+ });
622
+ return items;
623
+ }
624
+ // Topic 052 item 2 — `before |` / `after |` / `before p|` / `after p|`
625
+ // code-injection slot. Method symbols of the enclosing class with
626
+ // inheritance, no built-ins, no LookaheadIterator keywords. Same shape
627
+ // as trace_method but kept on a distinct scalar for semantic clarity
628
+ // (and to leave room for `around` etc. later).
629
+ if (symbolKinds === "code_injection_method" && info.enclosingClass) {
630
+ const items = [];
631
+ const seen = new Set();
632
+ appendSymbolsOfKinds(items, seen, symbolIndex, reachableFiles, {
633
+ kinds: ["method"],
634
+ container: info.enclosingClass,
635
+ inherited: true,
636
+ });
637
+ return items;
638
+ }
639
+ // Topic 052 item 3 — `filter { include ... }` class-target slot. Class
640
+ // symbols only — no built-ins, no `void`, no LookaheadIterator
641
+ // keywords. Covers blank `include |` and typed `include S|`.
642
+ if (symbolKinds === "filter_include_target") {
643
+ const items = [];
644
+ const seen = new Set();
645
+ appendSymbolsOfKinds(items, seen, symbolIndex, reachableFiles, {
646
+ kinds: ["class"],
647
+ });
648
+ return items;
649
+ }
650
+ // Topic 055 — `class C { sm name as |` (referenced_statemachine target).
651
+ // Offer SMs valid for class-local reuse: class-local statemachines from the
652
+ // enclosing class plus top-level standalone statemachine_definitions
653
+ // reachable from this file. No keywords, operators, types.
654
+ if (symbolKinds === "referenced_sm_target") {
655
+ const items = [];
656
+ const seen = new Set();
657
+ if (info.enclosingClass) {
658
+ // Class-local SMs use the convention `<className>.<smName>` for their
659
+ // container; symbol container === `${enclosingClass}.${name}`.
660
+ const local = symbolIndex
661
+ .getSymbols({ kind: "statemachine" })
662
+ .filter((s) => s.container === `${info.enclosingClass}.${s.name}`)
663
+ .filter((s) => reachableFiles.has(path.normalize(s.file)));
664
+ for (const sym of local) {
665
+ if (!seen.has(sym.name)) {
666
+ seen.add(sym.name);
667
+ items.push({
668
+ label: sym.name,
669
+ kind: symbolKindToCompletionKind("statemachine"),
670
+ detail: "statemachine",
671
+ });
672
+ }
673
+ }
674
+ }
675
+ // Top-level standalone statemachines: container === name (no class prefix).
676
+ const topLevel = symbolIndex
677
+ .getSymbols({ kind: "statemachine" })
678
+ .filter((s) => s.container === s.name)
622
679
  .filter((s) => reachableFiles.has(path.normalize(s.file)));
623
- for (const sym of symbols) {
680
+ for (const sym of topLevel) {
681
+ if (!seen.has(sym.name)) {
682
+ seen.add(sym.name);
683
+ items.push({
684
+ label: sym.name,
685
+ kind: symbolKindToCompletionKind("statemachine"),
686
+ detail: "top-level statemachine",
687
+ });
688
+ }
689
+ }
690
+ return items;
691
+ }
692
+ // Topic 055 — `isA T<sm as |...` first-segment slot. Same SM universe as
693
+ // referenced_sm_target but kept separate so future divergence (e.g., trait
694
+ // type parameter constraints) is easy to contain.
695
+ if (symbolKinds === "trait_sm_binding_target") {
696
+ const items = [];
697
+ const seen = new Set();
698
+ if (info.enclosingClass) {
699
+ const local = symbolIndex
700
+ .getSymbols({ kind: "statemachine" })
701
+ .filter((s) => s.container === `${info.enclosingClass}.${s.name}`)
702
+ .filter((s) => reachableFiles.has(path.normalize(s.file)));
703
+ for (const sym of local) {
704
+ if (!seen.has(sym.name)) {
705
+ seen.add(sym.name);
706
+ items.push({
707
+ label: sym.name,
708
+ kind: symbolKindToCompletionKind("statemachine"),
709
+ detail: "statemachine",
710
+ });
711
+ }
712
+ }
713
+ }
714
+ const topLevel = symbolIndex
715
+ .getSymbols({ kind: "statemachine" })
716
+ .filter((s) => s.container === s.name)
717
+ .filter((s) => reachableFiles.has(path.normalize(s.file)));
718
+ for (const sym of topLevel) {
624
719
  if (!seen.has(sym.name)) {
625
720
  seen.add(sym.name);
626
- items.push({ label: sym.name, kind: symbolKindToCompletionKind("method"), detail: "method" });
721
+ items.push({
722
+ label: sym.name,
723
+ kind: symbolKindToCompletionKind("statemachine"),
724
+ detail: "top-level statemachine",
725
+ });
627
726
  }
628
727
  }
629
728
  return items;
630
729
  }
730
+ // Topic 055 — `isA T<sm as <smName>(.<seg>)*.|...` dotted-state continuation.
731
+ // The analyzer captures (smName, statePrefix) from the in-progress
732
+ // qualified_name. Resolve smContainer as class-local first
733
+ // (`${enclosingClass}.${smName}`), falling back to the top-level standalone
734
+ // statemachine container (`${smName}` self-container). When the state
735
+ // prefix is empty, offer depth-1 states of the SM root; otherwise descend
736
+ // via `getChildStateNames` so nested paths like `Sm.S1.|` surface S1's
737
+ // direct children.
738
+ if (symbolKinds === "trait_sm_binding_state_target") {
739
+ const items = [];
740
+ const seen = new Set();
741
+ const enclosingClass = info.enclosingClass;
742
+ const smName = info.traitSmBindingSmName;
743
+ const statePrefix = info.traitSmBindingStatePrefix ?? [];
744
+ if (smName) {
745
+ const candidates = [];
746
+ if (enclosingClass)
747
+ candidates.push(`${enclosingClass}.${smName}`);
748
+ candidates.push(smName); // top-level standalone fallback
749
+ let smContainer;
750
+ for (const cand of candidates) {
751
+ const exists = symbolIndex
752
+ .getSymbols({ container: cand, kind: "statemachine", name: smName })
753
+ .some((s) => reachableFiles.has(path.normalize(s.file)));
754
+ if (exists) {
755
+ smContainer = cand;
756
+ break;
757
+ }
758
+ }
759
+ if (smContainer) {
760
+ if (statePrefix.length === 0) {
761
+ // Top-level states of the SM (depth 1).
762
+ const directStates = symbolIndex
763
+ .getSymbols({ container: smContainer, kind: "state" })
764
+ .filter((s) => reachableFiles.has(path.normalize(s.file)))
765
+ .filter((s) => (s.statePath?.length ?? 0) === 1);
766
+ for (const sym of directStates) {
767
+ if (!seen.has(sym.name)) {
768
+ seen.add(sym.name);
769
+ items.push({
770
+ label: sym.name,
771
+ kind: symbolKindToCompletionKind("state"),
772
+ detail: "state",
773
+ });
774
+ }
775
+ }
776
+ }
777
+ else {
778
+ // Nested states under the prefix (e.g., Sm.S1.|).
779
+ const childNames = symbolIndex.getChildStateNames(statePrefix, smContainer, reachableFiles);
780
+ for (const name of childNames) {
781
+ if (!seen.has(name)) {
782
+ seen.add(name);
783
+ items.push({
784
+ label: name,
785
+ kind: symbolKindToCompletionKind("state"),
786
+ detail: "state",
787
+ });
788
+ }
789
+ }
790
+ }
791
+ }
792
+ }
793
+ return items;
794
+ }
795
+ // Topic 052 item 4 — method parameter-type slot. Built-ins (excluding
796
+ // `void`) + class / interface / trait / enum. Same shape as
797
+ // decl_type_typed_prefix but on a distinct scalar so divergence is
798
+ // contained.
799
+ if (symbolKinds === "param_type_typed_prefix") {
800
+ return buildTypeCompletionItems(symbolIndex, reachableFiles, {
801
+ kinds: ["class", "interface", "trait", "enum"],
802
+ includeBuiltins: true,
803
+ excludeVoid: true,
804
+ });
805
+ }
631
806
  if (symbolKinds === "trace_state_method" && info.enclosingClass) {
632
807
  const items = [];
633
808
  const seen = new Set();
634
- // States from all SMs in the enclosing class
809
+ // States from all SMs in the enclosing class — pattern C, specialized.
635
810
  const states = symbolIndex
636
811
  .getSymbols({ kind: "state" })
637
812
  .filter((s) => s.container?.startsWith(info.enclosingClass + "."))
@@ -643,37 +818,42 @@ function buildSemanticCompletionItems(info, symbolKinds, symbolIndex, reachableF
643
818
  }
644
819
  }
645
820
  // Methods from the enclosing class
646
- const methods = symbolIndex
647
- .getSymbols({ kind: "method", container: info.enclosingClass, inherited: true })
648
- .filter((s) => reachableFiles.has(path.normalize(s.file)));
649
- for (const sym of methods) {
650
- if (!seen.has(sym.name)) {
651
- seen.add(sym.name);
652
- items.push({ label: sym.name, kind: symbolKindToCompletionKind("method"), detail: "method" });
653
- }
654
- }
821
+ appendSymbolsOfKinds(items, seen, symbolIndex, reachableFiles, {
822
+ kinds: ["method"],
823
+ container: info.enclosingClass,
824
+ inherited: true,
825
+ });
655
826
  return items;
656
827
  }
657
828
  if (symbolKinds === "trace_attribute" && info.enclosingClass) {
658
829
  const items = [];
659
830
  const seen = new Set();
660
- const symbols = symbolIndex
661
- .getSymbols({ kind: "attribute", container: info.enclosingClass, inherited: true })
662
- .filter((s) => reachableFiles.has(path.normalize(s.file)));
663
- for (const sym of symbols) {
664
- if (!seen.has(sym.name)) {
665
- seen.add(sym.name);
666
- items.push({ label: sym.name, kind: symbolKindToCompletionKind("attribute"), detail: "attribute" });
667
- }
668
- }
831
+ appendSymbolsOfKinds(items, seen, symbolIndex, reachableFiles, {
832
+ kinds: ["attribute"],
833
+ container: info.enclosingClass,
834
+ inherited: true,
835
+ });
669
836
  return items;
670
837
  }
671
838
  // Structured userStory body — curated tag starters only.
672
839
  if (symbolKinds === "userstory_body") {
673
- return USER_STORY_BODY_STARTERS.map((kw) => ({
840
+ const usItems = USER_STORY_BODY_STARTERS.map((kw) => ({
674
841
  label: kw,
675
842
  kind: node_1.CompletionItemKind.Keyword,
676
843
  }));
844
+ const usSeen = new Set(USER_STORY_BODY_STARTERS);
845
+ appendSnippetsForScope(usItems, usSeen, "userstory_body", snippetSupport);
846
+ return usItems;
847
+ }
848
+ // Partial inline association — arrow slot after the left multiplicity
849
+ // (e.g. `1 |`, `1 -|`, `1 <|`). Curated arrow operators only — no
850
+ // multiplicity, no type symbols, no class-body keywords.
851
+ if (symbolKinds === "association_arrow") {
852
+ return ASSOCIATION_ARROW_STARTERS.map((a) => ({
853
+ label: a,
854
+ kind: node_1.CompletionItemKind.Operator,
855
+ detail: "association arrow",
856
+ }));
677
857
  }
678
858
  // Partial inline association — right-multiplicity slot after the arrow
679
859
  // (e.g. `1 -> |`). Curated multiplicities only; no class-body junk.
@@ -687,22 +867,9 @@ function buildSemanticCompletionItems(info, symbolKinds, symbolIndex, reachableF
687
867
  // Partial inline association — right-type slot after the right multiplicity
688
868
  // (e.g. `1 -> * |`). Offer class symbols only, no keywords.
689
869
  if (symbolKinds === "association_type") {
690
- const atItems = [];
691
- const atSeen = new Set();
692
- const classes = symbolIndex
693
- .getSymbols({ kind: "class" })
694
- .filter((s) => reachableFiles.has(path.normalize(s.file)));
695
- for (const sym of classes) {
696
- if (!atSeen.has(sym.name)) {
697
- atSeen.add(sym.name);
698
- atItems.push({
699
- label: sym.name,
700
- kind: symbolKindToCompletionKind("class"),
701
- detail: "class",
702
- });
703
- }
704
- }
705
- return atItems;
870
+ return buildTypeCompletionItems(symbolIndex, reachableFiles, {
871
+ kinds: ["class"],
872
+ });
706
873
  }
707
874
  // Typed-prefix on the right_type identifier (e.g. `1 -> * O|`). Once the
708
875
  // parser commits to a complete `association_inline` / `association_member`,
@@ -710,31 +877,54 @@ function buildSemanticCompletionItems(info, symbolKinds, symbolIndex, reachableF
710
877
  // raw LookaheadIterator keyword junk; this narrower scope returns just the
711
878
  // legitimate type symbols (class / interface / trait) without keywords.
712
879
  if (symbolKinds === "association_typed_prefix") {
713
- const atItems = [];
714
- const atSeen = new Set();
715
- for (const symKind of ["class", "interface", "trait"]) {
716
- const symbols = symbolIndex
717
- .getSymbols({ kind: symKind })
718
- .filter((s) => reachableFiles.has(path.normalize(s.file)));
719
- for (const sym of symbols) {
720
- if (!atSeen.has(sym.name)) {
721
- atSeen.add(sym.name);
722
- atItems.push({
723
- label: sym.name,
724
- kind: symbolKindToCompletionKind(symKind),
725
- detail: symKind,
726
- });
727
- }
728
- }
729
- }
730
- return atItems;
880
+ return buildTypeCompletionItems(symbolIndex, reachableFiles, {
881
+ kinds: ["class", "interface", "trait"],
882
+ });
883
+ }
884
+ // Typed-prefix on the isa_declaration type identifier (topic 047 item 1).
885
+ // Same motivation as association_typed_prefix above: the generic
886
+ // (isa_declaration) @scope.class_interface_trait capture pulls in raw
887
+ // LookaheadIterator keyword junk when recovering from incomplete parses.
888
+ // This narrower scope returns only class / interface / trait symbols.
889
+ if (symbolKinds === "isa_typed_prefix") {
890
+ return buildTypeCompletionItems(symbolIndex, reachableFiles, {
891
+ kinds: ["class", "interface", "trait"],
892
+ });
893
+ }
894
+ // Typed-prefix on attribute/const declaration type identifier (topic 047
895
+ // item 2). Cursor sits inside a type_name under attribute_declaration or
896
+ // const_declaration. Default class_body scope leaks 54+ LookaheadIterator
897
+ // keywords (test, generic, class, isA, trace, ...) and surfaces zero type
898
+ // symbols. Symbol-only early-return: built-in types + class / interface /
899
+ // trait / enum. `void` is deliberately excluded — method-return only.
900
+ if (symbolKinds === "decl_type_typed_prefix") {
901
+ return buildTypeCompletionItems(symbolIndex, reachableFiles, {
902
+ kinds: ["class", "interface", "trait", "enum"],
903
+ includeBuiltins: true,
904
+ excludeVoid: true,
905
+ });
906
+ }
907
+ // Typed-prefix on method return-type identifier (topic 047 item 3).
908
+ // Same shape as decl_type_typed_prefix, but fires on method_declaration /
909
+ // abstract_method_declaration / method_signature / trait_method_signature
910
+ // and KEEPS `void`. Parameter types sit under `param` (not a method rule)
911
+ // and are excluded by detection, so they fall through to the class_body
912
+ // path and remain untouched by this branch.
913
+ if (symbolKinds === "return_type_typed_prefix") {
914
+ return buildTypeCompletionItems(symbolIndex, reachableFiles, {
915
+ kinds: ["class", "interface", "trait", "enum"],
916
+ includeBuiltins: true,
917
+ });
731
918
  }
732
919
  // Structured useCase body — userStoryTags plus useCaseStep starters.
733
920
  if (symbolKinds === "usecase_body") {
734
- return USE_CASE_BODY_STARTERS.map((kw) => ({
921
+ const ucItems = USE_CASE_BODY_STARTERS.map((kw) => ({
735
922
  label: kw,
736
923
  kind: node_1.CompletionItemKind.Keyword,
737
924
  }));
925
+ const ucSeen = new Set(USE_CASE_BODY_STARTERS);
926
+ appendSnippetsForScope(ucItems, ucSeen, "usecase_body", snippetSupport);
927
+ return ucItems;
738
928
  }
739
929
  // implementsReq scope — symbol-only, no keywords/operators. The req_implementation
740
930
  // grammar rule only permits `implementsReq <id>(, <id>)* ;`, so surfacing class /
@@ -757,33 +947,42 @@ function buildSemanticCompletionItems(info, symbolKinds, symbolIndex, reachableF
757
947
  }
758
948
  return reqItems;
759
949
  }
760
- // 1. Keywords from LookaheadIterator (filtered in constraint contexts)
761
- let keywords = info.keywords;
762
- if (symbolKinds === "own_attribute" ||
763
- symbolKinds === "guard_attribute_method" ||
764
- symbolKinds === "trace_attribute_method" ||
765
- symbolKinds === "sorted_attribute") {
766
- keywords = keywords.filter((kw) => !CONSTRAINT_BLOCKLIST.has(kw));
767
- }
768
- for (const kw of keywords) {
950
+ // Every specialized / curated scope has early-returned above. Everything
951
+ // else falls through to the raw-lookahead path — it is the ONLY place in
952
+ // this builder where `info.keywords` (LookaheadIterator output) reaches
953
+ // the user. See `buildLookaheadFallbackItems` for the contract.
954
+ return buildLookaheadFallbackItems(info, symbolKinds, symbolIndex, reachableFiles);
955
+ }
956
+ /**
957
+ * The raw-lookahead fallback path for `buildSemanticCompletionItems`.
958
+ *
959
+ * After topic 049 phase 2 this is reached only when `symbolKinds` is an
960
+ * array (`SymbolKind[]`) — every scalar scope has its own early-return
961
+ * branch above. This is the ONLY place in the builder where raw
962
+ * `info.keywords` (LookaheadIterator output) is surfaced to the user, and
963
+ * it is now array-only: no scalar scope surfaces raw lookahead anywhere.
964
+ *
965
+ * If you add a new scalar scope, route it via an early-return in
966
+ * `buildSemanticCompletionItems`, not through this path.
967
+ */
968
+ function buildLookaheadFallbackItems(info, symbolKinds, symbolIndex, reachableFiles) {
969
+ const items = [];
970
+ const seen = new Set();
971
+ // 1. Keywords from LookaheadIterator.
972
+ for (const kw of info.keywords) {
769
973
  if (!seen.has(kw)) {
770
974
  seen.add(kw);
771
975
  items.push({ label: kw, kind: node_1.CompletionItemKind.Keyword });
772
976
  }
773
977
  }
774
- // 2. Operators (skip in guard/constraint/trace contexts)
775
- if (symbolKinds !== "own_attribute" &&
776
- symbolKinds !== "guard_attribute_method" &&
777
- symbolKinds !== "trace_attribute_method" &&
778
- symbolKinds !== "sorted_attribute") {
779
- for (const op of info.operators) {
780
- if (!seen.has(op)) {
781
- seen.add(op);
782
- items.push({ label: op, kind: node_1.CompletionItemKind.Operator });
783
- }
978
+ // 2. Operators.
979
+ for (const op of info.operators) {
980
+ if (!seen.has(op)) {
981
+ seen.add(op);
982
+ items.push({ label: op, kind: node_1.CompletionItemKind.Operator });
784
983
  }
785
984
  }
786
- // 3. Built-in types (when in type-compatible scope)
985
+ // 3. Built-in types (when in type-compatible scope).
787
986
  if (Array.isArray(symbolKinds) &&
788
987
  symbolKinds.some((k) => ["class", "interface", "trait", "enum"].includes(k))) {
789
988
  for (const typ of keywords_1.BUILTIN_TYPES) {
@@ -797,62 +996,7 @@ function buildSemanticCompletionItems(info, symbolKinds, symbolIndex, reachableF
797
996
  }
798
997
  }
799
998
  }
800
- // 4. Constraint scope: only own attributes (Umple E28)
801
- if (symbolKinds === "own_attribute" && info.enclosingClass) {
802
- const symbols = symbolIndex
803
- .getSymbols({ container: info.enclosingClass, kind: "attribute" })
804
- .filter((s) => reachableFiles.has(path.normalize(s.file)));
805
- for (const sym of symbols) {
806
- if (!seen.has(sym.name)) {
807
- seen.add(sym.name);
808
- items.push({
809
- label: sym.name,
810
- kind: symbolKindToCompletionKind("attribute"),
811
- detail: "attribute",
812
- });
813
- }
814
- }
815
- return items;
816
- }
817
- // 5. Sorted key scope: attributes of the owner class (with inheritance)
818
- if (symbolKinds === "sorted_attribute" && info.sortedKeyOwner) {
819
- const symbols = symbolIndex
820
- .getSymbols({ container: info.sortedKeyOwner, kind: "attribute", inherited: true })
821
- .filter((s) => reachableFiles.has(path.normalize(s.file)));
822
- for (const sym of symbols) {
823
- if (!seen.has(sym.name)) {
824
- seen.add(sym.name);
825
- items.push({
826
- label: sym.name,
827
- kind: symbolKindToCompletionKind("attribute"),
828
- detail: "attribute",
829
- });
830
- }
831
- }
832
- return items;
833
- }
834
- // 6. Guard/trace scope: attributes + methods from enclosing class (with inheritance)
835
- if ((symbolKinds === "guard_attribute_method" ||
836
- symbolKinds === "trace_attribute_method") &&
837
- info.enclosingClass) {
838
- for (const kind of ["attribute", "method"]) {
839
- const symbols = symbolIndex
840
- .getSymbols({ container: info.enclosingClass, kind, inherited: true })
841
- .filter((s) => reachableFiles.has(path.normalize(s.file)));
842
- for (const sym of symbols) {
843
- if (!seen.has(sym.name)) {
844
- seen.add(sym.name);
845
- items.push({
846
- label: sym.name,
847
- kind: symbolKindToCompletionKind(kind),
848
- detail: kind,
849
- });
850
- }
851
- }
852
- }
853
- return items;
854
- }
855
- // 6. Symbol completions from index (scoped to reachable files)
999
+ // 4. Symbol completions from index (scoped to reachable files).
856
1000
  if (Array.isArray(symbolKinds)) {
857
1001
  for (const symKind of symbolKinds) {
858
1002
  let symbols;