@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/cli.js
CHANGED
|
@@ -967,7 +967,13 @@ function endsWith(owners, qualifier) {
|
|
|
967
967
|
const offset = owners.length - qualifier.length;
|
|
968
968
|
return qualifier.every((name, index) => owners[offset + index] === name);
|
|
969
969
|
}
|
|
970
|
-
function pick(declarations, scope, qualifier = []) {
|
|
970
|
+
function pick(declarations, scope, qualifier = [], accepts = () => true) {
|
|
971
|
+
const winner = choose(declarations, scope, qualifier);
|
|
972
|
+
if (winner === null || !accepts(winner) || scope.admits !== undefined && !scope.admits(winner))
|
|
973
|
+
return null;
|
|
974
|
+
return winner.ref;
|
|
975
|
+
}
|
|
976
|
+
function choose(declarations, scope, qualifier) {
|
|
971
977
|
const { path } = scope;
|
|
972
978
|
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));
|
|
973
979
|
const shared = new Set(visible.flatMap((declaration) => declaration.privateFile ? [] : [declaration.owners.join("\x00")]));
|
|
@@ -977,14 +983,14 @@ function pick(declarations, scope, qualifier = []) {
|
|
|
977
983
|
return null;
|
|
978
984
|
const [sameFile, otherInFile] = path === null ? [] : candidates.filter((declaration) => declaration.path === path);
|
|
979
985
|
if (sameFile)
|
|
980
|
-
return otherInFile ? null : sameFile
|
|
986
|
+
return otherInFile ? null : sameFile;
|
|
981
987
|
if (!second)
|
|
982
|
-
return first
|
|
988
|
+
return first;
|
|
983
989
|
if (path === null)
|
|
984
990
|
return null;
|
|
985
991
|
const ranked = candidates.map((declaration) => ({ declaration, shared: sharedDirectories(declaration.path, path) })).sort((a, b) => b.shared - a.shared);
|
|
986
992
|
const [best, runnerUp] = ranked;
|
|
987
|
-
return best && runnerUp && best.shared > runnerUp.shared ? best.declaration
|
|
993
|
+
return best && runnerUp && best.shared > runnerUp.shared ? best.declaration : null;
|
|
988
994
|
}
|
|
989
995
|
|
|
990
996
|
class TypeIndex {
|
|
@@ -1020,7 +1026,8 @@ class CallableIndex {
|
|
|
1020
1026
|
if (!CALLABLE_NAME.test(text))
|
|
1021
1027
|
return null;
|
|
1022
1028
|
const key = text.replace(/\(\)$/, "").replace(/::|#/g, ".");
|
|
1023
|
-
|
|
1029
|
+
const bare = !key.includes(".");
|
|
1030
|
+
return pick(this.byName.get(key) ?? [], scope, [], (site) => !bare || !site.member || site.path === scope.path);
|
|
1024
1031
|
}
|
|
1025
1032
|
resolveCall(name, scope, owner, qualifier = []) {
|
|
1026
1033
|
if (owner === null) {
|
|
@@ -1080,7 +1087,7 @@ class TypeLinker {
|
|
|
1080
1087
|
return { text, links };
|
|
1081
1088
|
}
|
|
1082
1089
|
mention(text, from, scope, relation = "mentions") {
|
|
1083
|
-
if (scope.typeParameters.has(text))
|
|
1090
|
+
if (scope.typeParameters.has(text) || scope.locals?.has(text))
|
|
1084
1091
|
return null;
|
|
1085
1092
|
const anywhere = { ...scope, sees: undefined };
|
|
1086
1093
|
const qualified = QUALIFIED_NAME.test(text) ? text.split(/::|\./) : null;
|
|
@@ -1155,6 +1162,14 @@ var BINDING_TYPES = new Set([
|
|
|
1155
1162
|
"lambda_literal",
|
|
1156
1163
|
"closure_expression"
|
|
1157
1164
|
]);
|
|
1165
|
+
function sight(found, call, node) {
|
|
1166
|
+
const line = node.startPosition.row + 1;
|
|
1167
|
+
const known = found.get(keyOf(call));
|
|
1168
|
+
if (!known)
|
|
1169
|
+
found.set(keyOf(call), { call, lines: [line] });
|
|
1170
|
+
else if (!known.lines.includes(line))
|
|
1171
|
+
known.lines.push(line);
|
|
1172
|
+
}
|
|
1158
1173
|
function readReferences(body, definitions) {
|
|
1159
1174
|
const calls = new Map;
|
|
1160
1175
|
const passes = new Map;
|
|
@@ -1167,12 +1182,12 @@ function readReferences(body, definitions) {
|
|
|
1167
1182
|
if (CALL_TYPES.has(node.type)) {
|
|
1168
1183
|
const call = callOf(node);
|
|
1169
1184
|
if (call)
|
|
1170
|
-
calls
|
|
1185
|
+
sight(calls, call, node);
|
|
1171
1186
|
}
|
|
1172
1187
|
for (const value of valuesOf(node)) {
|
|
1173
1188
|
const passed = passedOf(value);
|
|
1174
1189
|
if (passed)
|
|
1175
|
-
passes
|
|
1190
|
+
sight(passes, passed, value);
|
|
1176
1191
|
}
|
|
1177
1192
|
const declared = LOCAL_DECLARATIONS[node.type];
|
|
1178
1193
|
if (declared) {
|
|
@@ -1193,8 +1208,12 @@ function readReferences(body, definitions) {
|
|
|
1193
1208
|
}
|
|
1194
1209
|
}
|
|
1195
1210
|
return {
|
|
1196
|
-
calls: [...calls.values()].map((
|
|
1197
|
-
|
|
1211
|
+
calls: [...calls.values()].map(({ call, lines }) => ({
|
|
1212
|
+
...call,
|
|
1213
|
+
qualifier: call.via === "member" && call.qualifier.some((name) => locals.has(name)) ? [] : call.qualifier,
|
|
1214
|
+
lines: lines.sort((a, b) => a - b)
|
|
1215
|
+
})),
|
|
1216
|
+
passes: [...passes.values()].filter(({ call }) => !bindsName(bound, call)).map(({ call, lines }) => ({ ...call, lines: lines.sort((a, b) => a - b) }))
|
|
1198
1217
|
};
|
|
1199
1218
|
}
|
|
1200
1219
|
function bindsName(bound, value) {
|
|
@@ -1335,7 +1354,7 @@ class CallLinker {
|
|
|
1335
1354
|
const target = this.resolve(call, context);
|
|
1336
1355
|
if (target === null || target === from || kind === "passes" && this.accessors.has(target))
|
|
1337
1356
|
continue;
|
|
1338
|
-
this.graph.link(from, kind, target);
|
|
1357
|
+
this.graph.link(from, kind, target, call.lines);
|
|
1339
1358
|
}
|
|
1340
1359
|
}
|
|
1341
1360
|
registerAccessor(method) {
|
|
@@ -2706,6 +2725,7 @@ class SourceFileIndex {
|
|
|
2706
2725
|
entryPoints = new Map;
|
|
2707
2726
|
resolved = new Map;
|
|
2708
2727
|
seen = new Map;
|
|
2728
|
+
declarations = new Map;
|
|
2709
2729
|
add(path, ref, syntax, requests, exports) {
|
|
2710
2730
|
const normalized = posix3.normalize(path);
|
|
2711
2731
|
this.entries.set(normalized, { ref, syntax, requests, exports });
|
|
@@ -2739,6 +2759,12 @@ class SourceFileIndex {
|
|
|
2739
2759
|
exportsOf(path) {
|
|
2740
2760
|
return this.entries.get(posix3.normalize(path))?.exports ?? NO_EXPORTS;
|
|
2741
2761
|
}
|
|
2762
|
+
exportsName(path, name) {
|
|
2763
|
+
const entry = this.entries.get(posix3.normalize(path));
|
|
2764
|
+
if (entry?.syntax?.readExports === undefined)
|
|
2765
|
+
return true;
|
|
2766
|
+
return entry.exports.declared.some((declared) => declared.name === name);
|
|
2767
|
+
}
|
|
2742
2768
|
refOf(path) {
|
|
2743
2769
|
return this.entries.get(posix3.normalize(path))?.ref;
|
|
2744
2770
|
}
|
|
@@ -2755,17 +2781,72 @@ class SourceFileIndex {
|
|
|
2755
2781
|
const known = this.resolved.get(normalized);
|
|
2756
2782
|
if (known)
|
|
2757
2783
|
return known;
|
|
2758
|
-
const entry = this.entries.get(normalized);
|
|
2759
2784
|
const imported = new Set;
|
|
2760
|
-
for (const
|
|
2761
|
-
|
|
2762
|
-
|
|
2763
|
-
imported.add(target);
|
|
2764
|
-
}
|
|
2785
|
+
for (const { file } of this.resolvedRequests(normalized))
|
|
2786
|
+
if (file !== normalized)
|
|
2787
|
+
imported.add(file);
|
|
2765
2788
|
const result = [...imported];
|
|
2766
2789
|
this.resolved.set(normalized, result);
|
|
2767
2790
|
return result;
|
|
2768
2791
|
}
|
|
2792
|
+
importLinksOf(path) {
|
|
2793
|
+
const normalized = posix3.normalize(path);
|
|
2794
|
+
const links = new Map;
|
|
2795
|
+
const add = (to, line) => {
|
|
2796
|
+
if (to !== undefined)
|
|
2797
|
+
links.set(to, [...links.get(to) ?? [], line]);
|
|
2798
|
+
};
|
|
2799
|
+
for (const { file, target, line } of this.resolvedRequests(normalized)) {
|
|
2800
|
+
if (file === normalized)
|
|
2801
|
+
continue;
|
|
2802
|
+
const fileRef = this.refOf(file);
|
|
2803
|
+
if (target.whole)
|
|
2804
|
+
add(fileRef, line);
|
|
2805
|
+
for (const name of target.names)
|
|
2806
|
+
add(this.declarationOf(file, name, new Set) ?? fileRef, line);
|
|
2807
|
+
}
|
|
2808
|
+
return [...links].map(([to, lines]) => ({ to, lines: [...new Set(lines)] }));
|
|
2809
|
+
}
|
|
2810
|
+
resolvedRequests(path) {
|
|
2811
|
+
const entry = this.entries.get(path);
|
|
2812
|
+
const syntax = entry?.syntax;
|
|
2813
|
+
if (!syntax)
|
|
2814
|
+
return [];
|
|
2815
|
+
return entry.requests.flatMap(({ targets, line }) => {
|
|
2816
|
+
for (const target of targets) {
|
|
2817
|
+
const file = syntax.resolve(target.specifier, path, this);
|
|
2818
|
+
if (file !== null)
|
|
2819
|
+
return [{ file, target, line }];
|
|
2820
|
+
}
|
|
2821
|
+
return [];
|
|
2822
|
+
});
|
|
2823
|
+
}
|
|
2824
|
+
declare(path, name, ref) {
|
|
2825
|
+
const normalized = posix3.normalize(path);
|
|
2826
|
+
const names = this.declarations.get(normalized) ?? new Map;
|
|
2827
|
+
if (!names.has(name))
|
|
2828
|
+
names.set(name, ref);
|
|
2829
|
+
this.declarations.set(normalized, names);
|
|
2830
|
+
}
|
|
2831
|
+
declarationOf(path, name, visited) {
|
|
2832
|
+
if (visited.has(path))
|
|
2833
|
+
return null;
|
|
2834
|
+
visited.add(path);
|
|
2835
|
+
const { declared, reexports } = this.exportsOf(path);
|
|
2836
|
+
const local = declared.find((entry) => entry.as === name);
|
|
2837
|
+
if (local)
|
|
2838
|
+
return this.declarations.get(path)?.get(local.name) ?? null;
|
|
2839
|
+
for (const { specifier, names } of reexports) {
|
|
2840
|
+
const handed = names === null ? name : names.find((entry) => entry.as === name)?.name;
|
|
2841
|
+
if (handed === undefined)
|
|
2842
|
+
continue;
|
|
2843
|
+
const target = this.resolveImport(specifier, path);
|
|
2844
|
+
const found = target === null ? null : this.declarationOf(target, handed, visited);
|
|
2845
|
+
if (found !== null)
|
|
2846
|
+
return found;
|
|
2847
|
+
}
|
|
2848
|
+
return this.entries.get(path)?.syntax?.readExports === undefined ? this.declarations.get(path)?.get(name) ?? null : null;
|
|
2849
|
+
}
|
|
2769
2850
|
tracksImports(path) {
|
|
2770
2851
|
return (this.entries.get(posix3.normalize(path))?.syntax ?? null) !== null;
|
|
2771
2852
|
}
|
|
@@ -2811,7 +2892,7 @@ var C_INCLUDES = {
|
|
|
2811
2892
|
if (!path)
|
|
2812
2893
|
return [];
|
|
2813
2894
|
const text = path.type === "system_lib_string" ? path.text.slice(1, -1) : path.text.replace(/^"|"$/g, "");
|
|
2814
|
-
return text ? [[text]] : [];
|
|
2895
|
+
return text ? [{ targets: [{ specifier: text, whole: true, names: [] }], line: include.startPosition.row + 1 }] : [];
|
|
2815
2896
|
}),
|
|
2816
2897
|
resolve: (specifier, from, files) => {
|
|
2817
2898
|
const local = besides(from, specifier);
|
|
@@ -2832,7 +2913,7 @@ var ECMASCRIPT_IMPORTS = {
|
|
|
2832
2913
|
const source = node.type === "call_expression" ? dynamicSource(node) : node.childForFieldName("source") ?? requireClause(node)?.childForFieldName("source") ?? null;
|
|
2833
2914
|
const text = source?.namedChildren.find((child) => child?.type === "string_fragment")?.text;
|
|
2834
2915
|
if (text)
|
|
2835
|
-
requests.push([text]);
|
|
2916
|
+
requests.push({ targets: [{ specifier: text, ...takenFrom(node) }], line: node.startPosition.row + 1 });
|
|
2836
2917
|
}
|
|
2837
2918
|
return requests;
|
|
2838
2919
|
},
|
|
@@ -2877,6 +2958,20 @@ function packageBases(specifier, files) {
|
|
|
2877
2958
|
const rest = segments.slice(nameLength).join("/") || "index";
|
|
2878
2959
|
return [posix3.join(directory, "src", rest), posix3.join(directory, rest)];
|
|
2879
2960
|
}
|
|
2961
|
+
function takenFrom(statement) {
|
|
2962
|
+
if (statement.type === "call_expression" || requireClause(statement) !== null)
|
|
2963
|
+
return { whole: true, names: [] };
|
|
2964
|
+
const clause = statement.namedChildren.find((child) => child?.type === "import_clause" || child?.type === "export_clause");
|
|
2965
|
+
if (!clause)
|
|
2966
|
+
return { whole: true, names: [] };
|
|
2967
|
+
const specifier = clause.type === "import_clause" ? "import_specifier" : "export_specifier";
|
|
2968
|
+
const names = clause.descendantsOfType(specifier).flatMap((entry) => {
|
|
2969
|
+
const name = entry?.childForFieldName("name")?.text;
|
|
2970
|
+
return name === undefined ? [] : [name];
|
|
2971
|
+
});
|
|
2972
|
+
const whole = clause.namedChildren.some((child) => child?.type === "identifier" || child?.type === "namespace_import");
|
|
2973
|
+
return { whole, names };
|
|
2974
|
+
}
|
|
2880
2975
|
function requireClause(statement) {
|
|
2881
2976
|
return statement.namedChildren.find((child) => child?.type === "import_require_clause") ?? null;
|
|
2882
2977
|
}
|
|
@@ -2897,15 +2992,22 @@ var PYTHON_IMPORTS = {
|
|
|
2897
2992
|
const dotted = name?.type === "aliased_import" ? name.childForFieldName("name") : name;
|
|
2898
2993
|
return dotted ? [dotted.text] : [];
|
|
2899
2994
|
});
|
|
2995
|
+
const line = node.startPosition.row + 1;
|
|
2900
2996
|
if (node.type === "import_statement") {
|
|
2901
|
-
requests.push(...names.map((name) => [name]));
|
|
2997
|
+
requests.push(...names.map((name) => ({ targets: [{ specifier: name, whole: true, names: [] }], line })));
|
|
2902
2998
|
continue;
|
|
2903
2999
|
}
|
|
2904
3000
|
const module = node.childForFieldName("module_name")?.text;
|
|
2905
3001
|
if (!module)
|
|
2906
3002
|
continue;
|
|
2907
3003
|
const separator = module.endsWith(".") ? "" : ".";
|
|
2908
|
-
requests.push(...names.length > 0 ? names.map((name) =>
|
|
3004
|
+
requests.push(...names.length > 0 ? names.map((name) => ({
|
|
3005
|
+
targets: [
|
|
3006
|
+
{ specifier: `${module}${separator}${name}`, whole: true, names: [] },
|
|
3007
|
+
{ specifier: module, whole: false, names: [name] }
|
|
3008
|
+
],
|
|
3009
|
+
line
|
|
3010
|
+
})) : [{ targets: [{ specifier: module, whole: true, names: [] }], line }]);
|
|
2909
3011
|
}
|
|
2910
3012
|
return requests;
|
|
2911
3013
|
},
|
|
@@ -3954,11 +4056,8 @@ class CodePlanner {
|
|
|
3954
4056
|
return ref;
|
|
3955
4057
|
}
|
|
3956
4058
|
linkImports(from, path) {
|
|
3957
|
-
for (const
|
|
3958
|
-
|
|
3959
|
-
if (ref !== undefined)
|
|
3960
|
-
this.graph.link(from, "imports", ref);
|
|
3961
|
-
}
|
|
4059
|
+
for (const { to, lines } of this.sources.importLinksOf(path))
|
|
4060
|
+
this.graph.link(from, "imports", to, lines);
|
|
3962
4061
|
}
|
|
3963
4062
|
async extract(file) {
|
|
3964
4063
|
if (!this.registry.supports(file.path))
|
|
@@ -4064,6 +4163,7 @@ function terminate(style, statement) {
|
|
|
4064
4163
|
|
|
4065
4164
|
// src/scanner/code/symbols.ts
|
|
4066
4165
|
var CONSTRUCTIBLE2 = new Set(["class", "struct"]);
|
|
4166
|
+
var TEST_FILE = /(?:^|\/)(?:tests?|__tests__)\/|[._]test\.\w+$|\.spec\.\w+$|(?:^|\/)test_[^/]*\.py$/;
|
|
4067
4167
|
|
|
4068
4168
|
class SymbolPlanner {
|
|
4069
4169
|
graph;
|
|
@@ -4109,16 +4209,23 @@ class SymbolPlanner {
|
|
|
4109
4209
|
const isConstructor = scope.parent !== null && spec.constructorNames(scope.parent.name).includes(symbol.name);
|
|
4110
4210
|
const ownerDoc = isConstructor ? parseDoc(spec.docStyle, scope.parent?.comment ?? "") : null;
|
|
4111
4211
|
const family = familyOf(spec);
|
|
4212
|
+
const parameters = plannedParameters(symbol, doc, ownerDoc);
|
|
4213
|
+
const fields = plannedFields(symbol, doc, spec);
|
|
4112
4214
|
const typeScope = {
|
|
4113
4215
|
path: file.path,
|
|
4114
4216
|
family,
|
|
4115
4217
|
typeParameters: new Set([...scope.typeParameters, ...symbol.typeParameters]),
|
|
4116
4218
|
namespaceSeparator: spec.namespaceSeparator,
|
|
4117
|
-
sees: (path) => this.sources.sees(file.path, path)
|
|
4219
|
+
sees: (path) => this.sources.sees(file.path, path),
|
|
4220
|
+
admits: (declaration) => this.admits(file.path, declaration),
|
|
4221
|
+
locals: new Set([...parameters, ...fields].map((declaration) => declaration.name))
|
|
4118
4222
|
};
|
|
4119
4223
|
const ref = this.graph.add(symbol.kind, { name: symbol.name, parent: scope.parentRef, anchor }, (self) => this.symbolContent(symbol, doc, scope, typeScope, self));
|
|
4120
4224
|
const privateFile = spec.isPrivateFile?.(file.path) ?? false;
|
|
4121
|
-
const
|
|
4225
|
+
const exported = this.sources.exportsName(file.path, scope.owners[0] ?? symbol.name);
|
|
4226
|
+
const site = { ref, path: file.path, family, owners: scope.owners, privateFile, exported };
|
|
4227
|
+
if (scope.parent === null)
|
|
4228
|
+
this.sources.declare(file.path, symbol.name, ref);
|
|
4122
4229
|
if (TYPE_KINDS.has(symbol.kind) && !symbol.inferred) {
|
|
4123
4230
|
const outOfLine = scope.parent !== null && scope.parent.inferred && TYPE_KINDS.has(scope.parent.kind);
|
|
4124
4231
|
this.types.register(symbol.name, { ...site, local: privateFile && !outOfLine });
|
|
@@ -4130,10 +4237,10 @@ class SymbolPlanner {
|
|
|
4130
4237
|
if (symbol.accessor)
|
|
4131
4238
|
this.calls.registerAccessor(ref);
|
|
4132
4239
|
}
|
|
4133
|
-
for (const parameter of
|
|
4240
|
+
for (const parameter of parameters) {
|
|
4134
4241
|
this.graph.add("parameter", { name: parameter.name, parent: ref }, (self) => this.declarationContent(parameter, self, file, typeScope));
|
|
4135
4242
|
}
|
|
4136
|
-
for (const field of
|
|
4243
|
+
for (const field of fields) {
|
|
4137
4244
|
this.graph.add("field", { name: field.name, parent: ref }, async (self) => ({
|
|
4138
4245
|
...await this.declarationContent(field, self, file, typeScope),
|
|
4139
4246
|
annotations: field.annotations
|
|
@@ -4152,6 +4259,11 @@ class SymbolPlanner {
|
|
|
4152
4259
|
for (const child of symbol.children)
|
|
4153
4260
|
this.planSymbol(child, inner);
|
|
4154
4261
|
}
|
|
4262
|
+
admits(from, declaration) {
|
|
4263
|
+
if (TEST_FILE.test(declaration.path) && !TEST_FILE.test(from))
|
|
4264
|
+
return false;
|
|
4265
|
+
return declaration.exported || this.sources.packageOf(declaration.path) === this.sources.packageOf(from);
|
|
4266
|
+
}
|
|
4155
4267
|
async symbolContent(symbol, doc, scope, typeScope, self) {
|
|
4156
4268
|
const { spec } = scope.file;
|
|
4157
4269
|
if (symbol.heritage)
|
|
@@ -4953,8 +5065,10 @@ class GraphBuilder {
|
|
|
4953
5065
|
const route = routeOf(this.identities, ref);
|
|
4954
5066
|
return hrefOf(fragment ? { path: route.path, hash: fragment } : route);
|
|
4955
5067
|
}
|
|
4956
|
-
link(from, kind, to) {
|
|
4957
|
-
|
|
5068
|
+
link(from, kind, to, lines = []) {
|
|
5069
|
+
const key = `${from}:${kind}:${to}`;
|
|
5070
|
+
const merged = [...new Set([...this.edges.get(key)?.[3] ?? [], ...lines])].sort((a, b) => a - b);
|
|
5071
|
+
this.edges.set(key, merged.length > 0 ? [from, kind, to, merged] : [from, kind, to]);
|
|
4958
5072
|
}
|
|
4959
5073
|
async build() {
|
|
4960
5074
|
this.keepOnePackagePerDirectory();
|
|
@@ -8257,7 +8371,7 @@ async function prepareOutputDir(repo) {
|
|
|
8257
8371
|
// package.json
|
|
8258
8372
|
var package_default = {
|
|
8259
8373
|
name: "@g3nomaly/analyze",
|
|
8260
|
-
version: "0.1.
|
|
8374
|
+
version: "0.1.7",
|
|
8261
8375
|
description: "Turns a repository into one offline HTML documentation site, and queries the same linked model from the terminal and from hooks.",
|
|
8262
8376
|
keywords: [
|
|
8263
8377
|
"documentation",
|
|
@@ -9410,19 +9524,51 @@ function stepsOf(graph, node, direction, kinds) {
|
|
|
9410
9524
|
const members = graph.children(node).filter((child) => isDeclarationKind(graph.node(child).kind));
|
|
9411
9525
|
const edges = [node, ...members].flatMap((end) => direction === "out" ? graph.edgesFrom(end) : graph.edgesTo(end)).filter((edge) => kinds.length === 0 || kinds.includes(edge[1]));
|
|
9412
9526
|
const end = direction === "out" ? 2 : 0;
|
|
9413
|
-
const
|
|
9414
|
-
|
|
9527
|
+
const steps = new Map;
|
|
9528
|
+
for (const edge of edges) {
|
|
9415
9529
|
const { owner } = ownerOf(graph, edge[end]);
|
|
9416
9530
|
const via = edge[1] === "type" ? ownerOf(graph, edge[0]).via : null;
|
|
9417
9531
|
const key = `${edge[1]} ${owner} ${via ?? ""}`;
|
|
9418
|
-
|
|
9419
|
-
|
|
9420
|
-
|
|
9421
|
-
|
|
9422
|
-
|
|
9532
|
+
const known = steps.get(key);
|
|
9533
|
+
const lines = [...new Set([...known?.lines ?? [], ...edge[3] ?? []])].sort((a, b) => a - b);
|
|
9534
|
+
steps.set(key, { relation: edge[1], node: owner, via, source: known?.source ?? edge[0], lines });
|
|
9535
|
+
}
|
|
9536
|
+
return [...steps.values()];
|
|
9537
|
+
}
|
|
9538
|
+
function fileOf(graph, node) {
|
|
9539
|
+
for (const ref of [node, ...graph.ancestors(node).reverse()]) {
|
|
9540
|
+
const value = graph.node(ref);
|
|
9541
|
+
if (value.kind === "source" || isFileNode(value))
|
|
9542
|
+
return ref;
|
|
9543
|
+
}
|
|
9544
|
+
return node;
|
|
9545
|
+
}
|
|
9546
|
+
function fileStepsOf(graph, file, direction, kinds) {
|
|
9547
|
+
const end = direction === "out" ? 2 : 0;
|
|
9548
|
+
const steps = new Map;
|
|
9549
|
+
for (const inner of [file, ...graph.descendants(file)]) {
|
|
9550
|
+
for (const edge of direction === "out" ? graph.edgesFrom(inner) : graph.edgesTo(inner)) {
|
|
9551
|
+
if (kinds.length > 0 && !kinds.includes(edge[1]))
|
|
9552
|
+
continue;
|
|
9553
|
+
const other = fileOf(graph, edge[end]);
|
|
9554
|
+
if (other === file)
|
|
9555
|
+
continue;
|
|
9556
|
+
const key = `${edge[1]} ${other}`;
|
|
9557
|
+
const known = steps.get(key);
|
|
9558
|
+
const lines = [...new Set([...known?.lines ?? [], ...edge[3] ?? []])].sort((a, b) => a - b);
|
|
9559
|
+
steps.set(key, { relation: edge[1], node: other, via: null, source: known?.source ?? edge[0], lines });
|
|
9560
|
+
}
|
|
9561
|
+
}
|
|
9562
|
+
return [...steps.values()];
|
|
9423
9563
|
}
|
|
9424
9564
|
function referenceOf(graph, step) {
|
|
9425
|
-
|
|
9565
|
+
const path = step.lines.length > 0 ? pathOf(graph, step.source) : null;
|
|
9566
|
+
return {
|
|
9567
|
+
relation: step.relation,
|
|
9568
|
+
node: summarize(graph, step.node),
|
|
9569
|
+
via: step.via,
|
|
9570
|
+
location: path === null ? null : { path, lines: step.lines }
|
|
9571
|
+
};
|
|
9426
9572
|
}
|
|
9427
9573
|
function referencesOf(graph, node, options) {
|
|
9428
9574
|
const { direction, kinds } = options;
|
|
@@ -9520,7 +9666,9 @@ function describeNode(graph, node) {
|
|
|
9520
9666
|
};
|
|
9521
9667
|
}
|
|
9522
9668
|
// src/retrieval/walk.ts
|
|
9669
|
+
init_model();
|
|
9523
9670
|
var DEFAULT_WALK_LIMIT = 50;
|
|
9671
|
+
var WALK_LEVELS = ["entries", "files"];
|
|
9524
9672
|
function walkReferences(graph, start, options) {
|
|
9525
9673
|
const directions = ["out", "in"].filter((direction) => options.direction === "both" || options.direction === direction).map((direction) => {
|
|
9526
9674
|
const root = [];
|
|
@@ -9529,7 +9677,7 @@ function walkReferences(graph, start, options) {
|
|
|
9529
9677
|
let left = options.limit;
|
|
9530
9678
|
let omitted = 0;
|
|
9531
9679
|
for (let hop = 1;hop <= options.depth; hop++) {
|
|
9532
|
-
const candidates = directions.map((walk) => walk.frontier.flatMap(({ node, into }) => stepsOf(graph, node, walk.direction, options.kinds).map((step) => ({ step, into }))));
|
|
9680
|
+
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 }))));
|
|
9533
9681
|
const frontiers = directions.map(() => []);
|
|
9534
9682
|
const longest = Math.max(0, ...candidates.map((list) => list.length));
|
|
9535
9683
|
for (let index = 0;index < longest; index++) {
|
|
@@ -9563,19 +9711,27 @@ function walkReferences(graph, start, options) {
|
|
|
9563
9711
|
omitted
|
|
9564
9712
|
};
|
|
9565
9713
|
}
|
|
9566
|
-
function shortestPath(graph, from, to, kinds) {
|
|
9567
|
-
const
|
|
9714
|
+
function shortestPath(graph, from, to, kinds, includeMentions = false) {
|
|
9715
|
+
const follows = kinds.length > 0 || includeMentions ? kinds : EDGE_KINDS.filter((kind) => kind !== "mentions");
|
|
9716
|
+
const reached = new Map([
|
|
9717
|
+
[from, null]
|
|
9718
|
+
]);
|
|
9568
9719
|
for (let frontier = [from];frontier.length > 0 && !reached.has(to); ) {
|
|
9569
|
-
const
|
|
9720
|
+
const hop = new Map;
|
|
9570
9721
|
for (const node of frontier) {
|
|
9571
|
-
|
|
9722
|
+
const before = reached.get(node)?.mentions ?? 0;
|
|
9723
|
+
for (const step of stepsOf(graph, node, "out", follows)) {
|
|
9572
9724
|
if (reached.has(step.node))
|
|
9573
9725
|
continue;
|
|
9574
|
-
|
|
9575
|
-
|
|
9726
|
+
const mentions = before + (step.relation === "mentions" ? 1 : 0);
|
|
9727
|
+
const known = hop.get(step.node);
|
|
9728
|
+
if (known === undefined || mentions < known.mentions)
|
|
9729
|
+
hop.set(step.node, { previous: node, step, mentions });
|
|
9576
9730
|
}
|
|
9577
9731
|
}
|
|
9578
|
-
|
|
9732
|
+
for (const [node, way] of hop)
|
|
9733
|
+
reached.set(node, way);
|
|
9734
|
+
frontier = [...hop.keys()];
|
|
9579
9735
|
}
|
|
9580
9736
|
if (!reached.has(to))
|
|
9581
9737
|
return null;
|
|
@@ -9970,8 +10126,11 @@ class Retriever {
|
|
|
9970
10126
|
walk(node, options) {
|
|
9971
10127
|
return walkReferences(this.graph, node, options);
|
|
9972
10128
|
}
|
|
9973
|
-
|
|
9974
|
-
return
|
|
10129
|
+
fileOf(node) {
|
|
10130
|
+
return fileOf(this.graph, node);
|
|
10131
|
+
}
|
|
10132
|
+
path(from, to, kinds, includeMentions = false) {
|
|
10133
|
+
return shortestPath(this.graph, from, to, kinds, includeMentions);
|
|
9975
10134
|
}
|
|
9976
10135
|
exportGraph(options) {
|
|
9977
10136
|
return exportGraph(this.graph, options);
|
|
@@ -10287,9 +10446,15 @@ ${wrap2(ALL_NODE_KINDS.join(", "), 21)}
|
|
|
10287
10446
|
--outgoing refs: only what the node links to.
|
|
10288
10447
|
--depth <n> refs: hops to follow from the node. Default: 1.
|
|
10289
10448
|
--edge <kind> refs, path, graph: only edges of this kind, from the list below. Repeatable.
|
|
10449
|
+
path follows every kind but mentions when no --edge is given.
|
|
10450
|
+
--include-mentions path: also follow mentions, the links descriptions make. Of equally
|
|
10451
|
+
short chains, the one with the fewest mentions still wins.
|
|
10290
10452
|
--level <level> graph: ${either(EXPORT_LEVELS)}. Default: ${DEFAULT_EXPORT_LEVEL}. packages and
|
|
10291
10453
|
communities sum the edges between packages or communities; files, entries
|
|
10292
10454
|
and all are the levels of the graph page of the site.
|
|
10455
|
+
refs: ${either(WALK_LEVELS)}. Default: entries. files lists the links between
|
|
10456
|
+
the file of the node and other files, as the files level of the graph page
|
|
10457
|
+
draws them.
|
|
10293
10458
|
|
|
10294
10459
|
Environment: ANALYZE_REPO, ANALYZE_TITLE, ANALYZE_OUT, ANALYZE_SKILLS, ANALYZE_DOCS,
|
|
10295
10460
|
ANALYZE_EXCLUDE, ANALYZE_EMBEDDINGS (on or off) and ANALYZE_FROM set the same options. A
|
|
@@ -10312,7 +10477,8 @@ var FLAG_COMMANDS = {
|
|
|
10312
10477
|
outgoing: ["refs"],
|
|
10313
10478
|
depth: ["refs"],
|
|
10314
10479
|
edge: ["refs", "path", "graph"],
|
|
10315
|
-
|
|
10480
|
+
"include-mentions": ["path"],
|
|
10481
|
+
level: ["graph", "refs"]
|
|
10316
10482
|
};
|
|
10317
10483
|
var SCAN_FLAGS = ["repo", "skills", "docs", "exclude", "no-embeddings", "no-cache"];
|
|
10318
10484
|
function expandHome(path) {
|
|
@@ -10419,7 +10585,7 @@ function parseInvocation(argv, env, cwd) {
|
|
|
10419
10585
|
throw new UsageError("path takes two nodes. Run: g3n-analyze path <from> <to>, with quotes around a name with spaces.");
|
|
10420
10586
|
}
|
|
10421
10587
|
const [start = "", end = ""] = words;
|
|
10422
|
-
return { ...base, kind: "path", start, end, edges };
|
|
10588
|
+
return { ...base, kind: "path", start, end, edges, includeMentions: values["include-mentions"] === true };
|
|
10423
10589
|
}
|
|
10424
10590
|
if (text.length === 0)
|
|
10425
10591
|
throw new UsageError(`Missing node. Run: g3n-analyze ${command} <node>`);
|
|
@@ -10435,7 +10601,8 @@ function parseInvocation(argv, env, cwd) {
|
|
|
10435
10601
|
direction: values.incoming ? "in" : values.outgoing ? "out" : "both",
|
|
10436
10602
|
kinds: edges,
|
|
10437
10603
|
depth: countOf(values.depth, "depth") ?? 1,
|
|
10438
|
-
limit: countOf(values.limit, "limit") ?? DEFAULT_WALK_LIMIT
|
|
10604
|
+
limit: countOf(values.limit, "limit") ?? DEFAULT_WALK_LIMIT,
|
|
10605
|
+
level: values.level === undefined ? "entries" : oneOf(values.level, WALK_LEVELS, "level")
|
|
10439
10606
|
}
|
|
10440
10607
|
};
|
|
10441
10608
|
}
|
|
@@ -10475,6 +10642,7 @@ function parse(argv) {
|
|
|
10475
10642
|
depth: { type: "string" },
|
|
10476
10643
|
edge: { type: "string", multiple: true },
|
|
10477
10644
|
level: { type: "string" },
|
|
10645
|
+
"include-mentions": { type: "boolean" },
|
|
10478
10646
|
help: { type: "boolean", short: "h" },
|
|
10479
10647
|
version: { type: "boolean", short: "v" }
|
|
10480
10648
|
}
|
|
@@ -10536,16 +10704,23 @@ var pathSchema = z11.strictObject({
|
|
|
10536
10704
|
...warningShape,
|
|
10537
10705
|
start: z11.string().trim().min(1),
|
|
10538
10706
|
end: z11.string().trim().min(1),
|
|
10539
|
-
edges: z11.array(z11.enum(EDGE_KINDS)).readonly().optional()
|
|
10707
|
+
edges: z11.array(z11.enum(EDGE_KINDS)).readonly().optional(),
|
|
10708
|
+
includeMentions: z11.boolean().optional()
|
|
10540
10709
|
}).superRefine(checkSource).transform((fields) => ({
|
|
10541
10710
|
source: sourceOf(fields),
|
|
10542
10711
|
reporter: reporterOf(fields.onWarning),
|
|
10543
|
-
request: {
|
|
10712
|
+
request: {
|
|
10713
|
+
start: fields.start,
|
|
10714
|
+
end: fields.end,
|
|
10715
|
+
filter: filterOf(fields),
|
|
10716
|
+
kinds: fields.edges ?? [],
|
|
10717
|
+
includeMentions: fields.includeMentions ?? false
|
|
10718
|
+
}
|
|
10544
10719
|
}));
|
|
10545
10720
|
function pathIn(retriever, request) {
|
|
10546
10721
|
const start = nodeOf(retriever, request.start, request.filter);
|
|
10547
10722
|
const end = nodeOf(retriever, request.end, request.filter);
|
|
10548
|
-
const steps = retriever.path(start, end, request.kinds);
|
|
10723
|
+
const steps = retriever.path(start, end, request.kinds, request.includeMentions);
|
|
10549
10724
|
if (steps === null) {
|
|
10550
10725
|
const edges = request.kinds.length > 0 ? ` along ${request.kinds.join(", ")} edges` : "";
|
|
10551
10726
|
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.`);
|
|
@@ -10569,7 +10744,8 @@ var refsSchema = z12.strictObject({
|
|
|
10569
10744
|
outgoing: z12.boolean().optional(),
|
|
10570
10745
|
edges: z12.array(z12.enum(EDGE_KINDS)).readonly().optional(),
|
|
10571
10746
|
depth: z12.number().int().positive().optional(),
|
|
10572
|
-
limit: z12.number().int().positive().optional()
|
|
10747
|
+
limit: z12.number().int().positive().optional(),
|
|
10748
|
+
level: z12.enum(WALK_LEVELS).optional()
|
|
10573
10749
|
}).superRefine(checkSource).refine((fields) => !(fields.incoming && fields.outgoing), {
|
|
10574
10750
|
message: "incoming and outgoing exclude each other",
|
|
10575
10751
|
path: ["outgoing"]
|
|
@@ -10583,12 +10759,14 @@ var refsSchema = z12.strictObject({
|
|
|
10583
10759
|
direction: fields.incoming ? "in" : fields.outgoing ? "out" : "both",
|
|
10584
10760
|
kinds: fields.edges ?? [],
|
|
10585
10761
|
depth: fields.depth ?? 1,
|
|
10586
|
-
limit: fields.limit ?? DEFAULT_WALK_LIMIT
|
|
10762
|
+
limit: fields.limit ?? DEFAULT_WALK_LIMIT,
|
|
10763
|
+
level: fields.level ?? "entries"
|
|
10587
10764
|
}
|
|
10588
10765
|
}
|
|
10589
10766
|
}));
|
|
10590
10767
|
function refsIn(retriever, request) {
|
|
10591
|
-
const
|
|
10768
|
+
const target = nodeOf(retriever, request.target, request.filter);
|
|
10769
|
+
const node = request.walk.level === "files" ? retriever.fileOf(target) : target;
|
|
10592
10770
|
return { node: retriever.summarize(node), ...retriever.walk(node, request.walk) };
|
|
10593
10771
|
}
|
|
10594
10772
|
async function refs(options) {
|
|
@@ -10618,6 +10796,7 @@ async function show(options) {
|
|
|
10618
10796
|
|
|
10619
10797
|
// src/cli/render-text.ts
|
|
10620
10798
|
init_model();
|
|
10799
|
+
import { posix as posix26 } from "path";
|
|
10621
10800
|
function placeOf(node) {
|
|
10622
10801
|
if (node.path === null)
|
|
10623
10802
|
return "";
|
|
@@ -10650,8 +10829,13 @@ function summaryLine(node) {
|
|
|
10650
10829
|
return node.summary ? `${head}: ${node.summary}` : head;
|
|
10651
10830
|
}
|
|
10652
10831
|
function referenceLine(reference) {
|
|
10653
|
-
const {
|
|
10654
|
-
return `- ${
|
|
10832
|
+
const { node } = reference;
|
|
10833
|
+
return `- ${relationText(reference)}: ${labelOf(node)} (\`${node.id}\`)`;
|
|
10834
|
+
}
|
|
10835
|
+
function relationText({ relation, via, location }) {
|
|
10836
|
+
const through = via === null ? "" : ` via \`${via}\``;
|
|
10837
|
+
const at = location === null ? "" : ` (${posix26.basename(location.path)}:${location.lines.join(", ")})`;
|
|
10838
|
+
return `${relation}${through}${at}`;
|
|
10655
10839
|
}
|
|
10656
10840
|
function section(title, lines) {
|
|
10657
10841
|
return lines.length === 0 ? [] : [`## ${title}`, lines.join(`
|
|
@@ -10671,15 +10855,22 @@ function referenceSections(references) {
|
|
|
10671
10855
|
...section(`Linked from (${references.linkedFrom.length})`, references.linkedFrom.map(referenceLine))
|
|
10672
10856
|
];
|
|
10673
10857
|
}
|
|
10674
|
-
function
|
|
10675
|
-
|
|
10676
|
-
|
|
10677
|
-
|
|
10678
|
-
return
|
|
10858
|
+
function listLines(references, depth) {
|
|
10859
|
+
const byNode = new Map;
|
|
10860
|
+
for (const reference of references)
|
|
10861
|
+
byNode.set(reference.node.id, [...byNode.get(reference.node.id) ?? [], reference]);
|
|
10862
|
+
return [...byNode.values()].flatMap((group) => {
|
|
10863
|
+
const [first] = group;
|
|
10864
|
+
if (!first)
|
|
10865
|
+
return [];
|
|
10866
|
+
const relations = group.map(relationText).join(", ");
|
|
10867
|
+
const line = `${" ".repeat(depth)}- ${relations}: ${labelOf(first.node)} (\`${first.node.id}\`)`;
|
|
10868
|
+
return [line, ...listLines(group.flatMap((reference) => reference.next), depth + 1)];
|
|
10869
|
+
});
|
|
10679
10870
|
}
|
|
10680
10871
|
function walkSection(title, references) {
|
|
10681
|
-
const
|
|
10682
|
-
return section(`${title} (${
|
|
10872
|
+
const lines = listLines(references, 0);
|
|
10873
|
+
return section(`${title} (${lines.length})`, lines);
|
|
10683
10874
|
}
|
|
10684
10875
|
function contentText(content) {
|
|
10685
10876
|
return content.map(({ mediaType, schema }) => schema === null ? mediaType : `${mediaType}: \`${schema}\``).join(", ");
|
|
@@ -10806,7 +10997,7 @@ function renderPath(result) {
|
|
|
10806
10997
|
const title = `# path ${labelOf(start)} -> ${labelOf(end)} (${steps.length} ${steps.length === 1 ? "step" : "steps"})`;
|
|
10807
10998
|
const lines = [
|
|
10808
10999
|
`${labelOf(start)} (\`${start.id}\`)`,
|
|
10809
|
-
...steps.map((
|
|
11000
|
+
...steps.map((step) => ` -${relationText(step)}-> ${labelOf(step.node)} (\`${step.node.id}\`)`)
|
|
10810
11001
|
];
|
|
10811
11002
|
return [title, lines.join(`
|
|
10812
11003
|
`)].join(`
|
|
@@ -10911,13 +11102,13 @@ async function runQuery(invocation, onWarning) {
|
|
|
10911
11102
|
const { target, walk } = invocation;
|
|
10912
11103
|
const incoming = walk.direction === "in";
|
|
10913
11104
|
const outgoing = walk.direction === "out";
|
|
10914
|
-
const { depth, limit } = walk;
|
|
10915
|
-
const result = await refs({ ...common, target, incoming, outgoing, edges: walk.kinds, depth, limit });
|
|
11105
|
+
const { depth, limit, level } = walk;
|
|
11106
|
+
const result = await refs({ ...common, target, incoming, outgoing, edges: walk.kinds, depth, limit, level });
|
|
10916
11107
|
return outputOf({ kind: "refs", result });
|
|
10917
11108
|
}
|
|
10918
11109
|
case "path": {
|
|
10919
|
-
const { start, end, edges } = invocation;
|
|
10920
|
-
return outputOf({ kind: "path", result: await path2({ ...common, start, end, edges }) });
|
|
11110
|
+
const { start, end, edges, includeMentions } = invocation;
|
|
11111
|
+
return outputOf({ kind: "path", result: await path2({ ...common, start, end, edges, includeMentions }) });
|
|
10921
11112
|
}
|
|
10922
11113
|
case "list":
|
|
10923
11114
|
return outputOf({ kind: "list", result: await list2({ ...common, limit: invocation.limit ?? undefined }) });
|