@g3nomaly/analyze 0.1.6 → 0.1.7

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/index.js CHANGED
@@ -963,7 +963,13 @@ function endsWith(owners, qualifier) {
963
963
  const offset = owners.length - qualifier.length;
964
964
  return qualifier.every((name, index) => owners[offset + index] === name);
965
965
  }
966
- function pick(declarations, scope, qualifier = []) {
966
+ function pick(declarations, scope, qualifier = [], accepts = () => true) {
967
+ const winner = choose(declarations, scope, qualifier);
968
+ if (winner === null || !accepts(winner) || scope.admits !== undefined && !scope.admits(winner))
969
+ return null;
970
+ return winner.ref;
971
+ }
972
+ function choose(declarations, scope, qualifier) {
967
973
  const { path } = scope;
968
974
  const visible = declarations.filter((declaration) => (scope.family === null || declaration.family === scope.family) && !(declaration.local && path !== null && declaration.path !== path) && (scope.sees === undefined || declaration.path === path || scope.sees(declaration.path)) && endsWith(declaration.owners, qualifier));
969
975
  const shared = new Set(visible.flatMap((declaration) => declaration.privateFile ? [] : [declaration.owners.join("\x00")]));
@@ -973,14 +979,14 @@ function pick(declarations, scope, qualifier = []) {
973
979
  return null;
974
980
  const [sameFile, otherInFile] = path === null ? [] : candidates.filter((declaration) => declaration.path === path);
975
981
  if (sameFile)
976
- return otherInFile ? null : sameFile.ref;
982
+ return otherInFile ? null : sameFile;
977
983
  if (!second)
978
- return first.ref;
984
+ return first;
979
985
  if (path === null)
980
986
  return null;
981
987
  const ranked = candidates.map((declaration) => ({ declaration, shared: sharedDirectories(declaration.path, path) })).sort((a, b) => b.shared - a.shared);
982
988
  const [best, runnerUp] = ranked;
983
- return best && runnerUp && best.shared > runnerUp.shared ? best.declaration.ref : null;
989
+ return best && runnerUp && best.shared > runnerUp.shared ? best.declaration : null;
984
990
  }
985
991
 
986
992
  class TypeIndex {
@@ -1016,7 +1022,8 @@ class CallableIndex {
1016
1022
  if (!CALLABLE_NAME.test(text))
1017
1023
  return null;
1018
1024
  const key = text.replace(/\(\)$/, "").replace(/::|#/g, ".");
1019
- return pick(this.byName.get(key) ?? [], scope);
1025
+ const bare = !key.includes(".");
1026
+ return pick(this.byName.get(key) ?? [], scope, [], (site) => !bare || !site.member || site.path === scope.path);
1020
1027
  }
1021
1028
  resolveCall(name, scope, owner, qualifier = []) {
1022
1029
  if (owner === null) {
@@ -1076,7 +1083,7 @@ class TypeLinker {
1076
1083
  return { text, links };
1077
1084
  }
1078
1085
  mention(text, from, scope, relation = "mentions") {
1079
- if (scope.typeParameters.has(text))
1086
+ if (scope.typeParameters.has(text) || scope.locals?.has(text))
1080
1087
  return null;
1081
1088
  const anywhere = { ...scope, sees: undefined };
1082
1089
  const qualified = QUALIFIED_NAME.test(text) ? text.split(/::|\./) : null;
@@ -1151,6 +1158,14 @@ var BINDING_TYPES = new Set([
1151
1158
  "lambda_literal",
1152
1159
  "closure_expression"
1153
1160
  ]);
1161
+ function sight(found, call, node) {
1162
+ const line = node.startPosition.row + 1;
1163
+ const known = found.get(keyOf(call));
1164
+ if (!known)
1165
+ found.set(keyOf(call), { call, lines: [line] });
1166
+ else if (!known.lines.includes(line))
1167
+ known.lines.push(line);
1168
+ }
1154
1169
  function readReferences(body, definitions) {
1155
1170
  const calls = new Map;
1156
1171
  const passes = new Map;
@@ -1163,12 +1178,12 @@ function readReferences(body, definitions) {
1163
1178
  if (CALL_TYPES.has(node.type)) {
1164
1179
  const call = callOf(node);
1165
1180
  if (call)
1166
- calls.set(keyOf(call), call);
1181
+ sight(calls, call, node);
1167
1182
  }
1168
1183
  for (const value of valuesOf(node)) {
1169
1184
  const passed = passedOf(value);
1170
1185
  if (passed)
1171
- passes.set(keyOf(passed), passed);
1186
+ sight(passes, passed, value);
1172
1187
  }
1173
1188
  const declared = LOCAL_DECLARATIONS[node.type];
1174
1189
  if (declared) {
@@ -1189,8 +1204,12 @@ function readReferences(body, definitions) {
1189
1204
  }
1190
1205
  }
1191
1206
  return {
1192
- calls: [...calls.values()].map((call) => call.via === "member" && call.qualifier.some((name) => locals.has(name)) ? { ...call, qualifier: [] } : call),
1193
- passes: [...passes.values()].filter((value) => !bindsName(bound, value))
1207
+ calls: [...calls.values()].map(({ call, lines }) => ({
1208
+ ...call,
1209
+ qualifier: call.via === "member" && call.qualifier.some((name) => locals.has(name)) ? [] : call.qualifier,
1210
+ lines: lines.sort((a, b) => a - b)
1211
+ })),
1212
+ passes: [...passes.values()].filter(({ call }) => !bindsName(bound, call)).map(({ call, lines }) => ({ ...call, lines: lines.sort((a, b) => a - b) }))
1194
1213
  };
1195
1214
  }
1196
1215
  function bindsName(bound, value) {
@@ -1331,7 +1350,7 @@ class CallLinker {
1331
1350
  const target = this.resolve(call, context);
1332
1351
  if (target === null || target === from || kind === "passes" && this.accessors.has(target))
1333
1352
  continue;
1334
- this.graph.link(from, kind, target);
1353
+ this.graph.link(from, kind, target, call.lines);
1335
1354
  }
1336
1355
  }
1337
1356
  registerAccessor(method) {
@@ -2702,6 +2721,7 @@ class SourceFileIndex {
2702
2721
  entryPoints = new Map;
2703
2722
  resolved = new Map;
2704
2723
  seen = new Map;
2724
+ declarations = new Map;
2705
2725
  add(path, ref, syntax, requests, exports) {
2706
2726
  const normalized = posix3.normalize(path);
2707
2727
  this.entries.set(normalized, { ref, syntax, requests, exports });
@@ -2735,6 +2755,12 @@ class SourceFileIndex {
2735
2755
  exportsOf(path) {
2736
2756
  return this.entries.get(posix3.normalize(path))?.exports ?? NO_EXPORTS;
2737
2757
  }
2758
+ exportsName(path, name) {
2759
+ const entry = this.entries.get(posix3.normalize(path));
2760
+ if (entry?.syntax?.readExports === undefined)
2761
+ return true;
2762
+ return entry.exports.declared.some((declared) => declared.name === name);
2763
+ }
2738
2764
  refOf(path) {
2739
2765
  return this.entries.get(posix3.normalize(path))?.ref;
2740
2766
  }
@@ -2751,17 +2777,72 @@ class SourceFileIndex {
2751
2777
  const known = this.resolved.get(normalized);
2752
2778
  if (known)
2753
2779
  return known;
2754
- const entry = this.entries.get(normalized);
2755
2780
  const imported = new Set;
2756
- for (const request of entry?.syntax ? entry.requests : []) {
2757
- const target = request.map((specifier) => entry?.syntax?.resolve(specifier, normalized, this) ?? null).find((found) => found !== null);
2758
- if (target && target !== normalized)
2759
- imported.add(target);
2760
- }
2781
+ for (const { file } of this.resolvedRequests(normalized))
2782
+ if (file !== normalized)
2783
+ imported.add(file);
2761
2784
  const result = [...imported];
2762
2785
  this.resolved.set(normalized, result);
2763
2786
  return result;
2764
2787
  }
2788
+ importLinksOf(path) {
2789
+ const normalized = posix3.normalize(path);
2790
+ const links = new Map;
2791
+ const add = (to, line) => {
2792
+ if (to !== undefined)
2793
+ links.set(to, [...links.get(to) ?? [], line]);
2794
+ };
2795
+ for (const { file, target, line } of this.resolvedRequests(normalized)) {
2796
+ if (file === normalized)
2797
+ continue;
2798
+ const fileRef = this.refOf(file);
2799
+ if (target.whole)
2800
+ add(fileRef, line);
2801
+ for (const name of target.names)
2802
+ add(this.declarationOf(file, name, new Set) ?? fileRef, line);
2803
+ }
2804
+ return [...links].map(([to, lines]) => ({ to, lines: [...new Set(lines)] }));
2805
+ }
2806
+ resolvedRequests(path) {
2807
+ const entry = this.entries.get(path);
2808
+ const syntax = entry?.syntax;
2809
+ if (!syntax)
2810
+ return [];
2811
+ return entry.requests.flatMap(({ targets, line }) => {
2812
+ for (const target of targets) {
2813
+ const file = syntax.resolve(target.specifier, path, this);
2814
+ if (file !== null)
2815
+ return [{ file, target, line }];
2816
+ }
2817
+ return [];
2818
+ });
2819
+ }
2820
+ declare(path, name, ref) {
2821
+ const normalized = posix3.normalize(path);
2822
+ const names = this.declarations.get(normalized) ?? new Map;
2823
+ if (!names.has(name))
2824
+ names.set(name, ref);
2825
+ this.declarations.set(normalized, names);
2826
+ }
2827
+ declarationOf(path, name, visited) {
2828
+ if (visited.has(path))
2829
+ return null;
2830
+ visited.add(path);
2831
+ const { declared, reexports } = this.exportsOf(path);
2832
+ const local = declared.find((entry) => entry.as === name);
2833
+ if (local)
2834
+ return this.declarations.get(path)?.get(local.name) ?? null;
2835
+ for (const { specifier, names } of reexports) {
2836
+ const handed = names === null ? name : names.find((entry) => entry.as === name)?.name;
2837
+ if (handed === undefined)
2838
+ continue;
2839
+ const target = this.resolveImport(specifier, path);
2840
+ const found = target === null ? null : this.declarationOf(target, handed, visited);
2841
+ if (found !== null)
2842
+ return found;
2843
+ }
2844
+ return this.entries.get(path)?.syntax?.readExports === undefined ? this.declarations.get(path)?.get(name) ?? null : null;
2845
+ }
2765
2846
  tracksImports(path) {
2766
2847
  return (this.entries.get(posix3.normalize(path))?.syntax ?? null) !== null;
2767
2848
  }
@@ -2807,7 +2888,7 @@ var C_INCLUDES = {
2807
2888
  if (!path)
2808
2889
  return [];
2809
2890
  const text = path.type === "system_lib_string" ? path.text.slice(1, -1) : path.text.replace(/^"|"$/g, "");
2810
- return text ? [[text]] : [];
2891
+ return text ? [{ targets: [{ specifier: text, whole: true, names: [] }], line: include.startPosition.row + 1 }] : [];
2811
2892
  }),
2812
2893
  resolve: (specifier, from, files) => {
2813
2894
  const local = besides(from, specifier);
@@ -2828,7 +2909,7 @@ var ECMASCRIPT_IMPORTS = {
2828
2909
  const source = node.type === "call_expression" ? dynamicSource(node) : node.childForFieldName("source") ?? requireClause(node)?.childForFieldName("source") ?? null;
2829
2910
  const text = source?.namedChildren.find((child) => child?.type === "string_fragment")?.text;
2830
2911
  if (text)
2831
- requests.push([text]);
2912
+ requests.push({ targets: [{ specifier: text, ...takenFrom(node) }], line: node.startPosition.row + 1 });
2832
2913
  }
2833
2914
  return requests;
2834
2915
  },
@@ -2873,6 +2954,20 @@ function packageBases(specifier, files) {
2873
2954
  const rest = segments.slice(nameLength).join("/") || "index";
2874
2955
  return [posix3.join(directory, "src", rest), posix3.join(directory, rest)];
2875
2956
  }
2957
+ function takenFrom(statement) {
2958
+ if (statement.type === "call_expression" || requireClause(statement) !== null)
2959
+ return { whole: true, names: [] };
2960
+ const clause = statement.namedChildren.find((child) => child?.type === "import_clause" || child?.type === "export_clause");
2961
+ if (!clause)
2962
+ return { whole: true, names: [] };
2963
+ const specifier = clause.type === "import_clause" ? "import_specifier" : "export_specifier";
2964
+ const names = clause.descendantsOfType(specifier).flatMap((entry) => {
2965
+ const name = entry?.childForFieldName("name")?.text;
2966
+ return name === undefined ? [] : [name];
2967
+ });
2968
+ const whole = clause.namedChildren.some((child) => child?.type === "identifier" || child?.type === "namespace_import");
2969
+ return { whole, names };
2970
+ }
2876
2971
  function requireClause(statement) {
2877
2972
  return statement.namedChildren.find((child) => child?.type === "import_require_clause") ?? null;
2878
2973
  }
@@ -2893,15 +2988,22 @@ var PYTHON_IMPORTS = {
2893
2988
  const dotted = name?.type === "aliased_import" ? name.childForFieldName("name") : name;
2894
2989
  return dotted ? [dotted.text] : [];
2895
2990
  });
2991
+ const line = node.startPosition.row + 1;
2896
2992
  if (node.type === "import_statement") {
2897
- requests.push(...names.map((name) => [name]));
2993
+ requests.push(...names.map((name) => ({ targets: [{ specifier: name, whole: true, names: [] }], line })));
2898
2994
  continue;
2899
2995
  }
2900
2996
  const module = node.childForFieldName("module_name")?.text;
2901
2997
  if (!module)
2902
2998
  continue;
2903
2999
  const separator = module.endsWith(".") ? "" : ".";
2904
- requests.push(...names.length > 0 ? names.map((name) => [`${module}${separator}${name}`, module]) : [[module]]);
3000
+ requests.push(...names.length > 0 ? names.map((name) => ({
3001
+ targets: [
3002
+ { specifier: `${module}${separator}${name}`, whole: true, names: [] },
3003
+ { specifier: module, whole: false, names: [name] }
3004
+ ],
3005
+ line
3006
+ })) : [{ targets: [{ specifier: module, whole: true, names: [] }], line }]);
2905
3007
  }
2906
3008
  return requests;
2907
3009
  },
@@ -3950,11 +4052,8 @@ class CodePlanner {
3950
4052
  return ref;
3951
4053
  }
3952
4054
  linkImports(from, path) {
3953
- for (const target of this.sources.importsOf(path)) {
3954
- const ref = this.sources.refOf(target);
3955
- if (ref !== undefined)
3956
- this.graph.link(from, "imports", ref);
3957
- }
4055
+ for (const { to, lines } of this.sources.importLinksOf(path))
4056
+ this.graph.link(from, "imports", to, lines);
3958
4057
  }
3959
4058
  async extract(file) {
3960
4059
  if (!this.registry.supports(file.path))
@@ -4060,6 +4159,7 @@ function terminate(style, statement) {
4060
4159
 
4061
4160
  // src/scanner/code/symbols.ts
4062
4161
  var CONSTRUCTIBLE2 = new Set(["class", "struct"]);
4162
+ var TEST_FILE = /(?:^|\/)(?:tests?|__tests__)\/|[._]test\.\w+$|\.spec\.\w+$|(?:^|\/)test_[^/]*\.py$/;
4063
4163
 
4064
4164
  class SymbolPlanner {
4065
4165
  graph;
@@ -4105,16 +4205,23 @@ class SymbolPlanner {
4105
4205
  const isConstructor = scope.parent !== null && spec.constructorNames(scope.parent.name).includes(symbol.name);
4106
4206
  const ownerDoc = isConstructor ? parseDoc(spec.docStyle, scope.parent?.comment ?? "") : null;
4107
4207
  const family = familyOf(spec);
4208
+ const parameters = plannedParameters(symbol, doc, ownerDoc);
4209
+ const fields = plannedFields(symbol, doc, spec);
4108
4210
  const typeScope = {
4109
4211
  path: file.path,
4110
4212
  family,
4111
4213
  typeParameters: new Set([...scope.typeParameters, ...symbol.typeParameters]),
4112
4214
  namespaceSeparator: spec.namespaceSeparator,
4113
- sees: (path) => this.sources.sees(file.path, path)
4215
+ sees: (path) => this.sources.sees(file.path, path),
4216
+ admits: (declaration) => this.admits(file.path, declaration),
4217
+ locals: new Set([...parameters, ...fields].map((declaration) => declaration.name))
4114
4218
  };
4115
4219
  const ref = this.graph.add(symbol.kind, { name: symbol.name, parent: scope.parentRef, anchor }, (self) => this.symbolContent(symbol, doc, scope, typeScope, self));
4116
4220
  const privateFile = spec.isPrivateFile?.(file.path) ?? false;
4117
- const site = { ref, path: file.path, family, owners: scope.owners, privateFile };
4221
+ const exported = this.sources.exportsName(file.path, scope.owners[0] ?? symbol.name);
4222
+ const site = { ref, path: file.path, family, owners: scope.owners, privateFile, exported };
4223
+ if (scope.parent === null)
4224
+ this.sources.declare(file.path, symbol.name, ref);
4118
4225
  if (TYPE_KINDS.has(symbol.kind) && !symbol.inferred) {
4119
4226
  const outOfLine = scope.parent !== null && scope.parent.inferred && TYPE_KINDS.has(scope.parent.kind);
4120
4227
  this.types.register(symbol.name, { ...site, local: privateFile && !outOfLine });
@@ -4126,10 +4233,10 @@ class SymbolPlanner {
4126
4233
  if (symbol.accessor)
4127
4234
  this.calls.registerAccessor(ref);
4128
4235
  }
4129
- for (const parameter of plannedParameters(symbol, doc, ownerDoc)) {
4236
+ for (const parameter of parameters) {
4130
4237
  this.graph.add("parameter", { name: parameter.name, parent: ref }, (self) => this.declarationContent(parameter, self, file, typeScope));
4131
4238
  }
4132
- for (const field of plannedFields(symbol, doc, spec)) {
4239
+ for (const field of fields) {
4133
4240
  this.graph.add("field", { name: field.name, parent: ref }, async (self) => ({
4134
4241
  ...await this.declarationContent(field, self, file, typeScope),
4135
4242
  annotations: field.annotations
@@ -4148,6 +4255,11 @@ class SymbolPlanner {
4148
4255
  for (const child of symbol.children)
4149
4256
  this.planSymbol(child, inner);
4150
4257
  }
4258
+ admits(from, declaration) {
4259
+ if (TEST_FILE.test(declaration.path) && !TEST_FILE.test(from))
4260
+ return false;
4261
+ return declaration.exported || this.sources.packageOf(declaration.path) === this.sources.packageOf(from);
4262
+ }
4151
4263
  async symbolContent(symbol, doc, scope, typeScope, self) {
4152
4264
  const { spec } = scope.file;
4153
4265
  if (symbol.heritage)
@@ -4949,8 +5061,10 @@ class GraphBuilder {
4949
5061
  const route = routeOf(this.identities, ref);
4950
5062
  return hrefOf(fragment ? { path: route.path, hash: fragment } : route);
4951
5063
  }
4952
- link(from, kind, to) {
4953
- this.edges.set(`${from}:${kind}:${to}`, [from, kind, to]);
5064
+ link(from, kind, to, lines = []) {
5065
+ const key = `${from}:${kind}:${to}`;
5066
+ const merged = [...new Set([...this.edges.get(key)?.[3] ?? [], ...lines])].sort((a, b) => a - b);
5067
+ this.edges.set(key, merged.length > 0 ? [from, kind, to, merged] : [from, kind, to]);
4954
5068
  }
4955
5069
  async build() {
4956
5070
  this.keepOnePackagePerDirectory();
@@ -8253,7 +8367,7 @@ async function prepareOutputDir(repo) {
8253
8367
  // package.json
8254
8368
  var package_default = {
8255
8369
  name: "@g3nomaly/analyze",
8256
- version: "0.1.6",
8370
+ version: "0.1.7",
8257
8371
  description: "Turns a repository into one offline HTML documentation site, and queries the same linked model from the terminal and from hooks.",
8258
8372
  keywords: [
8259
8373
  "documentation",
@@ -9376,19 +9490,51 @@ function stepsOf(graph, node, direction, kinds) {
9376
9490
  const members = graph.children(node).filter((child) => isDeclarationKind(graph.node(child).kind));
9377
9491
  const edges = [node, ...members].flatMap((end) => direction === "out" ? graph.edgesFrom(end) : graph.edgesTo(end)).filter((edge) => kinds.length === 0 || kinds.includes(edge[1]));
9378
9492
  const end = direction === "out" ? 2 : 0;
9379
- const seen = new Set;
9380
- return edges.flatMap((edge) => {
9493
+ const steps = new Map;
9494
+ for (const edge of edges) {
9381
9495
  const { owner } = ownerOf(graph, edge[end]);
9382
9496
  const via = edge[1] === "type" ? ownerOf(graph, edge[0]).via : null;
9383
9497
  const key = `${edge[1]} ${owner} ${via ?? ""}`;
9384
- if (seen.has(key))
9385
- return [];
9386
- seen.add(key);
9387
- return [{ relation: edge[1], node: owner, via }];
9388
- });
9498
+ const known = steps.get(key);
9499
+ const lines = [...new Set([...known?.lines ?? [], ...edge[3] ?? []])].sort((a, b) => a - b);
9500
+ steps.set(key, { relation: edge[1], node: owner, via, source: known?.source ?? edge[0], lines });
9501
+ }
9502
+ return [...steps.values()];
9503
+ }
9504
+ function fileOf(graph, node) {
9505
+ for (const ref of [node, ...graph.ancestors(node).reverse()]) {
9506
+ const value = graph.node(ref);
9507
+ if (value.kind === "source" || isFileNode(value))
9508
+ return ref;
9509
+ }
9510
+ return node;
9511
+ }
9512
+ function fileStepsOf(graph, file, direction, kinds) {
9513
+ const end = direction === "out" ? 2 : 0;
9514
+ const steps = new Map;
9515
+ for (const inner of [file, ...graph.descendants(file)]) {
9516
+ for (const edge of direction === "out" ? graph.edgesFrom(inner) : graph.edgesTo(inner)) {
9517
+ if (kinds.length > 0 && !kinds.includes(edge[1]))
9518
+ continue;
9519
+ const other = fileOf(graph, edge[end]);
9520
+ if (other === file)
9521
+ continue;
9522
+ const key = `${edge[1]} ${other}`;
9523
+ const known = steps.get(key);
9524
+ const lines = [...new Set([...known?.lines ?? [], ...edge[3] ?? []])].sort((a, b) => a - b);
9525
+ steps.set(key, { relation: edge[1], node: other, via: null, source: known?.source ?? edge[0], lines });
9526
+ }
9527
+ }
9528
+ return [...steps.values()];
9389
9529
  }
9390
9530
  function referenceOf(graph, step) {
9391
- return { relation: step.relation, node: summarize(graph, step.node), via: step.via };
9531
+ const path = step.lines.length > 0 ? pathOf(graph, step.source) : null;
9532
+ return {
9533
+ relation: step.relation,
9534
+ node: summarize(graph, step.node),
9535
+ via: step.via,
9536
+ location: path === null ? null : { path, lines: step.lines }
9537
+ };
9392
9538
  }
9393
9539
  function referencesOf(graph, node, options) {
9394
9540
  const { direction, kinds } = options;
@@ -9486,7 +9632,9 @@ function describeNode(graph, node) {
9486
9632
  };
9487
9633
  }
9488
9634
  // src/retrieval/walk.ts
9635
+ init_model();
9489
9636
  var DEFAULT_WALK_LIMIT = 50;
9637
+ var WALK_LEVELS = ["entries", "files"];
9490
9638
  function walkReferences(graph, start, options) {
9491
9639
  const directions = ["out", "in"].filter((direction) => options.direction === "both" || options.direction === direction).map((direction) => {
9492
9640
  const root = [];
@@ -9495,7 +9643,7 @@ function walkReferences(graph, start, options) {
9495
9643
  let left = options.limit;
9496
9644
  let omitted = 0;
9497
9645
  for (let hop = 1;hop <= options.depth; hop++) {
9498
- const candidates = directions.map((walk) => walk.frontier.flatMap(({ node, into }) => stepsOf(graph, node, walk.direction, options.kinds).map((step) => ({ step, into }))));
9646
+ const candidates = directions.map((walk) => walk.frontier.flatMap(({ node, into }) => (options.level === "files" ? fileStepsOf : stepsOf)(graph, node, walk.direction, options.kinds).map((step) => ({ step, into }))));
9499
9647
  const frontiers = directions.map(() => []);
9500
9648
  const longest = Math.max(0, ...candidates.map((list) => list.length));
9501
9649
  for (let index = 0;index < longest; index++) {
@@ -9529,19 +9677,27 @@ function walkReferences(graph, start, options) {
9529
9677
  omitted
9530
9678
  };
9531
9679
  }
9532
- function shortestPath(graph, from, to, kinds) {
9533
- const reached = new Map([[from, null]]);
9680
+ function shortestPath(graph, from, to, kinds, includeMentions = false) {
9681
+ const follows = kinds.length > 0 || includeMentions ? kinds : EDGE_KINDS.filter((kind) => kind !== "mentions");
9682
+ const reached = new Map([
9683
+ [from, null]
9684
+ ]);
9534
9685
  for (let frontier = [from];frontier.length > 0 && !reached.has(to); ) {
9535
- const next = [];
9686
+ const hop = new Map;
9536
9687
  for (const node of frontier) {
9537
- for (const step of stepsOf(graph, node, "out", kinds)) {
9688
+ const before = reached.get(node)?.mentions ?? 0;
9689
+ for (const step of stepsOf(graph, node, "out", follows)) {
9538
9690
  if (reached.has(step.node))
9539
9691
  continue;
9540
- reached.set(step.node, { previous: node, step });
9541
- next.push(step.node);
9692
+ const mentions = before + (step.relation === "mentions" ? 1 : 0);
9693
+ const known = hop.get(step.node);
9694
+ if (known === undefined || mentions < known.mentions)
9695
+ hop.set(step.node, { previous: node, step, mentions });
9542
9696
  }
9543
9697
  }
9544
- frontier = next;
9698
+ for (const [node, way] of hop)
9699
+ reached.set(node, way);
9700
+ frontier = [...hop.keys()];
9545
9701
  }
9546
9702
  if (!reached.has(to))
9547
9703
  return null;
@@ -9936,8 +10092,11 @@ class Retriever {
9936
10092
  walk(node, options) {
9937
10093
  return walkReferences(this.graph, node, options);
9938
10094
  }
9939
- path(from, to, kinds) {
9940
- return shortestPath(this.graph, from, to, kinds);
10095
+ fileOf(node) {
10096
+ return fileOf(this.graph, node);
10097
+ }
10098
+ path(from, to, kinds, includeMentions = false) {
10099
+ return shortestPath(this.graph, from, to, kinds, includeMentions);
9941
10100
  }
9942
10101
  exportGraph(options) {
9943
10102
  return exportGraph(this.graph, options);
@@ -10191,16 +10350,23 @@ var pathSchema = z9.strictObject({
10191
10350
  ...warningShape,
10192
10351
  start: z9.string().trim().min(1),
10193
10352
  end: z9.string().trim().min(1),
10194
- edges: z9.array(z9.enum(EDGE_KINDS)).readonly().optional()
10353
+ edges: z9.array(z9.enum(EDGE_KINDS)).readonly().optional(),
10354
+ includeMentions: z9.boolean().optional()
10195
10355
  }).superRefine(checkSource).transform((fields) => ({
10196
10356
  source: sourceOf(fields),
10197
10357
  reporter: reporterOf(fields.onWarning),
10198
- request: { start: fields.start, end: fields.end, filter: filterOf(fields), kinds: fields.edges ?? [] }
10358
+ request: {
10359
+ start: fields.start,
10360
+ end: fields.end,
10361
+ filter: filterOf(fields),
10362
+ kinds: fields.edges ?? [],
10363
+ includeMentions: fields.includeMentions ?? false
10364
+ }
10199
10365
  }));
10200
10366
  function pathIn(retriever, request) {
10201
10367
  const start = nodeOf(retriever, request.start, request.filter);
10202
10368
  const end = nodeOf(retriever, request.end, request.filter);
10203
- const steps = retriever.path(start, end, request.kinds);
10369
+ const steps = retriever.path(start, end, request.kinds, request.includeMentions);
10204
10370
  if (steps === null) {
10205
10371
  const edges = request.kinds.length > 0 ? ` along ${request.kinds.join(", ")} edges` : "";
10206
10372
  throw new QueryError(`No path from "${request.start}" to "${request.end}"${edges}. A path follows edges the way they point, so try the other order.`);
@@ -10223,7 +10389,8 @@ var refsSchema = z10.strictObject({
10223
10389
  outgoing: z10.boolean().optional(),
10224
10390
  edges: z10.array(z10.enum(EDGE_KINDS)).readonly().optional(),
10225
10391
  depth: z10.number().int().positive().optional(),
10226
- limit: z10.number().int().positive().optional()
10392
+ limit: z10.number().int().positive().optional(),
10393
+ level: z10.enum(WALK_LEVELS).optional()
10227
10394
  }).superRefine(checkSource).refine((fields) => !(fields.incoming && fields.outgoing), {
10228
10395
  message: "incoming and outgoing exclude each other",
10229
10396
  path: ["outgoing"]
@@ -10237,12 +10404,14 @@ var refsSchema = z10.strictObject({
10237
10404
  direction: fields.incoming ? "in" : fields.outgoing ? "out" : "both",
10238
10405
  kinds: fields.edges ?? [],
10239
10406
  depth: fields.depth ?? 1,
10240
- limit: fields.limit ?? DEFAULT_WALK_LIMIT
10407
+ limit: fields.limit ?? DEFAULT_WALK_LIMIT,
10408
+ level: fields.level ?? "entries"
10241
10409
  }
10242
10410
  }
10243
10411
  }));
10244
10412
  function refsIn(retriever, request) {
10245
- const node = nodeOf(retriever, request.target, request.filter);
10413
+ const target = nodeOf(retriever, request.target, request.filter);
10414
+ const node = request.walk.level === "files" ? retriever.fileOf(target) : target;
10246
10415
  return { node: retriever.summarize(node), ...retriever.walk(node, request.walk) };
10247
10416
  }
10248
10417
  async function refs(options) {
@@ -661,8 +661,12 @@ export type DescribedNode = SymbolNode | OperationNode | DefinitionNode | TableN
661
661
  * @returns True for a symbol, an operation, a definition, a table and a query.
662
662
  */
663
663
  export declare function isDescribedNode(node: GraphNode): node is DescribedNode;
664
- /** A relation other than `parent` between two nodes, such as a parameter that has a class as its type. */
665
- export type Edge = readonly [from: NodeRef, kind: EdgeKind, to: NodeRef];
664
+ /**
665
+ * A relation other than `parent` between two nodes, such as a parameter that has a class as its type. `lines` are
666
+ * the lines of the file of `from` where the link is made, in order, such as those of a call or an import. An edge
667
+ * with no line of its own leaves them out.
668
+ */
669
+ export type Edge = readonly [from: NodeRef, kind: EdgeKind, to: NodeRef, lines?: readonly number[]];
666
670
  /**
667
671
  * What the bundle says about its own making.
668
672
  *
@@ -20,11 +20,16 @@ export declare const ALL_REFERENCES: ReferenceOptions;
20
20
  * @property relation - Kind of the edge.
21
21
  * @property node - The page or entry at the other end. A parameter or field there counts as its symbol.
22
22
  * @property via - For a `type` edge, the name of the parameter or field of the node it starts at, else `null`.
23
+ * @property source - Node the edge starts at, whose file holds `lines`.
24
+ * @property lines - Lines where the link is made, of every edge the step stands for, in order. Empty when the
25
+ * edges record none.
23
26
  */
24
27
  export interface Step {
25
28
  readonly relation: EdgeKind;
26
29
  readonly node: NodeRef;
27
30
  readonly via: string | null;
31
+ readonly source: NodeRef;
32
+ readonly lines: readonly number[];
28
33
  }
29
34
  /**
30
35
  * @brief Lists the edges of a node in one direction, each once. The edges of its parameters and fields count as its
@@ -37,12 +42,34 @@ export interface Step {
37
42
  * @returns The steps in edge order.
38
43
  */
39
44
  export declare function stepsOf(graph: Graph, node: NodeRef, direction: 'in' | 'out', kinds: readonly EdgeKind[]): Step[];
45
+ /**
46
+ * @brief Finds the file a node is in: the node itself when it is a file or a source file, else its closest such
47
+ * ancestor.
48
+ *
49
+ * @param graph - Graph the node belongs to.
50
+ * @param node - Any node.
51
+ * @returns The file, or the node itself when no file holds it, as for a folder or a package.
52
+ */
53
+ export declare function fileOf(graph: Graph, node: NodeRef): NodeRef;
54
+ /**
55
+ * @brief Lists the links of a file to other files in one direction, as the file level of the graph page draws
56
+ * them: every edge of the file and of the entries in it counts for the file, and every edge to another file or an
57
+ * entry in it counts for that file. Edges inside the file are left out. Nothing is stored for these links.
58
+ *
59
+ * @param graph - Graph the file belongs to.
60
+ * @param file - A file, as `fileOf` gives it.
61
+ * @param direction - `out` for what the file links to, `in` for what links to it.
62
+ * @param kinds - Edge kinds to keep. Empty keeps every kind.
63
+ * @returns One step per other file and edge kind, with the lines of every edge it stands for.
64
+ */
65
+ export declare function fileStepsOf(graph: Graph, file: NodeRef, direction: 'in' | 'out', kinds: readonly EdgeKind[]): Step[];
40
66
  /**
41
67
  * @brief Turns a step into the reference a report shows.
42
68
  *
43
69
  * @param graph - Graph the step belongs to.
44
70
  * @param step - The step.
45
- * @returns The relation, the summary of the other node, and the parameter or field it runs through.
71
+ * @returns The relation, the summary of the other node, the parameter or field it runs through, and where the link
72
+ * is made.
46
73
  */
47
74
  export declare function referenceOf(graph: Graph, step: Step): Reference;
48
75
  /**
@@ -1,5 +1,5 @@
1
- export { ALL_REFERENCES, describeNode, type ReferenceOptions, referencesOf } from './describe';
2
- export { DEFAULT_WALK_LIMIT, shortestPath, type Walk, type WalkOptions, type WalkReference, walkReferences } from './walk';
1
+ export { ALL_REFERENCES, describeNode, fileOf, type ReferenceOptions, referencesOf } from './describe';
2
+ export { DEFAULT_WALK_LIMIT, shortestPath, WALK_LEVELS, type Walk, type WalkLevel, type WalkOptions, type WalkReference, walkReferences, } from './walk';
3
3
  export { NO_FILTER, type NodeFilter, nodeTest, resultOf, resultsOf } from './filter';
4
4
  export { COMMUNITY_MEMBERS, type CommunityMember, EXPORT_LEVELS, type ExportEdge, type ExportLevel, type ExportNode, type GraphExport, type GraphExportOptions, exportGraph, } from './graph-export';
5
5
  export { codeBlock, htmlToMarkdown } from './markdown';
@@ -189,11 +189,18 @@ export interface TagReport {
189
189
  * @property relation - Kind of the edge.
190
190
  * @property node - The node at the other end. For a parameter or a field, the symbol that declares it.
191
191
  * @property via - Name of the parameter or field the edge starts at, when it does not start at a page or an entry.
192
+ * @property location - Where the link is made: the file of the node the edge starts at and the lines in it, e.g.
193
+ * `{ path: "packages/core/src/cli/commands/add.ts", lines: [86] }` for a call. `null` when the edge records no
194
+ * lines.
192
195
  */
193
196
  export interface Reference {
194
197
  readonly relation: EdgeKind;
195
198
  readonly node: NodeSummary;
196
199
  readonly via: string | null;
200
+ readonly location: {
201
+ readonly path: string;
202
+ readonly lines: readonly number[];
203
+ } | null;
197
204
  }
198
205
  /**
199
206
  * @brief Both directions of the edges of a node.
@@ -165,15 +165,23 @@ export declare class Retriever {
165
165
  * @returns What the node links to and what links to it, each with its next hop, and how many the limit left out.
166
166
  */
167
167
  walk(node: NodeRef, options: WalkOptions): Walk;
168
+ /**
169
+ * @brief Finds the file a node is in.
170
+ *
171
+ * @param node - Any node.
172
+ * @returns The node itself when it is a file, else its closest file ancestor, else the node.
173
+ */
174
+ fileOf(node: NodeRef): NodeRef;
168
175
  /**
169
176
  * @brief Finds the shortest chain of edges from one node to another, following edges the way they point.
170
177
  *
171
178
  * @param from - The node the chain starts at.
172
179
  * @param to - The node the chain ends at.
173
- * @param kinds - Edge kinds to follow. Empty follows every kind.
180
+ * @param kinds - Edge kinds to follow. Empty follows every kind but `mentions`, unless `includeMentions` is set.
181
+ * @param includeMentions - Whether an empty `kinds` follows `mentions` too.
174
182
  * @returns Each reference of the chain in order, or `null` when no chain leads from `from` to `to`.
175
183
  */
176
- path(from: NodeRef, to: NodeRef, kinds: readonly EdgeKind[]): Reference[] | null;
184
+ path(from: NodeRef, to: NodeRef, kinds: readonly EdgeKind[], includeMentions?: boolean): Reference[] | null;
177
185
  /**
178
186
  * @brief Exports one level of the bundle as nodes and edges.
179
187
  *