@arcmantle/lit-jsx 1.0.5 → 1.0.6
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.
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import * as babel from '@babel/core';
|
|
2
|
+
import { NodePath } from '@babel/traverse';
|
|
3
|
+
import * as t from '@babel/types';
|
|
4
|
+
export type BabelPlugins = NonNullable<NonNullable<babel.TransformOptions['parserOpts']>['plugins']>;
|
|
5
|
+
export interface ElementDefinition {
|
|
6
|
+
type: 'custom-element' | 'import' | 'local-variable' | 'unknown';
|
|
7
|
+
source?: string;
|
|
8
|
+
originalName?: string;
|
|
9
|
+
callExpression?: t.CallExpression;
|
|
10
|
+
}
|
|
11
|
+
export declare function findElementDefinition(path: NodePath<t.JSXOpeningElement>): ElementDefinition;
|
|
12
|
+
//# sourceMappingURL=import-discovery.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"import-discovery.d.ts","sourceRoot":"","sources":["../../src/compiler/import-discovery.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,KAAK,MAAM,aAAa,CAAC;AACrC,OAAiB,EAAgB,QAAQ,EAAS,MAAM,iBAAiB,CAAC;AAC1E,OAAO,KAAK,CAAC,MAAM,cAAc,CAAC;AAOlC,MAAM,MAAM,YAAY,GAAG,WAAW,CAAC,WAAW,CAAC,KAAK,CAAC,gBAAgB,CAAC,YAAY,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC;AAIrG,MAAM,WAAW,iBAAiB;IACjC,IAAI,EAAa,gBAAgB,GAAG,QAAQ,GAAG,gBAAgB,GAAG,SAAS,CAAC;IAC5E,MAAM,CAAC,EAAU,MAAM,CAAC;IACxB,YAAY,CAAC,EAAI,MAAM,CAAC;IACxB,cAAc,CAAC,EAAE,CAAC,CAAC,cAAc,CAAC;CAClC;AAID,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC,CAAC,iBAAiB,CAAC,GAAG,iBAAiB,CAiB5F"}
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
2
|
+
import { dirname, resolve } from 'node:path';
|
|
3
|
+
import * as babel from '@babel/core';
|
|
4
|
+
import traverse, { Binding, Hub, NodePath, Scope } from '@babel/traverse';
|
|
5
|
+
import * as t from '@babel/types';
|
|
6
|
+
// Cache for parsed files to avoid re-parsing
|
|
7
|
+
const fileCache = new Map();
|
|
8
|
+
// Helper function to find the definition of a JSX element
|
|
9
|
+
export function findElementDefinition(path) {
|
|
10
|
+
const elementName = path.node.name;
|
|
11
|
+
const hub = path.hub;
|
|
12
|
+
const currentFileName = hub.file.opts.filename;
|
|
13
|
+
// Only handle JSXIdentifier (not JSXMemberExpression or JSXNamespacedName)
|
|
14
|
+
if (!t.isJSXIdentifier(elementName))
|
|
15
|
+
return { type: 'unknown' };
|
|
16
|
+
const elementNameString = elementName.name;
|
|
17
|
+
//console.log('Tracing element:', elementNameString);
|
|
18
|
+
// Start tracing with the current file context
|
|
19
|
+
return traceElementDefinition(elementNameString, path.scope, currentFileName);
|
|
20
|
+
}
|
|
21
|
+
// Core recursive tracing function
|
|
22
|
+
function traceElementDefinition(elementName, scope, currentFileName, visitedFiles = new Set()) {
|
|
23
|
+
// Prevent infinite recursion
|
|
24
|
+
if (visitedFiles.has(`${currentFileName}:${elementName}`))
|
|
25
|
+
return { type: 'unknown' };
|
|
26
|
+
visitedFiles.add(`${currentFileName}:${elementName}`);
|
|
27
|
+
// Check if there's a binding for this identifier in the current scope
|
|
28
|
+
const binding = scope.getBinding(elementName);
|
|
29
|
+
if (!binding) {
|
|
30
|
+
//console.log('No binding found for:', elementName);
|
|
31
|
+
return { type: 'unknown' };
|
|
32
|
+
}
|
|
33
|
+
//console.log('Binding kind:', binding.kind, 'type:', binding.path.type);
|
|
34
|
+
// Handle imports
|
|
35
|
+
if (binding.kind === 'module' && t.isImportSpecifier(binding.path.node))
|
|
36
|
+
return traceImport(binding, currentFileName, elementName, visitedFiles);
|
|
37
|
+
// Handle local variables/constants
|
|
38
|
+
if (binding.kind === 'const' || binding.kind === 'let' || binding.kind === 'var')
|
|
39
|
+
return traceLocalVariable(binding, currentFileName, visitedFiles);
|
|
40
|
+
return { type: 'unknown' };
|
|
41
|
+
}
|
|
42
|
+
function traceImport(binding, currentFileName, elementName, visitedFiles) {
|
|
43
|
+
const importDeclaration = binding.path.parent;
|
|
44
|
+
if (!t.isImportDeclaration(importDeclaration))
|
|
45
|
+
return { type: 'unknown' };
|
|
46
|
+
const importSource = importDeclaration.source.value;
|
|
47
|
+
const currentDir = dirname(currentFileName);
|
|
48
|
+
const resolvedPath = resolve(currentDir, importSource);
|
|
49
|
+
//console.log('Tracing import from:', importSource);
|
|
50
|
+
//console.log('Resolved to:', resolvedPath);
|
|
51
|
+
// Use cached parsing
|
|
52
|
+
const ast = getOrParseFile(resolvedPath);
|
|
53
|
+
if (!ast)
|
|
54
|
+
return { type: 'unknown' };
|
|
55
|
+
let result = { type: 'unknown' };
|
|
56
|
+
traverse(ast, {
|
|
57
|
+
Program(programPath) {
|
|
58
|
+
// First try to find a local binding (normal export)
|
|
59
|
+
const localBinding = programPath.scope.getBinding(elementName);
|
|
60
|
+
if (localBinding) {
|
|
61
|
+
//console.log('Found local binding in imported file');
|
|
62
|
+
result = traceElementDefinition(elementName, programPath.scope, resolvedPath, visitedFiles);
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
// If no local binding found, check for re-exports
|
|
66
|
+
//console.log('No local binding, checking for re-exports...');
|
|
67
|
+
result = checkForReExports(programPath, elementName, resolvedPath, visitedFiles);
|
|
68
|
+
},
|
|
69
|
+
});
|
|
70
|
+
return result;
|
|
71
|
+
}
|
|
72
|
+
function traceLocalVariable(binding, currentFileName, visitedFiles) {
|
|
73
|
+
//console.log('Tracing local variable:', binding.kind, binding.path.type);
|
|
74
|
+
// Check if it's a variable declarator (const/let/var)
|
|
75
|
+
if (t.isVariableDeclarator(binding.path.node)) {
|
|
76
|
+
const declarator = binding.path.node;
|
|
77
|
+
// Check if it's assigned to a call expression
|
|
78
|
+
if (declarator.init && t.isCallExpression(declarator.init)) {
|
|
79
|
+
const callExpr = declarator.init;
|
|
80
|
+
// Check if it's a toJSX call
|
|
81
|
+
if (t.isIdentifier(callExpr.callee) && callExpr.callee.name === 'toJSX') {
|
|
82
|
+
return {
|
|
83
|
+
type: 'custom-element',
|
|
84
|
+
source: currentFileName,
|
|
85
|
+
callExpression: callExpr,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
// Could be assigned to another function call - trace that too
|
|
89
|
+
//console.log('Local variable assigned to call expression:', callExpr.callee);
|
|
90
|
+
}
|
|
91
|
+
// Check if it's assigned to an identifier (another variable)
|
|
92
|
+
if (declarator.init && t.isIdentifier(declarator.init)) {
|
|
93
|
+
const assignedIdentifier = declarator.init.name;
|
|
94
|
+
//console.log('Local variable assigned to identifier:', assignedIdentifier);
|
|
95
|
+
// Recursively trace this identifier in the same scope
|
|
96
|
+
return traceElementDefinition(assignedIdentifier, binding.path.scope, currentFileName, visitedFiles);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return { type: 'local-variable' };
|
|
100
|
+
}
|
|
101
|
+
function checkForReExports(programPath, elementName, currentFileName, visitedFiles) {
|
|
102
|
+
let result = { type: 'unknown' };
|
|
103
|
+
// Check all export declarations for re-exports
|
|
104
|
+
programPath.traverse({
|
|
105
|
+
ExportNamedDeclaration(exportPath) {
|
|
106
|
+
const node = exportPath.node;
|
|
107
|
+
if (!node.source || !node.specifiers)
|
|
108
|
+
return; // Skip if no source or specifiers
|
|
109
|
+
// Handle re-exports: export { X } from './file'
|
|
110
|
+
//console.log('Found re-export from:', node.source.value);
|
|
111
|
+
for (const specifier of node.specifiers) {
|
|
112
|
+
if (!t.isExportSpecifier(specifier))
|
|
113
|
+
continue; // Only handle export specifiers
|
|
114
|
+
const exportedName = t.isIdentifier(specifier.exported)
|
|
115
|
+
? specifier.exported.name
|
|
116
|
+
: specifier.exported.value;
|
|
117
|
+
// Check if this re-export matches our element name
|
|
118
|
+
if (exportedName !== elementName)
|
|
119
|
+
continue;
|
|
120
|
+
const originalName = specifier.local.name;
|
|
121
|
+
//console.log(`Found re-export: ${ originalName } as ${ exportedName }`);
|
|
122
|
+
// Resolve and trace the re-exported file
|
|
123
|
+
const reExportSource = node.source.value;
|
|
124
|
+
const currentDir = dirname(currentFileName);
|
|
125
|
+
const resolvedPath = resolve(currentDir, reExportSource);
|
|
126
|
+
if (!existsSync(resolvedPath))
|
|
127
|
+
continue; // Skip if file doesn't exist
|
|
128
|
+
//console.log('Tracing re-export to:', resolvedPath);
|
|
129
|
+
result = traceReExport(originalName, resolvedPath, visitedFiles);
|
|
130
|
+
// Stop traversing once we find the match
|
|
131
|
+
return exportPath.stop();
|
|
132
|
+
}
|
|
133
|
+
},
|
|
134
|
+
});
|
|
135
|
+
return result;
|
|
136
|
+
}
|
|
137
|
+
function traceReExport(elementName, filePath, visitedFiles) {
|
|
138
|
+
// Use cached parsing
|
|
139
|
+
const ast = getOrParseFile(filePath);
|
|
140
|
+
if (!ast)
|
|
141
|
+
return { type: 'unknown' };
|
|
142
|
+
let result = { type: 'unknown' };
|
|
143
|
+
traverse(ast, {
|
|
144
|
+
Program(programPath) {
|
|
145
|
+
// Continue tracing in the re-exported file
|
|
146
|
+
result = traceElementDefinition(elementName, programPath.scope, filePath, visitedFiles);
|
|
147
|
+
},
|
|
148
|
+
});
|
|
149
|
+
return result;
|
|
150
|
+
}
|
|
151
|
+
// Helper function to get or parse a file with caching
|
|
152
|
+
function getOrParseFile(filePath) {
|
|
153
|
+
// Check cache first
|
|
154
|
+
if (fileCache.has(filePath)) {
|
|
155
|
+
//console.log('Using cached AST for:', filePath);
|
|
156
|
+
return fileCache.get(filePath);
|
|
157
|
+
}
|
|
158
|
+
// File not in cache, parse it
|
|
159
|
+
if (!existsSync(filePath))
|
|
160
|
+
return;
|
|
161
|
+
const fileContent = readFileSync(filePath, 'utf-8');
|
|
162
|
+
try {
|
|
163
|
+
const ast = babel.parseSync(fileContent, {
|
|
164
|
+
filename: filePath,
|
|
165
|
+
parserOpts: {
|
|
166
|
+
plugins: ['jsx', 'typescript'],
|
|
167
|
+
},
|
|
168
|
+
});
|
|
169
|
+
if (ast) {
|
|
170
|
+
//console.log('Parsed and cached:', filePath);
|
|
171
|
+
fileCache.set(filePath, ast);
|
|
172
|
+
return ast;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
catch (error) {
|
|
176
|
+
console.log('Failed to parse file:', filePath, error);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
//# sourceMappingURL=import-discovery.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"import-discovery.js","sourceRoot":"","sources":["../../src/compiler/import-discovery.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
2
|
+
import { dirname, resolve } from 'node:path';
|
|
3
|
+
|
|
4
|
+
import * as babel from '@babel/core';
|
|
5
|
+
import traverse, { Binding, Hub, NodePath, Scope } from '@babel/traverse';
|
|
6
|
+
import * as t from '@babel/types';
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
// Cache for parsed files to avoid re-parsing
|
|
10
|
+
const fileCache: Map<string, t.File> = new Map();
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
export type BabelPlugins = NonNullable<NonNullable<babel.TransformOptions['parserOpts']>['plugins']>;
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
// Types for our discovery results
|
|
17
|
+
export interface ElementDefinition {
|
|
18
|
+
type: 'custom-element' | 'import' | 'local-variable' | 'unknown';
|
|
19
|
+
source?: string; // file path for imports
|
|
20
|
+
originalName?: string; // for imports/re-exports
|
|
21
|
+
callExpression?: t.CallExpression; // for toJSX calls
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
// Helper function to find the definition of a JSX element
|
|
26
|
+
export function findElementDefinition(path: NodePath<t.JSXOpeningElement>): ElementDefinition {
|
|
27
|
+
const elementName = path.node.name;
|
|
28
|
+
|
|
29
|
+
const hub = path.hub as Hub & { file: { opts: { filename: string; }; }; };
|
|
30
|
+
|
|
31
|
+
const currentFileName = hub.file.opts.filename;
|
|
32
|
+
|
|
33
|
+
// Only handle JSXIdentifier (not JSXMemberExpression or JSXNamespacedName)
|
|
34
|
+
if (!t.isJSXIdentifier(elementName))
|
|
35
|
+
return { type: 'unknown' };
|
|
36
|
+
|
|
37
|
+
const elementNameString = elementName.name;
|
|
38
|
+
|
|
39
|
+
//console.log('Tracing element:', elementNameString);
|
|
40
|
+
|
|
41
|
+
// Start tracing with the current file context
|
|
42
|
+
return traceElementDefinition(elementNameString, path.scope, currentFileName);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Core recursive tracing function
|
|
46
|
+
function traceElementDefinition(
|
|
47
|
+
elementName: string,
|
|
48
|
+
scope: Scope,
|
|
49
|
+
currentFileName: string,
|
|
50
|
+
visitedFiles: Set<string> = new Set(),
|
|
51
|
+
): ElementDefinition {
|
|
52
|
+
// Prevent infinite recursion
|
|
53
|
+
if (visitedFiles.has(`${ currentFileName }:${ elementName }`))
|
|
54
|
+
return { type: 'unknown' };
|
|
55
|
+
|
|
56
|
+
visitedFiles.add(`${ currentFileName }:${ elementName }`);
|
|
57
|
+
|
|
58
|
+
// Check if there's a binding for this identifier in the current scope
|
|
59
|
+
const binding = scope.getBinding(elementName);
|
|
60
|
+
|
|
61
|
+
if (!binding) {
|
|
62
|
+
//console.log('No binding found for:', elementName);
|
|
63
|
+
|
|
64
|
+
return { type: 'unknown' };
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
//console.log('Binding kind:', binding.kind, 'type:', binding.path.type);
|
|
68
|
+
|
|
69
|
+
// Handle imports
|
|
70
|
+
if (binding.kind === 'module' && t.isImportSpecifier(binding.path.node))
|
|
71
|
+
return traceImport(binding, currentFileName, elementName, visitedFiles);
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
// Handle local variables/constants
|
|
75
|
+
if (binding.kind === 'const' || binding.kind === 'let' || binding.kind === 'var')
|
|
76
|
+
return traceLocalVariable(binding, currentFileName, visitedFiles);
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
return { type: 'unknown' };
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function traceImport(
|
|
83
|
+
binding: Binding,
|
|
84
|
+
currentFileName: string,
|
|
85
|
+
elementName: string,
|
|
86
|
+
visitedFiles: Set<string>,
|
|
87
|
+
): ElementDefinition {
|
|
88
|
+
const importDeclaration = binding.path.parent;
|
|
89
|
+
|
|
90
|
+
if (!t.isImportDeclaration(importDeclaration))
|
|
91
|
+
return { type: 'unknown' };
|
|
92
|
+
|
|
93
|
+
const importSource = importDeclaration.source.value;
|
|
94
|
+
const currentDir = dirname(currentFileName);
|
|
95
|
+
const resolvedPath = resolve(currentDir, importSource);
|
|
96
|
+
|
|
97
|
+
//console.log('Tracing import from:', importSource);
|
|
98
|
+
//console.log('Resolved to:', resolvedPath);
|
|
99
|
+
|
|
100
|
+
// Use cached parsing
|
|
101
|
+
const ast = getOrParseFile(resolvedPath);
|
|
102
|
+
if (!ast)
|
|
103
|
+
return { type: 'unknown' };
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
let result: ElementDefinition = { type: 'unknown' };
|
|
107
|
+
|
|
108
|
+
traverse(ast, {
|
|
109
|
+
Program(programPath) {
|
|
110
|
+
// First try to find a local binding (normal export)
|
|
111
|
+
const localBinding = programPath.scope.getBinding(elementName);
|
|
112
|
+
|
|
113
|
+
if (localBinding) {
|
|
114
|
+
//console.log('Found local binding in imported file');
|
|
115
|
+
result = traceElementDefinition(elementName, programPath.scope, resolvedPath, visitedFiles);
|
|
116
|
+
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// If no local binding found, check for re-exports
|
|
121
|
+
//console.log('No local binding, checking for re-exports...');
|
|
122
|
+
result = checkForReExports(programPath, elementName, resolvedPath, visitedFiles);
|
|
123
|
+
},
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
return result;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function traceLocalVariable(
|
|
130
|
+
binding: Binding,
|
|
131
|
+
currentFileName: string,
|
|
132
|
+
visitedFiles: Set<string>,
|
|
133
|
+
): ElementDefinition {
|
|
134
|
+
//console.log('Tracing local variable:', binding.kind, binding.path.type);
|
|
135
|
+
|
|
136
|
+
// Check if it's a variable declarator (const/let/var)
|
|
137
|
+
if (t.isVariableDeclarator(binding.path.node)) {
|
|
138
|
+
const declarator = binding.path.node;
|
|
139
|
+
|
|
140
|
+
// Check if it's assigned to a call expression
|
|
141
|
+
if (declarator.init && t.isCallExpression(declarator.init)) {
|
|
142
|
+
const callExpr = declarator.init;
|
|
143
|
+
|
|
144
|
+
// Check if it's a toJSX call
|
|
145
|
+
if (t.isIdentifier(callExpr.callee) && callExpr.callee.name === 'toJSX') {
|
|
146
|
+
return {
|
|
147
|
+
type: 'custom-element',
|
|
148
|
+
source: currentFileName,
|
|
149
|
+
callExpression: callExpr,
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Could be assigned to another function call - trace that too
|
|
154
|
+
//console.log('Local variable assigned to call expression:', callExpr.callee);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Check if it's assigned to an identifier (another variable)
|
|
158
|
+
if (declarator.init && t.isIdentifier(declarator.init)) {
|
|
159
|
+
const assignedIdentifier = declarator.init.name;
|
|
160
|
+
//console.log('Local variable assigned to identifier:', assignedIdentifier);
|
|
161
|
+
|
|
162
|
+
// Recursively trace this identifier in the same scope
|
|
163
|
+
return traceElementDefinition(assignedIdentifier, binding.path.scope, currentFileName, visitedFiles);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
return { type: 'local-variable' };
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function checkForReExports(
|
|
171
|
+
programPath: babel.NodePath<babel.types.Program>,
|
|
172
|
+
elementName: string,
|
|
173
|
+
currentFileName: string,
|
|
174
|
+
visitedFiles: Set<string>,
|
|
175
|
+
): ElementDefinition {
|
|
176
|
+
let result: ElementDefinition = { type: 'unknown' };
|
|
177
|
+
|
|
178
|
+
// Check all export declarations for re-exports
|
|
179
|
+
programPath.traverse({
|
|
180
|
+
ExportNamedDeclaration(exportPath) {
|
|
181
|
+
const node = exportPath.node;
|
|
182
|
+
|
|
183
|
+
if (!node.source || !node.specifiers)
|
|
184
|
+
return; // Skip if no source or specifiers
|
|
185
|
+
|
|
186
|
+
// Handle re-exports: export { X } from './file'
|
|
187
|
+
//console.log('Found re-export from:', node.source.value);
|
|
188
|
+
|
|
189
|
+
for (const specifier of node.specifiers) {
|
|
190
|
+
if (!t.isExportSpecifier(specifier))
|
|
191
|
+
continue; // Only handle export specifiers
|
|
192
|
+
|
|
193
|
+
const exportedName = t.isIdentifier(specifier.exported)
|
|
194
|
+
? specifier.exported.name
|
|
195
|
+
: specifier.exported.value;
|
|
196
|
+
|
|
197
|
+
// Check if this re-export matches our element name
|
|
198
|
+
if (exportedName !== elementName)
|
|
199
|
+
continue;
|
|
200
|
+
|
|
201
|
+
const originalName = specifier.local.name;
|
|
202
|
+
//console.log(`Found re-export: ${ originalName } as ${ exportedName }`);
|
|
203
|
+
|
|
204
|
+
// Resolve and trace the re-exported file
|
|
205
|
+
const reExportSource = node.source.value;
|
|
206
|
+
const currentDir = dirname(currentFileName);
|
|
207
|
+
const resolvedPath = resolve(currentDir, reExportSource);
|
|
208
|
+
|
|
209
|
+
if (!existsSync(resolvedPath))
|
|
210
|
+
continue; // Skip if file doesn't exist
|
|
211
|
+
|
|
212
|
+
//console.log('Tracing re-export to:', resolvedPath);
|
|
213
|
+
result = traceReExport(originalName, resolvedPath, visitedFiles);
|
|
214
|
+
|
|
215
|
+
// Stop traversing once we find the match
|
|
216
|
+
return exportPath.stop();
|
|
217
|
+
}
|
|
218
|
+
},
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
return result;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function traceReExport(
|
|
225
|
+
elementName: string,
|
|
226
|
+
filePath: string,
|
|
227
|
+
visitedFiles: Set<string>,
|
|
228
|
+
): ElementDefinition {
|
|
229
|
+
// Use cached parsing
|
|
230
|
+
const ast = getOrParseFile(filePath);
|
|
231
|
+
if (!ast)
|
|
232
|
+
return { type: 'unknown' };
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
let result: ElementDefinition = { type: 'unknown' };
|
|
236
|
+
|
|
237
|
+
traverse(ast, {
|
|
238
|
+
Program(programPath) {
|
|
239
|
+
// Continue tracing in the re-exported file
|
|
240
|
+
result = traceElementDefinition(elementName, programPath.scope, filePath, visitedFiles);
|
|
241
|
+
},
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
return result;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
// Helper function to get or parse a file with caching
|
|
249
|
+
function getOrParseFile(filePath: string): t.File | undefined {
|
|
250
|
+
// Check cache first
|
|
251
|
+
if (fileCache.has(filePath)) {
|
|
252
|
+
//console.log('Using cached AST for:', filePath);
|
|
253
|
+
|
|
254
|
+
return fileCache.get(filePath)!;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// File not in cache, parse it
|
|
258
|
+
if (!existsSync(filePath))
|
|
259
|
+
return;
|
|
260
|
+
|
|
261
|
+
const fileContent = readFileSync(filePath, 'utf-8');
|
|
262
|
+
|
|
263
|
+
try {
|
|
264
|
+
const ast = babel.parseSync(fileContent, {
|
|
265
|
+
filename: filePath,
|
|
266
|
+
parserOpts: {
|
|
267
|
+
plugins: [ 'jsx', 'typescript' ] satisfies BabelPlugins,
|
|
268
|
+
},
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
if (ast) {
|
|
272
|
+
//console.log('Parsed and cached:', filePath);
|
|
273
|
+
fileCache.set(filePath, ast);
|
|
274
|
+
|
|
275
|
+
return ast;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
catch (error) {
|
|
279
|
+
console.log('Failed to parse file:', filePath, error);
|
|
280
|
+
}
|
|
281
|
+
}
|