@arcmantle/lit-jsx 1.0.6 → 1.0.8
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 +22 -16
- package/dist/compiler/babel-plugin.d.ts +4 -0
- package/dist/compiler/babel-plugin.d.ts.map +1 -0
- package/dist/compiler/babel-plugin.js +20 -0
- package/dist/compiler/babel-plugin.js.map +1 -0
- package/dist/compiler/babel-traverse.d.ts +3 -0
- package/dist/compiler/babel-traverse.d.ts.map +1 -0
- package/dist/compiler/babel-traverse.js +7 -0
- package/dist/compiler/babel-traverse.js.map +1 -0
- package/dist/compiler/compiler-utils.d.ts +3 -2
- package/dist/compiler/compiler-utils.d.ts.map +1 -1
- package/dist/compiler/compiler-utils.js +25 -23
- package/dist/compiler/compiler-utils.js.map +1 -1
- package/dist/compiler/config.d.ts +5 -1
- package/dist/compiler/config.d.ts.map +1 -1
- package/dist/compiler/config.js +2 -2
- package/dist/compiler/config.js.map +1 -1
- package/dist/compiler/create-logger.d.ts +3 -0
- package/dist/compiler/create-logger.d.ts.map +1 -0
- package/dist/compiler/create-logger.js +32 -0
- package/dist/compiler/create-logger.js.map +1 -0
- package/dist/compiler/import-discovery.d.ts +43 -4
- package/dist/compiler/import-discovery.d.ts.map +1 -1
- package/dist/compiler/import-discovery.js +306 -160
- package/dist/compiler/import-discovery.js.map +1 -1
- package/dist/compiler/preprocess.d.ts +1 -1
- package/dist/compiler/preprocess.d.ts.map +1 -1
- package/dist/compiler/preprocess.js +5 -4
- package/dist/compiler/preprocess.js.map +1 -1
- package/dist/compiler/transpiler.js +4 -4
- package/dist/compiler/transpiler.js.map +1 -1
- package/dist/compiler/vite-plugin.d.ts +1 -0
- package/dist/compiler/vite-plugin.d.ts.map +1 -1
- package/dist/compiler/vite-plugin.js +11 -15
- package/dist/compiler/vite-plugin.js.map +1 -1
- package/dist/runtime/type-helpers.d.ts +24 -36
- package/dist/runtime/type-helpers.d.ts.map +1 -1
- package/dist/runtime/type-helpers.js +22 -34
- package/dist/runtime/type-helpers.js.map +1 -1
- package/package.json +6 -3
- package/src/compiler/babel-plugin.ts +23 -0
- package/src/compiler/babel-traverse.ts +7 -0
- package/src/compiler/compiler-utils.ts +35 -25
- package/src/compiler/config.ts +7 -2
- package/src/compiler/create-logger.ts +35 -0
- package/src/compiler/import-discovery.ts +381 -202
- package/src/compiler/preprocess.ts +9 -7
- package/src/compiler/transpiler.ts +4 -4
- package/src/compiler/vite-plugin.ts +18 -24
- package/src/runtime/type-helpers.ts +24 -36
- package/dist/compiler/babel-preset.d.ts +0 -6
- package/dist/compiler/babel-preset.d.ts.map +0 -1
- package/dist/compiler/babel-preset.js +0 -27
- package/dist/compiler/babel-preset.js.map +0 -1
- package/src/compiler/babel-preset.ts +0 -34
|
@@ -1,281 +1,460 @@
|
|
|
1
1
|
import { existsSync, readFileSync } from 'node:fs';
|
|
2
|
-
import { dirname
|
|
2
|
+
import { dirname } from 'node:path';
|
|
3
3
|
|
|
4
4
|
import * as babel from '@babel/core';
|
|
5
|
-
import
|
|
5
|
+
import type { Binding, NodePath, Scope } from '@babel/traverse';
|
|
6
6
|
import * as t from '@babel/types';
|
|
7
|
+
import oxcResolver from 'oxc-resolver';
|
|
8
|
+
import type { Logger } from 'pino';
|
|
7
9
|
|
|
10
|
+
import { traverse } from './babel-traverse.ts';
|
|
11
|
+
import { getPathFilename, isComponent } from './compiler-utils.ts';
|
|
12
|
+
import { babelPlugins, debugMode } from './config.ts';
|
|
13
|
+
import { createLogger } from './create-logger.ts';
|
|
8
14
|
|
|
9
|
-
// Cache for parsed files to avoid re-parsing
|
|
10
|
-
const fileCache: Map<string, t.File> = new Map();
|
|
11
15
|
|
|
12
|
-
|
|
13
|
-
export type BabelPlugins = NonNullable<NonNullable<babel.TransformOptions['parserOpts']>['plugins']>;
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
// Types for our discovery results
|
|
17
16
|
export interface ElementDefinition {
|
|
18
|
-
type: 'custom-element' | 'import' | 'local-variable' | 'unknown';
|
|
19
|
-
source?: string;
|
|
20
|
-
originalName?: string;
|
|
21
|
-
|
|
17
|
+
type: 'custom-element' | 'import' | 'local-variable' | 'wildcard-export' | 'unknown';
|
|
18
|
+
source?: string;
|
|
19
|
+
originalName?: string;
|
|
20
|
+
localName?: string;
|
|
21
|
+
callExpression?: t.CallExpression;
|
|
22
|
+
resolvedPath?: string; // For lazy import resolution
|
|
23
|
+
referencedName?: string; // For lazy local reference resolution
|
|
22
24
|
}
|
|
23
25
|
|
|
26
|
+
interface FileSystemAdapter {
|
|
27
|
+
existsSync: (path: string) => boolean;
|
|
28
|
+
readFileSync: (path: string, encoding: 'utf-8') => string;
|
|
29
|
+
dirname: (path: string) => string;
|
|
30
|
+
}
|
|
24
31
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
32
|
+
interface ResolverAdapter {
|
|
33
|
+
sync: (basedir: string, module: string) => { path?: string; };
|
|
34
|
+
}
|
|
28
35
|
|
|
29
|
-
const hub = path.hub as Hub & { file: { opts: { filename: string; }; }; };
|
|
30
36
|
|
|
31
|
-
|
|
37
|
+
export class ImportDiscovery {
|
|
32
38
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
39
|
+
static readonly definitionCache: Map<string, Map<string, ElementDefinition>> = new Map();
|
|
40
|
+
static readonly fileBindingsCache: Map<string, ReadonlyMap<string, ElementDefinition>> = new Map();
|
|
41
|
+
static readonly fileDependencies: Map<string, Set<string>> = new Map();
|
|
42
|
+
static readonly EMPTY_BINDINGS: ReadonlyMap<string, ElementDefinition> = new Map();
|
|
36
43
|
|
|
37
|
-
|
|
44
|
+
static clearCacheForFileAndDependents(changedFilePath: string): void {
|
|
45
|
+
// Clear the changed file itself
|
|
46
|
+
ImportDiscovery.definitionCache.delete(changedFilePath);
|
|
47
|
+
ImportDiscovery.fileBindingsCache.delete(changedFilePath);
|
|
48
|
+
ImportDiscovery.fileDependencies.delete(changedFilePath);
|
|
38
49
|
|
|
39
|
-
|
|
50
|
+
// Find and clear all files that depend on the changed file in one pass
|
|
51
|
+
for (const [ file, dependencies ] of ImportDiscovery.fileDependencies) {
|
|
52
|
+
if (!dependencies.has(changedFilePath))
|
|
53
|
+
continue;
|
|
40
54
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
55
|
+
ImportDiscovery.definitionCache.delete(file);
|
|
56
|
+
ImportDiscovery.fileBindingsCache.delete(file);
|
|
57
|
+
ImportDiscovery.fileDependencies.delete(file);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
44
60
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
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' };
|
|
61
|
+
protected readonly visitedFiles: Set<string> = new Set();
|
|
62
|
+
protected readonly resolver: ResolverAdapter;
|
|
63
|
+
protected readonly log: Logger<never, boolean>;
|
|
64
|
+
protected readonly fs: FileSystemAdapter;
|
|
55
65
|
|
|
56
|
-
|
|
66
|
+
constructor() {
|
|
67
|
+
this.resolver = oxcResolver;
|
|
68
|
+
this.log = createLogger('import-discovery', debugMode.value);
|
|
69
|
+
this.fs = { existsSync, readFileSync, dirname };
|
|
70
|
+
}
|
|
57
71
|
|
|
58
|
-
|
|
59
|
-
|
|
72
|
+
/**
|
|
73
|
+
* Finds the definition of a JSX element in the given path.
|
|
74
|
+
*/
|
|
75
|
+
findElementDefinition(
|
|
76
|
+
path: NodePath<t.JSXOpeningElement>,
|
|
77
|
+
): ElementDefinition {
|
|
78
|
+
this.visitedFiles.clear();
|
|
79
|
+
|
|
80
|
+
const filePath = getPathFilename(path);
|
|
81
|
+
const cacheKey = String(path.node.start);
|
|
82
|
+
|
|
83
|
+
const fileCache = ImportDiscovery.definitionCache.get(filePath);
|
|
84
|
+
if (fileCache) {
|
|
85
|
+
const cached = fileCache.get(cacheKey);
|
|
86
|
+
if (cached)
|
|
87
|
+
return cached;
|
|
88
|
+
}
|
|
60
89
|
|
|
61
|
-
|
|
62
|
-
|
|
90
|
+
if (!t.isJSXIdentifier(path.node.name))
|
|
91
|
+
return { type: 'unknown' };
|
|
63
92
|
|
|
64
|
-
|
|
65
|
-
|
|
93
|
+
const elementName = path.node.name.name;
|
|
94
|
+
if (!isComponent(elementName))
|
|
95
|
+
return { type: 'unknown' };
|
|
66
96
|
|
|
67
|
-
|
|
97
|
+
const currentFileName = getPathFilename(path);
|
|
98
|
+
const result = this.traceElementDefinition(elementName, path.scope, currentFileName);
|
|
68
99
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
100
|
+
// Store in file-specific cache
|
|
101
|
+
const definitionCache = ImportDiscovery.definitionCache.get(filePath)
|
|
102
|
+
?? ImportDiscovery.definitionCache
|
|
103
|
+
.set(filePath, new Map())
|
|
104
|
+
.get(filePath)!;
|
|
72
105
|
|
|
106
|
+
definitionCache.set(cacheKey, result);
|
|
73
107
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
return traceLocalVariable(binding, currentFileName, visitedFiles);
|
|
108
|
+
return result;
|
|
109
|
+
}
|
|
77
110
|
|
|
111
|
+
// Trace the element definition recursively
|
|
112
|
+
protected traceElementDefinition(
|
|
113
|
+
elementName: string,
|
|
114
|
+
scope: Scope,
|
|
115
|
+
currentFileName: string,
|
|
116
|
+
): ElementDefinition {
|
|
117
|
+
const traceKey = `${ currentFileName }:${ elementName }`;
|
|
78
118
|
|
|
79
|
-
|
|
80
|
-
|
|
119
|
+
// Prevent infinite recursion
|
|
120
|
+
if (this.visitedFiles.has(traceKey))
|
|
121
|
+
return { type: 'unknown' };
|
|
81
122
|
|
|
82
|
-
|
|
83
|
-
binding: Binding,
|
|
84
|
-
currentFileName: string,
|
|
85
|
-
elementName: string,
|
|
86
|
-
visitedFiles: Set<string>,
|
|
87
|
-
): ElementDefinition {
|
|
88
|
-
const importDeclaration = binding.path.parent;
|
|
123
|
+
this.visitedFiles.add(traceKey);
|
|
89
124
|
|
|
90
|
-
|
|
91
|
-
|
|
125
|
+
// Use batched file analysis
|
|
126
|
+
const fileBindings = this.analyzeFileBindings(currentFileName);
|
|
92
127
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
128
|
+
// Check if we have this element in our batch analysis
|
|
129
|
+
if (fileBindings.has(elementName)) {
|
|
130
|
+
const definition = fileBindings.get(elementName)!;
|
|
96
131
|
|
|
97
|
-
|
|
98
|
-
|
|
132
|
+
// Resolve any lazy references
|
|
133
|
+
return this.resolveLazyDefinition(definition);
|
|
134
|
+
}
|
|
99
135
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
136
|
+
// Fallback to scope-based lookup for dynamic cases
|
|
137
|
+
const binding = scope.getBinding(elementName);
|
|
138
|
+
if (!binding)
|
|
139
|
+
return { type: 'unknown' };
|
|
104
140
|
|
|
141
|
+
// Use the fast analysis methods
|
|
142
|
+
const result = this.analyzeBindingFast(binding, currentFileName);
|
|
105
143
|
|
|
106
|
-
|
|
144
|
+
// Resolve any lazy references (imports, local references)
|
|
145
|
+
return this.resolveLazyDefinition(result);
|
|
146
|
+
}
|
|
107
147
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
148
|
+
// Analyze all relevant bindings in a file at once
|
|
149
|
+
protected analyzeFileBindings(filePath: string): ReadonlyMap<string, ElementDefinition> {
|
|
150
|
+
const fileBinding = ImportDiscovery.fileBindingsCache.get(filePath);
|
|
151
|
+
if (fileBinding)
|
|
152
|
+
return fileBinding;
|
|
112
153
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
result = traceElementDefinition(elementName, programPath.scope, resolvedPath, visitedFiles);
|
|
154
|
+
if (!this.fs.existsSync(filePath)) {
|
|
155
|
+
ImportDiscovery.fileBindingsCache.set(filePath, ImportDiscovery.EMPTY_BINDINGS);
|
|
116
156
|
|
|
117
|
-
|
|
118
|
-
|
|
157
|
+
return ImportDiscovery.EMPTY_BINDINGS;
|
|
158
|
+
}
|
|
119
159
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
result = checkForReExports(programPath, elementName, resolvedPath, visitedFiles);
|
|
123
|
-
},
|
|
124
|
-
});
|
|
160
|
+
const fileContent = this.fs.readFileSync(filePath, 'utf-8');
|
|
161
|
+
let ast: t.File;
|
|
125
162
|
|
|
126
|
-
|
|
127
|
-
|
|
163
|
+
try {
|
|
164
|
+
ast = babel.parseSync(fileContent, {
|
|
165
|
+
filename: filePath,
|
|
166
|
+
parserOpts: {
|
|
167
|
+
plugins: babelPlugins,
|
|
168
|
+
},
|
|
169
|
+
})!;
|
|
170
|
+
}
|
|
171
|
+
catch (error) {
|
|
172
|
+
// Failed to parse, cache empty result
|
|
173
|
+
ImportDiscovery.fileBindingsCache.set(filePath, ImportDiscovery.EMPTY_BINDINGS);
|
|
128
174
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
currentFileName: string,
|
|
132
|
-
visitedFiles: Set<string>,
|
|
133
|
-
): ElementDefinition {
|
|
134
|
-
//console.log('Tracing local variable:', binding.kind, binding.path.type);
|
|
175
|
+
return ImportDiscovery.EMPTY_BINDINGS;
|
|
176
|
+
}
|
|
135
177
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
const declarator = binding.path.node;
|
|
178
|
+
let programPath: babel.NodePath<babel.types.Program> = undefined as any;
|
|
179
|
+
traverse(ast, { Program(path) { programPath = path; path.stop(); } });
|
|
139
180
|
|
|
140
|
-
|
|
141
|
-
if (declarator.init && t.isCallExpression(declarator.init)) {
|
|
142
|
-
const callExpr = declarator.init;
|
|
181
|
+
const bindings: Map<string, ElementDefinition> = new Map();
|
|
143
182
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
return {
|
|
147
|
-
type: 'custom-element',
|
|
148
|
-
source: currentFileName,
|
|
149
|
-
callExpression: callExpr,
|
|
150
|
-
};
|
|
151
|
-
}
|
|
183
|
+
// 1. Analyze all relevant local bindings at once
|
|
184
|
+
this.analyzeFileDeclarations(programPath, filePath, bindings);
|
|
152
185
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
}
|
|
186
|
+
// 2. Analyze all exports at once
|
|
187
|
+
this.analyzeFileExports(programPath, filePath, bindings);
|
|
156
188
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
const assignedIdentifier = declarator.init.name;
|
|
160
|
-
//console.log('Local variable assigned to identifier:', assignedIdentifier);
|
|
189
|
+
const readonlyBindings = new Map(bindings) as ReadonlyMap<string, ElementDefinition>;
|
|
190
|
+
ImportDiscovery.fileBindingsCache.set(filePath, readonlyBindings);
|
|
161
191
|
|
|
162
|
-
|
|
163
|
-
return traceElementDefinition(assignedIdentifier, binding.path.scope, currentFileName, visitedFiles);
|
|
164
|
-
}
|
|
192
|
+
return readonlyBindings;
|
|
165
193
|
}
|
|
166
194
|
|
|
167
|
-
|
|
168
|
-
|
|
195
|
+
// Resolve lazy references in the definition
|
|
196
|
+
protected analyzeFileDeclarations(
|
|
197
|
+
programPath: babel.NodePath<babel.types.Program>,
|
|
198
|
+
filePath: string,
|
|
199
|
+
bindings: Map<string, ElementDefinition>,
|
|
200
|
+
): void {
|
|
201
|
+
for (const [ name, binding ] of Object.entries(programPath.scope.bindings)) {
|
|
202
|
+
// Skip function/import bindings that are clearly not component-related
|
|
203
|
+
if (binding.kind === 'module' || binding.kind === 'hoisted') {
|
|
204
|
+
if (!isComponent(name))
|
|
205
|
+
continue;
|
|
206
|
+
}
|
|
169
207
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
): ElementDefinition {
|
|
176
|
-
let result: ElementDefinition = { type: 'unknown' };
|
|
208
|
+
const definition = this.analyzeBindingFast(binding, filePath);
|
|
209
|
+
if (definition.type !== 'unknown')
|
|
210
|
+
bindings.set(name, definition);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
177
213
|
|
|
178
|
-
//
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
214
|
+
// Fast binding analysis without deep traversal
|
|
215
|
+
protected analyzeBindingFast(
|
|
216
|
+
binding: Binding,
|
|
217
|
+
filePath: string,
|
|
218
|
+
): ElementDefinition {
|
|
219
|
+
// Handle imports
|
|
220
|
+
if (binding.kind === 'module' && t.isImportSpecifier(binding.path.node))
|
|
221
|
+
return this.analyzeImportBinding(binding, filePath);
|
|
182
222
|
|
|
183
|
-
|
|
184
|
-
|
|
223
|
+
// Handle local variables
|
|
224
|
+
if (binding.kind === 'const' || binding.kind === 'let' || binding.kind === 'var')
|
|
225
|
+
return this.analyzeLocalBinding(binding, filePath);
|
|
185
226
|
|
|
186
|
-
|
|
187
|
-
|
|
227
|
+
return { type: 'unknown' };
|
|
228
|
+
}
|
|
188
229
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
230
|
+
// Analyze import without deep file traversal
|
|
231
|
+
protected analyzeImportBinding(binding: Binding, currentFileName: string): ElementDefinition {
|
|
232
|
+
const importDeclaration = binding.path.parent;
|
|
233
|
+
if (!t.isImportDeclaration(importDeclaration))
|
|
234
|
+
return { type: 'unknown' };
|
|
235
|
+
|
|
236
|
+
const importSpecifier = binding.path.node;
|
|
237
|
+
if (!t.isImportSpecifier(importSpecifier))
|
|
238
|
+
return { type: 'unknown' };
|
|
239
|
+
|
|
240
|
+
const originalExportedName = t.isIdentifier(importSpecifier.imported)
|
|
241
|
+
? importSpecifier.imported.name
|
|
242
|
+
: importSpecifier.imported.value;
|
|
243
|
+
|
|
244
|
+
const importSource = importDeclaration.source.value;
|
|
245
|
+
const currentDir = dirname(currentFileName);
|
|
246
|
+
|
|
247
|
+
const resolvedResult = this.resolver.sync(currentDir, importSource);
|
|
248
|
+
const resolvedPath = resolvedResult.path;
|
|
249
|
+
|
|
250
|
+
if (!resolvedPath)
|
|
251
|
+
return { type: 'unknown' };
|
|
252
|
+
|
|
253
|
+
// Instead of deep traversal, just mark as import and resolve lazily
|
|
254
|
+
return {
|
|
255
|
+
type: 'import',
|
|
256
|
+
source: importSource,
|
|
257
|
+
originalName: originalExportedName,
|
|
258
|
+
localName: binding.identifier.name,
|
|
259
|
+
// Store resolved path for later lookup
|
|
260
|
+
resolvedPath: resolvedPath,
|
|
261
|
+
};
|
|
262
|
+
}
|
|
192
263
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
264
|
+
// Analyze local binding without recursion
|
|
265
|
+
protected analyzeLocalBinding(binding: Binding, filePath: string): ElementDefinition {
|
|
266
|
+
if (!t.isVariableDeclarator(binding.path.node))
|
|
267
|
+
return { type: 'local-variable' };
|
|
196
268
|
|
|
197
|
-
// Check if this re-export matches our element name
|
|
198
|
-
if (exportedName !== elementName)
|
|
199
|
-
continue;
|
|
200
269
|
|
|
201
|
-
|
|
202
|
-
|
|
270
|
+
const declarator = binding.path.node;
|
|
271
|
+
if (!declarator.init)
|
|
272
|
+
return { type: 'local-variable' };
|
|
203
273
|
|
|
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
274
|
|
|
209
|
-
|
|
210
|
-
|
|
275
|
+
// Check for toComponent/toTag calls
|
|
276
|
+
if (t.isCallExpression(declarator.init)) {
|
|
277
|
+
if (this.isToComponentOrTagCall(declarator.init, binding.path.scope)) {
|
|
278
|
+
return {
|
|
279
|
+
type: 'custom-element',
|
|
280
|
+
source: filePath,
|
|
281
|
+
callExpression: declarator.init,
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
}
|
|
211
285
|
|
|
212
|
-
|
|
213
|
-
|
|
286
|
+
// For identifier assignments, store reference for later resolution
|
|
287
|
+
if (t.isIdentifier(declarator.init)) {
|
|
288
|
+
return {
|
|
289
|
+
type: 'local-variable',
|
|
290
|
+
source: filePath,
|
|
291
|
+
referencedName: declarator.init.name,
|
|
292
|
+
};
|
|
293
|
+
}
|
|
214
294
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
}
|
|
218
|
-
},
|
|
219
|
-
});
|
|
295
|
+
return { type: 'local-variable' };
|
|
296
|
+
}
|
|
220
297
|
|
|
221
|
-
|
|
222
|
-
|
|
298
|
+
// Helper function to check if a call expression is a toComponent/toTag call
|
|
299
|
+
// even if the function has been renamed through imports
|
|
300
|
+
protected isToComponentOrTagCall(
|
|
301
|
+
callExpr: t.CallExpression,
|
|
302
|
+
scope: Scope,
|
|
303
|
+
): boolean {
|
|
304
|
+
if (!t.isIdentifier(callExpr.callee))
|
|
305
|
+
return false;
|
|
223
306
|
|
|
224
|
-
|
|
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' };
|
|
307
|
+
const functionName = callExpr.callee.name;
|
|
233
308
|
|
|
309
|
+
// Check direct names first (fast path)
|
|
310
|
+
if (functionName === 'toComponent' || functionName === 'toTag')
|
|
311
|
+
return true;
|
|
234
312
|
|
|
235
|
-
|
|
313
|
+
// Check if this identifier is bound to an import that originally was toComponent/toTag
|
|
314
|
+
const binding = scope.getBinding(functionName);
|
|
315
|
+
if (!binding || binding.kind !== 'module')
|
|
316
|
+
return false;
|
|
236
317
|
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
// Continue tracing in the re-exported file
|
|
240
|
-
result = traceElementDefinition(elementName, programPath.scope, filePath, visitedFiles);
|
|
241
|
-
},
|
|
242
|
-
});
|
|
318
|
+
if (!t.isImportSpecifier(binding.path.node))
|
|
319
|
+
return false;
|
|
243
320
|
|
|
244
|
-
|
|
245
|
-
|
|
321
|
+
const importSpecifier = binding.path.node;
|
|
322
|
+
const originalImportedName = t.isIdentifier(importSpecifier.imported)
|
|
323
|
+
? importSpecifier.imported.name
|
|
324
|
+
: importSpecifier.imported.value;
|
|
246
325
|
|
|
326
|
+
// Check if the original imported name was toComponent or toTag
|
|
327
|
+
const isOriginallyToComponentOrTag =
|
|
328
|
+
originalImportedName === 'toComponent'
|
|
329
|
+
|| originalImportedName === 'toTag';
|
|
247
330
|
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
// Check cache first
|
|
251
|
-
if (fileCache.has(filePath)) {
|
|
252
|
-
//console.log('Using cached AST for:', filePath);
|
|
331
|
+
return isOriginallyToComponentOrTag;
|
|
332
|
+
}
|
|
253
333
|
|
|
254
|
-
|
|
334
|
+
// Analyze all exports in one pass
|
|
335
|
+
protected analyzeFileExports(
|
|
336
|
+
programPath: babel.NodePath<babel.types.Program>,
|
|
337
|
+
filePath: string,
|
|
338
|
+
bindings: Map<string, ElementDefinition>,
|
|
339
|
+
): void {
|
|
340
|
+
for (const statement of programPath.node.body) {
|
|
341
|
+
// Handle named exports: export { X } from './file' or export { X }
|
|
342
|
+
if (t.isExportNamedDeclaration(statement)) {
|
|
343
|
+
for (const specifier of statement.specifiers) {
|
|
344
|
+
if (!t.isExportSpecifier(specifier))
|
|
345
|
+
continue;
|
|
346
|
+
|
|
347
|
+
const exportedName = t.isIdentifier(specifier.exported)
|
|
348
|
+
? specifier.exported.name
|
|
349
|
+
: specifier.exported.value;
|
|
350
|
+
|
|
351
|
+
const localName = specifier.local.name;
|
|
352
|
+
|
|
353
|
+
if (!isComponent(exportedName))
|
|
354
|
+
continue;
|
|
355
|
+
|
|
356
|
+
// For re-exports with source
|
|
357
|
+
if (statement.source) {
|
|
358
|
+
const definition = {
|
|
359
|
+
type: 'import' as const,
|
|
360
|
+
source: statement.source.value,
|
|
361
|
+
originalName: localName,
|
|
362
|
+
localName: exportedName,
|
|
363
|
+
};
|
|
364
|
+
bindings.set(exportedName, definition);
|
|
365
|
+
}
|
|
366
|
+
else {
|
|
367
|
+
// For local exports, reference the local binding
|
|
368
|
+
const definition = {
|
|
369
|
+
type: 'local-variable' as const,
|
|
370
|
+
source: filePath,
|
|
371
|
+
referencedName: localName,
|
|
372
|
+
localName: exportedName,
|
|
373
|
+
};
|
|
374
|
+
bindings.set(exportedName, definition);
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
// Handle wildcard exports: export * from './file'
|
|
379
|
+
else if (t.isExportAllDeclaration(statement) && statement.source) {
|
|
380
|
+
// For wildcard exports, we need to mark this as a wildcard re-export
|
|
381
|
+
// The actual resolution will happen in resolveLazyDefinition
|
|
382
|
+
const definition = {
|
|
383
|
+
type: 'wildcard-export' as const,
|
|
384
|
+
source: statement.source.value,
|
|
385
|
+
};
|
|
386
|
+
// Store with a special key to indicate wildcard export
|
|
387
|
+
bindings.set('*', definition);
|
|
388
|
+
}
|
|
389
|
+
}
|
|
255
390
|
}
|
|
256
391
|
|
|
257
|
-
//
|
|
258
|
-
|
|
259
|
-
|
|
392
|
+
// Resolve lazy definitions (imports, references)
|
|
393
|
+
protected resolveLazyDefinition(definition: ElementDefinition): ElementDefinition {
|
|
394
|
+
if (definition.type === 'import' && definition.resolvedPath && definition.originalName) {
|
|
395
|
+
if (definition.source) {
|
|
396
|
+
const currentFile = definition.source;
|
|
397
|
+
const dependsOn = definition.resolvedPath;
|
|
260
398
|
|
|
261
|
-
|
|
399
|
+
let fileDependencies = ImportDiscovery.fileDependencies.get(currentFile);
|
|
400
|
+
if (!fileDependencies) {
|
|
401
|
+
fileDependencies = new Set<string>();
|
|
402
|
+
ImportDiscovery.fileDependencies.set(currentFile, fileDependencies);
|
|
403
|
+
}
|
|
262
404
|
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
filename: filePath,
|
|
266
|
-
parserOpts: {
|
|
267
|
-
plugins: [ 'jsx', 'typescript' ] satisfies BabelPlugins,
|
|
268
|
-
},
|
|
269
|
-
});
|
|
405
|
+
fileDependencies.add(dependsOn);
|
|
406
|
+
}
|
|
270
407
|
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
408
|
+
// Recursively analyze the imported file
|
|
409
|
+
const importedBindings = this.analyzeFileBindings(definition.resolvedPath);
|
|
410
|
+
const binding = importedBindings.get(definition.originalName);
|
|
411
|
+
|
|
412
|
+
if (binding) // Recursively resolve the found definition
|
|
413
|
+
return this.resolveLazyDefinition(binding);
|
|
414
|
+
|
|
415
|
+
// If specific export not found, check for wildcard exports
|
|
416
|
+
const wildcardExport = importedBindings.get('*');
|
|
417
|
+
if (wildcardExport && wildcardExport.type === 'wildcard-export') {
|
|
418
|
+
// Resolve the wildcard export by looking in the target file
|
|
419
|
+
const currentDir = this.fs.dirname(definition.resolvedPath);
|
|
420
|
+
const resolvedResult = this.resolver.sync(currentDir, wildcardExport.source!);
|
|
421
|
+
const resolvedPath = resolvedResult.path;
|
|
422
|
+
|
|
423
|
+
if (resolvedPath) {
|
|
424
|
+
// Create a new import definition for the wildcard target
|
|
425
|
+
const wildcardTargetDefinition: ElementDefinition = {
|
|
426
|
+
type: 'import',
|
|
427
|
+
source: wildcardExport.source,
|
|
428
|
+
originalName: definition.originalName,
|
|
429
|
+
localName: definition.localName,
|
|
430
|
+
resolvedPath: resolvedPath,
|
|
431
|
+
};
|
|
432
|
+
|
|
433
|
+
return this.resolveLazyDefinition(wildcardTargetDefinition);
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
if (definition.type === 'local-variable' && definition.referencedName && definition.source) {
|
|
439
|
+
// Resolve local references
|
|
440
|
+
const fileBindings = this.analyzeFileBindings(definition.source);
|
|
441
|
+
const binding = fileBindings.get(definition.referencedName);
|
|
274
442
|
|
|
275
|
-
|
|
443
|
+
if (binding)
|
|
444
|
+
return this.resolveLazyDefinition(binding);
|
|
276
445
|
}
|
|
446
|
+
|
|
447
|
+
return definition;
|
|
277
448
|
}
|
|
278
|
-
|
|
279
|
-
console.log('Failed to parse file:', filePath, error);
|
|
280
|
-
}
|
|
449
|
+
|
|
281
450
|
}
|
|
451
|
+
|
|
452
|
+
|
|
453
|
+
let discovery: ImportDiscovery;
|
|
454
|
+
export const findElementDefinition = (
|
|
455
|
+
...args: Parameters<ImportDiscovery['findElementDefinition']>
|
|
456
|
+
): ReturnType<ImportDiscovery['findElementDefinition']> => {
|
|
457
|
+
discovery ??= new ImportDiscovery();
|
|
458
|
+
|
|
459
|
+
return discovery.findElementDefinition(...args);
|
|
460
|
+
};
|