@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/README.md +6 -2
- package/dist/api/path.d.ts +9 -2
- package/dist/api/refs.d.ts +9 -1
- package/dist/cli.js +265 -74
- package/dist/index.js +226 -57
- package/dist/model/index.d.ts +6 -2
- package/dist/retrieval/describe.d.ts +28 -1
- package/dist/retrieval/index.d.ts +2 -2
- package/dist/retrieval/report.d.ts +7 -0
- package/dist/retrieval/retriever.d.ts +10 -2
- package/dist/retrieval/summary.d.ts +8 -0
- package/dist/retrieval/walk.d.ts +14 -3
- package/dist/scanner/code/calls.d.ts +11 -3
- package/dist/scanner/code/imports.d.ts +69 -4
- package/dist/scanner/code/planner.d.ts +2 -1
- package/dist/scanner/code/symbols.d.ts +8 -0
- package/dist/scanner/code/types.d.ts +15 -1
- package/dist/scanner/graph/builder.d.ts +4 -2
- package/dist/scanner/index.d.ts +1 -1
- package/package.json +1 -1
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
|
|
982
|
+
return otherInFile ? null : sameFile;
|
|
977
983
|
if (!second)
|
|
978
|
-
return first
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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((
|
|
1193
|
-
|
|
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
|
|
2757
|
-
|
|
2758
|
-
|
|
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) =>
|
|
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
|
|
3954
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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.
|
|
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
|
|
9380
|
-
|
|
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
|
-
|
|
9385
|
-
|
|
9386
|
-
|
|
9387
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
9686
|
+
const hop = new Map;
|
|
9536
9687
|
for (const node of frontier) {
|
|
9537
|
-
|
|
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
|
-
|
|
9541
|
-
|
|
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
|
-
|
|
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
|
-
|
|
9940
|
-
return
|
|
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: {
|
|
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
|
|
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) {
|
package/dist/model/index.d.ts
CHANGED
|
@@ -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
|
-
/**
|
|
665
|
-
|
|
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,
|
|
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
|
*
|