@fuzdev/fuz_ui 0.194.0 → 0.195.0
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/DeclarationDetail.svelte +139 -64
- package/dist/DeclarationDetail.svelte.d.ts +9 -0
- package/dist/DeclarationDetail.svelte.d.ts.map +1 -1
- package/dist/LibraryDetail.svelte +14 -10
- package/dist/LibraryDetail.svelte.d.ts +7 -0
- package/dist/LibraryDetail.svelte.d.ts.map +1 -1
- package/dist/declaration.svelte.d.ts +233 -25
- package/dist/declaration.svelte.d.ts.map +1 -1
- package/dist/declaration.svelte.js +71 -23
- package/dist/library.svelte.js +1 -1
- package/dist/library_gen.d.ts +24 -17
- package/dist/library_gen.d.ts.map +1 -1
- package/dist/library_gen.js +52 -32
- package/dist/library_output.d.ts +5 -4
- package/dist/library_output.d.ts.map +1 -1
- package/dist/library_output.js +11 -8
- package/dist/module.svelte.d.ts +21 -7
- package/dist/module.svelte.d.ts.map +1 -1
- package/dist/module.svelte.js +26 -11
- package/dist/tsdoc_mdz.d.ts +6 -1
- package/dist/tsdoc_mdz.d.ts.map +1 -1
- package/dist/tsdoc_mdz.js +23 -2
- package/package.json +10 -8
- package/src/lib/declaration.svelte.ts +90 -35
- package/src/lib/library.svelte.ts +1 -1
- package/src/lib/library_gen.ts +65 -42
- package/src/lib/library_output.ts +11 -8
- package/src/lib/module.svelte.ts +29 -16
- package/src/lib/tsdoc_mdz.ts +25 -2
- package/dist/analysis_context.d.ts +0 -199
- package/dist/analysis_context.d.ts.map +0 -1
- package/dist/analysis_context.js +0 -138
- package/dist/library_analysis.d.ts +0 -112
- package/dist/library_analysis.d.ts.map +0 -1
- package/dist/library_analysis.js +0 -106
- package/dist/library_generate.d.ts +0 -94
- package/dist/library_generate.d.ts.map +0 -1
- package/dist/library_generate.js +0 -147
- package/dist/library_pipeline.d.ts +0 -113
- package/dist/library_pipeline.d.ts.map +0 -1
- package/dist/library_pipeline.js +0 -160
- package/dist/module_helpers.d.ts +0 -334
- package/dist/module_helpers.d.ts.map +0 -1
- package/dist/module_helpers.js +0 -317
- package/dist/svelte_helpers.d.ts +0 -92
- package/dist/svelte_helpers.d.ts.map +0 -1
- package/dist/svelte_helpers.js +0 -367
- package/dist/ts_helpers.d.ts +0 -181
- package/dist/ts_helpers.d.ts.map +0 -1
- package/dist/ts_helpers.js +0 -674
- package/dist/tsdoc_helpers.d.ts +0 -119
- package/dist/tsdoc_helpers.d.ts.map +0 -1
- package/dist/tsdoc_helpers.js +0 -207
- package/src/lib/analysis_context.ts +0 -254
- package/src/lib/library_analysis.ts +0 -168
- package/src/lib/library_generate.ts +0 -215
- package/src/lib/library_pipeline.ts +0 -221
- package/src/lib/module_helpers.ts +0 -501
- package/src/lib/svelte_helpers.ts +0 -539
- package/src/lib/ts_helpers.ts +0 -862
- package/src/lib/tsdoc_helpers.ts +0 -246
|
@@ -1,539 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Svelte component analysis helpers.
|
|
3
|
-
*
|
|
4
|
-
* Extracts metadata from Svelte components using svelte2tsx transformations:
|
|
5
|
-
*
|
|
6
|
-
* - Component props with types and JSDoc
|
|
7
|
-
* - Component-level documentation
|
|
8
|
-
* - Type information
|
|
9
|
-
*
|
|
10
|
-
* Workflow: Transform Svelte to TypeScript via svelte2tsx, parse the transformed
|
|
11
|
-
* TypeScript with the TS Compiler API, extract component-level JSDoc from original source.
|
|
12
|
-
*
|
|
13
|
-
* **Svelte 5 only**: The svelte2tsx output format changed significantly between versions.
|
|
14
|
-
* This module requires Svelte 5+ and will throw a clear error if an older version is detected.
|
|
15
|
-
* There is no Svelte 4 compatibility layer.
|
|
16
|
-
*
|
|
17
|
-
* All functions are prefixed with `svelte_` for clarity.
|
|
18
|
-
*
|
|
19
|
-
* @module
|
|
20
|
-
*/
|
|
21
|
-
|
|
22
|
-
import ts from 'typescript';
|
|
23
|
-
import {svelte2tsx} from 'svelte2tsx';
|
|
24
|
-
import {TraceMap, originalPositionFor} from '@jridgewell/trace-mapping';
|
|
25
|
-
import {VERSION} from 'svelte/compiler';
|
|
26
|
-
import type {DeclarationJson, ComponentPropInfo} from '@fuzdev/fuz_util/source_json.js';
|
|
27
|
-
|
|
28
|
-
import {tsdoc_parse, tsdoc_apply_to_declaration} from './tsdoc_helpers.js';
|
|
29
|
-
import {ts_extract_module_comment} from './ts_helpers.js';
|
|
30
|
-
import {
|
|
31
|
-
type SourceFileInfo,
|
|
32
|
-
type ModuleSourceOptions,
|
|
33
|
-
module_get_component_name,
|
|
34
|
-
module_extract_dependencies,
|
|
35
|
-
} from './module_helpers.js';
|
|
36
|
-
import type {AnalysisContext} from './analysis_context.js';
|
|
37
|
-
// Import shared types from library_analysis (type-only import avoids circular runtime dependency)
|
|
38
|
-
import type {ModuleAnalysis} from './library_analysis.js';
|
|
39
|
-
|
|
40
|
-
/** Guard to ensure version check runs only once. */
|
|
41
|
-
let svelte_version_checked = false;
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* Assert Svelte 5+ is installed (lazy, runs once on first use).
|
|
45
|
-
* Throws a clear error message if an older version is detected.
|
|
46
|
-
*/
|
|
47
|
-
const svelte_assert_version = (): void => {
|
|
48
|
-
if (svelte_version_checked) return;
|
|
49
|
-
svelte_version_checked = true;
|
|
50
|
-
const [major] = VERSION.split('.');
|
|
51
|
-
if (parseInt(major!, 10) < 5) {
|
|
52
|
-
throw new Error(
|
|
53
|
-
`Svelte ${VERSION} detected but Svelte 5+ is required for source analysis. ` +
|
|
54
|
-
`The svelte2tsx output format changed significantly between versions.`,
|
|
55
|
-
);
|
|
56
|
-
}
|
|
57
|
-
};
|
|
58
|
-
|
|
59
|
-
/** Result of analyzing a Svelte file. */
|
|
60
|
-
export interface SvelteFileAnalysis {
|
|
61
|
-
/** The component declaration metadata. */
|
|
62
|
-
declaration: DeclarationJson;
|
|
63
|
-
/** Module-level documentation comment, if present. */
|
|
64
|
-
module_comment?: string;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
/**
|
|
68
|
-
* Analyze a Svelte component file and extract module metadata.
|
|
69
|
-
*
|
|
70
|
-
* Wraps `svelte_analyze_file` and adds dependency information
|
|
71
|
-
* from the source file info if available.
|
|
72
|
-
*
|
|
73
|
-
* This is a high-level function suitable for building documentation or library metadata.
|
|
74
|
-
* For lower-level analysis, use `svelte_analyze_file` directly.
|
|
75
|
-
*
|
|
76
|
-
* Returns raw analysis data matching `ModuleAnalysis` structure.
|
|
77
|
-
* Consumer decides filtering policy (Svelte components are never nodocs).
|
|
78
|
-
*
|
|
79
|
-
* @param source_file - the source file info (from Gro filer, file system, or other source)
|
|
80
|
-
* @param module_path - the module path (relative to source root)
|
|
81
|
-
* @param checker - TypeScript type checker
|
|
82
|
-
* @param options - module source options for path extraction
|
|
83
|
-
* @param ctx - analysis context for collecting diagnostics
|
|
84
|
-
* @returns module analysis matching `ModuleAnalysis` structure
|
|
85
|
-
*/
|
|
86
|
-
export const svelte_analyze_module = (
|
|
87
|
-
source_file: SourceFileInfo,
|
|
88
|
-
module_path: string,
|
|
89
|
-
checker: ts.TypeChecker,
|
|
90
|
-
options: ModuleSourceOptions,
|
|
91
|
-
ctx: AnalysisContext,
|
|
92
|
-
): ModuleAnalysis => {
|
|
93
|
-
// Use the existing helper for core analysis
|
|
94
|
-
const {declaration, module_comment} = svelte_analyze_file(source_file, module_path, checker, ctx);
|
|
95
|
-
|
|
96
|
-
// Extract dependencies and dependents if provided
|
|
97
|
-
const {dependencies, dependents} = module_extract_dependencies(source_file, options);
|
|
98
|
-
|
|
99
|
-
return {
|
|
100
|
-
path: module_path,
|
|
101
|
-
module_comment,
|
|
102
|
-
// Wrap declaration in DeclarationAnalysis format (Svelte components are never nodocs)
|
|
103
|
-
declarations: [{declaration, nodocs: false}],
|
|
104
|
-
dependencies,
|
|
105
|
-
dependents,
|
|
106
|
-
star_exports: [],
|
|
107
|
-
re_exports: [],
|
|
108
|
-
};
|
|
109
|
-
};
|
|
110
|
-
|
|
111
|
-
/**
|
|
112
|
-
* Analyze a Svelte component file.
|
|
113
|
-
*
|
|
114
|
-
* This is a high-level function that handles the complete workflow:
|
|
115
|
-
* 1. Transform Svelte source to TypeScript via svelte2tsx
|
|
116
|
-
* 2. Extract component metadata (props, documentation)
|
|
117
|
-
* 3. Extract module-level documentation
|
|
118
|
-
*
|
|
119
|
-
* Suitable for use in documentation generators, build tools, and analysis.
|
|
120
|
-
*
|
|
121
|
-
* @param source_file - source file info with path and content
|
|
122
|
-
* @param module_path - module path relative to source root (e.g., 'Alert.svelte')
|
|
123
|
-
* @param checker - TypeScript type checker for type resolution
|
|
124
|
-
* @param ctx - analysis context for collecting diagnostics
|
|
125
|
-
* @returns component declaration and optional module-level comment
|
|
126
|
-
*/
|
|
127
|
-
export const svelte_analyze_file = (
|
|
128
|
-
source_file: SourceFileInfo,
|
|
129
|
-
module_path: string,
|
|
130
|
-
checker: ts.TypeChecker,
|
|
131
|
-
ctx: AnalysisContext,
|
|
132
|
-
): SvelteFileAnalysis => {
|
|
133
|
-
svelte_assert_version();
|
|
134
|
-
const svelte_source = source_file.content;
|
|
135
|
-
|
|
136
|
-
// Check if component uses TypeScript
|
|
137
|
-
const is_ts_file = svelte_source.includes('lang="ts"');
|
|
138
|
-
|
|
139
|
-
// Transform Svelte to TS
|
|
140
|
-
const ts_result = svelte2tsx(svelte_source, {
|
|
141
|
-
filename: source_file.id,
|
|
142
|
-
isTsFile: is_ts_file,
|
|
143
|
-
emitOnTemplateError: true, // Handle malformed templates gracefully
|
|
144
|
-
});
|
|
145
|
-
|
|
146
|
-
// Create source map for position mapping back to original .svelte file
|
|
147
|
-
let source_map: TraceMap | null = null;
|
|
148
|
-
try {
|
|
149
|
-
// svelte2tsx returns a magic-string SourceMap which is compatible with TraceMap
|
|
150
|
-
// Cast to unknown first since the types don't perfectly align but are compatible
|
|
151
|
-
source_map = new TraceMap(
|
|
152
|
-
ts_result.map as unknown as ConstructorParameters<typeof TraceMap>[0],
|
|
153
|
-
);
|
|
154
|
-
} catch (_error) {
|
|
155
|
-
// If source map parsing fails, diagnostics will use virtual file positions
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
// Get component name from filename
|
|
159
|
-
const component_name = module_get_component_name(module_path);
|
|
160
|
-
|
|
161
|
-
// Create a temporary source file from the original Svelte content for JSDoc extraction
|
|
162
|
-
const temp_source = ts.createSourceFile(
|
|
163
|
-
source_file.id,
|
|
164
|
-
svelte_source,
|
|
165
|
-
ts.ScriptTarget.Latest,
|
|
166
|
-
true,
|
|
167
|
-
);
|
|
168
|
-
|
|
169
|
-
// Analyze the component using the existing lower-level function
|
|
170
|
-
const declaration = svelte_analyze_component(
|
|
171
|
-
ts_result.code,
|
|
172
|
-
temp_source,
|
|
173
|
-
checker,
|
|
174
|
-
component_name,
|
|
175
|
-
module_path,
|
|
176
|
-
source_map,
|
|
177
|
-
ctx,
|
|
178
|
-
);
|
|
179
|
-
|
|
180
|
-
// Extract module-level comment from the script content
|
|
181
|
-
const script_content = svelte_extract_script_content(svelte_source);
|
|
182
|
-
const module_comment = script_content ? svelte_extract_module_comment(script_content) : undefined;
|
|
183
|
-
|
|
184
|
-
return {declaration, module_comment};
|
|
185
|
-
};
|
|
186
|
-
|
|
187
|
-
/**
|
|
188
|
-
* Analyze a Svelte component from its svelte2tsx transformation.
|
|
189
|
-
*/
|
|
190
|
-
export const svelte_analyze_component = (
|
|
191
|
-
ts_code: string,
|
|
192
|
-
source_file: ts.SourceFile,
|
|
193
|
-
checker: ts.TypeChecker,
|
|
194
|
-
component_name: string,
|
|
195
|
-
file_path: string,
|
|
196
|
-
source_map: TraceMap | null,
|
|
197
|
-
ctx: AnalysisContext,
|
|
198
|
-
): DeclarationJson => {
|
|
199
|
-
const result: DeclarationJson = {
|
|
200
|
-
name: component_name,
|
|
201
|
-
kind: 'component',
|
|
202
|
-
};
|
|
203
|
-
|
|
204
|
-
try {
|
|
205
|
-
// Create a virtual source file from the svelte2tsx output
|
|
206
|
-
// svelte2tsx generates plain TypeScript (not TSX/JSX)
|
|
207
|
-
const virtual_source = ts.createSourceFile(
|
|
208
|
-
source_file.fileName + '.ts',
|
|
209
|
-
ts_code,
|
|
210
|
-
ts.ScriptTarget.Latest,
|
|
211
|
-
true,
|
|
212
|
-
ts.ScriptKind.TS,
|
|
213
|
-
);
|
|
214
|
-
|
|
215
|
-
// Extract component-level TSDoc from svelte2tsx transformed output
|
|
216
|
-
const component_tsdoc = svelte_extract_component_tsdoc(virtual_source);
|
|
217
|
-
tsdoc_apply_to_declaration(result, component_tsdoc);
|
|
218
|
-
|
|
219
|
-
// Extract props from svelte2tsx transformed output
|
|
220
|
-
const props = svelte_extract_props(
|
|
221
|
-
virtual_source,
|
|
222
|
-
checker,
|
|
223
|
-
component_name,
|
|
224
|
-
file_path,
|
|
225
|
-
source_map,
|
|
226
|
-
ctx,
|
|
227
|
-
);
|
|
228
|
-
if (props.length > 0) {
|
|
229
|
-
result.props = props;
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
// Extract source line from original file
|
|
233
|
-
const start_pos = source_file.getLineAndCharacterOfPosition(0);
|
|
234
|
-
result.source_line = start_pos.line + 1;
|
|
235
|
-
} catch (error) {
|
|
236
|
-
throw new Error(`Failed to analyze Svelte component ${component_name}`, {cause: error});
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
return result;
|
|
240
|
-
};
|
|
241
|
-
|
|
242
|
-
/**
|
|
243
|
-
* Extract the content of the main `<script>` tag from Svelte source.
|
|
244
|
-
*
|
|
245
|
-
* Matches `<script>` or `<script lang="ts">` but not `<script module>`.
|
|
246
|
-
* Returns undefined if no matching script tag is found.
|
|
247
|
-
*/
|
|
248
|
-
export const svelte_extract_script_content = (svelte_source: string): string | undefined => {
|
|
249
|
-
// Match <script> or <script lang="ts"> but not <script module>
|
|
250
|
-
// Captures the content between opening and closing tags
|
|
251
|
-
const script_regex = /<script(?:\s+lang=["']ts["'])?(?:\s*)>([^]*?)<\/script>/i;
|
|
252
|
-
const match = script_regex.exec(svelte_source);
|
|
253
|
-
return match?.[1];
|
|
254
|
-
};
|
|
255
|
-
|
|
256
|
-
/**
|
|
257
|
-
* Extract module-level comment from Svelte script content.
|
|
258
|
-
*
|
|
259
|
-
* Requires `@module` tag to identify module comments. The tag line is stripped
|
|
260
|
-
* from the output.
|
|
261
|
-
*
|
|
262
|
-
* @param script_content - the content of the `<script>` tag
|
|
263
|
-
* @returns the cleaned module comment text, or undefined if none found
|
|
264
|
-
*/
|
|
265
|
-
export const svelte_extract_module_comment = (script_content: string): string | undefined => {
|
|
266
|
-
// Parse the script content as TypeScript and reuse the shared extraction logic
|
|
267
|
-
const source_file = ts.createSourceFile(
|
|
268
|
-
'script.ts',
|
|
269
|
-
script_content,
|
|
270
|
-
ts.ScriptTarget.Latest,
|
|
271
|
-
true,
|
|
272
|
-
ts.ScriptKind.TS,
|
|
273
|
-
);
|
|
274
|
-
return ts_extract_module_comment(source_file);
|
|
275
|
-
};
|
|
276
|
-
|
|
277
|
-
/**
|
|
278
|
-
* Extract component-level TSDoc comment from svelte2tsx transformed output.
|
|
279
|
-
*
|
|
280
|
-
* svelte2tsx places component-level JSDoc inside the $$render() function,
|
|
281
|
-
* attached to a variable statement (usually before the props destructuring).
|
|
282
|
-
* This function searches the AST recursively to find it.
|
|
283
|
-
*/
|
|
284
|
-
const svelte_extract_component_tsdoc = (
|
|
285
|
-
source_file: ts.SourceFile,
|
|
286
|
-
): ReturnType<typeof tsdoc_parse> => {
|
|
287
|
-
let found_tsdoc: ReturnType<typeof tsdoc_parse> = undefined;
|
|
288
|
-
|
|
289
|
-
// Recursively search for component-level JSDoc
|
|
290
|
-
function visit(node: ts.Node) {
|
|
291
|
-
if (found_tsdoc) return; // Already found, stop searching
|
|
292
|
-
|
|
293
|
-
// Skip PropertySignature nodes - those are prop-level JSDoc, not component-level
|
|
294
|
-
if (ts.isPropertySignature(node)) {
|
|
295
|
-
return; // Don't recurse into property signatures
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
// Check for JSDoc on VariableStatement or VariableDeclaration
|
|
299
|
-
// Component-level JSDoc is attached to these node types
|
|
300
|
-
if (ts.isVariableStatement(node) || ts.isVariableDeclaration(node)) {
|
|
301
|
-
const tsdoc = tsdoc_parse(node, source_file);
|
|
302
|
-
if (tsdoc) {
|
|
303
|
-
found_tsdoc = tsdoc;
|
|
304
|
-
return;
|
|
305
|
-
}
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
// Continue searching child nodes
|
|
309
|
-
ts.forEachChild(node, visit);
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
visit(source_file);
|
|
313
|
-
return found_tsdoc;
|
|
314
|
-
};
|
|
315
|
-
|
|
316
|
-
/**
|
|
317
|
-
* Extract prop info from a property signature member.
|
|
318
|
-
*/
|
|
319
|
-
const svelte_extract_prop_from_member = (
|
|
320
|
-
member: ts.PropertySignature,
|
|
321
|
-
source_file: ts.SourceFile,
|
|
322
|
-
checker: ts.TypeChecker,
|
|
323
|
-
component_name: string,
|
|
324
|
-
file_path: string,
|
|
325
|
-
source_map: TraceMap | null,
|
|
326
|
-
ctx: AnalysisContext,
|
|
327
|
-
): ComponentPropInfo | undefined => {
|
|
328
|
-
if (!ts.isIdentifier(member.name)) return undefined;
|
|
329
|
-
|
|
330
|
-
const prop_name = member.name.text;
|
|
331
|
-
const optional = !!member.questionToken;
|
|
332
|
-
|
|
333
|
-
// Get type string
|
|
334
|
-
let type_string = 'any';
|
|
335
|
-
if (member.type) {
|
|
336
|
-
type_string = member.type.getText(source_file);
|
|
337
|
-
} else {
|
|
338
|
-
// Try to get type from type checker
|
|
339
|
-
try {
|
|
340
|
-
const prop_type = checker.getTypeAtLocation(member);
|
|
341
|
-
type_string = checker.typeToString(prop_type);
|
|
342
|
-
} catch (err) {
|
|
343
|
-
// Fallback to 'any' but report diagnostic with mapped position
|
|
344
|
-
const {line, character} = source_file.getLineAndCharacterOfPosition(member.getStart());
|
|
345
|
-
|
|
346
|
-
// Map virtual position back to original .svelte file if source map available
|
|
347
|
-
let final_line: number | null = line + 1;
|
|
348
|
-
let final_column: number | null = character + 1;
|
|
349
|
-
if (source_map) {
|
|
350
|
-
const original = originalPositionFor(source_map, {line: line + 1, column: character});
|
|
351
|
-
// When line is found, column is guaranteed to be present (same mapping entry)
|
|
352
|
-
if (original.line !== null) {
|
|
353
|
-
final_line = original.line;
|
|
354
|
-
final_column = original.column + 1;
|
|
355
|
-
}
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
ctx.add({
|
|
359
|
-
kind: 'svelte_prop_failed',
|
|
360
|
-
file: file_path,
|
|
361
|
-
line: final_line,
|
|
362
|
-
column: final_column,
|
|
363
|
-
message: `Failed to resolve type for prop "${prop_name}" in ${component_name}, falling back to 'any': ${err instanceof Error ? err.message : String(err)}`,
|
|
364
|
-
severity: 'warning',
|
|
365
|
-
component_name,
|
|
366
|
-
prop_name,
|
|
367
|
-
});
|
|
368
|
-
}
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
// Extract TSDoc description
|
|
372
|
-
let description: string | undefined;
|
|
373
|
-
const tsdoc = tsdoc_parse(member, source_file);
|
|
374
|
-
if (tsdoc) {
|
|
375
|
-
description = tsdoc.text;
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
// Extract default value (if available in TSDoc or initializer)
|
|
379
|
-
let default_value: string | undefined;
|
|
380
|
-
const default_match = description?.match(/@default\s+(.+)/);
|
|
381
|
-
if (default_match) {
|
|
382
|
-
default_value = default_match[1]!.trim();
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
return {
|
|
386
|
-
name: prop_name,
|
|
387
|
-
type: type_string,
|
|
388
|
-
...(optional && {optional}),
|
|
389
|
-
description,
|
|
390
|
-
default_value,
|
|
391
|
-
};
|
|
392
|
-
};
|
|
393
|
-
|
|
394
|
-
/**
|
|
395
|
-
* Extract bindable prop names from svelte2tsx transformed output.
|
|
396
|
-
*
|
|
397
|
-
* svelte2tsx marks bindable props with `__sveltets_$$bindings('prop1', 'prop2', ...)`.
|
|
398
|
-
* This function extracts those prop names.
|
|
399
|
-
*/
|
|
400
|
-
const svelte_extract_bindable_props = (virtual_source: ts.SourceFile): Set<string> => {
|
|
401
|
-
const bindable_props: Set<string> = new Set();
|
|
402
|
-
|
|
403
|
-
// Search for __sveltets_$$bindings call
|
|
404
|
-
function visit(node: ts.Node) {
|
|
405
|
-
if (ts.isCallExpression(node)) {
|
|
406
|
-
const expr = node.expression;
|
|
407
|
-
if (ts.isIdentifier(expr) && expr.text === '__sveltets_$$bindings') {
|
|
408
|
-
// Extract string literal arguments
|
|
409
|
-
for (const arg of node.arguments) {
|
|
410
|
-
if (ts.isStringLiteral(arg)) {
|
|
411
|
-
bindable_props.add(arg.text);
|
|
412
|
-
}
|
|
413
|
-
}
|
|
414
|
-
}
|
|
415
|
-
}
|
|
416
|
-
ts.forEachChild(node, visit);
|
|
417
|
-
}
|
|
418
|
-
|
|
419
|
-
visit(virtual_source);
|
|
420
|
-
return bindable_props;
|
|
421
|
-
};
|
|
422
|
-
|
|
423
|
-
/**
|
|
424
|
-
* Extract props from a type node (handles type literals and intersection types).
|
|
425
|
-
*
|
|
426
|
-
* @mutates props - adds extracted prop info to the array
|
|
427
|
-
*/
|
|
428
|
-
const svelte_extract_props_from_type = (
|
|
429
|
-
type_node: ts.TypeNode,
|
|
430
|
-
virtual_source: ts.SourceFile,
|
|
431
|
-
checker: ts.TypeChecker,
|
|
432
|
-
bindable_props: Set<string>,
|
|
433
|
-
props: Array<ComponentPropInfo>,
|
|
434
|
-
component_name: string,
|
|
435
|
-
file_path: string,
|
|
436
|
-
source_map: TraceMap | null,
|
|
437
|
-
ctx: AnalysisContext,
|
|
438
|
-
): void => {
|
|
439
|
-
if (ts.isTypeLiteralNode(type_node)) {
|
|
440
|
-
// Handle direct type literal: { prop1: type1, prop2: type2 }
|
|
441
|
-
for (const member of type_node.members) {
|
|
442
|
-
if (ts.isPropertySignature(member)) {
|
|
443
|
-
const prop_info = svelte_extract_prop_from_member(
|
|
444
|
-
member,
|
|
445
|
-
virtual_source,
|
|
446
|
-
checker,
|
|
447
|
-
component_name,
|
|
448
|
-
file_path,
|
|
449
|
-
source_map,
|
|
450
|
-
ctx,
|
|
451
|
-
);
|
|
452
|
-
if (prop_info) {
|
|
453
|
-
// Mark as bindable if found in bindings
|
|
454
|
-
if (bindable_props.has(prop_info.name)) {
|
|
455
|
-
prop_info.bindable = true;
|
|
456
|
-
}
|
|
457
|
-
props.push(prop_info);
|
|
458
|
-
}
|
|
459
|
-
}
|
|
460
|
-
}
|
|
461
|
-
} else if (ts.isIntersectionTypeNode(type_node)) {
|
|
462
|
-
// Handle intersection type: TypeA & TypeB & { prop: type }
|
|
463
|
-
for (const type_part of type_node.types) {
|
|
464
|
-
svelte_extract_props_from_type(
|
|
465
|
-
type_part,
|
|
466
|
-
virtual_source,
|
|
467
|
-
checker,
|
|
468
|
-
bindable_props,
|
|
469
|
-
props,
|
|
470
|
-
component_name,
|
|
471
|
-
file_path,
|
|
472
|
-
source_map,
|
|
473
|
-
ctx,
|
|
474
|
-
);
|
|
475
|
-
}
|
|
476
|
-
}
|
|
477
|
-
// Skip other type references like SvelteHTMLElements['details'] since we can't easily resolve them
|
|
478
|
-
};
|
|
479
|
-
|
|
480
|
-
/**
|
|
481
|
-
* Extract props from svelte2tsx transformed output.
|
|
482
|
-
*
|
|
483
|
-
* svelte2tsx generates a `$$ComponentProps` type alias containing the component props.
|
|
484
|
-
* This function extracts prop metadata from that type.
|
|
485
|
-
*/
|
|
486
|
-
const svelte_extract_props = (
|
|
487
|
-
virtual_source: ts.SourceFile,
|
|
488
|
-
checker: ts.TypeChecker,
|
|
489
|
-
component_name: string,
|
|
490
|
-
file_path: string,
|
|
491
|
-
source_map: TraceMap | null,
|
|
492
|
-
ctx: AnalysisContext,
|
|
493
|
-
): Array<ComponentPropInfo> => {
|
|
494
|
-
const props: Array<ComponentPropInfo> = [];
|
|
495
|
-
const bindable_props = svelte_extract_bindable_props(virtual_source);
|
|
496
|
-
|
|
497
|
-
// Look for $$ComponentProps type alias or Props interface
|
|
498
|
-
ts.forEachChild(virtual_source, (node) => {
|
|
499
|
-
// Check for type alias ($$ComponentProps)
|
|
500
|
-
if (ts.isTypeAliasDeclaration(node) && node.name.text === '$$ComponentProps') {
|
|
501
|
-
svelte_extract_props_from_type(
|
|
502
|
-
node.type,
|
|
503
|
-
virtual_source,
|
|
504
|
-
checker,
|
|
505
|
-
bindable_props,
|
|
506
|
-
props,
|
|
507
|
-
component_name,
|
|
508
|
-
file_path,
|
|
509
|
-
source_map,
|
|
510
|
-
ctx,
|
|
511
|
-
);
|
|
512
|
-
}
|
|
513
|
-
// Also check for Props interface (fallback/older format)
|
|
514
|
-
else if (ts.isInterfaceDeclaration(node) && node.name.text === 'Props') {
|
|
515
|
-
for (const member of node.members) {
|
|
516
|
-
if (ts.isPropertySignature(member)) {
|
|
517
|
-
const prop_info = svelte_extract_prop_from_member(
|
|
518
|
-
member,
|
|
519
|
-
virtual_source,
|
|
520
|
-
checker,
|
|
521
|
-
component_name,
|
|
522
|
-
file_path,
|
|
523
|
-
source_map,
|
|
524
|
-
ctx,
|
|
525
|
-
);
|
|
526
|
-
if (prop_info) {
|
|
527
|
-
// Mark as bindable if found in bindings
|
|
528
|
-
if (bindable_props.has(prop_info.name)) {
|
|
529
|
-
prop_info.bindable = true;
|
|
530
|
-
}
|
|
531
|
-
props.push(prop_info);
|
|
532
|
-
}
|
|
533
|
-
}
|
|
534
|
-
}
|
|
535
|
-
}
|
|
536
|
-
});
|
|
537
|
-
|
|
538
|
-
return props;
|
|
539
|
-
};
|