@jskit-ai/architecture-inspector 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +99 -10
- package/package.json +4 -2
- package/src/calls/cache.js +96 -0
- package/src/calls/evaluate.js +469 -0
- package/src/calls/features.js +273 -0
- package/src/calls/functions.js +204 -0
- package/src/calls/index.js +300 -0
- package/src/calls/program.js +143 -0
- package/src/calls/resolve.js +719 -0
- package/src/calls/symbols.js +178 -0
- package/src/calls/transports.js +194 -0
- package/src/cli.js +93 -14
- package/src/inspectVueArchitecture.js +457 -43
- package/src/previewLocator.js +30 -0
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
import ts from "typescript";
|
|
2
|
+
import { isFunctionLike, unwrapExpression } from "./functions.js";
|
|
3
|
+
|
|
4
|
+
const FEATURE_PACKAGE = "@jskit-ai/kernel";
|
|
5
|
+
const MAX_ACTION_DEPTH = 6;
|
|
6
|
+
|
|
7
|
+
function memberNamed(objectLiteral, name) {
|
|
8
|
+
return objectLiteral.properties.find((property) =>
|
|
9
|
+
property.name && !ts.isComputedPropertyName(property.name) && property.name.text === name);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function isActionObject(node) {
|
|
13
|
+
return Boolean(node) && ts.isObjectLiteralExpression(node) &&
|
|
14
|
+
Boolean(memberNamed(node, "id")) && Boolean(memberNamed(node, "execute"));
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// JSKIT's defineFeature dependency injection, read statically:
|
|
18
|
+
// requires/optional tokens, provides tokens, setup outputs and the actions()
|
|
19
|
+
// definitions. Feature setup and actions parameters are bound to the
|
|
20
|
+
// expressions their providers return. One provider is exact; several are
|
|
21
|
+
// ambiguous; none leaves the parameter unbound.
|
|
22
|
+
function bindFeatures({ calls, symbols, evaluator }) {
|
|
23
|
+
const features = [];
|
|
24
|
+
for (const call of calls) {
|
|
25
|
+
const callee = unwrapExpression(call.expression);
|
|
26
|
+
if (!ts.isIdentifier(callee) || !symbols.isImportOf(callee, FEATURE_PACKAGE, ["defineFeature"])) {
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
29
|
+
const definition = unwrapExpression(call.arguments[0]);
|
|
30
|
+
if (definition && ts.isObjectLiteralExpression(definition)) {
|
|
31
|
+
features.push(readFeature(definition));
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function memberFunctions(member) {
|
|
36
|
+
if (!member) {
|
|
37
|
+
return [];
|
|
38
|
+
}
|
|
39
|
+
if (isFunctionLike(member)) {
|
|
40
|
+
return [member];
|
|
41
|
+
}
|
|
42
|
+
if (ts.isPropertyAssignment(member)) {
|
|
43
|
+
return symbols.expressionImplementations(member.initializer, 0).filter(isFunctionLike);
|
|
44
|
+
}
|
|
45
|
+
if (ts.isShorthandPropertyAssignment(member)) {
|
|
46
|
+
return symbols.symbolImplementations(symbols.symbolAt(member.name)).filter(isFunctionLike);
|
|
47
|
+
}
|
|
48
|
+
return [];
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function tokens(member) {
|
|
52
|
+
const result = new Map();
|
|
53
|
+
const value = member && ts.isPropertyAssignment(member) ? unwrapExpression(member.initializer) : null;
|
|
54
|
+
if (!value || !ts.isObjectLiteralExpression(value)) {
|
|
55
|
+
return result;
|
|
56
|
+
}
|
|
57
|
+
for (const property of value.properties) {
|
|
58
|
+
const initializer = ts.isPropertyAssignment(property) ? property.initializer
|
|
59
|
+
: ts.isShorthandPropertyAssignment(property) ? property.name : null;
|
|
60
|
+
const token = initializer && evaluator.evaluateLiteral(initializer);
|
|
61
|
+
if (token && property.name && !ts.isComputedPropertyName(property.name)) {
|
|
62
|
+
result.set(property.name.text, token);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return result;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function returnedObject(expression) {
|
|
69
|
+
let value = unwrapExpression(expression);
|
|
70
|
+
if (value && ts.isIdentifier(value)) {
|
|
71
|
+
const declaration = symbols.declarationOf(value);
|
|
72
|
+
value = declaration && ts.isVariableDeclaration(declaration) ? unwrapExpression(declaration.initializer) : null;
|
|
73
|
+
}
|
|
74
|
+
return value && ts.isObjectLiteralExpression(value) ? value : null;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function setupOutputs(setups) {
|
|
78
|
+
const outputs = new Map();
|
|
79
|
+
for (const setup of setups) {
|
|
80
|
+
for (const expression of evaluator.functionReturns(setup)) {
|
|
81
|
+
const object = returnedObject(expression);
|
|
82
|
+
for (const property of object?.properties || []) {
|
|
83
|
+
if (ts.isShorthandPropertyAssignment(property)) {
|
|
84
|
+
outputs.set(property.name.text, [...(outputs.get(property.name.text) || []), property.name]);
|
|
85
|
+
} else if (ts.isPropertyAssignment(property) && !ts.isComputedPropertyName(property.name)) {
|
|
86
|
+
outputs.set(property.name.text, [...(outputs.get(property.name.text) || []), property.initializer]);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return outputs;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function readFeature(definition) {
|
|
95
|
+
const setups = memberFunctions(memberNamed(definition, "setup"));
|
|
96
|
+
const actionsMember = memberNamed(definition, "actions");
|
|
97
|
+
return {
|
|
98
|
+
definition,
|
|
99
|
+
requires: new Map([...tokens(memberNamed(definition, "optional")), ...tokens(memberNamed(definition, "requires"))]),
|
|
100
|
+
provides: tokens(memberNamed(definition, "provides")),
|
|
101
|
+
setups,
|
|
102
|
+
actionFunctions: memberFunctions(actionsMember),
|
|
103
|
+
actionsMember,
|
|
104
|
+
outputs: setupOutputs(setups)
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const providers = new Map();
|
|
109
|
+
for (const feature of features) {
|
|
110
|
+
for (const [name, token] of feature.provides) {
|
|
111
|
+
for (const expression of feature.outputs.get(name) || []) {
|
|
112
|
+
providers.set(token, [...(providers.get(token) || []), expression]);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const bindings = new Map();
|
|
118
|
+
function bindPattern(fn, valuesFor) {
|
|
119
|
+
const parameter = fn.parameters[0];
|
|
120
|
+
if (!parameter || !ts.isObjectBindingPattern(parameter.name)) {
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
for (const element of parameter.name.elements) {
|
|
124
|
+
const key = element.propertyName || element.name;
|
|
125
|
+
if (!ts.isIdentifier(key) || !ts.isIdentifier(element.name)) {
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
const expressions = valuesFor(key.text);
|
|
129
|
+
if (expressions.length) {
|
|
130
|
+
bindings.set(element, { expressions, exact: expressions.length === 1 });
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
for (const feature of features) {
|
|
135
|
+
const provided = (name) => providers.get(feature.requires.get(name)) || [];
|
|
136
|
+
for (const setup of feature.setups) {
|
|
137
|
+
bindPattern(setup, provided);
|
|
138
|
+
}
|
|
139
|
+
for (const actions of feature.actionFunctions) {
|
|
140
|
+
bindPattern(actions, (name) => feature.outputs.get(name) || provided(name));
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
return { features, bindings, actions: collectFeatureActions(features, { symbols, evaluator }) };
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Action definitions inside each feature's actions() result: object literals
|
|
148
|
+
// with `id` and `execute`, possibly wrapped by a pass-through call, through
|
|
149
|
+
// arrays, spreads, project factory functions and literal-array `.map`.
|
|
150
|
+
function collectFeatureActions(features, { symbols, evaluator }) {
|
|
151
|
+
const found = [];
|
|
152
|
+
const seenFunctions = new Set();
|
|
153
|
+
|
|
154
|
+
function collect(node, indexes, depth) {
|
|
155
|
+
const value = unwrapExpression(node);
|
|
156
|
+
if (!value || depth > MAX_ACTION_DEPTH) {
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
if (ts.isObjectLiteralExpression(value)) {
|
|
160
|
+
if (isActionObject(value)) {
|
|
161
|
+
found.push({ object: value, indexes });
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
for (const property of value.properties) {
|
|
165
|
+
if (ts.isSpreadAssignment(property)) {
|
|
166
|
+
collect(property.expression, indexes, depth + 1);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
if (ts.isArrayLiteralExpression(value)) {
|
|
172
|
+
for (const element of value.elements) {
|
|
173
|
+
collect(ts.isSpreadElement(element) ? element.expression : element, indexes, depth + 1);
|
|
174
|
+
}
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
if (ts.isConditionalExpression(value)) {
|
|
178
|
+
collect(value.whenTrue, indexes, depth + 1);
|
|
179
|
+
collect(value.whenFalse, indexes, depth + 1);
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
if (isFunctionLike(value)) {
|
|
183
|
+
for (const expression of evaluator.functionReturns(value)) {
|
|
184
|
+
collect(expression, indexes, depth + 1);
|
|
185
|
+
}
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
if (ts.isIdentifier(value)) {
|
|
189
|
+
const declaration = symbols.declarationOf(value);
|
|
190
|
+
if (declaration && ts.isVariableDeclaration(declaration) && declaration.initializer) {
|
|
191
|
+
collect(declaration.initializer, indexes, depth + 1);
|
|
192
|
+
}
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
if (ts.isCallExpression(value)) {
|
|
196
|
+
collectCall(value, indexes, depth);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function collectCall(call, indexes, depth) {
|
|
201
|
+
const passedThrough = call.arguments.map(unwrapExpression).filter(isActionObject);
|
|
202
|
+
if (passedThrough.length) {
|
|
203
|
+
for (const object of passedThrough) {
|
|
204
|
+
found.push({ object, indexes });
|
|
205
|
+
}
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
const callee = unwrapExpression(call.expression);
|
|
209
|
+
const callback = unwrapExpression(call.arguments[0]);
|
|
210
|
+
if (ts.isPropertyAccessExpression(callee) && callee.name.text === "map" && isFunctionLike(callback)) {
|
|
211
|
+
const elements = evaluator.literalArray(callee.expression);
|
|
212
|
+
const returns = evaluator.functionReturns(callback);
|
|
213
|
+
if (!elements) {
|
|
214
|
+
returns.forEach((expression) => collect(expression, indexes, depth + 1));
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
elements.forEach((_, index) => {
|
|
218
|
+
const elementIndexes = new Map(indexes).set(callback, index);
|
|
219
|
+
returns.forEach((expression) => collect(expression, elementIndexes, depth + 1));
|
|
220
|
+
});
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
const implementations = symbols.expressionImplementations(callee, 0).filter(isFunctionLike);
|
|
224
|
+
if (implementations.length) {
|
|
225
|
+
for (const fn of implementations) {
|
|
226
|
+
if (!seenFunctions.has(fn)) {
|
|
227
|
+
seenFunctions.add(fn);
|
|
228
|
+
collect(fn, indexes, depth + 1);
|
|
229
|
+
seenFunctions.delete(fn);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
if (ts.isPropertyAccessExpression(callee)) {
|
|
235
|
+
collect(callee.expression, indexes, depth + 1);
|
|
236
|
+
}
|
|
237
|
+
for (const argument of call.arguments) {
|
|
238
|
+
collect(argument, indexes, depth + 1);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
const actions = [];
|
|
243
|
+
const executeFunctions = (member) => {
|
|
244
|
+
if (isFunctionLike(member)) {
|
|
245
|
+
return [member];
|
|
246
|
+
}
|
|
247
|
+
if (ts.isPropertyAssignment(member)) {
|
|
248
|
+
return symbols.expressionImplementations(member.initializer, 0).filter(isFunctionLike);
|
|
249
|
+
}
|
|
250
|
+
if (ts.isShorthandPropertyAssignment(member)) {
|
|
251
|
+
return symbols.symbolImplementations(symbols.symbolAt(member.name)).filter(isFunctionLike);
|
|
252
|
+
}
|
|
253
|
+
return [];
|
|
254
|
+
};
|
|
255
|
+
for (const feature of features) {
|
|
256
|
+
found.length = 0;
|
|
257
|
+
if (feature.actionFunctions.length) {
|
|
258
|
+
feature.actionFunctions.forEach((fn) => collect(fn, new Map(), 0));
|
|
259
|
+
} else if (feature.actionsMember && ts.isPropertyAssignment(feature.actionsMember)) {
|
|
260
|
+
collect(feature.actionsMember.initializer, new Map(), 0);
|
|
261
|
+
}
|
|
262
|
+
for (const { object, indexes } of found) {
|
|
263
|
+
const idMember = memberNamed(object, "id");
|
|
264
|
+
const idExpression = ts.isPropertyAssignment(idMember) ? idMember.initializer
|
|
265
|
+
: ts.isShorthandPropertyAssignment(idMember) ? idMember.name : null;
|
|
266
|
+
const actionId = idExpression && evaluator.evaluateLiteral(idExpression, { indexes, values: new Map() });
|
|
267
|
+
actions.push({ actionId, object, execute: executeFunctions(memberNamed(object, "execute")) });
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
return actions;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
export { bindFeatures };
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
import ts from "typescript";
|
|
2
|
+
|
|
3
|
+
const SKIPPED_MODIFIERS = new Set([ts.SyntaxKind.ExportKeyword, ts.SyntaxKind.DefaultKeyword, ts.SyntaxKind.DeclareKeyword]);
|
|
4
|
+
|
|
5
|
+
function isFunctionLike(node) {
|
|
6
|
+
return Boolean(node) && (
|
|
7
|
+
ts.isFunctionDeclaration(node) ||
|
|
8
|
+
ts.isFunctionExpression(node) ||
|
|
9
|
+
ts.isArrowFunction(node) ||
|
|
10
|
+
ts.isMethodDeclaration(node) ||
|
|
11
|
+
ts.isConstructorDeclaration(node) ||
|
|
12
|
+
ts.isGetAccessorDeclaration(node) ||
|
|
13
|
+
ts.isSetAccessorDeclaration(node)
|
|
14
|
+
) && Boolean(node.body);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function unwrapExpression(node) {
|
|
18
|
+
let current = node;
|
|
19
|
+
while (current && (
|
|
20
|
+
ts.isParenthesizedExpression(current) ||
|
|
21
|
+
ts.isAsExpression(current) ||
|
|
22
|
+
ts.isNonNullExpression(current) ||
|
|
23
|
+
ts.isSatisfiesExpression(current) ||
|
|
24
|
+
ts.isTypeAssertionExpression(current)
|
|
25
|
+
)) {
|
|
26
|
+
current = current.expression;
|
|
27
|
+
}
|
|
28
|
+
return current;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// The canonical start (spec Decision 6): the first token of the tree-sitter
|
|
32
|
+
// function node, excluding decorators and the export/default wrapper that
|
|
33
|
+
// tree-sitter places outside the function node. Leading comments are trivia.
|
|
34
|
+
function canonicalStart(node) {
|
|
35
|
+
const sourceFile = node.getSourceFile();
|
|
36
|
+
const modifiers = node.modifiers || [];
|
|
37
|
+
let skippedEnd = -1;
|
|
38
|
+
for (const modifier of modifiers) {
|
|
39
|
+
if (ts.isDecorator(modifier) || SKIPPED_MODIFIERS.has(modifier.kind)) {
|
|
40
|
+
skippedEnd = modifier.end;
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
return modifier.getStart(sourceFile);
|
|
44
|
+
}
|
|
45
|
+
return skippedEnd < 0 ? node.getStart(sourceFile) : ts.skipTrivia(sourceFile.text, skippedEnd);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function point(sourceFile, position) {
|
|
49
|
+
const { line, character } = sourceFile.getLineAndCharacterOfPosition(position);
|
|
50
|
+
return { line: line + 1, column: character + 1 };
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function functionKind(node) {
|
|
54
|
+
if (ts.isConstructorDeclaration(node)) {
|
|
55
|
+
return "constructor";
|
|
56
|
+
}
|
|
57
|
+
if (ts.isMethodDeclaration(node) || ts.isGetAccessorDeclaration(node) || ts.isSetAccessorDeclaration(node)) {
|
|
58
|
+
return "method";
|
|
59
|
+
}
|
|
60
|
+
if (ts.isArrowFunction(node)) {
|
|
61
|
+
return "arrow";
|
|
62
|
+
}
|
|
63
|
+
return ts.isFunctionExpression(node) ? "closure" : "function";
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function propertyNameText(name, sourceFile) {
|
|
67
|
+
if (!name) {
|
|
68
|
+
return "";
|
|
69
|
+
}
|
|
70
|
+
if (ts.isIdentifier(name) || ts.isPrivateIdentifier(name) || ts.isStringLiteralLike(name) || ts.isNumericLiteral(name)) {
|
|
71
|
+
return name.text;
|
|
72
|
+
}
|
|
73
|
+
return name.getText(sourceFile);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// The name a function is declared or bound under; "" when anonymous.
|
|
77
|
+
function functionName(node) {
|
|
78
|
+
const sourceFile = node.getSourceFile();
|
|
79
|
+
if (ts.isConstructorDeclaration(node)) {
|
|
80
|
+
return "constructor";
|
|
81
|
+
}
|
|
82
|
+
if (node.name) {
|
|
83
|
+
return propertyNameText(node.name, sourceFile);
|
|
84
|
+
}
|
|
85
|
+
let parent = node.parent;
|
|
86
|
+
while (parent && (ts.isParenthesizedExpression(parent) || ts.isAsExpression(parent) || ts.isSatisfiesExpression(parent))) {
|
|
87
|
+
parent = parent.parent;
|
|
88
|
+
}
|
|
89
|
+
if (!parent) {
|
|
90
|
+
return "";
|
|
91
|
+
}
|
|
92
|
+
if ((ts.isVariableDeclaration(parent) || ts.isPropertyDeclaration(parent) || ts.isPropertyAssignment(parent)) && parent.name) {
|
|
93
|
+
return ts.isObjectBindingPattern(parent.name) || ts.isArrayBindingPattern(parent.name) ? "" : propertyNameText(parent.name, sourceFile);
|
|
94
|
+
}
|
|
95
|
+
if (ts.isBinaryExpression(parent) && parent.operatorToken.kind === ts.SyntaxKind.EqualsToken && unwrapExpression(parent.right) === node) {
|
|
96
|
+
const target = parent.left;
|
|
97
|
+
if (ts.isIdentifier(target)) {
|
|
98
|
+
return target.text;
|
|
99
|
+
}
|
|
100
|
+
if (ts.isPropertyAccessExpression(target)) {
|
|
101
|
+
return target.name.text;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
if (ts.isExportAssignment(parent)) {
|
|
105
|
+
return "default";
|
|
106
|
+
}
|
|
107
|
+
return "";
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// The enclosing class, or the variable an enclosing object literal is bound to.
|
|
111
|
+
function functionContainer(node) {
|
|
112
|
+
const parent = ts.isPropertyAssignment(node.parent) || ts.isPropertyDeclaration(node.parent) ? node.parent.parent : node.parent;
|
|
113
|
+
if (parent && (ts.isClassDeclaration(parent) || ts.isClassExpression(parent))) {
|
|
114
|
+
return parent.name?.text || (ts.isVariableDeclaration(parent.parent) && ts.isIdentifier(parent.parent.name) ? parent.parent.name.text : undefined);
|
|
115
|
+
}
|
|
116
|
+
if (parent && ts.isObjectLiteralExpression(parent)) {
|
|
117
|
+
let holder = parent.parent;
|
|
118
|
+
while (holder && (ts.isParenthesizedExpression(holder) || ts.isAsExpression(holder) || ts.isCallExpression(holder))) {
|
|
119
|
+
holder = holder.parent;
|
|
120
|
+
}
|
|
121
|
+
if (holder && ts.isVariableDeclaration(holder) && ts.isIdentifier(holder.name)) {
|
|
122
|
+
return holder.name.text;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
return undefined;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function isTopLevelDeclaration(node) {
|
|
129
|
+
if (ts.isFunctionDeclaration(node)) {
|
|
130
|
+
return ts.isSourceFile(node.parent);
|
|
131
|
+
}
|
|
132
|
+
const declaration = node.parent;
|
|
133
|
+
return Boolean(declaration) && ts.isVariableDeclaration(declaration) &&
|
|
134
|
+
declaration.initializer === node &&
|
|
135
|
+
ts.isVariableStatement(declaration.parent?.parent) &&
|
|
136
|
+
ts.isSourceFile(declaration.parent.parent.parent);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Every function-like with a body in one script, plus the file's top-level
|
|
140
|
+
// `module` function (`fn:<path>#module`, 1:1 to the end of the file).
|
|
141
|
+
// Positions are 1-based and cover the original file.
|
|
142
|
+
function collectFileFunctions({ file, hash, sourceFile, checker }) {
|
|
143
|
+
const records = [];
|
|
144
|
+
const nodes = [];
|
|
145
|
+
const end = point(sourceFile, Math.max(1, sourceFile.text.length - (sourceFile.text.endsWith("\n") ? 1 : 0)));
|
|
146
|
+
const moduleRecord = {
|
|
147
|
+
id: `fn:${file}#module`,
|
|
148
|
+
resolution: "resolved",
|
|
149
|
+
file,
|
|
150
|
+
name: "",
|
|
151
|
+
kind: "module",
|
|
152
|
+
start: { line: 1, column: 1 },
|
|
153
|
+
end,
|
|
154
|
+
evidence: [{ path: file, hash, start: { line: 1, column: 1 }, end }]
|
|
155
|
+
};
|
|
156
|
+
const exported = new Set();
|
|
157
|
+
const moduleSymbol = checker.getSymbolAtLocation(sourceFile);
|
|
158
|
+
for (const symbol of moduleSymbol ? checker.getExportsOfModule(moduleSymbol) : []) {
|
|
159
|
+
const target = symbol.flags & ts.SymbolFlags.Alias ? safeAliased(checker, symbol) : symbol;
|
|
160
|
+
for (const declaration of target?.declarations || []) {
|
|
161
|
+
exported.add(declaration);
|
|
162
|
+
if (ts.isVariableDeclaration(declaration) && declaration.initializer) {
|
|
163
|
+
exported.add(unwrapExpression(declaration.initializer));
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
const visit = (node) => {
|
|
168
|
+
if (isFunctionLike(node)) {
|
|
169
|
+
const startPosition = canonicalStart(node);
|
|
170
|
+
const start = point(sourceFile, startPosition);
|
|
171
|
+
const finish = point(sourceFile, node.end);
|
|
172
|
+
const container = functionContainer(node);
|
|
173
|
+
const record = {
|
|
174
|
+
id: `fn:${file}#${start.line}:${start.column}`,
|
|
175
|
+
resolution: "resolved",
|
|
176
|
+
file,
|
|
177
|
+
name: functionName(node),
|
|
178
|
+
kind: functionKind(node),
|
|
179
|
+
...(container ? { container } : {}),
|
|
180
|
+
...(isTopLevelDeclaration(node) && exported.has(node) ? { exported: true } : {}),
|
|
181
|
+
start,
|
|
182
|
+
end: finish,
|
|
183
|
+
evidence: [{ path: file, hash, start, end: finish }]
|
|
184
|
+
};
|
|
185
|
+
records.push(record);
|
|
186
|
+
nodes.push({ node, record, startPosition });
|
|
187
|
+
}
|
|
188
|
+
ts.forEachChild(node, visit);
|
|
189
|
+
};
|
|
190
|
+
visit(sourceFile);
|
|
191
|
+
// An empty file has no code, so no module function either.
|
|
192
|
+
const module = sourceFile.text.length ? moduleRecord : null;
|
|
193
|
+
return { module, functions: module ? [module, ...records] : records, nodes };
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function safeAliased(checker, symbol) {
|
|
197
|
+
try {
|
|
198
|
+
return checker.getAliasedSymbol(symbol);
|
|
199
|
+
} catch {
|
|
200
|
+
return symbol;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
export { collectFileFunctions, isFunctionLike, point, safeAliased, unwrapExpression };
|