ts-axi 0.1.16 → 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 +32 -13
- 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
|
@@ -390,6 +390,25 @@ export function collectDeclarations(sf, file) {
|
|
|
390
390
|
!(ts.isFunctionDeclaration(node) || ts.isMethodDeclaration(node) || ts.isConstructorDeclaration(node)),
|
|
391
391
|
});
|
|
392
392
|
};
|
|
393
|
+
const classMembers = (cls, exported, qualified) => {
|
|
394
|
+
for (const member of cls.members) {
|
|
395
|
+
if (ts.isSemicolonClassElement(member) || ts.isClassStaticBlockDeclaration(member) || ts.isIndexSignatureDeclaration(member))
|
|
396
|
+
continue;
|
|
397
|
+
const memberName = ts.isConstructorDeclaration(member) ? "constructor" : nameText(member.name, sf);
|
|
398
|
+
if (!memberName)
|
|
399
|
+
continue;
|
|
400
|
+
const nameNode = ts.isConstructorDeclaration(member)
|
|
401
|
+
? member.getChildren(sf).find((c) => c.kind === ts.SyntaxKind.ConstructorKeyword)
|
|
402
|
+
: member.name;
|
|
403
|
+
add(member, nameNode, memberName, exported && !isPrivateMember(member), qualified);
|
|
404
|
+
}
|
|
405
|
+
};
|
|
406
|
+
/** `module.exports = class X {...}`, `exports.X = class {...}`, `export default class {...}` expression. */
|
|
407
|
+
const exportedClass = (cls, fallback) => {
|
|
408
|
+
const name = cls.name?.text ?? fallback;
|
|
409
|
+
add(cls, cls.name, name, true);
|
|
410
|
+
classMembers(cls, true, name);
|
|
411
|
+
};
|
|
393
412
|
const nested = (raw) => {
|
|
394
413
|
raws.push({ ...raw, qualifiedName: raw.qualifiedName ?? (raw.parentName ? `${raw.parentName}.${raw.name}` : raw.name), hasBody: true });
|
|
395
414
|
};
|
|
@@ -422,6 +441,10 @@ export function collectDeclarations(sf, file) {
|
|
|
422
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) });
|
|
423
442
|
else if (scope && ts.isIdentifier(value))
|
|
424
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
|
+
}
|
|
425
448
|
}
|
|
426
449
|
else if (scope && ts.isShorthandPropertyAssignment(prop)) {
|
|
427
450
|
aliasedMember(scope, prop.name, name, holder, exported, prop.name);
|
|
@@ -737,7 +760,9 @@ export function collectDeclarations(sf, file) {
|
|
|
737
760
|
return;
|
|
738
761
|
const target = compact(left, sf);
|
|
739
762
|
if (target === "module.exports") {
|
|
740
|
-
if (ts.
|
|
763
|
+
if (ts.isClassExpression(right))
|
|
764
|
+
exportedClass(right, "exports");
|
|
765
|
+
else if (ts.isObjectLiteralExpression(right))
|
|
741
766
|
objectMembers(right, "exports", true);
|
|
742
767
|
else if (asFunction(right))
|
|
743
768
|
returnedMembers(asFunction(right).body, "exports", true);
|
|
@@ -746,6 +771,8 @@ export function collectDeclarations(sf, file) {
|
|
|
746
771
|
return;
|
|
747
772
|
}
|
|
748
773
|
if (target === `exports.${left.name.text}` || target === `module.exports.${left.name.text}`) {
|
|
774
|
+
if (ts.isClassExpression(right))
|
|
775
|
+
return exportedClass(right, left.name.text);
|
|
749
776
|
if (ts.isObjectLiteralExpression(right))
|
|
750
777
|
objectMembers(right, left.name.text, true);
|
|
751
778
|
else
|
|
@@ -829,7 +856,9 @@ export function collectDeclarations(sf, file) {
|
|
|
829
856
|
}
|
|
830
857
|
else if (ts.isExportAssignment(statement)) {
|
|
831
858
|
const value = unwrap(statement.expression);
|
|
832
|
-
if (ts.
|
|
859
|
+
if (ts.isClassExpression(value))
|
|
860
|
+
exportedClass(value, "default");
|
|
861
|
+
else if (ts.isObjectLiteralExpression(value))
|
|
833
862
|
objectMembers(value, "default", true);
|
|
834
863
|
else
|
|
835
864
|
stateArray(statement.expression);
|
|
@@ -876,17 +905,7 @@ export function collectDeclarations(sf, file) {
|
|
|
876
905
|
add(statement, statement.name, name, exported, parent);
|
|
877
906
|
const qualified = parent ? `${parent}.${name}` : name;
|
|
878
907
|
if (ts.isClassDeclaration(statement)) {
|
|
879
|
-
|
|
880
|
-
if (ts.isSemicolonClassElement(member) || ts.isClassStaticBlockDeclaration(member) || ts.isIndexSignatureDeclaration(member))
|
|
881
|
-
continue;
|
|
882
|
-
const memberName = ts.isConstructorDeclaration(member) ? "constructor" : nameText(member.name, sf);
|
|
883
|
-
if (!memberName)
|
|
884
|
-
continue;
|
|
885
|
-
const nameNode = ts.isConstructorDeclaration(member)
|
|
886
|
-
? member.getChildren(sf).find((c) => c.kind === ts.SyntaxKind.ConstructorKeyword)
|
|
887
|
-
: member.name;
|
|
888
|
-
add(member, nameNode, memberName, exported && !isPrivateMember(member), qualified);
|
|
889
|
-
}
|
|
908
|
+
classMembers(statement, exported, qualified);
|
|
890
909
|
}
|
|
891
910
|
else if (ts.isInterfaceDeclaration(statement)) {
|
|
892
911
|
for (const member of statement.members) {
|
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": [
|