@nudojs/lsp 0.3.0 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/server.js CHANGED
@@ -73,7 +73,7 @@ var require_ms = __commonJS({
73
73
  options = options || {};
74
74
  var type = typeof val;
75
75
  if (type === "string" && val.length > 0) {
76
- return parse3(val);
76
+ return parse4(val);
77
77
  } else if (type === "number" && isFinite(val)) {
78
78
  return options.long ? fmtLong(val) : fmtShort(val);
79
79
  }
@@ -81,7 +81,7 @@ var require_ms = __commonJS({
81
81
  "val is not a non-empty string or a valid number. val=" + JSON.stringify(val)
82
82
  );
83
83
  };
84
- function parse3(str) {
84
+ function parse4(str) {
85
85
  str = String(str);
86
86
  if (str.length > 100) {
87
87
  return;
@@ -17328,7 +17328,7 @@ var require_resolve_uri_umd = __commonJS({
17328
17328
  }
17329
17329
  url.path = path;
17330
17330
  }
17331
- function resolve2(input, base) {
17331
+ function resolve3(input, base) {
17332
17332
  if (!input && !base)
17333
17333
  return "";
17334
17334
  const url = parseUrl(input);
@@ -17381,7 +17381,7 @@ var require_resolve_uri_umd = __commonJS({
17381
17381
  return url.scheme + "//" + url.user + url.host + url.port + url.path + queryHash;
17382
17382
  }
17383
17383
  }
17384
- return resolve2;
17384
+ return resolve3;
17385
17385
  }));
17386
17386
  }
17387
17387
  });
@@ -17600,11 +17600,11 @@ var require_trace_mapping_umd = __commonJS({
17600
17600
  state.lastNeedle = needle;
17601
17601
  return state.lastIndex = binarySearch(haystack, needle, low, high);
17602
17602
  }
17603
- function parse3(map) {
17603
+ function parse4(map) {
17604
17604
  return typeof map === "string" ? JSON.parse(map) : map;
17605
17605
  }
17606
17606
  var FlattenMap = function(map, mapUrl) {
17607
- const parsed = parse3(map);
17607
+ const parsed = parse4(map);
17608
17608
  if (!("sections" in parsed)) {
17609
17609
  return new TraceMap(parsed, mapUrl);
17610
17610
  }
@@ -17668,7 +17668,7 @@ var require_trace_mapping_umd = __commonJS({
17668
17668
  }
17669
17669
  }
17670
17670
  function addSection(input, mapUrl, mappings, sources, sourcesContent, names, ignoreList, lineOffset, columnOffset, stopLine, stopColumn) {
17671
- const parsed = parse3(input);
17671
+ const parsed = parse4(input);
17672
17672
  if ("sections" in parsed) return recurse(...arguments);
17673
17673
  const map = new TraceMap(parsed, mapUrl);
17674
17674
  const sourcesOffset = sources.length;
@@ -17718,7 +17718,7 @@ var require_trace_mapping_umd = __commonJS({
17718
17718
  constructor(map, mapUrl) {
17719
17719
  const isString = typeof map === "string";
17720
17720
  if (!isString && map._decodedMemo) return map;
17721
- const parsed = parse3(map);
17721
+ const parsed = parse4(map);
17722
17722
  const { version, file, names, sourceRoot, sources, sourcesContent } = parsed;
17723
17723
  this.version = version;
17724
17724
  this.file = file;
@@ -17727,8 +17727,8 @@ var require_trace_mapping_umd = __commonJS({
17727
17727
  this.sources = sources;
17728
17728
  this.sourcesContent = sourcesContent;
17729
17729
  this.ignoreList = parsed.ignoreList || parsed.x_google_ignoreList || void 0;
17730
- const resolve2 = resolver(mapUrl, sourceRoot);
17731
- this.resolvedSources = sources.map(resolve2);
17730
+ const resolve3 = resolver(mapUrl, sourceRoot);
17731
+ this.resolvedSources = sources.map(resolve3);
17732
17732
  const { mappings } = parsed;
17733
17733
  if (typeof mappings === "string") {
17734
17734
  this._encoded = mappings;
@@ -28350,14 +28350,14 @@ var require_lib6 = __commonJS({
28350
28350
  super.checkParams(node, false, true);
28351
28351
  this.scope.exit();
28352
28352
  }
28353
- forwardNoArrowParamsConversionAt(node, parse4) {
28353
+ forwardNoArrowParamsConversionAt(node, parse5) {
28354
28354
  let result;
28355
28355
  if (this.state.noArrowParamsConversionAt.includes(this.offsetToSourcePos(node.start))) {
28356
28356
  this.state.noArrowParamsConversionAt.push(this.state.start);
28357
- result = parse4();
28357
+ result = parse5();
28358
28358
  this.state.noArrowParamsConversionAt.pop();
28359
28359
  } else {
28360
- result = parse4();
28360
+ result = parse5();
28361
28361
  }
28362
28362
  return result;
28363
28363
  }
@@ -39752,7 +39752,7 @@ var require_lib6 = __commonJS({
39752
39752
  return result;
39753
39753
  }
39754
39754
  };
39755
- function parse3(input, options) {
39755
+ function parse4(input, options) {
39756
39756
  var _options;
39757
39757
  if (((_options = options) == null ? void 0 : _options.sourceType) === "unambiguous") {
39758
39758
  options = Object.assign({}, options);
@@ -39839,7 +39839,7 @@ var require_lib6 = __commonJS({
39839
39839
  }
39840
39840
  return cls;
39841
39841
  }
39842
- exports.parse = parse3;
39842
+ exports.parse = parse4;
39843
39843
  exports.parseExpression = parseExpression;
39844
39844
  exports.tokTypes = tokTypes;
39845
39845
  }
@@ -41773,7 +41773,7 @@ var require_introspection = __commonJS({
41773
41773
  exports.isStatic = isStatic;
41774
41774
  exports.matchesPattern = matchesPattern;
41775
41775
  exports.referencesImport = referencesImport;
41776
- exports.resolve = resolve2;
41776
+ exports.resolve = resolve3;
41777
41777
  exports.willIMaybeExecuteBefore = willIMaybeExecuteBefore;
41778
41778
  var _t = require_lib3();
41779
41779
  var {
@@ -42031,7 +42031,7 @@ var require_introspection = __commonJS({
42031
42031
  nodeMap.set(target.node, result);
42032
42032
  return result;
42033
42033
  }
42034
- function resolve2(dangerous, resolved) {
42034
+ function resolve3(dangerous, resolved) {
42035
42035
  return _resolve.call(this, dangerous, resolved) || this;
42036
42036
  }
42037
42037
  function _resolve(dangerous, resolved) {
@@ -43396,29 +43396,37 @@ import {
43396
43396
  InlayHintKind
43397
43397
  } from "vscode-languageserver/node";
43398
43398
  import { TextDocument } from "vscode-languageserver-textdocument";
43399
- import { typeValueToString as typeValueToString2 } from "@nudojs/core";
43399
+ import { readFileSync as readFileSync3 } from "fs";
43400
43400
  import {
43401
43401
  getTypeAtPosition,
43402
+ getHoverAtPosition as getHoverAtPosition2,
43402
43403
  getCompletionsAtPosition,
43403
- getCasesForFile
43404
+ getCasesForFile,
43405
+ buildSemanticTokens,
43406
+ isNudoTargetPath,
43407
+ collectAbsInlays as collectAbsInlays2
43404
43408
  } from "@nudojs/service";
43405
- import { parse as parse2 } from "@nudojs/parser";
43409
+ import { parse as parse3 } from "@nudojs/parser";
43406
43410
 
43407
43411
  // src/symbols.ts
43408
43412
  var import_traverse = __toESM(require_lib8(), 1);
43413
+ import { parse } from "@nudojs/parser";
43414
+ import { existsSync, readFileSync, readdirSync, statSync } from "fs";
43415
+ import { dirname, join, resolve } from "path";
43409
43416
  function locFromNode(node) {
43410
43417
  return {
43411
43418
  start: { line: node.loc?.start.line ?? 1, column: node.loc?.start.column ?? 0 },
43412
43419
  end: { line: node.loc?.end.line ?? 1, column: node.loc?.end.column ?? 0 }
43413
43420
  };
43414
43421
  }
43422
+ function traverseFn() {
43423
+ return typeof import_traverse.default === "function" ? import_traverse.default : import_traverse.default.default;
43424
+ }
43415
43425
  function buildSymbolTable(ast, uri) {
43416
43426
  const definitions = /* @__PURE__ */ new Map();
43417
43427
  const references = [];
43418
- const traverseFn = typeof import_traverse.default === "function" ? import_traverse.default : import_traverse.default.default;
43419
43428
  try {
43420
- traverseFn(ast, {
43421
- // Collect definitions
43429
+ traverseFn()(ast, {
43422
43430
  FunctionDeclaration(path) {
43423
43431
  if (path.node.id) {
43424
43432
  definitions.set(path.node.id.name, {
@@ -43449,7 +43457,6 @@ function buildSymbolTable(ast, uri) {
43449
43457
  });
43450
43458
  }
43451
43459
  },
43452
- // Collect references
43453
43460
  Identifier(path) {
43454
43461
  if (path.parentPath?.node.type === "FunctionDeclaration" && path.parentPath.node.id === path.node) return;
43455
43462
  if (path.parentPath?.node.type === "VariableDeclarator" && path.parentPath.node.id === path.node) return;
@@ -43473,9 +43480,8 @@ function findReferences(symbolTable, name) {
43473
43480
  }
43474
43481
  function findIdentifierAtPosition(ast, line, column) {
43475
43482
  let found = null;
43476
- const traverseFn = typeof import_traverse.default === "function" ? import_traverse.default : import_traverse.default.default;
43477
43483
  try {
43478
- traverseFn(ast, {
43484
+ traverseFn()(ast, {
43479
43485
  Identifier(path) {
43480
43486
  const loc = path.node.loc;
43481
43487
  if (!loc) return;
@@ -43489,34 +43495,378 @@ function findIdentifierAtPosition(ast, line, column) {
43489
43495
  }
43490
43496
  return found;
43491
43497
  }
43498
+ var SymbolKind = {
43499
+ Function: 12,
43500
+ Variable: 13,
43501
+ Class: 5,
43502
+ Method: 6,
43503
+ Property: 7,
43504
+ Constant: 14
43505
+ };
43506
+ function rangeOfLoc(loc) {
43507
+ return {
43508
+ start: { line: loc.start.line - 1, character: loc.start.column },
43509
+ end: { line: loc.end.line - 1, character: loc.end.column }
43510
+ };
43511
+ }
43512
+ function rangeOfNode(node) {
43513
+ return rangeOfLoc(locFromNode(node));
43514
+ }
43515
+ function documentSymbols(ast) {
43516
+ const out = [];
43517
+ const program = ast.program ?? ast;
43518
+ const body = program.body ?? [];
43519
+ const pushFn = (fnNode, idNode, exported) => {
43520
+ if (!idNode?.name) return;
43521
+ const range = rangeOfNode(fnNode);
43522
+ const selection = rangeOfNode(idNode);
43523
+ const children = [];
43524
+ const params = (fnNode.params ?? []).map((p) => p?.name ?? (p?.type === "RestElement" ? `...${p.argument?.name}` : "_")).filter(Boolean);
43525
+ out.push({
43526
+ name: idNode.name,
43527
+ detail: `(${params.join(", ")})${exported ? " export" : ""}`,
43528
+ kind: SymbolKind.Function,
43529
+ range,
43530
+ selectionRange: selection,
43531
+ children: children.length ? children : void 0
43532
+ });
43533
+ };
43534
+ for (const stmt of body) {
43535
+ let decl = stmt;
43536
+ let exported = false;
43537
+ if (stmt.type === "ExportNamedDeclaration" && stmt.declaration) {
43538
+ decl = stmt.declaration;
43539
+ exported = true;
43540
+ } else if (stmt.type === "ExportDefaultDeclaration") {
43541
+ decl = stmt.declaration;
43542
+ exported = true;
43543
+ }
43544
+ if (decl?.type === "FunctionDeclaration") {
43545
+ pushFn(decl, decl.id, exported);
43546
+ continue;
43547
+ }
43548
+ if (decl?.type === "ClassDeclaration" && decl.id) {
43549
+ out.push({
43550
+ name: decl.id.name,
43551
+ detail: exported ? "export class" : "class",
43552
+ kind: SymbolKind.Class,
43553
+ range: rangeOfNode(decl),
43554
+ selectionRange: rangeOfNode(decl.id),
43555
+ children: (decl.body?.body ?? []).flatMap((m) => {
43556
+ const key = m.key?.name ?? m.key?.value;
43557
+ if (!key || m.type !== "ClassMethod" && m.type !== "ClassProperty") return [];
43558
+ return [{
43559
+ name: String(key),
43560
+ kind: m.type === "ClassMethod" ? SymbolKind.Method : SymbolKind.Property,
43561
+ range: rangeOfNode(m),
43562
+ selectionRange: rangeOfNode(m.key)
43563
+ }];
43564
+ })
43565
+ });
43566
+ continue;
43567
+ }
43568
+ if (decl?.type === "VariableDeclaration") {
43569
+ for (const d of decl.declarations ?? []) {
43570
+ if (d.id?.type !== "Identifier") continue;
43571
+ const init = d.init;
43572
+ const isFn = init && (init.type === "ArrowFunctionExpression" || init.type === "FunctionExpression");
43573
+ out.push({
43574
+ name: d.id.name,
43575
+ detail: exported ? "export const" : "const",
43576
+ kind: isFn ? SymbolKind.Function : SymbolKind.Variable,
43577
+ range: rangeOfNode(d),
43578
+ selectionRange: rangeOfNode(d.id)
43579
+ });
43580
+ }
43581
+ }
43582
+ }
43583
+ return out;
43584
+ }
43585
+ function collectImportBindings(ast, fromFile) {
43586
+ const map = /* @__PURE__ */ new Map();
43587
+ const program = ast.program ?? ast;
43588
+ for (const stmt of program.body ?? []) {
43589
+ if (stmt.type !== "ImportDeclaration") continue;
43590
+ const spec = stmt.source.value;
43591
+ const resolvedPath = resolveRelativeModule(spec, fromFile);
43592
+ for (const specNode of stmt.specifiers ?? []) {
43593
+ if (specNode.type === "ImportSpecifier") {
43594
+ const imported = specNode.imported.type === "Identifier" ? specNode.imported.name : String(specNode.imported.value);
43595
+ map.set(specNode.local.name, { spec, importedName: imported, resolvedPath });
43596
+ } else if (specNode.type === "ImportDefaultSpecifier") {
43597
+ map.set(specNode.local.name, { spec, importedName: "default", resolvedPath });
43598
+ } else if (specNode.type === "ImportNamespaceSpecifier") {
43599
+ map.set(specNode.local.name, { spec, importedName: "*", resolvedPath });
43600
+ }
43601
+ }
43602
+ }
43603
+ return map;
43604
+ }
43605
+ function resolveRelativeModule(spec, fromFile) {
43606
+ if (!spec.startsWith(".") && !spec.startsWith("/")) return void 0;
43607
+ const base = resolve(dirname(fromFile), spec);
43608
+ for (const cand of [
43609
+ base,
43610
+ `${base}.js`,
43611
+ `${base}.cjs`,
43612
+ `${base}.mjs`,
43613
+ `${base}.ts`,
43614
+ `${base}.mts`,
43615
+ `${base}.cts`,
43616
+ join(base, "index.js"),
43617
+ join(base, "index.mjs"),
43618
+ join(base, "index.ts")
43619
+ ]) {
43620
+ try {
43621
+ if (existsSync(cand) && statSync(cand).isFile()) return cand;
43622
+ } catch {
43623
+ }
43624
+ }
43625
+ return void 0;
43626
+ }
43627
+ function readSourceIfExists(filePath) {
43628
+ try {
43629
+ return readFileSync(filePath, "utf-8");
43630
+ } catch {
43631
+ return void 0;
43632
+ }
43633
+ }
43634
+ function findExportDefinition(targetSource, exportName) {
43635
+ let ast;
43636
+ try {
43637
+ ast = parse(targetSource);
43638
+ } catch {
43639
+ return null;
43640
+ }
43641
+ const program = ast.program ?? ast;
43642
+ for (const stmt of program.body ?? []) {
43643
+ let decl = stmt;
43644
+ if (stmt.type === "ExportNamedDeclaration" && stmt.declaration) {
43645
+ decl = stmt.declaration;
43646
+ } else if (stmt.type === "ExportDefaultDeclaration") {
43647
+ decl = stmt.declaration;
43648
+ if (exportName === "default") {
43649
+ if (decl?.type === "FunctionDeclaration" && decl.id) {
43650
+ return { name: decl.id.name, kind: "function", loc: locFromNode(decl.id) };
43651
+ }
43652
+ if (decl?.type === "ClassDeclaration" && decl.id) {
43653
+ return { name: decl.id.name, kind: "class", loc: locFromNode(decl.id) };
43654
+ }
43655
+ if (decl?.type === "Identifier") {
43656
+ return { name: decl.name, kind: "variable", loc: locFromNode(decl) };
43657
+ }
43658
+ return null;
43659
+ }
43660
+ continue;
43661
+ }
43662
+ if (decl?.type === "FunctionDeclaration" && decl.id?.name === exportName) {
43663
+ return { name: decl.id.name, kind: "function", loc: locFromNode(decl.id) };
43664
+ }
43665
+ if (decl?.type === "ClassDeclaration" && decl.id?.name === exportName) {
43666
+ return { name: decl.id.name, kind: "class", loc: locFromNode(decl.id) };
43667
+ }
43668
+ if (decl?.type === "VariableDeclaration") {
43669
+ for (const d of decl.declarations ?? []) {
43670
+ if (d.id?.type === "Identifier" && d.id.name === exportName) {
43671
+ return { name: d.id.name, kind: "variable", loc: locFromNode(d.id) };
43672
+ }
43673
+ }
43674
+ }
43675
+ if (stmt.type === "ExportNamedDeclaration" && !stmt.declaration && stmt.specifiers) {
43676
+ for (const s of stmt.specifiers) {
43677
+ if (s.type !== "ExportSpecifier") continue;
43678
+ const exported = s.exported.type === "Identifier" ? s.exported.name : String(s.exported.value);
43679
+ if (exported !== exportName) continue;
43680
+ const local = s.local.type === "Identifier" ? s.local.name : String(s.local.value);
43681
+ const table = buildSymbolTable(ast, "");
43682
+ const def = table.definitions.get(local);
43683
+ if (def) return { name: local, kind: def.kind, loc: def.loc };
43684
+ }
43685
+ }
43686
+ }
43687
+ return null;
43688
+ }
43689
+ function findCrossFileDefinition(fromFile, source, ident) {
43690
+ let ast;
43691
+ try {
43692
+ ast = parse(source);
43693
+ } catch {
43694
+ return null;
43695
+ }
43696
+ const bindings = collectImportBindings(ast, fromFile);
43697
+ const binding = bindings.get(ident);
43698
+ if (!binding?.resolvedPath) return null;
43699
+ if (binding.importedName === "*") return null;
43700
+ const targetSource = readSourceIfExists(binding.resolvedPath);
43701
+ if (targetSource === void 0) return null;
43702
+ const def = findExportDefinition(targetSource, binding.importedName);
43703
+ if (!def) return null;
43704
+ return { filePath: binding.resolvedPath, loc: def.loc, name: def.name };
43705
+ }
43706
+ function findCrossFileReferences(definingFile, exportName, options = {}) {
43707
+ const out = [];
43708
+ const root = options.rootDir ?? dirname(definingFile);
43709
+ const maxFiles = options.maxFiles ?? 200;
43710
+ const candidates = new Set(options.extraFiles ?? []);
43711
+ try {
43712
+ collectJsFiles(root, candidates, maxFiles, 0);
43713
+ } catch {
43714
+ }
43715
+ const definingNorm = resolve(definingFile);
43716
+ for (const cand of candidates) {
43717
+ if (resolve(cand) === definingNorm) continue;
43718
+ const src = readSourceIfExists(cand);
43719
+ if (src === void 0) continue;
43720
+ let ast;
43721
+ try {
43722
+ ast = parse(src);
43723
+ } catch {
43724
+ continue;
43725
+ }
43726
+ const bindings = collectImportBindings(ast, cand);
43727
+ for (const [local, b] of bindings) {
43728
+ if (!b.resolvedPath || resolve(b.resolvedPath) !== definingNorm) continue;
43729
+ if (b.importedName !== exportName && b.importedName !== "*") continue;
43730
+ const table = buildSymbolTable(ast, cand);
43731
+ for (const ref of table.references) {
43732
+ if (ref.name !== local) continue;
43733
+ out.push({ name: local, loc: ref.loc, uri: cand });
43734
+ }
43735
+ }
43736
+ }
43737
+ return out;
43738
+ }
43739
+ function collectJsFiles(dir, out, max, depth) {
43740
+ if (out.size >= max || depth > 6) return;
43741
+ let entries;
43742
+ try {
43743
+ entries = readdirSync(dir);
43744
+ } catch {
43745
+ return;
43746
+ }
43747
+ for (const name of entries) {
43748
+ if (name === "node_modules" || name === ".git" || name === "dist") continue;
43749
+ const full = join(dir, name);
43750
+ try {
43751
+ const st = statSync(full);
43752
+ if (st.isDirectory()) {
43753
+ collectJsFiles(full, out, max, depth + 1);
43754
+ } else if (/\.(js|mjs|cjs|ts|mts)$/.test(name) && !/\.d\.ts$/.test(name)) {
43755
+ out.add(full);
43756
+ }
43757
+ } catch {
43758
+ }
43759
+ }
43760
+ }
43761
+ function resolveDefinition(fromFile, source, ident) {
43762
+ const ast = parse(source);
43763
+ const table = buildSymbolTable(ast, fromFile);
43764
+ const local = findDefinition(table, ident);
43765
+ if (local) return { filePath: fromFile, loc: local.loc, name: local.name };
43766
+ return findCrossFileDefinition(fromFile, source, ident);
43767
+ }
43768
+ function resolveReferences(fromFile, source, ident, options = {}) {
43769
+ const includeDecl = options.includeDeclaration !== false;
43770
+ const ast = parse(source);
43771
+ const table = buildSymbolTable(ast, fromFile);
43772
+ const localRefs = findReferences(table, ident);
43773
+ const localDef = findDefinition(table, ident);
43774
+ if (localDef) {
43775
+ const exportName = localDef.name;
43776
+ const isExported = isExportedName(ast, exportName);
43777
+ const cross2 = isExported ? findCrossFileReferences(fromFile, exportName, { extraFiles: options.extraFiles }) : [];
43778
+ const decl = includeDecl ? [{ name: localDef.name, loc: localDef.loc, uri: fromFile }] : [];
43779
+ return [...decl, ...localRefs, ...cross2];
43780
+ }
43781
+ const crossDef = findCrossFileDefinition(fromFile, source, ident);
43782
+ if (!crossDef) return localRefs;
43783
+ const defSource = readSourceIfExists(crossDef.filePath) ?? "";
43784
+ let defAst = null;
43785
+ try {
43786
+ defAst = parse(defSource);
43787
+ } catch {
43788
+ defAst = null;
43789
+ }
43790
+ const defLocalRefs = [];
43791
+ let defDecl = [];
43792
+ if (defAst) {
43793
+ const defTable = buildSymbolTable(defAst, crossDef.filePath);
43794
+ defLocalRefs.push(...findReferences(defTable, crossDef.name));
43795
+ const d = findDefinition(defTable, crossDef.name);
43796
+ if (d && includeDecl) {
43797
+ defDecl = [{ name: d.name, loc: d.loc, uri: crossDef.filePath }];
43798
+ }
43799
+ }
43800
+ const cross = findCrossFileReferences(crossDef.filePath, crossDef.name, {
43801
+ extraFiles: [...options.extraFiles ?? [], fromFile]
43802
+ });
43803
+ const seen = /* @__PURE__ */ new Set();
43804
+ const all = [];
43805
+ for (const r of [...defDecl, ...defLocalRefs, ...localRefs, ...cross]) {
43806
+ const key = `${r.uri ?? ""}:${r.loc.start.line}:${r.loc.start.column}`;
43807
+ if (seen.has(key)) continue;
43808
+ seen.add(key);
43809
+ all.push(r);
43810
+ }
43811
+ return all;
43812
+ }
43813
+ function isExportedName(ast, name) {
43814
+ const program = ast.program ?? ast;
43815
+ for (const stmt of program.body ?? []) {
43816
+ if (stmt.type === "ExportNamedDeclaration") {
43817
+ if (stmt.declaration) {
43818
+ const d = stmt.declaration;
43819
+ if (d.type === "FunctionDeclaration" && d.id?.name === name) return true;
43820
+ if (d.type === "ClassDeclaration" && d.id?.name === name) return true;
43821
+ if (d.type === "VariableDeclaration") {
43822
+ for (const dec of d.declarations ?? []) {
43823
+ if (dec.id?.type === "Identifier" && dec.id.name === name) return true;
43824
+ }
43825
+ }
43826
+ }
43827
+ for (const s of stmt.specifiers ?? []) {
43828
+ if (s.type !== "ExportSpecifier") continue;
43829
+ const exported = s.exported.type === "Identifier" ? s.exported.name : String(s.exported.value);
43830
+ if (exported === name) return true;
43831
+ }
43832
+ }
43833
+ if (stmt.type === "ExportDefaultDeclaration") {
43834
+ const d = stmt.declaration;
43835
+ if (d?.id?.name === name) return true;
43836
+ if (d?.type === "Identifier" && d.name === name) return true;
43837
+ }
43838
+ }
43839
+ return false;
43840
+ }
43492
43841
 
43493
43842
  // src/semantic-tokens.ts
43494
- var TOKEN_TYPES = [
43495
- "function",
43496
- "variable",
43497
- "parameter",
43498
- "property",
43499
- "type",
43500
- "keyword",
43501
- "string",
43502
- "number",
43503
- "comment",
43504
- "decorator"
43505
- ];
43506
- var TOKEN_MODIFIERS = [
43507
- "declaration",
43508
- "readonly",
43509
- "deprecated",
43510
- "unreachable"
43511
- ];
43843
+ import {
43844
+ SEMANTIC_TOKEN_TYPES,
43845
+ SEMANTIC_TOKEN_MODIFIERS,
43846
+ encodeSemanticTokens
43847
+ } from "@nudojs/service";
43512
43848
 
43513
43849
  // src/validation.ts
43850
+ import { dirname as dirname2, resolve as resolvePath } from "path";
43514
43851
  import {
43515
43852
  analyzeFile,
43516
43853
  analyzeFileAsync,
43517
43854
  buildModuleGraph,
43518
- computeDirtySet
43855
+ computeDirtySet,
43856
+ defaultLoadModule,
43857
+ clearAnalysisSessionCaches,
43858
+ evictAbsModuleCacheFiles,
43859
+ evictBPathCacheForFiles,
43860
+ evictAnalysisFileCacheForFiles,
43861
+ evictFnAnalysisCacheForFiles
43519
43862
  } from "@nudojs/service";
43863
+ import {
43864
+ checkSource,
43865
+ pTrue,
43866
+ evictGeneralizeMemoForPaths,
43867
+ evictCheckSourceMemoForPaths,
43868
+ extractNudoImports
43869
+ } from "@nudojs/core";
43520
43870
  import {
43521
43871
  DiagnosticSeverity,
43522
43872
  DiagnosticTag
@@ -43524,15 +43874,65 @@ import {
43524
43874
  var analysisCache = /* @__PURE__ */ new Map();
43525
43875
  var knownFiles = /* @__PURE__ */ new Set();
43526
43876
  var moduleGraphCache = /* @__PURE__ */ new Map();
43877
+ var nudoDepParents = /* @__PURE__ */ new Map();
43878
+ function normPath(p) {
43879
+ return p.replace(/\\/g, "/");
43880
+ }
43881
+ function registerNudoImportDeps(filePath, source) {
43882
+ const parent = normPath(resolvePath(filePath));
43883
+ for (const set of nudoDepParents.values()) {
43884
+ set.delete(parent);
43885
+ }
43886
+ const imports = extractNudoImports(source);
43887
+ for (const imp of imports) {
43888
+ if (!imp.spec.startsWith(".") && !imp.spec.startsWith("/")) continue;
43889
+ const dep = normPath(resolvePath(dirname2(filePath), imp.spec));
43890
+ let set = nudoDepParents.get(dep);
43891
+ if (!set) {
43892
+ set = /* @__PURE__ */ new Set();
43893
+ nudoDepParents.set(dep, set);
43894
+ }
43895
+ set.add(parent);
43896
+ }
43897
+ }
43898
+ async function handleNudoDepFileChanged(nudoPath, deps) {
43899
+ const p = normPath(resolvePath(nudoPath));
43900
+ evictGeneralizeMemoForPaths([p]);
43901
+ evictCheckSourceMemoForPaths([p]);
43902
+ const parents = nudoDepParents.get(p);
43903
+ if (!parents || parents.size === 0) return;
43904
+ const parentList = [...parents];
43905
+ evictBPathCacheForFiles(parentList);
43906
+ evictAnalysisFileCacheForFiles(parentList);
43907
+ evictFnAnalysisCacheForFiles(parentList);
43908
+ for (const parent of parentList) {
43909
+ const doc = deps.getOpenDocumentByPath?.(parent);
43910
+ if (!doc) continue;
43911
+ await validateText(
43912
+ parent,
43913
+ doc.uri,
43914
+ doc.getText(),
43915
+ doc.version,
43916
+ deps,
43917
+ false
43918
+ );
43919
+ }
43920
+ }
43527
43921
  function forgetValidatedFile(filePath) {
43922
+ const parent = normPath(resolvePath(filePath));
43528
43923
  knownFiles.delete(filePath);
43529
43924
  analysisCache.delete(filePath);
43925
+ for (const set of nudoDepParents.values()) {
43926
+ set.delete(parent);
43927
+ }
43530
43928
  }
43531
43929
  function evictModuleGraphCacheEntries(uris) {
43532
- for (const uri of uris) moduleGraphCache.delete(uriToFilePath(uri));
43930
+ const paths = uris.map(uriToFilePath);
43931
+ for (const p of paths) moduleGraphCache.delete(p);
43932
+ evictAbsModuleCacheFiles(paths);
43533
43933
  }
43534
43934
  function hasNudoDirectives(source) {
43535
- return /@nudo:(case|mock|pure|skip|sample|returns|env|mock-module|as|replace)\b/.test(source);
43935
+ return /@nudo:(case|mock|pure|skip|sample|refine|import|env|mock-module|as|replace)\b/.test(source);
43536
43936
  }
43537
43937
  function uriToFilePath(uri) {
43538
43938
  return uri.startsWith("file://") ? decodeURIComponent(uri.slice(7)) : uri;
@@ -43578,6 +43978,43 @@ function toLspDiagnostic(d, uri) {
43578
43978
  }
43579
43979
  return diag;
43580
43980
  }
43981
+ function lspLoadModule(spec, fromFile) {
43982
+ return defaultLoadModule(spec, fromFile);
43983
+ }
43984
+ function checkToLspDiagnostics(filePath, source, loadModule) {
43985
+ try {
43986
+ const report = checkSource(filePath, source, pTrue, {
43987
+ loadModule: loadModule ?? lspLoadModule,
43988
+ fromFile: filePath
43989
+ });
43990
+ return report.issues.filter((i) => i.severity === "error" || i.severity === "warning").map((i) => {
43991
+ const line = (i.line ?? 1) - 1;
43992
+ const col = i.column ?? 0;
43993
+ const parts = [i.message];
43994
+ if (i.actual) parts.push(`actual: ${i.actual}`);
43995
+ if (i.expected) parts.push(`expected: ${i.expected}`);
43996
+ if (i.suggestion) parts.push(i.suggestion);
43997
+ return {
43998
+ severity: i.severity === "error" ? DiagnosticSeverity.Error : i.severity === "warning" ? DiagnosticSeverity.Warning : DiagnosticSeverity.Information,
43999
+ range: {
44000
+ start: { line, character: col },
44001
+ end: { line, character: col + 1 }
44002
+ },
44003
+ message: parts.join(" \xB7 "),
44004
+ source: "nudo-check",
44005
+ code: i.code,
44006
+ data: {
44007
+ actual: i.actual,
44008
+ expected: i.expected,
44009
+ fn: i.fn,
44010
+ suggestions: i.suggestion ? [i.suggestion] : []
44011
+ }
44012
+ };
44013
+ });
44014
+ } catch {
44015
+ return [];
44016
+ }
44017
+ }
43581
44018
  async function validateText(filePath, uri, text, version, deps, propagate = false) {
43582
44019
  if (deps.isNudoUri && !deps.isNudoUri(uri)) {
43583
44020
  deps.sendDiagnostics({ uri, diagnostics: [] });
@@ -43600,7 +44037,10 @@ async function validateText(filePath, uri, text, version, deps, propagate = fals
43600
44037
  }
43601
44038
  analysisCache.set(filePath, { version, result });
43602
44039
  knownFiles.add(filePath);
43603
- deps.sendDiagnostics({ uri, diagnostics: result.diagnostics.map((d) => toLspDiagnostic(d, uri)) });
44040
+ registerNudoImportDeps(filePath, text);
44041
+ const checkDiags = checkToLspDiagnostics(filePath, text);
44042
+ const evalDiags = result.diagnostics.map((d) => toLspDiagnostic(d, uri));
44043
+ deps.sendDiagnostics({ uri, diagnostics: [...checkDiags, ...evalDiags] });
43604
44044
  if (!propagate || !deps.getOpenDocumentByPath) return;
43605
44045
  let dependents;
43606
44046
  try {
@@ -43612,22 +44052,31 @@ async function validateText(filePath, uri, text, version, deps, propagate = fals
43612
44052
  if (dirtyPath === filePath) continue;
43613
44053
  const doc = deps.getOpenDocumentByPath(dirtyPath);
43614
44054
  if (!doc) continue;
44055
+ evictBPathCacheForFiles([dirtyPath]);
44056
+ evictAnalysisFileCacheForFiles([dirtyPath]);
44057
+ evictFnAnalysisCacheForFiles([dirtyPath]);
43615
44058
  await validateText(dirtyPath, doc.uri, doc.getText(), doc.version, deps, false);
43616
44059
  }
43617
44060
  }
43618
44061
 
43619
44062
  // src/agent-tools.ts
43620
- import { readFileSync } from "fs";
43621
- import { resolve } from "path";
43622
- import { analyzeFile as analyzeFile2, buildCaseDirective } from "@nudojs/service";
43623
- import { parse } from "@nudojs/parser";
43624
- import { T, typeValueToString } from "@nudojs/core";
44063
+ import { readFileSync as readFileSync2 } from "fs";
44064
+ import { resolve as resolve2 } from "path";
44065
+ import {
44066
+ analyzeFile as analyzeFile2,
44067
+ buildCaseDirective,
44068
+ getHoverAtPosition,
44069
+ collectAbsInlays,
44070
+ serializeInferJson
44071
+ } from "@nudojs/service";
44072
+ import { parse as parse2 } from "@nudojs/parser";
44073
+ import { T, typeValueToString, checkSource as checkSource2, serializeCheckJson, pTrue as pTrue2 } from "@nudojs/core";
43625
44074
  function textResult(text) {
43626
44075
  return { content: [{ type: "text", text }] };
43627
44076
  }
43628
44077
  function normalizeFilePath(file) {
43629
44078
  const path = file.startsWith("file://") ? decodeURIComponent(file.slice(7)) : file;
43630
- return resolve(path);
44079
+ return resolve2(path);
43631
44080
  }
43632
44081
  var BARE_PRIMITIVES = /* @__PURE__ */ new Set(["number", "string", "boolean", "bigint", "symbol"]);
43633
44082
  function splitTopLevelUnion(expr) {
@@ -43692,7 +44141,7 @@ function injectBindings(source, bindings) {
43692
44141
  const applied = [];
43693
44142
  const unapplied = [];
43694
44143
  if (bindings.length === 0) return { source, applied, unapplied };
43695
- const ast = parse(source);
44144
+ const ast = parse2(source);
43696
44145
  const declLines = /* @__PURE__ */ new Map();
43697
44146
  for (const stmt of ast.program.body) {
43698
44147
  const names = /* @__PURE__ */ new Set();
@@ -43731,11 +44180,111 @@ function injectBindings(source, bindings) {
43731
44180
  function readSource(filePath, deps = {}) {
43732
44181
  const open = deps.getOpenText?.(filePath);
43733
44182
  if (open) return open.text;
43734
- return (deps.readFile ?? ((p) => readFileSync(p, "utf-8")))(filePath);
44183
+ return (deps.readFile ?? ((p) => readFileSync2(p, "utf-8")))(filePath);
43735
44184
  }
43736
44185
  function analysisError(err) {
43737
44186
  return textResult(`Error: ${err.message}`);
43738
44187
  }
44188
+ function checkTool(params, deps = {}) {
44189
+ try {
44190
+ const filePath = normalizeFilePath(params.file);
44191
+ const source = params.source ?? readSource(filePath, deps);
44192
+ const report = checkSource2(filePath, source, pTrue2, {
44193
+ loadModule: params.loadModule ?? lspLoadModule,
44194
+ fromFile: filePath
44195
+ });
44196
+ const json = serializeCheckJson(report);
44197
+ if (params.format === "json") {
44198
+ return textResult(JSON.stringify(json, null, 2));
44199
+ }
44200
+ const lines = [
44201
+ json.ok ? "nudo check OK" : "nudo check FAILED",
44202
+ `${json.summary.errors} error \xB7 ${json.summary.warnings} warning \xB7 ${json.summary.functions} fn`
44203
+ ];
44204
+ for (const i of json.issues) {
44205
+ if (i.severity === "info") continue;
44206
+ const loc = i.line != null ? ` L${i.line}` : "";
44207
+ lines.push(`[${i.severity}]${loc} ${i.message}`);
44208
+ if (i.actual) lines.push(` actual: ${i.actual}`);
44209
+ if (i.expected) lines.push(` expected: ${i.expected}`);
44210
+ }
44211
+ lines.push("");
44212
+ lines.push(JSON.stringify(json, null, 2));
44213
+ return textResult(lines.join("\n"));
44214
+ } catch (err) {
44215
+ return analysisError(err);
44216
+ }
44217
+ }
44218
+ function hoverTool(params, deps = {}) {
44219
+ try {
44220
+ const filePath = normalizeFilePath(params.file);
44221
+ const source = params.source ?? readSource(filePath, deps);
44222
+ const hover = getHoverAtPosition(filePath, source, params.line, params.column);
44223
+ const payload = {
44224
+ file: filePath,
44225
+ line: params.line,
44226
+ column: params.column,
44227
+ abs: hover?.abs ?? null,
44228
+ absMultiline: hover?.absMultiline ?? null,
44229
+ intension: hover?.intension ?? null,
44230
+ /** 有损外延,仅对照 */
44231
+ ext: hover?.typeText ?? null
44232
+ };
44233
+ if (params.includeInlays) {
44234
+ payload.inlays = collectAbsInlays(source, {
44235
+ loadModule: params.loadModule ?? lspLoadModule,
44236
+ fromFile: filePath
44237
+ });
44238
+ }
44239
+ return textResult(JSON.stringify(payload, null, 2));
44240
+ } catch (err) {
44241
+ return analysisError(err);
44242
+ }
44243
+ }
44244
+ function inferTool(params, deps = {}) {
44245
+ try {
44246
+ const filePath = normalizeFilePath(params.file);
44247
+ const source = params.source ?? readSource(filePath, deps);
44248
+ const result = analyzeFile2(filePath, source);
44249
+ let json = serializeInferJson(result, filePath);
44250
+ if (params.functions && params.functions.length > 0) {
44251
+ const keep = new Set(params.functions);
44252
+ const filtered = json.functions.filter((f) => keep.has(f.name));
44253
+ let cases = 0;
44254
+ for (const f of filtered) {
44255
+ cases += f.cases?.length ?? 0;
44256
+ }
44257
+ const summary = {
44258
+ ...json.summary,
44259
+ functions: filtered.length,
44260
+ cases
44261
+ };
44262
+ json = { ...json, functions: filtered, summary };
44263
+ }
44264
+ if (params.format === "json") {
44265
+ return textResult(JSON.stringify(json, null, 2));
44266
+ }
44267
+ const lines = [
44268
+ `nudo infer ${json.file}`,
44269
+ `${json.summary.functions} fn \xB7 ${json.summary.cases} case \xB7 ${json.summary.diagnostics} diag`
44270
+ ];
44271
+ for (const f of json.functions) {
44272
+ lines.push("");
44273
+ lines.push(`${f.name}${f.entryOnly ? " [entry-only]" : ""}`);
44274
+ for (const c of f.cases) {
44275
+ const args = c.args.join(", ");
44276
+ lines.push(` ${c.name}: (${args}) => ${c.result}`);
44277
+ if (c.intension?.abs) lines.push(` abs: ${c.intension.abs}`);
44278
+ else if (c.intension?.display) lines.push(` intension: ${c.intension.display}`);
44279
+ }
44280
+ }
44281
+ lines.push("");
44282
+ lines.push(JSON.stringify(json, null, 2));
44283
+ return textResult(lines.join("\n"));
44284
+ } catch (err) {
44285
+ return analysisError(err);
44286
+ }
44287
+ }
43739
44288
  function whatIf(params, deps = {}) {
43740
44289
  try {
43741
44290
  const filePath = normalizeFilePath(params.file);
@@ -43820,6 +44369,9 @@ var NUDO_COMMANDS = [
43820
44369
  "nudo.whatIf",
43821
44370
  "nudo.suggestCase",
43822
44371
  "nudo.trace",
44372
+ "nudo.check",
44373
+ "nudo.hover",
44374
+ "nudo.infer",
43823
44375
  "nudo.selectCase",
43824
44376
  "nudo.getActiveCases"
43825
44377
  ];
@@ -43848,6 +44400,8 @@ connection.onInitialize((_params) => ({
43848
44400
  definitionProvider: true,
43849
44401
  referencesProvider: true,
43850
44402
  renameProvider: true,
44403
+ documentSymbolProvider: true,
44404
+ workspaceSymbolProvider: true,
43851
44405
  codeActionProvider: {
43852
44406
  codeActionKinds: ["quickfix"]
43853
44407
  },
@@ -43857,8 +44411,8 @@ connection.onInitialize((_params) => ({
43857
44411
  semanticTokensProvider: {
43858
44412
  full: true,
43859
44413
  legend: {
43860
- tokenTypes: [...TOKEN_TYPES],
43861
- tokenModifiers: [...TOKEN_MODIFIERS]
44414
+ tokenTypes: [...SEMANTIC_TOKEN_TYPES],
44415
+ tokenModifiers: [...SEMANTIC_TOKEN_MODIFIERS]
43862
44416
  }
43863
44417
  },
43864
44418
  executeCommandProvider: {
@@ -43906,16 +44460,27 @@ function registerWatchedFilesListener(listener) {
43906
44460
  if (idx >= 0) watchedFilesListeners.splice(idx, 1);
43907
44461
  };
43908
44462
  }
44463
+ function isNudoDepPath(filePath) {
44464
+ return /\.nudo\.(js|mjs|ts)$/.test(filePath);
44465
+ }
43909
44466
  function handleWatchedFilesChanges(changes, isOpen) {
43910
44467
  const gone = [];
44468
+ const nudoTouched = [];
43911
44469
  for (const change of changes) {
43912
- if (change.type !== FileChangeType.Deleted) continue;
43913
- if (isOpen(change.uri)) continue;
43914
- gone.push(change.uri);
43915
- forgetValidatedFile(uriToFilePath(change.uri));
43916
- activeCases.delete(change.uri);
43917
- nudoFileCache.delete(change.uri);
43918
- connection.sendDiagnostics({ uri: change.uri, diagnostics: [] });
44470
+ const filePath = uriToFilePath(change.uri);
44471
+ if (change.type === FileChangeType.Deleted) {
44472
+ if (isOpen(change.uri)) continue;
44473
+ gone.push(change.uri);
44474
+ forgetValidatedFile(filePath);
44475
+ activeCases.delete(change.uri);
44476
+ nudoFileCache.delete(change.uri);
44477
+ connection.sendDiagnostics({ uri: change.uri, diagnostics: [] });
44478
+ if (isNudoDepPath(filePath)) nudoTouched.push(filePath);
44479
+ continue;
44480
+ }
44481
+ if (isNudoDepPath(filePath)) {
44482
+ nudoTouched.push(filePath);
44483
+ }
43919
44484
  }
43920
44485
  if (gone.length > 0) {
43921
44486
  for (const listener of [...watchedFilesListeners]) {
@@ -43925,6 +44490,13 @@ function handleWatchedFilesChanges(changes, isOpen) {
43925
44490
  }
43926
44491
  }
43927
44492
  }
44493
+ if (nudoTouched.length > 0) {
44494
+ const deps = validationDeps();
44495
+ for (const p of nudoTouched) {
44496
+ void handleNudoDepFileChanged(p, deps).catch(() => {
44497
+ });
44498
+ }
44499
+ }
43928
44500
  return gone;
43929
44501
  }
43930
44502
  connection.onDidChangeWatchedFiles(
@@ -43959,14 +44531,27 @@ connection.onHover((params) => {
43959
44531
  const column = params.position.character;
43960
44532
  const cases = getActiveCasesForUri(params.textDocument.uri);
43961
44533
  try {
43962
- const tv = getTypeAtPosition(filePath, source, line, column, cases);
43963
- if (!tv) return null;
44534
+ const hover = getHoverAtPosition2(filePath, source, line, column, cases);
44535
+ if (!hover) return null;
44536
+ const lines = [];
44537
+ if (hover.absMultiline) {
44538
+ lines.push("```nudo", hover.absMultiline, "```");
44539
+ } else if (hover.abs) {
44540
+ lines.push("```nudo", hover.abs, "```");
44541
+ }
44542
+ if (hover.intension && hover.intension !== hover.abs) {
44543
+ lines.push("```nudo", hover.intension, "```");
44544
+ }
44545
+ if (hover.typeText && hover.typeText !== hover.intension && hover.typeText !== hover.abs) {
44546
+ lines.push("```nudo", `ext: ${hover.typeText}`, "```");
44547
+ }
44548
+ if (lines.length === 0) {
44549
+ lines.push("```nudo", hover.typeText, "```");
44550
+ }
43964
44551
  return {
43965
44552
  contents: {
43966
44553
  kind: MarkupKind.Markdown,
43967
- value: `\`\`\`nudo
43968
- ${typeValueToString2(tv)}
43969
- \`\`\``
44554
+ value: lines.join("\n")
43970
44555
  }
43971
44556
  };
43972
44557
  } catch {
@@ -44048,89 +44633,195 @@ connection.languages.inlayHint.on((params) => {
44048
44633
  paddingLeft: true
44049
44634
  });
44050
44635
  }
44636
+ try {
44637
+ for (const abs of collectAbsInlays2(source, {
44638
+ loadModule: lspLoadModule,
44639
+ fromFile: filePath
44640
+ })) {
44641
+ const lineIdx = abs.line - 1;
44642
+ if (lineIdx < 0 || lineIdx >= lines.length) continue;
44643
+ hints.push({
44644
+ position: { line: lineIdx, character: abs.character },
44645
+ label: abs.label,
44646
+ kind: abs.kind === "parameter" ? InlayHintKind.Parameter : InlayHintKind.Type,
44647
+ paddingLeft: true
44648
+ });
44649
+ }
44650
+ } catch {
44651
+ }
44051
44652
  return hints;
44052
44653
  } catch {
44053
44654
  return [];
44054
44655
  }
44055
44656
  });
44657
+ function filePathToUri(filePath) {
44658
+ if (filePath.startsWith("file://")) return filePath;
44659
+ const normalized = filePath.replace(/\\/g, "/");
44660
+ return normalized.startsWith("/") ? `file://${normalized}` : `file:///${normalized}`;
44661
+ }
44662
+ function navigationExtraFiles(currentPath) {
44663
+ const open = documents.all().map((d) => uriToFilePath(d.uri));
44664
+ const all = /* @__PURE__ */ new Set([...open, ...knownFiles, currentPath]);
44665
+ return [...all];
44666
+ }
44667
+ connection.onDocumentSymbol((params) => {
44668
+ const document2 = documents.get(params.textDocument.uri);
44669
+ if (!document2) return [];
44670
+ if (!isNudoFile(params.textDocument.uri)) return [];
44671
+ try {
44672
+ const ast = parse3(document2.getText());
44673
+ const toLsp = (s) => ({
44674
+ name: s.name,
44675
+ detail: s.detail,
44676
+ kind: s.kind,
44677
+ range: s.range,
44678
+ selectionRange: s.selectionRange,
44679
+ children: s.children?.map(toLsp)
44680
+ });
44681
+ return documentSymbols(ast).map(toLsp);
44682
+ } catch {
44683
+ return [];
44684
+ }
44685
+ });
44686
+ connection.onWorkspaceSymbol((params) => {
44687
+ const query = params.query.toLowerCase();
44688
+ const out = [];
44689
+ const files = /* @__PURE__ */ new Set([...knownFiles]);
44690
+ for (const d of documents.all()) files.add(uriToFilePath(d.uri));
44691
+ for (const filePath of files) {
44692
+ const doc = documents.all().find((d) => uriToFilePath(d.uri) === filePath);
44693
+ let source;
44694
+ if (doc) {
44695
+ source = doc.getText();
44696
+ } else {
44697
+ try {
44698
+ source = readFileSync3(filePath, "utf-8");
44699
+ } catch {
44700
+ source = void 0;
44701
+ }
44702
+ }
44703
+ if (source === void 0) continue;
44704
+ try {
44705
+ const ast = parse3(source);
44706
+ const uri = doc?.uri ?? filePathToUri(filePath);
44707
+ for (const sym of documentSymbols(ast)) {
44708
+ if (query && !sym.name.toLowerCase().includes(query)) continue;
44709
+ out.push({
44710
+ name: sym.name,
44711
+ kind: sym.kind,
44712
+ location: {
44713
+ uri,
44714
+ range: {
44715
+ start: { line: sym.selectionRange.start.line, character: sym.selectionRange.start.character },
44716
+ end: { line: sym.selectionRange.end.line, character: sym.selectionRange.end.character }
44717
+ }
44718
+ }
44719
+ });
44720
+ }
44721
+ } catch {
44722
+ }
44723
+ }
44724
+ return out;
44725
+ });
44056
44726
  connection.onDefinition((params) => {
44057
44727
  const document2 = documents.get(params.textDocument.uri);
44058
44728
  if (!document2) return null;
44059
44729
  if (!isNudoFile(params.textDocument.uri)) return null;
44060
44730
  const source = document2.getText();
44061
- const ast = parse2(source);
44062
- const table = buildSymbolTable(ast, params.textDocument.uri);
44731
+ const filePath = uriToFilePath(params.textDocument.uri);
44063
44732
  const line = params.position.line + 1;
44064
44733
  const column = params.position.character;
44065
- const identAtPos = findIdentifierAtPosition(ast, line, column);
44066
- if (!identAtPos) return null;
44067
- const def = findDefinition(table, identAtPos);
44068
- if (!def) return null;
44069
- return {
44070
- uri: params.textDocument.uri,
44071
- range: {
44072
- start: { line: def.loc.start.line - 1, character: def.loc.start.column },
44073
- end: { line: def.loc.end.line - 1, character: def.loc.end.column }
44074
- }
44075
- };
44734
+ try {
44735
+ const ast = parse3(source);
44736
+ const identAtPos = findIdentifierAtPosition(ast, line, column);
44737
+ if (!identAtPos) return null;
44738
+ const def = resolveDefinition(filePath, source, identAtPos);
44739
+ if (!def) return null;
44740
+ return {
44741
+ uri: filePathToUri(def.filePath),
44742
+ range: {
44743
+ start: { line: def.loc.start.line - 1, character: def.loc.start.column },
44744
+ end: { line: def.loc.end.line - 1, character: def.loc.end.column }
44745
+ }
44746
+ };
44747
+ } catch {
44748
+ return null;
44749
+ }
44076
44750
  });
44077
44751
  connection.onReferences((params) => {
44078
44752
  const document2 = documents.get(params.textDocument.uri);
44079
44753
  if (!document2) return [];
44080
44754
  if (!isNudoFile(params.textDocument.uri)) return [];
44081
44755
  const source = document2.getText();
44082
- const ast = parse2(source);
44083
- const table = buildSymbolTable(ast, params.textDocument.uri);
44756
+ const filePath = uriToFilePath(params.textDocument.uri);
44084
44757
  const line = params.position.line + 1;
44085
44758
  const column = params.position.character;
44086
- const identAtPos = findIdentifierAtPosition(ast, line, column);
44087
- if (!identAtPos) return [];
44088
- const refs = findReferences(table, identAtPos);
44089
- return refs.map((ref) => ({
44090
- uri: params.textDocument.uri,
44091
- range: {
44092
- start: { line: ref.loc.start.line - 1, character: ref.loc.start.column },
44093
- end: { line: ref.loc.end.line - 1, character: ref.loc.end.column }
44094
- }
44095
- }));
44759
+ try {
44760
+ const ast = parse3(source);
44761
+ const identAtPos = findIdentifierAtPosition(ast, line, column);
44762
+ if (!identAtPos) return [];
44763
+ const refs = resolveReferences(filePath, source, identAtPos, {
44764
+ extraFiles: navigationExtraFiles(filePath),
44765
+ includeDeclaration: params.context?.includeDeclaration !== false
44766
+ });
44767
+ return refs.map((ref) => ({
44768
+ uri: ref.uri ? filePathToUri(ref.uri) : params.textDocument.uri,
44769
+ range: {
44770
+ start: { line: ref.loc.start.line - 1, character: ref.loc.start.column },
44771
+ end: { line: ref.loc.end.line - 1, character: ref.loc.end.column }
44772
+ }
44773
+ }));
44774
+ } catch {
44775
+ return [];
44776
+ }
44096
44777
  });
44097
44778
  connection.onRenameRequest((params) => {
44098
44779
  const document2 = documents.get(params.textDocument.uri);
44099
44780
  if (!document2) return null;
44100
44781
  if (!isNudoFile(params.textDocument.uri)) return null;
44101
44782
  const source = document2.getText();
44102
- const ast = parse2(source);
44103
- const table = buildSymbolTable(ast, params.textDocument.uri);
44783
+ const filePath = uriToFilePath(params.textDocument.uri);
44104
44784
  const line = params.position.line + 1;
44105
44785
  const column = params.position.character;
44106
- const identAtPos = findIdentifierAtPosition(ast, line, column);
44107
- if (!identAtPos) return null;
44108
- const def = findDefinition(table, identAtPos);
44109
- const refs = findReferences(table, identAtPos);
44110
- const edits = [];
44111
- if (def) {
44112
- edits.push({
44113
- range: {
44114
- start: { line: def.loc.start.line - 1, character: def.loc.start.column },
44115
- end: { line: def.loc.end.line - 1, character: def.loc.end.column }
44116
- },
44117
- newText: params.newName
44118
- });
44119
- }
44120
- for (const ref of refs) {
44121
- edits.push({
44122
- range: {
44123
- start: { line: ref.loc.start.line - 1, character: ref.loc.start.column },
44124
- end: { line: ref.loc.end.line - 1, character: ref.loc.end.column }
44125
- },
44126
- newText: params.newName
44127
- });
44128
- }
44129
- return {
44130
- changes: {
44131
- [params.textDocument.uri]: edits
44786
+ try {
44787
+ const ast = parse3(source);
44788
+ const identAtPos = findIdentifierAtPosition(ast, line, column);
44789
+ if (!identAtPos) return null;
44790
+ const def = resolveDefinition(filePath, source, identAtPos);
44791
+ const refs = resolveReferences(filePath, source, identAtPos, {
44792
+ extraFiles: navigationExtraFiles(filePath)
44793
+ });
44794
+ const changes = {};
44795
+ const push = (uri, loc) => {
44796
+ const list = changes[uri] ??= [];
44797
+ list.push({
44798
+ range: {
44799
+ start: { line: loc.start.line - 1, character: loc.start.column },
44800
+ end: { line: loc.end.line - 1, character: loc.end.column }
44801
+ },
44802
+ newText: params.newName
44803
+ });
44804
+ };
44805
+ if (def) {
44806
+ push(filePathToUri(def.filePath), def.loc);
44807
+ }
44808
+ for (const ref of refs) {
44809
+ push(ref.uri ? filePathToUri(ref.uri) : params.textDocument.uri, ref.loc);
44810
+ }
44811
+ for (const uri of Object.keys(changes)) {
44812
+ const seen = /* @__PURE__ */ new Set();
44813
+ changes[uri] = changes[uri].filter((e) => {
44814
+ const key = `${e.range.start.line}:${e.range.start.character}:${e.range.end.line}:${e.range.end.character}`;
44815
+ if (seen.has(key)) return false;
44816
+ seen.add(key);
44817
+ return true;
44818
+ });
44132
44819
  }
44133
- };
44820
+ if (Object.keys(changes).length === 0) return null;
44821
+ return { changes };
44822
+ } catch {
44823
+ return null;
44824
+ }
44134
44825
  });
44135
44826
  connection.onCodeAction((params) => {
44136
44827
  const document2 = documents.get(params.textDocument.uri);
@@ -44153,14 +44844,6 @@ connection.onCodeAction((params) => {
44153
44844
  }
44154
44845
  });
44155
44846
  }
44156
- if (diag.code === "nudo-assertion-failed") {
44157
- actions.push({
44158
- title: "Update @nudo:returns to match inferred type",
44159
- kind: "quickfix",
44160
- diagnostics: [diag],
44161
- isPreferred: false
44162
- });
44163
- }
44164
44847
  }
44165
44848
  return actions;
44166
44849
  });
@@ -44174,7 +44857,7 @@ connection.onSignatureHelp((params) => {
44174
44857
  const column = params.position.character;
44175
44858
  const cases = getActiveCasesForUri(params.textDocument.uri);
44176
44859
  try {
44177
- const ast = parse2(source);
44860
+ const ast = parse3(source);
44178
44861
  const callInfo = findEnclosingCall(ast, line, column);
44179
44862
  if (!callInfo) return null;
44180
44863
  const fnType = getTypeAtPosition(filePath, source, callInfo.calleeLine, callInfo.calleeCol, cases);
@@ -44239,7 +44922,12 @@ connection.languages.semanticTokens.on((params) => {
44239
44922
  const document2 = documents.get(params.textDocument.uri);
44240
44923
  if (!document2) return { data: [] };
44241
44924
  if (!isNudoFile(params.textDocument.uri)) return { data: [] };
44242
- return { data: [] };
44925
+ try {
44926
+ const filePath = uriToFilePath(document2.uri);
44927
+ return { data: buildSemanticTokens(filePath, document2.getText()) };
44928
+ } catch {
44929
+ return { data: [] };
44930
+ }
44243
44931
  });
44244
44932
  function uriForFileOrUri(params) {
44245
44933
  if (params.uri) return params.uri;
@@ -44281,6 +44969,12 @@ function dispatchNudoCommand(command, arg) {
44281
44969
  return suggestCase(arg, agentToolDeps);
44282
44970
  case "nudo.trace":
44283
44971
  return trace(arg, agentToolDeps);
44972
+ case "nudo.check":
44973
+ return checkTool(arg, agentToolDeps);
44974
+ case "nudo.hover":
44975
+ return hoverTool(arg, agentToolDeps);
44976
+ case "nudo.infer":
44977
+ return inferTool(arg, agentToolDeps);
44284
44978
  case "nudo.selectCase":
44285
44979
  return handleSelectCase(arg);
44286
44980
  case "nudo.getActiveCases":
@@ -44300,6 +44994,9 @@ function dispatchAgentRequest(command, params) {
44300
44994
  connection.onRequest("nudo/whatIf", (params) => dispatchAgentRequest("nudo.whatIf", params));
44301
44995
  connection.onRequest("nudo/suggestCase", (params) => dispatchAgentRequest("nudo.suggestCase", params));
44302
44996
  connection.onRequest("nudo/trace", (params) => dispatchAgentRequest("nudo.trace", params));
44997
+ connection.onRequest("nudo/check", (params) => dispatchAgentRequest("nudo.check", params));
44998
+ connection.onRequest("nudo/hover", (params) => dispatchAgentRequest("nudo.hover", params));
44999
+ connection.onRequest("nudo/infer", (params) => dispatchAgentRequest("nudo.infer", params));
44303
45000
  connection.languages.diagnostics.on((params) => {
44304
45001
  const document2 = documents.get(params.textDocument.uri);
44305
45002
  if (!document2) return { kind: "full", items: [] };
@@ -44317,7 +45014,7 @@ connection.languages.diagnostics.on((params) => {
44317
45014
  });
44318
45015
  var nudoFileCache = /* @__PURE__ */ new Map();
44319
45016
  function isNudoFile(uri) {
44320
- if (!uri.endsWith(".js") && !uri.endsWith(".ts") && !uri.endsWith(".mjs")) return false;
45017
+ if (!isNudoTargetPath(uriToFilePath(uri))) return false;
44321
45018
  const cached = nudoFileCache.get(uri);
44322
45019
  if (cached !== void 0) return cached;
44323
45020
  const doc = documents.get(uri);