ts-axi 0.1.17 → 0.1.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/src/project.js +101 -1
- package/dist/src/symbols.js +4 -0
- package/package.json +1 -1
package/dist/src/project.js
CHANGED
|
@@ -547,9 +547,91 @@ export function createSemanticProject(index, scope) {
|
|
|
547
547
|
}
|
|
548
548
|
return undefined;
|
|
549
549
|
};
|
|
550
|
-
const
|
|
550
|
+
/** Where findReferences stops on CommonJS: a `module.exports` property of the Symbol, and the locals of `const { x } = require()`. */
|
|
551
|
+
const commonJsHop = (fileName, start) => {
|
|
552
|
+
const sf = program.getSourceFile(fileName);
|
|
553
|
+
if (!sf)
|
|
554
|
+
return undefined;
|
|
555
|
+
const id = nodeAt(sf, start);
|
|
556
|
+
if (!ts.isIdentifier(id))
|
|
557
|
+
return undefined;
|
|
558
|
+
const parent = id.parent;
|
|
559
|
+
if ((ts.isShorthandPropertyAssignment(parent) && parent.name === id) || (ts.isPropertyAssignment(parent) && parent.initializer === id)) {
|
|
560
|
+
return isModuleExportsObject(parent.parent) ? parent.name.getStart(sf) : undefined;
|
|
561
|
+
}
|
|
562
|
+
const element = ts.isBindingElement(parent) ? parent : undefined;
|
|
563
|
+
if (!element || !ts.isIdentifier(element.name) || !ts.isObjectBindingPattern(element.parent))
|
|
564
|
+
return undefined;
|
|
565
|
+
const decl = element.parent.parent;
|
|
566
|
+
if (!ts.isVariableDeclaration(decl) || !decl.initializer || !isRequireCall(decl.initializer))
|
|
567
|
+
return undefined;
|
|
568
|
+
return element.name.getStart(sf);
|
|
569
|
+
};
|
|
570
|
+
/** findReferences of the Symbol at `pos`, with the CommonJS hops of `commonJsHop` merged into the target group. */
|
|
571
|
+
const referenceGroups = (absFile, pos) => {
|
|
551
572
|
const twin = declarationTwin(absFile, pos);
|
|
552
573
|
const groups = [...(service.findReferences(absFile, pos) ?? []), ...(twin ? service.findReferences(twin.file, twin.pos) ?? [] : [])];
|
|
574
|
+
const covers = (r) => resolve(r.fileName) === resolve(absFile) && r.textSpan.start <= pos && pos <= r.textSpan.start + r.textSpan.length;
|
|
575
|
+
const target = groups.find((g) => g.references.some(covers));
|
|
576
|
+
if (!target)
|
|
577
|
+
return { groups, extra: [] };
|
|
578
|
+
const seen = new Set(groups.flatMap((g) => g.references.map((r) => `${resolve(r.fileName)}#${r.textSpan.start}`)));
|
|
579
|
+
const extra = [];
|
|
580
|
+
const hops = new Set();
|
|
581
|
+
const queue = [...target.references];
|
|
582
|
+
for (let i = 0; i < queue.length && i < 500; i++) {
|
|
583
|
+
const hop = commonJsHop(queue[i].fileName, queue[i].textSpan.start);
|
|
584
|
+
const hopKey = `${resolve(queue[i].fileName)}#${hop}`;
|
|
585
|
+
if (hop === undefined || hops.has(hopKey))
|
|
586
|
+
continue;
|
|
587
|
+
hops.add(hopKey);
|
|
588
|
+
for (const g of service.findReferences(queue[i].fileName, hop) ?? []) {
|
|
589
|
+
for (const r of g.references) {
|
|
590
|
+
const key = `${resolve(r.fileName)}#${r.textSpan.start}`;
|
|
591
|
+
// A renamed local (`{ x: y }`) declares `y`; its uses are the References.
|
|
592
|
+
if (seen.has(key) || (r.isDefinition && resolve(r.fileName) === resolve(queue[i].fileName) && r.textSpan.start === hop && hop !== queue[i].textSpan.start))
|
|
593
|
+
continue;
|
|
594
|
+
seen.add(key);
|
|
595
|
+
const entry = { ...r, isDefinition: false };
|
|
596
|
+
extra.push(entry);
|
|
597
|
+
queue.push(entry);
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
return { groups: extra.length === 0 ? groups : groups.map((g) => (g === target ? { ...g, references: [...g.references, ...extra] } : g)), extra };
|
|
602
|
+
};
|
|
603
|
+
/** Call sites among CommonJS-hop References; call hierarchy does not reach a destructured `require()` local. */
|
|
604
|
+
const commonJsCalls = (refs) => {
|
|
605
|
+
const sites = [];
|
|
606
|
+
for (const ref of refs) {
|
|
607
|
+
const sf = program.getSourceFile(ref.fileName);
|
|
608
|
+
const loc = location(ref.fileName, ref.textSpan.start);
|
|
609
|
+
if (!sf || !loc)
|
|
610
|
+
continue;
|
|
611
|
+
const id = nodeAt(sf, ref.textSpan.start);
|
|
612
|
+
const call = id.parent;
|
|
613
|
+
if (!(ts.isCallExpression(call) || ts.isNewExpression(call)) || call.expression !== id)
|
|
614
|
+
continue;
|
|
615
|
+
const item = enclosingItem(sf, call);
|
|
616
|
+
sites.push({ ...loc, caller: callerName(item), item });
|
|
617
|
+
}
|
|
618
|
+
return sites;
|
|
619
|
+
};
|
|
620
|
+
/** Call hierarchy item of the nearest named function around `node`, else the file itself. */
|
|
621
|
+
const enclosingItem = (sf, node) => {
|
|
622
|
+
for (let n = node.parent; n && !ts.isSourceFile(n); n = n.parent) {
|
|
623
|
+
if (!ts.isFunctionLike(n))
|
|
624
|
+
continue;
|
|
625
|
+
const prepared = service.prepareCallHierarchy(sf.fileName, n.getStart(sf));
|
|
626
|
+
const item = Array.isArray(prepared) ? prepared[0] : prepared;
|
|
627
|
+
if (item && item.kind !== ts.ScriptElementKind.scriptElement)
|
|
628
|
+
return item;
|
|
629
|
+
}
|
|
630
|
+
const span = { start: 0, length: sf.end };
|
|
631
|
+
return { name: sf.fileName, kind: ts.ScriptElementKind.scriptElement, file: sf.fileName, span, selectionSpan: span };
|
|
632
|
+
};
|
|
633
|
+
const split = (absFile, pos) => {
|
|
634
|
+
const { groups } = referenceGroups(absFile, pos);
|
|
553
635
|
const descriptors = [];
|
|
554
636
|
let target = -1;
|
|
555
637
|
let flagged = -1;
|
|
@@ -661,6 +743,7 @@ export function createSemanticProject(index, scope) {
|
|
|
661
743
|
const seen = new Set();
|
|
662
744
|
return items
|
|
663
745
|
.flatMap((item) => splitSites(item.file, item.selectionSpan.start, [...service.provideCallHierarchyIncomingCalls(item.file, item.selectionSpan.start), ...twinCalls]))
|
|
746
|
+
.concat(commonJsCalls(referenceGroups(absFile, pos).extra))
|
|
664
747
|
.filter((site) => {
|
|
665
748
|
const key = `${site.file}:${site.line}:${site.col}`;
|
|
666
749
|
return !seen.has(key) && Boolean(seen.add(key));
|
|
@@ -726,3 +809,20 @@ export function openProject(cwd, options = {}) {
|
|
|
726
809
|
const index = createSyntacticIndex(root, project);
|
|
727
810
|
return { cwd, root, location, index, ...(project.implicit ? { warning: IMPLICIT_WARNING } : {}) };
|
|
728
811
|
}
|
|
812
|
+
/** The object of `module.exports = {...}`. */
|
|
813
|
+
function isModuleExportsObject(node) {
|
|
814
|
+
if (!ts.isObjectLiteralExpression(node))
|
|
815
|
+
return false;
|
|
816
|
+
let assign = node.parent;
|
|
817
|
+
while (ts.isParenthesizedExpression(assign))
|
|
818
|
+
assign = assign.parent;
|
|
819
|
+
if (!ts.isBinaryExpression(assign) || assign.operatorToken.kind !== ts.SyntaxKind.EqualsToken)
|
|
820
|
+
return false;
|
|
821
|
+
const left = assign.left;
|
|
822
|
+
return ts.isPropertyAccessExpression(left) && ts.isIdentifier(left.expression) && left.expression.text === "module" && left.name.text === "exports";
|
|
823
|
+
}
|
|
824
|
+
function isRequireCall(expr) {
|
|
825
|
+
while (ts.isParenthesizedExpression(expr))
|
|
826
|
+
expr = expr.expression;
|
|
827
|
+
return ts.isCallExpression(expr) && ts.isIdentifier(expr.expression) && expr.expression.text === "require" && expr.arguments.length === 1;
|
|
828
|
+
}
|
package/dist/src/symbols.js
CHANGED
|
@@ -441,6 +441,10 @@ export function collectDeclarations(sf, file) {
|
|
|
441
441
|
nested({ start: prop.getStart(sf), end: prop.end, kind: "method", name, parentName: holder, exported, signature: headUntil(prop, fn, sf), namePos: prop.name.getStart(sf) });
|
|
442
442
|
else if (scope && ts.isIdentifier(value))
|
|
443
443
|
aliasedMember(scope, value, name, holder, exported, prop.name);
|
|
444
|
+
else if (holder === "exports" && !ts.isIdentifier(value)) {
|
|
445
|
+
// `module.exports = { x: make(...) }`: the property is the only Declaration a destructured require can reach.
|
|
446
|
+
nested({ start: prop.getStart(sf), end: prop.end, kind: "property", name, parentName: holder, exported, signature: clip(tokenText(prop, sf, prop.getStart(sf), prop.end)), namePos: prop.name.getStart(sf) });
|
|
447
|
+
}
|
|
444
448
|
}
|
|
445
449
|
else if (scope && ts.isShorthandPropertyAssignment(prop)) {
|
|
446
450
|
aliasedMember(scope, prop.name, name, holder, exported, prop.name);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ts-axi",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.18",
|
|
4
4
|
"description": "AXI-compliant TypeScript symbol CLI for agents - definitions, references, callers, file outlines and change impact (blast radius) with token-efficient TOON output",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"keywords": [
|