@mobileai/react-native 0.9.26 → 0.9.27
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 +10 -10
- package/bin/generate-map.cjs +511 -120
- package/lib/module/components/AIAgent.js +25 -3
- package/lib/module/components/AgentChatBar.js +3 -3
- package/lib/module/core/AgentRuntime.js +89 -23
- package/lib/module/core/FiberTreeWalker.js +312 -34
- package/lib/module/core/systemPrompt.js +30 -19
- package/lib/module/services/MobileAIKnowledgeRetriever.js +1 -1
- package/lib/module/services/telemetry/MobileAI.js +1 -1
- package/lib/module/tools/tapTool.js +77 -6
- package/lib/typescript/src/core/AgentRuntime.d.ts +8 -1
- package/lib/typescript/src/core/types.d.ts +2 -1
- package/lib/typescript/src/services/MobileAIKnowledgeRetriever.d.ts +1 -1
- package/lib/typescript/src/tools/tapTool.d.ts +3 -2
- package/lib/typescript/test-tree.d.ts +2 -0
- package/package.json +1 -1
package/bin/generate-map.cjs
CHANGED
|
@@ -87,6 +87,7 @@ function deduplicateAndPrioritize(elements) {
|
|
|
87
87
|
function extractContentFromAST(sourceCode, filePath) {
|
|
88
88
|
const elements = [];
|
|
89
89
|
const navigationLinks = [];
|
|
90
|
+
const visibleText = [];
|
|
90
91
|
|
|
91
92
|
let ast;
|
|
92
93
|
try {
|
|
@@ -125,8 +126,10 @@ function extractContentFromAST(sourceCode, filePath) {
|
|
|
125
126
|
const screenTarget = getStringAttribute(astPath.node, 'screen');
|
|
126
127
|
if (screenTarget) navigationLinks.push(screenTarget);
|
|
127
128
|
} else {
|
|
128
|
-
const
|
|
129
|
-
|
|
129
|
+
const buttonLabels = findChildTextContentRecursive(astPath);
|
|
130
|
+
for (const buttonLabel of buttonLabels) {
|
|
131
|
+
elements.push(`${buttonLabel} (button)`);
|
|
132
|
+
}
|
|
130
133
|
}
|
|
131
134
|
break;
|
|
132
135
|
}
|
|
@@ -171,11 +174,21 @@ function extractContentFromAST(sourceCode, filePath) {
|
|
|
171
174
|
if (Array.isArray(target)) navigationLinks.push(...target);
|
|
172
175
|
else if (target) navigationLinks.push(target);
|
|
173
176
|
},
|
|
177
|
+
|
|
178
|
+
JSXElement(astPath) {
|
|
179
|
+
const elementName = getJSXElementName(astPath.node.openingElement.name);
|
|
180
|
+
if (elementName !== 'Text') return;
|
|
181
|
+
const labels = extractTextCandidatesRecursive(astPath.node, astPath.scope);
|
|
182
|
+
for (const label of labels) {
|
|
183
|
+
visibleText.push(label);
|
|
184
|
+
}
|
|
185
|
+
},
|
|
174
186
|
});
|
|
175
187
|
|
|
176
188
|
return {
|
|
177
189
|
elements: deduplicateAndPrioritize(elements),
|
|
178
190
|
navigationLinks: [...new Set(navigationLinks)],
|
|
191
|
+
visibleText: dedupeLabels(visibleText).slice(0, 6),
|
|
179
192
|
};
|
|
180
193
|
}
|
|
181
194
|
|
|
@@ -490,6 +503,9 @@ function buildDescription(extracted) {
|
|
|
490
503
|
if (extracted.elements.length > 0) {
|
|
491
504
|
return extracted.elements.join(', ');
|
|
492
505
|
}
|
|
506
|
+
if (extracted.visibleText?.length) {
|
|
507
|
+
return extracted.visibleText.join(', ');
|
|
508
|
+
}
|
|
493
509
|
return 'Screen content';
|
|
494
510
|
}
|
|
495
511
|
|
|
@@ -553,7 +569,7 @@ function findSiblingTextLabel(switchPath) {
|
|
|
553
569
|
if (!parent?.node || !t.isJSXElement(parent.node)) return null;
|
|
554
570
|
for (const child of parent.node.children) {
|
|
555
571
|
if (t.isJSXElement(child)) {
|
|
556
|
-
const text =
|
|
572
|
+
const text = extractTextCandidatesRecursive(child, parent.scope)[0];
|
|
557
573
|
if (text) return text;
|
|
558
574
|
}
|
|
559
575
|
}
|
|
@@ -562,30 +578,138 @@ function findSiblingTextLabel(switchPath) {
|
|
|
562
578
|
|
|
563
579
|
function findChildTextContentRecursive(pressablePath) {
|
|
564
580
|
const jsxElement = pressablePath.parent;
|
|
565
|
-
if (!t.isJSXElement(jsxElement)) return
|
|
566
|
-
return
|
|
581
|
+
if (!t.isJSXElement(jsxElement)) return [];
|
|
582
|
+
return extractTextCandidatesRecursive(jsxElement, pressablePath.scope);
|
|
567
583
|
}
|
|
568
584
|
|
|
569
|
-
function
|
|
570
|
-
if (depth > 4) return
|
|
585
|
+
function extractTextCandidatesRecursive(element, scope, depth = 0) {
|
|
586
|
+
if (depth > 4) return [];
|
|
587
|
+
const labels = [];
|
|
571
588
|
for (const child of element.children) {
|
|
572
589
|
if (t.isJSXText(child)) {
|
|
573
|
-
const text = child.value
|
|
574
|
-
if (text)
|
|
590
|
+
const text = normalizeLabel(child.value);
|
|
591
|
+
if (text) labels.push(text);
|
|
575
592
|
}
|
|
576
593
|
if (t.isJSXExpressionContainer(child) && !t.isJSXEmptyExpression(child.expression)) {
|
|
577
|
-
|
|
578
|
-
if (hint) return hint;
|
|
594
|
+
labels.push(...resolveExpressionCandidates(child.expression, scope));
|
|
579
595
|
}
|
|
580
596
|
if (t.isJSXElement(child)) {
|
|
581
597
|
const childName = getJSXElementName(child.openingElement.name);
|
|
582
598
|
if (ICON_EXACT.has(childName) || childName.endsWith('Icon') ||
|
|
583
599
|
childName.endsWith('_Dark') || childName.endsWith('_Light')) continue;
|
|
584
|
-
|
|
585
|
-
if (text) return text;
|
|
600
|
+
labels.push(...extractTextCandidatesRecursive(child, scope, depth + 1));
|
|
586
601
|
}
|
|
587
602
|
}
|
|
588
|
-
return
|
|
603
|
+
return dedupeLabels(labels);
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
function resolveExpressionCandidates(node, scope, depth = 0) {
|
|
607
|
+
if (!node || depth > 6) return [];
|
|
608
|
+
if (t.isStringLiteral(node)) return [node.value];
|
|
609
|
+
if (t.isNumericLiteral(node)) return [String(node.value)];
|
|
610
|
+
if (t.isTemplateLiteral(node) && node.quasis.length > 0) {
|
|
611
|
+
const text = normalizeLabel(node.quasis.map(q => q.value.raw).join(' '));
|
|
612
|
+
return text ? [text] : [];
|
|
613
|
+
}
|
|
614
|
+
if (t.isIdentifier(node)) {
|
|
615
|
+
return resolveBindingCandidates(node.name, scope, depth + 1);
|
|
616
|
+
}
|
|
617
|
+
if (t.isConditionalExpression(node)) {
|
|
618
|
+
return dedupeLabels([
|
|
619
|
+
...resolveExpressionCandidates(node.consequent, scope, depth + 1),
|
|
620
|
+
...resolveExpressionCandidates(node.alternate, scope, depth + 1),
|
|
621
|
+
]);
|
|
622
|
+
}
|
|
623
|
+
if (t.isLogicalExpression(node)) {
|
|
624
|
+
return dedupeLabels([
|
|
625
|
+
...resolveExpressionCandidates(node.left, scope, depth + 1),
|
|
626
|
+
...resolveExpressionCandidates(node.right, scope, depth + 1),
|
|
627
|
+
]);
|
|
628
|
+
}
|
|
629
|
+
const hint = extractSemanticHint(node, depth + 1);
|
|
630
|
+
return hint ? [hint] : [];
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
function resolveBindingCandidates(name, scope, depth) {
|
|
634
|
+
if (!scope || depth > 6) return [];
|
|
635
|
+
const binding = scope.getBinding?.(name);
|
|
636
|
+
if (!binding?.path) return [];
|
|
637
|
+
|
|
638
|
+
if (binding.path.isVariableDeclarator()) {
|
|
639
|
+
return resolveExpressionCandidates(binding.path.node.init, binding.path.scope, depth + 1);
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
if (binding.path.isIdentifier() && binding.path.listKey === 'params') {
|
|
643
|
+
return dedupeLabels(
|
|
644
|
+
resolveFunctionParamValues(binding.path, depth + 1).flatMap(valueNode =>
|
|
645
|
+
resolveExpressionCandidates(valueNode, binding.path.scope, depth + 1)
|
|
646
|
+
)
|
|
647
|
+
);
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
return [];
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
function resolveFunctionParamValues(paramPath, depth) {
|
|
654
|
+
if (depth > 6) return [];
|
|
655
|
+
const functionPath = paramPath.findParent(p => p.isFunction());
|
|
656
|
+
const callPath = functionPath?.parentPath;
|
|
657
|
+
if (!callPath?.isCallExpression()) return [];
|
|
658
|
+
|
|
659
|
+
const callee = callPath.node.callee;
|
|
660
|
+
if (!t.isMemberExpression(callee) || !t.isIdentifier(callee.property)) return [];
|
|
661
|
+
if (!['map', 'flatMap'].includes(callee.property.name)) return [];
|
|
662
|
+
|
|
663
|
+
const sourceValues = resolveExpressionValueNodes(callee.object, callPath.scope, depth + 1);
|
|
664
|
+
const results = [];
|
|
665
|
+
|
|
666
|
+
for (const sourceNode of sourceValues) {
|
|
667
|
+
if (!t.isArrayExpression(sourceNode)) continue;
|
|
668
|
+
for (const element of sourceNode.elements) {
|
|
669
|
+
if (element && !t.isSpreadElement(element)) {
|
|
670
|
+
results.push(element);
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
return results;
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
function resolveExpressionValueNodes(node, scope, depth) {
|
|
679
|
+
if (!node || depth > 6) return [];
|
|
680
|
+
if (
|
|
681
|
+
t.isStringLiteral(node) ||
|
|
682
|
+
t.isNumericLiteral(node) ||
|
|
683
|
+
t.isArrayExpression(node) ||
|
|
684
|
+
t.isTemplateLiteral(node)
|
|
685
|
+
) {
|
|
686
|
+
return [node];
|
|
687
|
+
}
|
|
688
|
+
if (t.isIdentifier(node)) {
|
|
689
|
+
const binding = scope?.getBinding?.(node.name);
|
|
690
|
+
if (binding?.path?.isVariableDeclarator()) {
|
|
691
|
+
return resolveExpressionValueNodes(binding.path.node.init, binding.path.scope, depth + 1);
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
return [];
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
function normalizeLabel(text) {
|
|
698
|
+
return text ? text.replace(/\s+/g, ' ').trim() : '';
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
function dedupeLabels(labels) {
|
|
702
|
+
const seen = new Set();
|
|
703
|
+
const result = [];
|
|
704
|
+
for (const label of labels) {
|
|
705
|
+
const normalized = normalizeLabel(label);
|
|
706
|
+
if (!normalized) continue;
|
|
707
|
+
const key = normalized.toLowerCase();
|
|
708
|
+
if (seen.has(key)) continue;
|
|
709
|
+
seen.add(key);
|
|
710
|
+
result.push(normalized);
|
|
711
|
+
}
|
|
712
|
+
return result;
|
|
589
713
|
}
|
|
590
714
|
|
|
591
715
|
|
|
@@ -667,18 +791,20 @@ function scanExpoRouterApp(projectRoot) {
|
|
|
667
791
|
|
|
668
792
|
const screens = [];
|
|
669
793
|
const layoutTitles = new Map();
|
|
794
|
+
const extractedCache = new Map();
|
|
795
|
+
const resolvedImplementationCache = new Map();
|
|
670
796
|
extractLayoutTitles(appDir, appDir, layoutTitles);
|
|
671
|
-
scanDirectory(appDir, appDir, screens, layoutTitles);
|
|
797
|
+
scanDirectory(appDir, appDir, screens, layoutTitles, projectRoot, extractedCache, resolvedImplementationCache);
|
|
672
798
|
return screens;
|
|
673
799
|
}
|
|
674
800
|
|
|
675
|
-
function scanDirectory(dir, appRoot, screens, layoutTitles) {
|
|
801
|
+
function scanDirectory(dir, appRoot, screens, layoutTitles, projectRoot, extractedCache, resolvedImplementationCache) {
|
|
676
802
|
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
677
803
|
for (const entry of entries) {
|
|
678
804
|
const fullPath = path.join(dir, entry.name);
|
|
679
805
|
if (entry.isDirectory()) {
|
|
680
806
|
if (entry.name.startsWith('.') || entry.name === 'node_modules') continue;
|
|
681
|
-
scanDirectory(fullPath, appRoot, screens, layoutTitles);
|
|
807
|
+
scanDirectory(fullPath, appRoot, screens, layoutTitles, projectRoot, extractedCache, resolvedImplementationCache);
|
|
682
808
|
continue;
|
|
683
809
|
}
|
|
684
810
|
if (!entry.name.match(/\.(tsx?|jsx?)$/)) continue;
|
|
@@ -686,18 +812,68 @@ function scanDirectory(dir, appRoot, screens, layoutTitles) {
|
|
|
686
812
|
if (entry.name.startsWith('+') || entry.name.startsWith('_')) continue;
|
|
687
813
|
|
|
688
814
|
const routeName = filePathToRouteName(fullPath, appRoot);
|
|
689
|
-
const sourceCode = fs.readFileSync(fullPath, 'utf-8');
|
|
690
|
-
const extracted = extractContentFromAST(sourceCode, fullPath);
|
|
691
815
|
const title = layoutTitles.get(routeName);
|
|
816
|
+
const routeCandidate = buildScreenCandidate(routeName, title, fullPath, extractedCache);
|
|
817
|
+
const resolvedImplementation = resolvedImplementationCache.get(fullPath) || resolveProxyScreenFile(fullPath, projectRoot);
|
|
818
|
+
resolvedImplementationCache.set(fullPath, resolvedImplementation);
|
|
819
|
+
const implementationCandidate = resolvedImplementation !== fullPath
|
|
820
|
+
? buildScreenCandidate(routeName, title, resolvedImplementation, extractedCache)
|
|
821
|
+
: routeCandidate;
|
|
822
|
+
const chosenCandidate = scoreScreenCandidate(implementationCandidate) > scoreScreenCandidate(routeCandidate)
|
|
823
|
+
? implementationCandidate
|
|
824
|
+
: routeCandidate;
|
|
692
825
|
|
|
693
826
|
screens.push({
|
|
694
|
-
routeName
|
|
695
|
-
|
|
696
|
-
|
|
827
|
+
routeName: chosenCandidate.routeName,
|
|
828
|
+
filePath: chosenCandidate.filePath,
|
|
829
|
+
title: chosenCandidate.title,
|
|
830
|
+
description: chosenCandidate.description,
|
|
831
|
+
navigationLinks: chosenCandidate.navigationLinks,
|
|
697
832
|
});
|
|
698
833
|
}
|
|
699
834
|
}
|
|
700
835
|
|
|
836
|
+
function buildScreenCandidate(routeName, title, filePath, extractedCache) {
|
|
837
|
+
let extracted = extractedCache.get(filePath);
|
|
838
|
+
if (!extracted) {
|
|
839
|
+
const sourceCode = fs.readFileSync(filePath, 'utf-8');
|
|
840
|
+
extracted = extractContentFromAST(sourceCode, filePath);
|
|
841
|
+
extractedCache.set(filePath, extracted);
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
return {
|
|
845
|
+
routeName,
|
|
846
|
+
filePath,
|
|
847
|
+
title,
|
|
848
|
+
description: buildDescription(extracted),
|
|
849
|
+
navigationLinks: extracted.navigationLinks,
|
|
850
|
+
};
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
function scoreScreenCandidate(screen) {
|
|
854
|
+
let score = 0;
|
|
855
|
+
|
|
856
|
+
if (fs.existsSync(screen.filePath)) score += 20;
|
|
857
|
+
if (screen.description && screen.description !== 'Screen content') score += 80;
|
|
858
|
+
if (screen.navigationLinks.length > 0) score += 12;
|
|
859
|
+
if (screen.title) score += 4;
|
|
860
|
+
|
|
861
|
+
const describedElements = screen.description
|
|
862
|
+
.split(',')
|
|
863
|
+
.map(part => part.trim())
|
|
864
|
+
.filter(part => part && part !== 'Screen content').length;
|
|
865
|
+
score += Math.min(describedElements, 8) * 6;
|
|
866
|
+
|
|
867
|
+
const componentOnlyElements = screen.description
|
|
868
|
+
.split(',')
|
|
869
|
+
.map(part => part.trim())
|
|
870
|
+
.filter(part => part.endsWith('(component)')).length;
|
|
871
|
+
if (componentOnlyElements > 0) score -= componentOnlyElements * 20;
|
|
872
|
+
if (describedElements > 0 && componentOnlyElements === describedElements) score -= 120;
|
|
873
|
+
|
|
874
|
+
return score;
|
|
875
|
+
}
|
|
876
|
+
|
|
701
877
|
// ─── React Navigation Scanner ─────────────────────────────────
|
|
702
878
|
|
|
703
879
|
const NAVIGATOR_FUNCTIONS = [
|
|
@@ -978,43 +1154,93 @@ function extractTitleFromOptions(optionsNode) {
|
|
|
978
1154
|
return null;
|
|
979
1155
|
}
|
|
980
1156
|
|
|
1157
|
+
const FILE_EXTENSIONS = ['.tsx', '.ts', '.jsx', '.js'];
|
|
1158
|
+
const DEFAULT_PROXY_HOPS = 10;
|
|
1159
|
+
const moduleProxyInfoCache = new Map();
|
|
1160
|
+
const tsConfigResolutionCache = new Map();
|
|
1161
|
+
|
|
981
1162
|
function resolveComponentPath(componentName, imports, currentFile) {
|
|
982
1163
|
const parts = componentName.split('.');
|
|
983
1164
|
const baseComponent = parts[0];
|
|
984
1165
|
const property = parts[1];
|
|
985
1166
|
|
|
1167
|
+
if (!baseComponent) return currentFile;
|
|
1168
|
+
|
|
986
1169
|
const importPath = imports.get(baseComponent);
|
|
987
1170
|
if (!importPath) return currentFile;
|
|
988
1171
|
|
|
989
|
-
|
|
990
|
-
if (
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1172
|
+
const resolvedBase = resolveImportSpecifier(importPath, currentFile, _projectRoot);
|
|
1173
|
+
if (!resolvedBase) return currentFile;
|
|
1174
|
+
|
|
1175
|
+
if (!property || resolvedBase === currentFile) return resolvedBase;
|
|
1176
|
+
|
|
1177
|
+
return traceExportProperty(resolvedBase, property, _projectRoot) || resolvedBase;
|
|
1178
|
+
}
|
|
1179
|
+
|
|
1180
|
+
function resolveProxyScreenFile(entryFile, projectRoot, maxHops = DEFAULT_PROXY_HOPS) {
|
|
1181
|
+
let currentFile = path.resolve(entryFile);
|
|
1182
|
+
let exportName = 'default';
|
|
1183
|
+
const visited = new Set();
|
|
1184
|
+
|
|
1185
|
+
for (let hop = 0; hop < maxHops; hop++) {
|
|
1186
|
+
const visitKey = `${currentFile}::${exportName}`;
|
|
1187
|
+
if (visited.has(visitKey)) return currentFile;
|
|
1188
|
+
visited.add(visitKey);
|
|
1189
|
+
|
|
1190
|
+
const next = resolveExportTarget(currentFile, exportName, projectRoot);
|
|
1191
|
+
if (!next) return currentFile;
|
|
1192
|
+
if (next.filePath === currentFile && next.exportName === exportName) return currentFile;
|
|
1193
|
+
|
|
1194
|
+
currentFile = next.filePath;
|
|
1195
|
+
exportName = next.exportName;
|
|
1196
|
+
}
|
|
1197
|
+
|
|
1198
|
+
return currentFile;
|
|
1199
|
+
}
|
|
1200
|
+
|
|
1201
|
+
function resolveExportTarget(filePath, exportName, projectRoot) {
|
|
1202
|
+
const info = getModuleProxyInfo(filePath);
|
|
1203
|
+
if (!info) return null;
|
|
1204
|
+
|
|
1205
|
+
if (exportName === 'default') {
|
|
1206
|
+
if (info.defaultExportLocal) {
|
|
1207
|
+
const imported = info.imports.get(info.defaultExportLocal);
|
|
1208
|
+
if (imported) {
|
|
1209
|
+
const resolved = resolveImportSpecifier(imported.source, filePath, projectRoot);
|
|
1210
|
+
if (resolved) {
|
|
1211
|
+
return { filePath: resolved, exportName: imported.importedName };
|
|
1006
1212
|
}
|
|
1007
1213
|
}
|
|
1214
|
+
return null;
|
|
1008
1215
|
}
|
|
1009
|
-
|
|
1216
|
+
|
|
1217
|
+
if (info.hasLocalDefaultExport) return null;
|
|
1010
1218
|
}
|
|
1011
1219
|
|
|
1012
|
-
|
|
1220
|
+
const link = info.exportLinks.find(candidate => candidate.exportedName === exportName);
|
|
1221
|
+
if (!link) return null;
|
|
1013
1222
|
|
|
1014
|
-
|
|
1223
|
+
if (link.source) {
|
|
1224
|
+
const resolved = resolveImportSpecifier(link.source, filePath, projectRoot);
|
|
1225
|
+
if (!resolved) return null;
|
|
1226
|
+
return { filePath: resolved, exportName: link.importedName || 'default' };
|
|
1227
|
+
}
|
|
1228
|
+
|
|
1229
|
+
if (link.localName) {
|
|
1230
|
+
const imported = info.imports.get(link.localName);
|
|
1231
|
+
if (imported) {
|
|
1232
|
+
const resolved = resolveImportSpecifier(imported.source, filePath, projectRoot);
|
|
1233
|
+
if (!resolved) return null;
|
|
1234
|
+
return { filePath: resolved, exportName: imported.importedName };
|
|
1235
|
+
}
|
|
1236
|
+
|
|
1237
|
+
if (info.localBindings.has(link.localName)) return null;
|
|
1238
|
+
}
|
|
1239
|
+
|
|
1240
|
+
return null;
|
|
1015
1241
|
}
|
|
1016
1242
|
|
|
1017
|
-
function traceExportProperty(filePath,
|
|
1243
|
+
function traceExportProperty(filePath, propertyName, projectRoot) {
|
|
1018
1244
|
if (!fs.existsSync(filePath)) return null;
|
|
1019
1245
|
const sourceCode = fs.readFileSync(filePath, 'utf-8');
|
|
1020
1246
|
let ast;
|
|
@@ -1022,112 +1248,277 @@ function traceExportProperty(filePath, objectName, propertyName) {
|
|
|
1022
1248
|
ast = parse(sourceCode, { sourceType: 'module', plugins: ['jsx', 'typescript', 'decorators-legacy'] });
|
|
1023
1249
|
} catch { return null; }
|
|
1024
1250
|
|
|
1025
|
-
const
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
const
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1251
|
+
const imports = new Map();
|
|
1252
|
+
let importName = null;
|
|
1253
|
+
|
|
1254
|
+
for (const node of ast.program.body) {
|
|
1255
|
+
if (t.isImportDeclaration(node)) {
|
|
1256
|
+
const source = node.source.value;
|
|
1257
|
+
for (const specifier of node.specifiers) {
|
|
1258
|
+
if (t.isImportDefaultSpecifier(specifier)) {
|
|
1259
|
+
imports.set(specifier.local.name, source);
|
|
1260
|
+
} else if (t.isImportSpecifier(specifier)) {
|
|
1261
|
+
imports.set(specifier.local.name, t.isIdentifier(specifier.imported) ? specifier.imported.name : specifier.imported.value);
|
|
1035
1262
|
}
|
|
1036
1263
|
}
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1264
|
+
continue;
|
|
1265
|
+
}
|
|
1266
|
+
|
|
1267
|
+
if (t.isVariableDeclaration(node)) {
|
|
1268
|
+
for (const declaration of node.declarations) {
|
|
1269
|
+
if (t.isIdentifier(declaration.id) && t.isObjectExpression(declaration.init)) {
|
|
1270
|
+
for (const property of declaration.init.properties) {
|
|
1271
|
+
if (
|
|
1272
|
+
t.isObjectProperty(property) &&
|
|
1273
|
+
!property.computed &&
|
|
1274
|
+
t.isIdentifier(property.key) &&
|
|
1275
|
+
property.key.name === propertyName &&
|
|
1276
|
+
t.isIdentifier(property.value)
|
|
1277
|
+
) {
|
|
1278
|
+
importName = property.value.name;
|
|
1279
|
+
}
|
|
1048
1280
|
}
|
|
1049
1281
|
}
|
|
1050
1282
|
}
|
|
1051
|
-
|
|
1052
|
-
// Also collect all local variable assignments for later unwrapping
|
|
1053
|
-
if (init) {
|
|
1054
|
-
localVarInits.set(id.name, init);
|
|
1055
|
-
}
|
|
1056
1283
|
}
|
|
1057
|
-
}
|
|
1284
|
+
}
|
|
1058
1285
|
|
|
1059
|
-
if (!
|
|
1286
|
+
if (!importName) return null;
|
|
1060
1287
|
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
if (!ref) return null;
|
|
1288
|
+
const importPath = imports.get(importName);
|
|
1289
|
+
if (!importPath) return null;
|
|
1064
1290
|
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1291
|
+
return resolveImportSpecifier(importPath, filePath, projectRoot);
|
|
1292
|
+
}
|
|
1293
|
+
|
|
1294
|
+
function resolveImportSpecifier(importPath, currentFile, projectRoot) {
|
|
1295
|
+
const direct = resolveImportCandidate(importPath, currentFile, projectRoot);
|
|
1296
|
+
return direct ? path.resolve(direct) : null;
|
|
1297
|
+
}
|
|
1298
|
+
|
|
1299
|
+
function resolveImportCandidate(importPath, currentFile, projectRoot) {
|
|
1300
|
+
if (!importPath || importPath.includes('node_modules')) return null;
|
|
1301
|
+
|
|
1302
|
+
if (importPath.startsWith('.')) {
|
|
1303
|
+
return resolveFilePath(path.dirname(currentFile), importPath);
|
|
1304
|
+
}
|
|
1305
|
+
|
|
1306
|
+
const tsConfig = getTsConfigResolution(projectRoot);
|
|
1307
|
+
for (const alias of tsConfig.aliases) {
|
|
1308
|
+
const wildcardValue = matchAlias(importPath, alias.keyPrefix, alias.keySuffix);
|
|
1309
|
+
if (wildcardValue === null) continue;
|
|
1310
|
+
const target = `${alias.targetPrefix}${wildcardValue}${alias.targetSuffix}`;
|
|
1311
|
+
const resolved = resolveFilePath(projectRoot, target);
|
|
1312
|
+
if (resolved) return resolved;
|
|
1069
1313
|
}
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1314
|
+
|
|
1315
|
+
if (tsConfig.baseUrl) {
|
|
1316
|
+
const resolved = resolveFilePath(tsConfig.baseUrl, importPath);
|
|
1317
|
+
if (resolved) return resolved;
|
|
1073
1318
|
}
|
|
1074
|
-
|
|
1319
|
+
|
|
1320
|
+
const srcResolved = resolveFilePath(path.join(projectRoot, 'src'), importPath);
|
|
1321
|
+
if (srcResolved) return srcResolved;
|
|
1322
|
+
|
|
1323
|
+
const rootResolved = resolveFilePath(projectRoot, importPath);
|
|
1324
|
+
if (rootResolved) return rootResolved;
|
|
1325
|
+
|
|
1326
|
+
return null;
|
|
1075
1327
|
}
|
|
1076
1328
|
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
* Handles: Identifier (import lookup), React.lazy(() => import('path')),
|
|
1080
|
-
* React.memo(X), connect(mapState)(X), observer(X), withX(X), etc.
|
|
1081
|
-
* Returns the relative import path string or null.
|
|
1082
|
-
*/
|
|
1083
|
-
function unwrapToComponentRef(node, imports, localVars, depth = 0) {
|
|
1084
|
-
if (!node || depth > 5) return null; // safety: prevent infinite recursion
|
|
1329
|
+
function resolveFilePath(baseDir, rawPath) {
|
|
1330
|
+
const candidateBase = path.resolve(baseDir, rawPath);
|
|
1085
1331
|
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1332
|
+
if (fs.existsSync(candidateBase) && fs.statSync(candidateBase).isFile()) {
|
|
1333
|
+
return candidateBase;
|
|
1334
|
+
}
|
|
1335
|
+
|
|
1336
|
+
for (const extension of FILE_EXTENSIONS) {
|
|
1337
|
+
const withExtension = `${candidateBase}${extension}`;
|
|
1338
|
+
if (fs.existsSync(withExtension)) return withExtension;
|
|
1339
|
+
}
|
|
1340
|
+
|
|
1341
|
+
for (const extension of FILE_EXTENSIONS) {
|
|
1342
|
+
const indexPath = path.join(candidateBase, `index${extension}`);
|
|
1343
|
+
if (fs.existsSync(indexPath)) return indexPath;
|
|
1344
|
+
}
|
|
1090
1345
|
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1346
|
+
return null;
|
|
1347
|
+
}
|
|
1348
|
+
|
|
1349
|
+
function getTsConfigResolution(projectRoot) {
|
|
1350
|
+
const cached = tsConfigResolutionCache.get(projectRoot);
|
|
1351
|
+
if (cached) return cached;
|
|
1352
|
+
|
|
1353
|
+
const tsConfigPath = path.join(projectRoot, 'tsconfig.json');
|
|
1354
|
+
const resolution = { aliases: [], baseUrl: undefined };
|
|
1355
|
+
|
|
1356
|
+
try {
|
|
1357
|
+
if (fs.existsSync(tsConfigPath)) {
|
|
1358
|
+
const raw = JSON.parse(fs.readFileSync(tsConfigPath, 'utf-8'));
|
|
1359
|
+
const compilerOptions = (raw && raw.compilerOptions) || {};
|
|
1360
|
+
if (typeof compilerOptions.baseUrl === 'string') {
|
|
1361
|
+
resolution.baseUrl = path.resolve(projectRoot, compilerOptions.baseUrl);
|
|
1362
|
+
}
|
|
1363
|
+
resolution.aliases = normalizePathAliases(compilerOptions.paths || {});
|
|
1364
|
+
}
|
|
1365
|
+
} catch {
|
|
1366
|
+
// Ignore malformed tsconfig and fall back to conventional resolution.
|
|
1367
|
+
}
|
|
1368
|
+
|
|
1369
|
+
tsConfigResolutionCache.set(projectRoot, resolution);
|
|
1370
|
+
return resolution;
|
|
1371
|
+
}
|
|
1372
|
+
|
|
1373
|
+
function normalizePathAliases(pathsConfig) {
|
|
1374
|
+
const aliases = [];
|
|
1375
|
+
for (const [key, values] of Object.entries(pathsConfig)) {
|
|
1376
|
+
const target = values && values[0];
|
|
1377
|
+
if (!target) continue;
|
|
1378
|
+
const [keyPrefix, keySuffix] = splitAliasPattern(key);
|
|
1379
|
+
const [targetPrefix, targetSuffix] = splitAliasPattern(target);
|
|
1380
|
+
aliases.push({ keyPrefix, keySuffix, targetPrefix, targetSuffix });
|
|
1381
|
+
}
|
|
1382
|
+
return aliases;
|
|
1383
|
+
}
|
|
1384
|
+
|
|
1385
|
+
function splitAliasPattern(pattern) {
|
|
1386
|
+
const starIndex = pattern.indexOf('*');
|
|
1387
|
+
if (starIndex === -1) return [pattern, ''];
|
|
1388
|
+
return [pattern.slice(0, starIndex), pattern.slice(starIndex + 1)];
|
|
1389
|
+
}
|
|
1094
1390
|
|
|
1391
|
+
function matchAlias(value, prefix, suffix) {
|
|
1392
|
+
if (!value.startsWith(prefix)) return null;
|
|
1393
|
+
if (suffix && !value.endsWith(suffix)) return null;
|
|
1394
|
+
return value.slice(prefix.length, suffix ? value.length - suffix.length : undefined);
|
|
1395
|
+
}
|
|
1396
|
+
|
|
1397
|
+
function getModuleProxyInfo(filePath) {
|
|
1398
|
+
const cached = moduleProxyInfoCache.get(filePath);
|
|
1399
|
+
if (cached !== undefined) return cached;
|
|
1400
|
+
|
|
1401
|
+
if (!fs.existsSync(filePath)) {
|
|
1402
|
+
moduleProxyInfoCache.set(filePath, null);
|
|
1403
|
+
return null;
|
|
1404
|
+
}
|
|
1405
|
+
|
|
1406
|
+
let ast;
|
|
1407
|
+
try {
|
|
1408
|
+
ast = parse(fs.readFileSync(filePath, 'utf-8'), {
|
|
1409
|
+
sourceType: 'module',
|
|
1410
|
+
plugins: ['jsx', 'typescript', 'decorators-legacy'],
|
|
1411
|
+
});
|
|
1412
|
+
} catch {
|
|
1413
|
+
moduleProxyInfoCache.set(filePath, null);
|
|
1095
1414
|
return null;
|
|
1096
1415
|
}
|
|
1097
1416
|
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1417
|
+
const info = {
|
|
1418
|
+
imports: new Map(),
|
|
1419
|
+
exportLinks: [],
|
|
1420
|
+
localBindings: new Set(),
|
|
1421
|
+
defaultExportLocal: undefined,
|
|
1422
|
+
hasLocalDefaultExport: false,
|
|
1423
|
+
};
|
|
1424
|
+
|
|
1425
|
+
for (const node of ast.program.body) {
|
|
1426
|
+
if (t.isImportDeclaration(node)) {
|
|
1427
|
+
const source = node.source.value;
|
|
1428
|
+
for (const specifier of node.specifiers) {
|
|
1429
|
+
if (t.isImportDefaultSpecifier(specifier)) {
|
|
1430
|
+
info.imports.set(specifier.local.name, { source, importedName: 'default' });
|
|
1431
|
+
} else if (t.isImportSpecifier(specifier)) {
|
|
1432
|
+
info.imports.set(specifier.local.name, {
|
|
1433
|
+
source,
|
|
1434
|
+
importedName: t.isIdentifier(specifier.imported) ? specifier.imported.name : specifier.imported.value,
|
|
1435
|
+
});
|
|
1111
1436
|
}
|
|
1112
1437
|
}
|
|
1438
|
+
continue;
|
|
1439
|
+
}
|
|
1440
|
+
|
|
1441
|
+
if (t.isExportDefaultDeclaration(node)) {
|
|
1442
|
+
if (t.isIdentifier(node.declaration)) {
|
|
1443
|
+
info.defaultExportLocal = node.declaration.name;
|
|
1444
|
+
} else {
|
|
1445
|
+
info.hasLocalDefaultExport = true;
|
|
1446
|
+
collectLocalBindingNames(node.declaration, info.localBindings);
|
|
1447
|
+
}
|
|
1448
|
+
continue;
|
|
1449
|
+
}
|
|
1450
|
+
|
|
1451
|
+
if (t.isExportNamedDeclaration(node)) {
|
|
1452
|
+
if (node.declaration) {
|
|
1453
|
+
collectLocalBindingNames(node.declaration, info.localBindings);
|
|
1454
|
+
}
|
|
1455
|
+
|
|
1456
|
+
for (const specifier of node.specifiers) {
|
|
1457
|
+
if (!t.isExportSpecifier(specifier)) continue;
|
|
1458
|
+
|
|
1459
|
+
info.exportLinks.push({
|
|
1460
|
+
exportedName: getModuleExportedName(specifier.exported),
|
|
1461
|
+
source: node.source ? node.source.value : undefined,
|
|
1462
|
+
importedName: node.source ? getModuleExportedName(specifier.local) : undefined,
|
|
1463
|
+
localName: node.source ? undefined : getModuleExportedName(specifier.local),
|
|
1464
|
+
});
|
|
1465
|
+
}
|
|
1113
1466
|
}
|
|
1467
|
+
}
|
|
1468
|
+
|
|
1469
|
+
moduleProxyInfoCache.set(filePath, info);
|
|
1470
|
+
return info;
|
|
1471
|
+
}
|
|
1472
|
+
|
|
1473
|
+
function getModuleExportedName(node) {
|
|
1474
|
+
return t.isIdentifier(node) ? node.name : node.value;
|
|
1475
|
+
}
|
|
1114
1476
|
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
if (node.
|
|
1118
|
-
|
|
1119
|
-
|
|
1477
|
+
function collectLocalBindingNames(node, bindings) {
|
|
1478
|
+
if (t.isFunctionDeclaration(node) || t.isClassDeclaration(node)) {
|
|
1479
|
+
if (node.id) bindings.add(node.id.name);
|
|
1480
|
+
return;
|
|
1481
|
+
}
|
|
1482
|
+
|
|
1483
|
+
if (t.isVariableDeclaration(node)) {
|
|
1484
|
+
for (const declaration of node.declarations) {
|
|
1485
|
+
collectBindingNamesFromPattern(declaration.id, bindings);
|
|
1120
1486
|
}
|
|
1487
|
+
}
|
|
1488
|
+
}
|
|
1489
|
+
|
|
1490
|
+
function collectBindingNamesFromPattern(pattern, bindings) {
|
|
1491
|
+
if (t.isIdentifier(pattern)) {
|
|
1492
|
+
bindings.add(pattern.name);
|
|
1493
|
+
return;
|
|
1494
|
+
}
|
|
1121
1495
|
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1496
|
+
if (t.isObjectPattern(pattern)) {
|
|
1497
|
+
for (const property of pattern.properties) {
|
|
1498
|
+
if (t.isRestElement(property)) {
|
|
1499
|
+
collectBindingNamesFromPattern(property.argument, bindings);
|
|
1500
|
+
} else if (t.isObjectProperty(property)) {
|
|
1501
|
+
collectBindingNamesFromPattern(property.value, bindings);
|
|
1502
|
+
}
|
|
1125
1503
|
}
|
|
1504
|
+
return;
|
|
1505
|
+
}
|
|
1126
1506
|
|
|
1127
|
-
|
|
1507
|
+
if (t.isArrayPattern(pattern)) {
|
|
1508
|
+
for (const element of pattern.elements) {
|
|
1509
|
+
if (element) collectBindingNamesFromPattern(element, bindings);
|
|
1510
|
+
}
|
|
1511
|
+
return;
|
|
1128
1512
|
}
|
|
1129
1513
|
|
|
1130
|
-
|
|
1514
|
+
if (t.isAssignmentPattern(pattern)) {
|
|
1515
|
+
collectBindingNamesFromPattern(pattern.left, bindings);
|
|
1516
|
+
return;
|
|
1517
|
+
}
|
|
1518
|
+
|
|
1519
|
+
if (t.isRestElement(pattern)) {
|
|
1520
|
+
collectBindingNamesFromPattern(pattern.argument, bindings);
|
|
1521
|
+
}
|
|
1131
1522
|
}
|
|
1132
1523
|
|
|
1133
1524
|
// ─── Main ──────────────────────────────────────────────────────
|