ts-axi 0.1.2 → 0.1.4
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 +16 -1
- package/dist/src/commands/common.js +83 -16
- package/dist/src/commands/def.js +6 -6
- package/dist/src/commands/home.js +35 -43
- package/dist/src/commands/impact.js +15 -23
- package/dist/src/commands/refs.js +95 -15
- package/dist/src/impact.js +5 -27
- package/dist/src/importers.js +182 -0
- package/dist/src/project.js +110 -73
- package/dist/src/scope.js +119 -0
- package/dist/src/symbols.js +324 -20
- package/dist/src/walk.js +96 -0
- package/dist/src/workspace.js +297 -0
- package/package.json +1 -1
package/dist/src/symbols.js
CHANGED
|
@@ -155,6 +155,14 @@ function bodyOf(node) {
|
|
|
155
155
|
function openBrace(node, sf) {
|
|
156
156
|
return node.getChildren(sf).find((c) => c.kind === ts.SyntaxKind.OpenBraceToken)?.getStart(sf);
|
|
157
157
|
}
|
|
158
|
+
/** Text up to the body of a function initializer, or up to the end of the type annotation (the initializer is the body). */
|
|
159
|
+
function headText(node, sf, init, typeEnd) {
|
|
160
|
+
if (isFunctionInitializer(init)) {
|
|
161
|
+
const text = tokenText(node, sf, node.getStart(sf), init.body.getStart(sf));
|
|
162
|
+
return ts.isArrowFunction(init) ? text.replace(/\s*=>$/, " =>") : text;
|
|
163
|
+
}
|
|
164
|
+
return tokenText(node, sf, node.getStart(sf), typeEnd);
|
|
165
|
+
}
|
|
158
166
|
/** Uncapped Signature text; `signatureOf` clips it for display. */
|
|
159
167
|
export function fullSignatureOf(node, sf) {
|
|
160
168
|
if (ts.isVariableDeclaration(node)) {
|
|
@@ -163,13 +171,13 @@ export function fullSignatureOf(node, sf) {
|
|
|
163
171
|
const head = ts.isVariableDeclarationList(list) && ts.isVariableStatement(statement)
|
|
164
172
|
? tokenText(statement, sf, statement.getStart(sf), list.declarations[0].getStart(sf))
|
|
165
173
|
: "";
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
text = text.replace(/\s*=>$/, " =>");
|
|
171
|
-
return `${head} ${text}`.trim();
|
|
174
|
+
return `${head} ${headText(node, sf, node.initializer, (node.type ?? node.exclamationToken ?? node.name).end)}`.trim();
|
|
175
|
+
}
|
|
176
|
+
if (ts.isPropertyDeclaration(node)) {
|
|
177
|
+
return headText(node, sf, node.initializer, (node.type ?? node.exclamationToken ?? node.questionToken ?? node.name).end).replace(/;$/, "");
|
|
172
178
|
}
|
|
179
|
+
if (ts.isEnumMember(node))
|
|
180
|
+
return tokenText(node, sf, node.getStart(sf), node.name.end);
|
|
173
181
|
if (ts.isClassDeclaration(node) || ts.isClassExpression(node)) {
|
|
174
182
|
return tokenText(node, sf, node.getStart(sf), openBrace(node, sf) ?? node.end);
|
|
175
183
|
}
|
|
@@ -216,12 +224,76 @@ function nameText(name, sf) {
|
|
|
216
224
|
return name.text;
|
|
217
225
|
return name.getText(sf);
|
|
218
226
|
}
|
|
227
|
+
/** Registration verbs whose `('Name', fn)` call introduces a nested Declaration (AngularJS style). */
|
|
228
|
+
const REGISTRATION_VERBS = new Set(["controller", "service", "factory", "directive", "filter", "component", "run", "config", "provider"]);
|
|
229
|
+
/** Verbs whose callback is a constructor: its `this.x = function` statements are members. */
|
|
230
|
+
const CONSTRUCTOR_VERBS = new Set(["controller", "service", "provider"]);
|
|
231
|
+
/** `Template.x.<hook>(fn)` lifecycle callbacks. */
|
|
232
|
+
const TEMPLATE_HOOKS = new Set(["onCreated", "onRendered", "onDestroyed"]);
|
|
233
|
+
function unwrap(expr) {
|
|
234
|
+
let e = expr;
|
|
235
|
+
for (;;) {
|
|
236
|
+
if (ts.isParenthesizedExpression(e))
|
|
237
|
+
e = e.expression;
|
|
238
|
+
else if (ts.isPrefixUnaryExpression(e))
|
|
239
|
+
e = e.operand;
|
|
240
|
+
else if (ts.isVoidExpression(e))
|
|
241
|
+
e = e.expression;
|
|
242
|
+
else
|
|
243
|
+
return e;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
function asFunction(expr) {
|
|
247
|
+
if (!expr)
|
|
248
|
+
return undefined;
|
|
249
|
+
const e = unwrap(expr);
|
|
250
|
+
return ts.isFunctionExpression(e) || ts.isArrowFunction(e) ? e : undefined;
|
|
251
|
+
}
|
|
252
|
+
/** Statements of an IIFE body: `(function () {...})()`, `!function () {...}()`, `(function () {...}).call(this)`. */
|
|
253
|
+
function iifeBody(expr) {
|
|
254
|
+
if (!expr)
|
|
255
|
+
return undefined;
|
|
256
|
+
const call = unwrap(expr);
|
|
257
|
+
if (!ts.isCallExpression(call))
|
|
258
|
+
return undefined;
|
|
259
|
+
let callee = unwrap(call.expression);
|
|
260
|
+
if (ts.isPropertyAccessExpression(callee) && (callee.name.text === "call" || callee.name.text === "apply"))
|
|
261
|
+
callee = unwrap(callee.expression);
|
|
262
|
+
const fn = asFunction(callee);
|
|
263
|
+
return fn && ts.isBlock(fn.body) ? fn.body.statements : undefined;
|
|
264
|
+
}
|
|
265
|
+
/** The callback of a registration: a function, or the last element of a DI array. */
|
|
266
|
+
function registeredFunction(arg) {
|
|
267
|
+
if (!arg)
|
|
268
|
+
return undefined;
|
|
269
|
+
if (ts.isArrayLiteralExpression(arg))
|
|
270
|
+
return asFunction(arg.elements[arg.elements.length - 1]);
|
|
271
|
+
return asFunction(arg);
|
|
272
|
+
}
|
|
273
|
+
function isStringName(node) {
|
|
274
|
+
return node !== undefined && (ts.isStringLiteral(node) || ts.isNoSubstitutionTemplateLiteral(node));
|
|
275
|
+
}
|
|
276
|
+
function compact(node, sf) {
|
|
277
|
+
return node.getText(sf).replace(/\s+/g, "");
|
|
278
|
+
}
|
|
279
|
+
/** Parameter list (and return type) of a function as `(a, b): T`. */
|
|
280
|
+
function paramsText(fn, sf) {
|
|
281
|
+
const params = fn.parameters.map((p) => tokenText(p, sf, p.getStart(sf), p.end)).join(", ");
|
|
282
|
+
return `(${params})${fn.type ? `: ${tokenText(fn.type, sf, fn.type.getStart(sf), fn.type.end)}` : ""}`;
|
|
283
|
+
}
|
|
284
|
+
/** `node` text up to the body of its function value; `=>` kept for arrows. */
|
|
285
|
+
function headUntil(node, fn, sf) {
|
|
286
|
+
const text = tokenText(node, sf, node.getStart(sf), fn.body.getStart(sf));
|
|
287
|
+
return ts.isArrowFunction(fn) ? text.replace(/\s*=>$/, " =>") : text;
|
|
288
|
+
}
|
|
219
289
|
/**
|
|
220
|
-
*
|
|
221
|
-
* enum members and namespace members following their parent
|
|
290
|
+
* Declarations of a file in source order: top-level ones with class, interface
|
|
291
|
+
* and enum members and namespace members following their parent, plus nested
|
|
292
|
+
* declarations of script-style code (registrations, IIFE helpers, prototype and
|
|
293
|
+
* object-literal methods; see GLOSSARY.md Declaration).
|
|
222
294
|
*/
|
|
223
295
|
export function collectDeclarations(sf, file) {
|
|
224
|
-
const
|
|
296
|
+
const raws = [];
|
|
225
297
|
const local = localExportNames(sf);
|
|
226
298
|
const add = (node, nameNode, name, exported, parent) => {
|
|
227
299
|
const kind = kindOf(node);
|
|
@@ -231,26 +303,228 @@ export function collectDeclarations(sf, file) {
|
|
|
231
303
|
? node.parent.parent
|
|
232
304
|
: node;
|
|
233
305
|
const start = startNode.getStart(sf);
|
|
234
|
-
const startLc = sf.getLineAndCharacterOfPosition(start);
|
|
235
|
-
const namePos = nameNode ? nameNode.getStart(sf) : start;
|
|
236
306
|
const body = bodyOf(ts.isVariableDeclaration(node) && isFunctionInitializer(node.initializer) ? node.initializer : node);
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
col: startLc.character + 1,
|
|
241
|
-
endLine: sf.getLineAndCharacterOfPosition(node.end).line + 1,
|
|
307
|
+
raws.push({
|
|
308
|
+
start,
|
|
309
|
+
end: node.end,
|
|
242
310
|
kind,
|
|
243
311
|
name,
|
|
244
312
|
qualifiedName: parent ? `${parent}.${name}` : name,
|
|
245
313
|
exported,
|
|
246
|
-
signature:
|
|
314
|
+
signature: fullSignatureOf(node, sf),
|
|
247
315
|
...(parent ? { parentName: parent } : {}),
|
|
248
|
-
namePos,
|
|
249
|
-
nameLine: sf.getLineAndCharacterOfPosition(namePos).line + 1,
|
|
316
|
+
namePos: nameNode ? nameNode.getStart(sf) : start,
|
|
250
317
|
hasBody: body !== undefined ||
|
|
251
318
|
!(ts.isFunctionDeclaration(node) || ts.isMethodDeclaration(node) || ts.isConstructorDeclaration(node)),
|
|
252
319
|
});
|
|
253
320
|
};
|
|
321
|
+
const nested = (raw) => {
|
|
322
|
+
raws.push({ ...raw, qualifiedName: raw.qualifiedName ?? (raw.parentName ? `${raw.parentName}.${raw.name}` : raw.name), hasBody: true });
|
|
323
|
+
};
|
|
324
|
+
/** Method and function-valued properties of an object literal as `<holder>.<name>`. */
|
|
325
|
+
const objectMembers = (obj, holder, exported) => {
|
|
326
|
+
for (const prop of obj.properties) {
|
|
327
|
+
const name = nameText(prop.name, sf);
|
|
328
|
+
if (!name || !prop.name)
|
|
329
|
+
continue;
|
|
330
|
+
if (ts.isMethodDeclaration(prop)) {
|
|
331
|
+
nested({ start: prop.getStart(sf), end: prop.end, kind: "method", name, parentName: holder, exported, signature: fullSignatureOf(prop, sf), namePos: prop.name.getStart(sf) });
|
|
332
|
+
}
|
|
333
|
+
else if (ts.isPropertyAssignment(prop)) {
|
|
334
|
+
const fn = asFunction(prop.initializer);
|
|
335
|
+
if (fn)
|
|
336
|
+
nested({ start: prop.getStart(sf), end: prop.end, kind: "method", name, parentName: holder, exported, signature: headUntil(prop, fn, sf), namePos: prop.name.getStart(sf) });
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
};
|
|
340
|
+
/** `this.x = function` statements at the top level of a constructor-like body. */
|
|
341
|
+
const thisMembers = (body, owner, exported) => {
|
|
342
|
+
if (!body || !ts.isBlock(body))
|
|
343
|
+
return;
|
|
344
|
+
for (const statement of body.statements) {
|
|
345
|
+
if (!ts.isExpressionStatement(statement) || !ts.isBinaryExpression(statement.expression))
|
|
346
|
+
continue;
|
|
347
|
+
const { left, operatorToken, right } = statement.expression;
|
|
348
|
+
const fn = asFunction(right);
|
|
349
|
+
if (operatorToken.kind !== ts.SyntaxKind.EqualsToken || !fn || !ts.isPropertyAccessExpression(left) || left.expression.kind !== ts.SyntaxKind.ThisKeyword)
|
|
350
|
+
continue;
|
|
351
|
+
nested({ start: statement.getStart(sf), end: statement.end, kind: "method", name: left.name.text, parentName: owner, exported, signature: headUntil(statement.expression, fn, sf), namePos: left.name.getStart(sf) });
|
|
352
|
+
}
|
|
353
|
+
};
|
|
354
|
+
/** Top-level object literals returned by a factory: its API. */
|
|
355
|
+
const returnedMembers = (body, owner) => {
|
|
356
|
+
if (ts.isObjectLiteralExpression(unwrap(body)))
|
|
357
|
+
return objectMembers(unwrap(body), owner, true);
|
|
358
|
+
if (!ts.isBlock(body))
|
|
359
|
+
return;
|
|
360
|
+
for (const statement of body.statements) {
|
|
361
|
+
if (ts.isReturnStatement(statement) && statement.expression && ts.isObjectLiteralExpression(unwrap(statement.expression))) {
|
|
362
|
+
objectMembers(unwrap(statement.expression), owner, true);
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
};
|
|
366
|
+
const topLevelExported = (name) => raws.find((r) => r.qualifiedName === name && r.parentName === undefined)?.exported ?? false;
|
|
367
|
+
const isConstructorName = (name) => /^[A-Z]/.test(name);
|
|
368
|
+
/** `.controller('Name', fn)` and friends; `.run(fn)` / `.config(fn)` take the verb as name. */
|
|
369
|
+
const registration = (call, verb) => {
|
|
370
|
+
const [first, second] = call.arguments;
|
|
371
|
+
const named = isStringName(first);
|
|
372
|
+
if (!named && verb !== "run" && verb !== "config")
|
|
373
|
+
return;
|
|
374
|
+
const name = named ? first.text : verb;
|
|
375
|
+
const arg = named ? second : first;
|
|
376
|
+
const callee = call.expression;
|
|
377
|
+
const start = named ? first.getStart(sf) : callee.name.getStart(sf);
|
|
378
|
+
if (verb === "component" && arg && ts.isObjectLiteralExpression(arg)) {
|
|
379
|
+
nested({ start, end: arg.end, kind: "variable", name, exported: true, signature: `component ${name}`, namePos: arg.getStart(sf), nameless: true });
|
|
380
|
+
objectMembers(arg, name, true);
|
|
381
|
+
return;
|
|
382
|
+
}
|
|
383
|
+
const fn = registeredFunction(arg);
|
|
384
|
+
if (!fn)
|
|
385
|
+
return;
|
|
386
|
+
const inject = arg && ts.isArrayLiteralExpression(arg) ? ` [${arg.elements.slice(0, -1).map((e) => e.getText(sf)).join(", ")}]` : "";
|
|
387
|
+
nested({
|
|
388
|
+
start,
|
|
389
|
+
end: arg.end,
|
|
390
|
+
kind: "function",
|
|
391
|
+
name,
|
|
392
|
+
qualifiedName: name,
|
|
393
|
+
parentName: verb,
|
|
394
|
+
exported: true,
|
|
395
|
+
signature: `${verb} ${name}${paramsText(fn, sf)}`,
|
|
396
|
+
namePos: (ts.isFunctionExpression(fn) && fn.name ? fn.name : fn).getStart(sf),
|
|
397
|
+
nameless: true,
|
|
398
|
+
});
|
|
399
|
+
// The DI array is compared, not displayed: reordering injections is a contract change.
|
|
400
|
+
if (inject)
|
|
401
|
+
raws[raws.length - 1].signature += `\n${inject.trim()}`;
|
|
402
|
+
if (CONSTRUCTOR_VERBS.has(verb))
|
|
403
|
+
thisMembers(fn.body, name, true);
|
|
404
|
+
if (verb === "factory" || verb === "service")
|
|
405
|
+
returnedMembers(fn.body, name);
|
|
406
|
+
};
|
|
407
|
+
/** Registrations along a call chain (`angular.module('x').controller(...).service(...)`), in source order. */
|
|
408
|
+
const callChain = (expr) => {
|
|
409
|
+
const calls = [];
|
|
410
|
+
let e = unwrap(expr);
|
|
411
|
+
while (ts.isCallExpression(e)) {
|
|
412
|
+
const callee = e.expression;
|
|
413
|
+
if (ts.isPropertyAccessExpression(callee)) {
|
|
414
|
+
calls.push({ call: e, name: callee.name.text });
|
|
415
|
+
e = callee.expression;
|
|
416
|
+
}
|
|
417
|
+
else {
|
|
418
|
+
break;
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
for (const { call, name } of calls.reverse()) {
|
|
422
|
+
const callee = call.expression;
|
|
423
|
+
const receiver = compact(callee.expression, sf);
|
|
424
|
+
const first = call.arguments[0];
|
|
425
|
+
if (receiver === "Meteor" && name === "methods" && first && ts.isObjectLiteralExpression(first)) {
|
|
426
|
+
objectMembers(first, "methods", true);
|
|
427
|
+
}
|
|
428
|
+
else if (receiver.startsWith("Template") && (name === "helpers" || name === "events") && first && ts.isObjectLiteralExpression(first)) {
|
|
429
|
+
objectMembers(first, `${receiver}.${name}`, true);
|
|
430
|
+
}
|
|
431
|
+
else if (receiver.startsWith("Template") && TEMPLATE_HOOKS.has(name) && asFunction(first)) {
|
|
432
|
+
nested({ start: call.getStart(sf), end: call.end, kind: "method", name, parentName: receiver, exported: true, signature: `${receiver}.${headUntil(first, asFunction(first), sf)}`, namePos: callee.name.getStart(sf) });
|
|
433
|
+
}
|
|
434
|
+
else if (REGISTRATION_VERBS.has(name)) {
|
|
435
|
+
registration(call, name);
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
};
|
|
439
|
+
/** `exports.x = fn`, `module.exports = {...}`, `Foo.prototype.bar = fn`, `Foo.bar = fn`. */
|
|
440
|
+
const assignment = (statement, expr) => {
|
|
441
|
+
const left = expr.left;
|
|
442
|
+
const right = unwrap(expr.right);
|
|
443
|
+
if (!ts.isPropertyAccessExpression(left))
|
|
444
|
+
return;
|
|
445
|
+
const target = compact(left, sf);
|
|
446
|
+
if (target === "module.exports") {
|
|
447
|
+
if (ts.isObjectLiteralExpression(right))
|
|
448
|
+
objectMembers(right, "exports", true);
|
|
449
|
+
return;
|
|
450
|
+
}
|
|
451
|
+
const owner = left.expression;
|
|
452
|
+
if (ts.isPropertyAccessExpression(owner) && owner.name.text === "prototype" && ts.isIdentifier(owner.expression)) {
|
|
453
|
+
const cls = owner.expression.text;
|
|
454
|
+
const fn = asFunction(right);
|
|
455
|
+
if (fn)
|
|
456
|
+
nested({ start: statement.getStart(sf), end: statement.end, kind: "method", name: left.name.text, parentName: cls, exported: topLevelExported(cls), signature: headUntil(expr, fn, sf), namePos: left.name.getStart(sf) });
|
|
457
|
+
return;
|
|
458
|
+
}
|
|
459
|
+
if (ts.isIdentifier(left.name) && left.name.text === "prototype" && ts.isIdentifier(owner) && ts.isObjectLiteralExpression(right)) {
|
|
460
|
+
objectMembers(right, owner.text, topLevelExported(owner.text));
|
|
461
|
+
return;
|
|
462
|
+
}
|
|
463
|
+
const fn = asFunction(right);
|
|
464
|
+
if (!fn)
|
|
465
|
+
return;
|
|
466
|
+
const holder = target === `module.exports.${left.name.text}` ? "exports" : ts.isIdentifier(owner) ? owner.text : undefined;
|
|
467
|
+
if (holder === undefined)
|
|
468
|
+
return;
|
|
469
|
+
const exported = holder === "exports" || topLevelExported(holder);
|
|
470
|
+
nested({ start: statement.getStart(sf), end: statement.end, kind: "method", name: left.name.text, parentName: holder, exported, signature: headUntil(expr, fn, sf), namePos: left.name.getStart(sf) });
|
|
471
|
+
};
|
|
472
|
+
/** Script-style patterns of one module-level statement; `inIife` adds the IIFE's own functions. */
|
|
473
|
+
const scriptStatement = (statement, inIife) => {
|
|
474
|
+
if (ts.isFunctionDeclaration(statement)) {
|
|
475
|
+
if (!statement.name || !statement.body)
|
|
476
|
+
return;
|
|
477
|
+
if (inIife)
|
|
478
|
+
add(statement, statement.name, statement.name.text, false);
|
|
479
|
+
if (isConstructorName(statement.name.text))
|
|
480
|
+
thisMembers(statement.body, statement.name.text, inIife ? false : topLevelExported(statement.name.text));
|
|
481
|
+
}
|
|
482
|
+
else if (ts.isVariableStatement(statement)) {
|
|
483
|
+
for (const decl of statement.declarationList.declarations) {
|
|
484
|
+
if (!ts.isIdentifier(decl.name) || !decl.initializer)
|
|
485
|
+
continue;
|
|
486
|
+
const name = decl.name.text;
|
|
487
|
+
const fn = asFunction(decl.initializer);
|
|
488
|
+
if (inIife && fn)
|
|
489
|
+
add(decl, decl.name, name, false);
|
|
490
|
+
const exported = inIife ? false : topLevelExported(name);
|
|
491
|
+
const init = unwrap(decl.initializer);
|
|
492
|
+
if (ts.isObjectLiteralExpression(init))
|
|
493
|
+
objectMembers(init, name, exported);
|
|
494
|
+
else if (fn && isConstructorName(name))
|
|
495
|
+
thisMembers(fn.body, name, exported);
|
|
496
|
+
else {
|
|
497
|
+
const body = iifeBody(decl.initializer);
|
|
498
|
+
if (body)
|
|
499
|
+
scriptStatements(body, true);
|
|
500
|
+
else
|
|
501
|
+
callChain(decl.initializer);
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
else if (ts.isExpressionStatement(statement)) {
|
|
506
|
+
const expr = unwrap(statement.expression);
|
|
507
|
+
const body = iifeBody(expr);
|
|
508
|
+
if (body)
|
|
509
|
+
scriptStatements(body, true);
|
|
510
|
+
else if (ts.isBinaryExpression(expr) && expr.operatorToken.kind === ts.SyntaxKind.EqualsToken)
|
|
511
|
+
assignment(statement, expr);
|
|
512
|
+
else
|
|
513
|
+
callChain(expr);
|
|
514
|
+
}
|
|
515
|
+
else if (ts.isIfStatement(statement)) {
|
|
516
|
+
scriptStatement(statement.thenStatement, inIife);
|
|
517
|
+
if (statement.elseStatement)
|
|
518
|
+
scriptStatement(statement.elseStatement, inIife);
|
|
519
|
+
}
|
|
520
|
+
else if (ts.isBlock(statement)) {
|
|
521
|
+
scriptStatements(statement.statements, inIife);
|
|
522
|
+
}
|
|
523
|
+
};
|
|
524
|
+
const scriptStatements = (statements, inIife) => {
|
|
525
|
+
for (const statement of statements)
|
|
526
|
+
scriptStatement(statement, inIife);
|
|
527
|
+
};
|
|
254
528
|
const visitStatements = (statements, parent, parentExported) => {
|
|
255
529
|
for (const statement of statements) {
|
|
256
530
|
if (ts.isVariableStatement(statement)) {
|
|
@@ -260,6 +534,8 @@ export function collectDeclarations(sf, file) {
|
|
|
260
534
|
const exported = parent ? parentExported && hasModifier(statement, ts.SyntaxKind.ExportKeyword) : isTopLevelExported(statement, decl.name.text, local);
|
|
261
535
|
add(decl, decl.name, decl.name.text, exported, parent);
|
|
262
536
|
}
|
|
537
|
+
if (!parent)
|
|
538
|
+
scriptStatement(statement, false);
|
|
263
539
|
continue;
|
|
264
540
|
}
|
|
265
541
|
if (ts.isFunctionDeclaration(statement) ||
|
|
@@ -307,11 +583,39 @@ export function collectDeclarations(sf, file) {
|
|
|
307
583
|
else if (ts.isModuleDeclaration(statement) && statement.body && ts.isModuleBlock(statement.body)) {
|
|
308
584
|
visitStatements(statement.body.statements, qualified, exported);
|
|
309
585
|
}
|
|
586
|
+
else if (ts.isFunctionDeclaration(statement) && !parent) {
|
|
587
|
+
scriptStatement(statement, false);
|
|
588
|
+
}
|
|
589
|
+
continue;
|
|
310
590
|
}
|
|
591
|
+
if (!parent)
|
|
592
|
+
scriptStatement(statement, false);
|
|
311
593
|
}
|
|
312
594
|
};
|
|
313
595
|
visitStatements(sf.statements, undefined, true);
|
|
314
|
-
|
|
596
|
+
// Nested declarations are found after their holder statement; source order is start order.
|
|
597
|
+
const ordered = raws.map((r, i) => ({ r, i })).sort((a, b) => a.r.start - b.r.start || a.i - b.i);
|
|
598
|
+
return ordered.map(({ r }) => {
|
|
599
|
+
const startLc = sf.getLineAndCharacterOfPosition(r.start);
|
|
600
|
+
const full = r.signature;
|
|
601
|
+
return {
|
|
602
|
+
file,
|
|
603
|
+
line: startLc.line + 1,
|
|
604
|
+
col: startLc.character + 1,
|
|
605
|
+
endLine: sf.getLineAndCharacterOfPosition(r.end).line + 1,
|
|
606
|
+
kind: r.kind,
|
|
607
|
+
name: r.name,
|
|
608
|
+
qualifiedName: r.qualifiedName,
|
|
609
|
+
exported: r.exported,
|
|
610
|
+
signature: clip(full.split("\n")[0]),
|
|
611
|
+
...(r.parentName !== undefined ? { parentName: r.parentName } : {}),
|
|
612
|
+
namePos: r.namePos,
|
|
613
|
+
nameLine: sf.getLineAndCharacterOfPosition(r.namePos).line + 1,
|
|
614
|
+
hasBody: r.hasBody,
|
|
615
|
+
fullSignature: full,
|
|
616
|
+
...(r.nameless ? { nameless: true } : {}),
|
|
617
|
+
};
|
|
618
|
+
});
|
|
315
619
|
}
|
|
316
620
|
/** Declarations sharing file and qualified name (overloads, merging) are one Symbol. */
|
|
317
621
|
export function symbolKey(decl) {
|
package/dist/src/walk.js
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
// The one file listing for every walk (implicit project, home count, workspace detection,
|
|
2
|
+
// importer index, outside-project search): `git ls-files` inside a repo, else an fs walk with
|
|
3
|
+
// one skip rule. Node builtins only: the home view imports it.
|
|
4
|
+
import { execFileSync } from "node:child_process";
|
|
5
|
+
import { existsSync, readdirSync } from "node:fs";
|
|
6
|
+
import { join, relative, sep } from "node:path";
|
|
7
|
+
/** Build output, dependencies, caches and tool/editor state (agent worktrees live in `.claude/`). */
|
|
8
|
+
export const SKIP_DIRS = new Set([
|
|
9
|
+
"node_modules", "dist", "build", "coverage", "tmp", ".git", ".nx", ".angular", ".claude", ".cursor", ".idea", ".vscode",
|
|
10
|
+
]);
|
|
11
|
+
/** A directory below the root that holds its own `.git` (nested repo or worktree) is another checkout. */
|
|
12
|
+
export function isNestedCheckout(dir, root) {
|
|
13
|
+
return dir !== root && existsSync(join(dir, ".git"));
|
|
14
|
+
}
|
|
15
|
+
export function skipDir(name, path, root) {
|
|
16
|
+
return SKIP_DIRS.has(name) || isNestedCheckout(path, root);
|
|
17
|
+
}
|
|
18
|
+
/** Files below `root` (absolute paths) under the shared skip rule; `capped` when a bound stopped it. */
|
|
19
|
+
export function walkFiles(root, options) {
|
|
20
|
+
const files = [];
|
|
21
|
+
const queue = [root];
|
|
22
|
+
let capped = false;
|
|
23
|
+
while (queue.length > 0) {
|
|
24
|
+
if ((options.maxFiles !== undefined && files.length >= options.maxFiles) || (options.deadline !== undefined && Date.now() > options.deadline)) {
|
|
25
|
+
capped = true;
|
|
26
|
+
break;
|
|
27
|
+
}
|
|
28
|
+
const dir = options.breadthFirst ? queue.shift() : queue.pop();
|
|
29
|
+
let entries;
|
|
30
|
+
try {
|
|
31
|
+
entries = readdirSync(dir, { withFileTypes: true });
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
if (options.breadthFirst)
|
|
37
|
+
entries.sort((a, b) => a.name.localeCompare(b.name));
|
|
38
|
+
for (const entry of entries) {
|
|
39
|
+
const path = join(dir, entry.name);
|
|
40
|
+
if (entry.isDirectory()) {
|
|
41
|
+
if (!skipDir(entry.name, path, root))
|
|
42
|
+
queue.push(path);
|
|
43
|
+
}
|
|
44
|
+
else if (entry.isFile() && options.accept(entry.name)) {
|
|
45
|
+
files.push(path);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return { files, capped };
|
|
50
|
+
}
|
|
51
|
+
/** Shortest path first, then alphabetical: the canonical copy before nested duplicates. */
|
|
52
|
+
export function byDepthThenPath(a, b) {
|
|
53
|
+
return a.split("/").length - b.split("/").length || a.localeCompare(b);
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Tracked + untracked-not-ignored files, repo-relative (one local, read-only `git ls-files` call):
|
|
57
|
+
* git never descends into nested repos or worktrees, and gitignored dirs drop out. Undefined
|
|
58
|
+
* when `root` is not a git checkout (`.git` dir, or file for a linked worktree) or git is missing.
|
|
59
|
+
*/
|
|
60
|
+
export function gitFiles(root) {
|
|
61
|
+
if (!existsSync(join(root, ".git")))
|
|
62
|
+
return undefined;
|
|
63
|
+
try {
|
|
64
|
+
const raw = execFileSync("git", ["ls-files", "-z", "--cached", "--others", "--exclude-standard"], {
|
|
65
|
+
cwd: root,
|
|
66
|
+
encoding: "utf8",
|
|
67
|
+
maxBuffer: 512 * 1024 * 1024,
|
|
68
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
69
|
+
});
|
|
70
|
+
// A nested repo shows up as one `dir/` entry; deleted-but-tracked files are dropped by the caller's accept/exists check.
|
|
71
|
+
return [...new Set(raw.split("\0").filter((f) => f !== "" && !f.endsWith("/")))];
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
return undefined;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Files below `root` whose basename passes `accept`: the git listing (skip-dir segments removed,
|
|
79
|
+
* missing files dropped) or, outside git, the bounded fs walk.
|
|
80
|
+
*/
|
|
81
|
+
export function listFiles(root, accept, bounds = {}) {
|
|
82
|
+
const git = gitFiles(root);
|
|
83
|
+
if (git) {
|
|
84
|
+
const files = git
|
|
85
|
+
.filter((f) => accept(f.slice(f.lastIndexOf("/") + 1)) && !f.split("/").some((seg) => SKIP_DIRS.has(seg)))
|
|
86
|
+
.map((f) => join(root, f))
|
|
87
|
+
.filter((f) => existsSync(f))
|
|
88
|
+
.sort();
|
|
89
|
+
return { files, capped: false, listedBy: "git" };
|
|
90
|
+
}
|
|
91
|
+
const walked = walkFiles(root, { ...bounds, accept });
|
|
92
|
+
return { files: walked.files.sort(), capped: walked.capped, listedBy: "walk" };
|
|
93
|
+
}
|
|
94
|
+
export function toRel(root, abs) {
|
|
95
|
+
return relative(root, abs).split(sep).join("/");
|
|
96
|
+
}
|