@fragments-sdk/cli 0.15.0 → 0.15.1

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.
Files changed (118) hide show
  1. package/dist/{ai-client-I6MDWNYA.js → ai-client-LSLQGOMM.js} +1 -2
  2. package/dist/bin.js +463 -71
  3. package/dist/bin.js.map +1 -1
  4. package/dist/chunk-5JF26E55.js +1255 -0
  5. package/dist/chunk-5JF26E55.js.map +1 -0
  6. package/dist/{chunk-XJQ5BIWI.js → chunk-6SQPP47U.js} +30 -314
  7. package/dist/chunk-6SQPP47U.js.map +1 -0
  8. package/dist/{chunk-65WSVDV5.js → chunk-HQ6A6DTV.js} +1386 -1097
  9. package/dist/chunk-HQ6A6DTV.js.map +1 -0
  10. package/dist/chunk-MHIBEEW4.js +511 -0
  11. package/dist/chunk-MHIBEEW4.js.map +1 -0
  12. package/dist/{chunk-CZD3AD4Q.js → chunk-ONUP6Z4W.js} +17 -6
  13. package/dist/chunk-ONUP6Z4W.js.map +1 -0
  14. package/dist/{codebase-scanner-VOTPXRYW.js → codebase-scanner-MQHUZC2G.js} +1 -2
  15. package/dist/{converter-JLINP7CJ.js → converter-7XM3Y6NJ.js} +1 -2
  16. package/dist/{converter-JLINP7CJ.js.map → converter-7XM3Y6NJ.js.map} +1 -1
  17. package/dist/core/index.js +0 -1
  18. package/dist/create-IH4R45GE.js +806 -0
  19. package/dist/create-IH4R45GE.js.map +1 -0
  20. package/dist/{generate-A4FP5426.js → generate-PVOLUAAC.js} +3 -4
  21. package/dist/{generate-A4FP5426.js.map → generate-PVOLUAAC.js.map} +1 -1
  22. package/dist/{govern-scan-UCBZR6D6.js → govern-scan-OYFZYOQW.js} +142 -9
  23. package/dist/govern-scan-OYFZYOQW.js.map +1 -0
  24. package/dist/index.d.ts +2 -22
  25. package/dist/index.js +8 -7
  26. package/dist/index.js.map +1 -1
  27. package/dist/{init-HGSM35XA.js → init-SSGUSP7Z.js} +3 -4
  28. package/dist/{init-HGSM35XA.js.map → init-SSGUSP7Z.js.map} +1 -1
  29. package/dist/{init-cloud-MQ6GRJAZ.js → init-cloud-3DNKPWFB.js} +29 -4
  30. package/dist/{init-cloud-MQ6GRJAZ.js.map → init-cloud-3DNKPWFB.js.map} +1 -1
  31. package/dist/mcp-bin.js +1 -2
  32. package/dist/mcp-bin.js.map +1 -1
  33. package/dist/node-37AUE74M.js +65 -0
  34. package/dist/push-contracts-WY32TFP6.js +84 -0
  35. package/dist/push-contracts-WY32TFP6.js.map +1 -0
  36. package/dist/{scan-VNNKACG2.js → scan-PKSYSTRR.js} +5 -5
  37. package/dist/{scan-generate-TWRHNU5M.js → scan-generate-VY27PIOX.js} +8 -9
  38. package/dist/scan-generate-VY27PIOX.js.map +1 -0
  39. package/dist/{scanner-7LAZYPWZ.js → scanner-4KZNOXAK.js} +1 -2
  40. package/dist/{service-FHQU7YS7.js → service-QJGWUIVL.js} +16 -9
  41. package/dist/{snapshot-KQEQ6XHL.js → snapshot-WIJMEIFT.js} +1 -2
  42. package/dist/{snapshot-KQEQ6XHL.js.map → snapshot-WIJMEIFT.js.map} +1 -1
  43. package/dist/{static-viewer-63PG6FWY.js → static-viewer-7QIBQZRC.js} +1 -2
  44. package/dist/{test-UQYUCZIS.js → test-64Z5BKBA.js} +2 -3
  45. package/dist/{test-UQYUCZIS.js.map → test-64Z5BKBA.js.map} +1 -1
  46. package/dist/token-normalizer-TEPOVBPV.js +312 -0
  47. package/dist/token-normalizer-TEPOVBPV.js.map +1 -0
  48. package/dist/token-parser-32KOIOFN.js +22 -0
  49. package/dist/token-parser-32KOIOFN.js.map +1 -0
  50. package/dist/{tokens-6GYKDV6U.js → tokens-NZWFQIAB.js} +7 -7
  51. package/dist/{tokens-generate-VTZV5EEW.js → tokens-generate-5JQSJ27E.js} +1 -2
  52. package/dist/{tokens-generate-VTZV5EEW.js.map → tokens-generate-5JQSJ27E.js.map} +1 -1
  53. package/dist/tokens-push-HY3KO36V.js +148 -0
  54. package/dist/tokens-push-HY3KO36V.js.map +1 -0
  55. package/package.json +5 -3
  56. package/src/bin.ts +90 -0
  57. package/src/commands/__fixtures__/shadcn-label-wrapper/src/components/ui/label.contract.json +1 -1
  58. package/src/commands/__fixtures__/shadcn-label-wrapper/src/components/ui/primitive.contract.json +1 -1
  59. package/src/commands/__tests__/build-freshness.test.ts +231 -0
  60. package/src/commands/__tests__/create.test.ts +71 -0
  61. package/src/commands/__tests__/drift-sync.test.ts +1 -1
  62. package/src/commands/__tests__/govern.test.ts +258 -0
  63. package/src/commands/__tests__/init.test.ts +1 -1
  64. package/src/commands/__tests__/scan-generate.test.ts +1 -1
  65. package/src/commands/build.ts +54 -1
  66. package/src/commands/context.ts +1 -1
  67. package/src/commands/create.ts +536 -0
  68. package/src/commands/doctor.ts +3 -2
  69. package/src/commands/govern-scan.ts +187 -8
  70. package/src/commands/govern.ts +65 -2
  71. package/src/commands/init-cloud.ts +32 -4
  72. package/src/commands/push-contracts.ts +112 -0
  73. package/src/commands/scan-generate.ts +1 -1
  74. package/src/commands/scan.ts +13 -0
  75. package/src/commands/sync.ts +2 -2
  76. package/src/commands/tokens-push.ts +199 -0
  77. package/src/core/__tests__/token-resolver.test.ts +1 -1
  78. package/src/core/component-extractor.test.ts +1 -1
  79. package/src/core/drift-verifier.ts +1 -1
  80. package/src/core/extractor-adapter.ts +1 -1
  81. package/src/index.ts +3 -3
  82. package/src/migrate/fragment-to-contract.ts +2 -2
  83. package/src/service/index.ts +8 -0
  84. package/src/service/tailwind-v4-parser.ts +314 -0
  85. package/src/service/token-parser.ts +56 -0
  86. package/src/setup.ts +10 -39
  87. package/src/theme/__tests__/component-contrast.test.ts +2 -2
  88. package/src/theme/__tests__/serializer.test.ts +1 -1
  89. package/src/theme/generator.ts +16 -1
  90. package/src/theme/schema.ts +8 -0
  91. package/src/theme/serializer.ts +13 -9
  92. package/src/theme/types.ts +8 -0
  93. package/src/validators.ts +1 -2
  94. package/dist/chunk-65WSVDV5.js.map +0 -1
  95. package/dist/chunk-7WHVW72L.js +0 -2664
  96. package/dist/chunk-7WHVW72L.js.map +0 -1
  97. package/dist/chunk-CZD3AD4Q.js.map +0 -1
  98. package/dist/chunk-MN3TJ3D5.js +0 -695
  99. package/dist/chunk-MN3TJ3D5.js.map +0 -1
  100. package/dist/chunk-XJQ5BIWI.js.map +0 -1
  101. package/dist/chunk-Z7EY4VHE.js +0 -50
  102. package/dist/govern-scan-UCBZR6D6.js.map +0 -1
  103. package/dist/sass.node-4XJK6YBF.js +0 -130708
  104. package/dist/sass.node-4XJK6YBF.js.map +0 -1
  105. package/dist/scan-generate-TWRHNU5M.js.map +0 -1
  106. package/src/build.ts +0 -736
  107. package/src/core/auto-props.ts +0 -464
  108. package/src/core/component-extractor.ts +0 -1121
  109. package/src/core/token-resolver.ts +0 -155
  110. package/src/viewer/preview-adapter.ts +0 -116
  111. /package/dist/{ai-client-I6MDWNYA.js.map → ai-client-LSLQGOMM.js.map} +0 -0
  112. /package/dist/{chunk-Z7EY4VHE.js.map → codebase-scanner-MQHUZC2G.js.map} +0 -0
  113. /package/dist/{codebase-scanner-VOTPXRYW.js.map → node-37AUE74M.js.map} +0 -0
  114. /package/dist/{scan-VNNKACG2.js.map → scan-PKSYSTRR.js.map} +0 -0
  115. /package/dist/{scanner-7LAZYPWZ.js.map → scanner-4KZNOXAK.js.map} +0 -0
  116. /package/dist/{service-FHQU7YS7.js.map → service-QJGWUIVL.js.map} +0 -0
  117. /package/dist/{static-viewer-63PG6FWY.js.map → static-viewer-7QIBQZRC.js.map} +0 -0
  118. /package/dist/{tokens-6GYKDV6U.js.map → tokens-NZWFQIAB.js.map} +0 -0
@@ -1,1121 +0,0 @@
1
- /**
2
- * ComponentExtractor — persistent LanguageService-based prop extraction.
3
- *
4
- * Replaces auto-props.ts with:
5
- * - Persistent ts.LanguageService (not throwaway ts.createProgram())
6
- * - Incremental invalidation via projectVersion
7
- * - Compound component (Object.assign) sub-component prop extraction
8
- * - Full type serialization to PropMeta format
9
- *
10
- * Zero additional dependencies — uses raw TypeScript APIs.
11
- */
12
-
13
- import ts from 'typescript';
14
- import { existsSync, readFileSync, statSync, readdirSync } from 'node:fs';
15
- import { resolve, dirname, join } from 'node:path';
16
-
17
- // ---------------------------------------------------------------------------
18
- // Public types
19
- // ---------------------------------------------------------------------------
20
-
21
- export interface ComponentExtractor {
22
- /** Extract metadata for a single component by export name */
23
- extract(filePath: string, exportName?: string): ComponentMeta | null;
24
-
25
- /** Extract all exported components found in a file */
26
- extractAll(filePath: string): ComponentMeta[];
27
-
28
- /** Notify extractor that a file changed (incremental) */
29
- invalidate(filePath: string): void;
30
-
31
- /** Clean up resources */
32
- dispose(): void;
33
- }
34
-
35
- export interface ComponentMeta {
36
- name: string;
37
- filePath: string;
38
- description: string;
39
- props: Record<string, PropMeta>;
40
- composition: CompositionMeta | null;
41
- exports: string[];
42
- dependencies: string[];
43
- }
44
-
45
- export interface PropMeta {
46
- name: string;
47
- type: string;
48
- typeKind: PropTypeKind;
49
- values?: string[];
50
- default?: string;
51
- description?: string;
52
- required: boolean;
53
- source: 'local' | 'inherited';
54
- }
55
-
56
- export type PropTypeKind =
57
- | 'string' | 'number' | 'boolean'
58
- | 'enum'
59
- | 'function'
60
- | 'node'
61
- | 'element'
62
- | 'object' | 'array'
63
- | 'union'
64
- | 'custom';
65
-
66
- export interface CompositionMeta {
67
- pattern: 'compound' | 'controlled' | 'simple';
68
- parts: PartMeta[];
69
- required: string[];
70
- }
71
-
72
- export interface PartMeta {
73
- name: string;
74
- props: Record<string, PropMeta>;
75
- }
76
-
77
- // ---------------------------------------------------------------------------
78
- // Internal types
79
- // ---------------------------------------------------------------------------
80
-
81
- interface ResolvedComponent {
82
- name: string;
83
- propsType: ts.Type | null;
84
- componentNode: ts.Node | null;
85
- compoundParts: Map<string, ts.Type> | null;
86
- }
87
-
88
- // ---------------------------------------------------------------------------
89
- // Factory
90
- // ---------------------------------------------------------------------------
91
-
92
- export function createComponentExtractor(tsconfigPath?: string): ComponentExtractor {
93
- let projectVersion = 0;
94
- const fileVersions = new Map<string, number>();
95
- const fileSnapshots = new Map<string, ts.IScriptSnapshot>();
96
-
97
- // Parse tsconfig or use inferred settings
98
- const rootDir = tsconfigPath ? dirname(resolve(tsconfigPath)) : process.cwd();
99
- const parsedCommandLine = tsconfigPath
100
- ? parseTsConfig(tsconfigPath)
101
- : inferCompilerOptions(rootDir);
102
-
103
- const scriptFileNames = new Set(parsedCommandLine.fileNames);
104
-
105
- const host: ts.LanguageServiceHost = {
106
- getProjectVersion: () => projectVersion.toString(),
107
- getScriptVersion: (fileName) => (fileVersions.get(fileName) ?? 0).toString(),
108
- getScriptSnapshot: (fileName) => {
109
- const cached = fileSnapshots.get(fileName);
110
- if (cached) return cached;
111
-
112
- let text: string;
113
- try {
114
- text = readFileSync(fileName, 'utf-8');
115
- } catch {
116
- return undefined;
117
- }
118
- const snapshot = ts.ScriptSnapshot.fromString(text);
119
- fileSnapshots.set(fileName, snapshot);
120
- return snapshot;
121
- },
122
- getScriptFileNames: () => [...scriptFileNames],
123
- getCompilationSettings: () => parsedCommandLine.options,
124
- getCurrentDirectory: () => rootDir,
125
- getDefaultLibFileName: ts.getDefaultLibFilePath,
126
- fileExists: ts.sys.fileExists,
127
- readFile: ts.sys.readFile,
128
- readDirectory: ts.sys.readDirectory,
129
- directoryExists: ts.sys.directoryExists,
130
- getDirectories: ts.sys.getDirectories,
131
- resolveModuleNames: (moduleNames, containingFile) =>
132
- moduleNames.map((moduleName) => {
133
- const resolved = ts.resolveModuleName(
134
- moduleName,
135
- containingFile,
136
- parsedCommandLine.options,
137
- {
138
- fileExists: ts.sys.fileExists,
139
- readFile: ts.sys.readFile,
140
- realpath: ts.sys.realpath,
141
- directoryExists: ts.sys.directoryExists,
142
- getDirectories: ts.sys.getDirectories,
143
- getCurrentDirectory: () => rootDir,
144
- }
145
- );
146
-
147
- return resolved.resolvedModule;
148
- }),
149
- };
150
-
151
- const languageService = ts.createLanguageService(host, ts.createDocumentRegistry());
152
-
153
- function ensureFile(filePath: string): void {
154
- const resolved = resolve(filePath);
155
- if (!scriptFileNames.has(resolved)) {
156
- scriptFileNames.add(resolved);
157
- projectVersion++;
158
- }
159
- }
160
-
161
- function getChecker(): ts.TypeChecker {
162
- const program = languageService.getProgram();
163
- if (!program) throw new Error('Failed to get program from LanguageService');
164
- return program.getTypeChecker();
165
- }
166
-
167
- function getSourceFile(filePath: string): ts.SourceFile | undefined {
168
- return languageService.getProgram()?.getSourceFile(resolve(filePath));
169
- }
170
-
171
- return {
172
- extract(filePath: string, exportName?: string): ComponentMeta | null {
173
- const resolved = resolve(filePath);
174
- ensureFile(resolved);
175
-
176
- const sourceFile = getSourceFile(resolved);
177
- if (!sourceFile) return null;
178
-
179
- const checker = getChecker();
180
- const moduleSymbol = checker.getSymbolAtLocation(sourceFile);
181
- if (!moduleSymbol) return null;
182
-
183
- const exports = checker.getExportsOfModule(moduleSymbol);
184
- const targetName = exportName ?? findPrimaryExport(exports, sourceFile);
185
- if (!targetName) return null;
186
-
187
- const exportSymbol = exports.find(s => s.getName() === targetName);
188
- if (!exportSymbol) return null;
189
-
190
- const component = resolveExportedComponent(checker, exportSymbol, sourceFile);
191
- if (!component) return null;
192
-
193
- return buildComponentMeta(checker, component, resolved, sourceFile, exports);
194
- },
195
-
196
- extractAll(filePath: string): ComponentMeta[] {
197
- const resolved = resolve(filePath);
198
- ensureFile(resolved);
199
-
200
- const sourceFile = getSourceFile(resolved);
201
- if (!sourceFile) return [];
202
-
203
- const checker = getChecker();
204
- const moduleSymbol = checker.getSymbolAtLocation(sourceFile);
205
- if (!moduleSymbol) return [];
206
-
207
- const exports = checker.getExportsOfModule(moduleSymbol);
208
- const results: ComponentMeta[] = [];
209
-
210
- for (const exportSymbol of exports) {
211
- const name = exportSymbol.getName();
212
- // Only consider PascalCase exports as potential components
213
- if (!/^[A-Z]/.test(name)) continue;
214
-
215
- const component = resolveExportedComponent(checker, exportSymbol, sourceFile);
216
- if (!component) continue;
217
-
218
- const meta = buildComponentMeta(checker, component, resolved, sourceFile, exports);
219
- if (meta) results.push(meta);
220
- }
221
-
222
- return results;
223
- },
224
-
225
- invalidate(filePath: string): void {
226
- const resolved = resolve(filePath);
227
- fileVersions.set(resolved, (fileVersions.get(resolved) ?? 0) + 1);
228
- fileSnapshots.delete(resolved);
229
- projectVersion++;
230
- },
231
-
232
- dispose(): void {
233
- languageService.dispose();
234
- fileSnapshots.clear();
235
- fileVersions.clear();
236
- },
237
- };
238
- }
239
-
240
- // ---------------------------------------------------------------------------
241
- // tsconfig parsing
242
- // ---------------------------------------------------------------------------
243
-
244
- function parseTsConfig(tsconfigPath: string): ts.ParsedCommandLine {
245
- const resolved = resolve(tsconfigPath);
246
- const configFile = ts.readConfigFile(resolved, ts.sys.readFile);
247
- if (configFile.error) {
248
- throw new Error(`Failed to read tsconfig: ${ts.flattenDiagnosticMessageText(configFile.error.messageText, '\n')}`);
249
- }
250
-
251
- return ts.parseJsonConfigFileContent(
252
- configFile.config,
253
- ts.sys,
254
- dirname(resolved),
255
- undefined,
256
- resolved,
257
- );
258
- }
259
-
260
- function inferCompilerOptions(rootDir: string): ts.ParsedCommandLine {
261
- return {
262
- options: {
263
- target: ts.ScriptTarget.ES2022,
264
- module: ts.ModuleKind.ESNext,
265
- moduleResolution: ts.ModuleResolutionKind.Bundler,
266
- jsx: ts.JsxEmit.ReactJSX,
267
- allowSyntheticDefaultImports: true,
268
- esModuleInterop: true,
269
- skipLibCheck: true,
270
- strict: false,
271
- noEmit: true,
272
- },
273
- fileNames: [],
274
- errors: [],
275
- };
276
- }
277
-
278
- // ---------------------------------------------------------------------------
279
- // Export resolution
280
- // ---------------------------------------------------------------------------
281
-
282
- /**
283
- * Find the "primary" export — prefers the default export or the first PascalCase export.
284
- */
285
- function findPrimaryExport(exports: ts.Symbol[], sourceFile: ts.SourceFile): string | null {
286
- // Default export
287
- const defaultExport = exports.find(s => s.getName() === 'default');
288
- if (defaultExport) return 'default';
289
-
290
- // First PascalCase named export
291
- for (const s of exports) {
292
- if (/^[A-Z][a-zA-Z0-9]*$/.test(s.getName())) {
293
- return s.getName();
294
- }
295
- }
296
-
297
- return null;
298
- }
299
-
300
- /**
301
- * Resolve an export symbol to a component with its props type.
302
- * Handles: direct functions, forwardRef, memo, Object.assign, FC<Props>.
303
- */
304
- function resolveExportedComponent(
305
- checker: ts.TypeChecker,
306
- exportSymbol: ts.Symbol,
307
- sourceFile: ts.SourceFile,
308
- ): ResolvedComponent | null {
309
- const name = exportSymbol.getName();
310
-
311
- // Follow aliases (re-exports)
312
- let symbol = exportSymbol;
313
- if (symbol.flags & ts.SymbolFlags.Alias) {
314
- symbol = checker.getAliasedSymbol(symbol);
315
- }
316
-
317
- const declarations = symbol.getDeclarations();
318
- if (!declarations || declarations.length === 0) return null;
319
-
320
- const declaration = declarations[0];
321
-
322
- // Variable declaration: const X = ... (forwardRef, memo, Object.assign, arrow, FC)
323
- if (ts.isVariableDeclaration(declaration) && declaration.initializer) {
324
- return resolveFromExpression(checker, name, declaration.initializer, declaration, sourceFile);
325
- }
326
-
327
- // Function declaration: function X(props: Props) { ... }
328
- if (ts.isFunctionDeclaration(declaration)) {
329
- const propsType = extractPropsFromFunctionLike(checker, declaration);
330
- return propsType ? { name, propsType, componentNode: declaration, compoundParts: null } : null;
331
- }
332
-
333
- // Export assignment: export default X
334
- if (ts.isExportAssignment(declaration) && declaration.expression) {
335
- return resolveFromExpression(checker, name, declaration.expression, null, sourceFile);
336
- }
337
-
338
- return null;
339
- }
340
-
341
- function resolveFromExpression(
342
- checker: ts.TypeChecker,
343
- name: string,
344
- expression: ts.Expression,
345
- variableDecl: ts.VariableDeclaration | null,
346
- sourceFile: ts.SourceFile,
347
- ): ResolvedComponent | null {
348
- // Unwrap parentheses, type assertions
349
- expression = unwrapExpression(expression);
350
-
351
- // Arrow function or function expression
352
- if (ts.isArrowFunction(expression) || ts.isFunctionExpression(expression)) {
353
- const propsType = extractPropsFromFunctionLike(checker, expression);
354
- return propsType ? { name, propsType, componentNode: expression, compoundParts: null } : null;
355
- }
356
-
357
- // Call expression: React.forwardRef, React.memo, Object.assign
358
- if (ts.isCallExpression(expression)) {
359
- return resolveCallExpression(checker, name, expression, variableDecl, sourceFile);
360
- }
361
-
362
- // Identifier reference — follow to its declaration
363
- if (ts.isIdentifier(expression)) {
364
- const sym = checker.getSymbolAtLocation(expression);
365
- if (sym) {
366
- const decls = sym.getDeclarations();
367
- if (decls && decls.length > 0) {
368
- const decl = decls[0];
369
- if (ts.isVariableDeclaration(decl) && decl.initializer) {
370
- return resolveFromExpression(checker, name, decl.initializer, decl, sourceFile);
371
- }
372
- if (ts.isFunctionDeclaration(decl)) {
373
- const propsType = extractPropsFromFunctionLike(checker, decl);
374
- return propsType ? { name, propsType, componentNode: decl, compoundParts: null } : null;
375
- }
376
- }
377
- }
378
- }
379
-
380
- // FC<Props> typed variable
381
- if (variableDecl?.type && ts.isTypeReferenceNode(variableDecl.type)) {
382
- const typeName = variableDecl.type.typeName.getText(sourceFile);
383
- if (typeName.includes('FC') || typeName.includes('FunctionComponent')) {
384
- const typeArg = variableDecl.type.typeArguments?.[0];
385
- if (typeArg) {
386
- const propsType = checker.getTypeFromTypeNode(typeArg);
387
- return { name, propsType, componentNode: expression, compoundParts: null };
388
- }
389
- }
390
- }
391
-
392
- return null;
393
- }
394
-
395
- function resolveCallExpression(
396
- checker: ts.TypeChecker,
397
- name: string,
398
- call: ts.CallExpression,
399
- variableDecl: ts.VariableDeclaration | null,
400
- sourceFile: ts.SourceFile,
401
- ): ResolvedComponent | null {
402
- const callee = call.expression;
403
-
404
- // Object.assign(Root, { Header, Body, ... })
405
- if (isObjectAssignCall(callee, sourceFile)) {
406
- return resolveObjectAssign(checker, name, call, sourceFile);
407
- }
408
-
409
- // React.forwardRef<Ref, Props>(fn) or forwardRef<Ref, Props>(fn)
410
- if (isForwardRefCall(callee)) {
411
- return resolveForwardRef(checker, name, call, sourceFile);
412
- }
413
-
414
- // React.memo(Component) or memo(Component)
415
- if (isMemoCall(callee)) {
416
- const innerArg = call.arguments[0];
417
- if (innerArg) {
418
- return resolveFromExpression(checker, name, innerArg, variableDecl, sourceFile);
419
- }
420
- }
421
-
422
- return null;
423
- }
424
-
425
- // ---------------------------------------------------------------------------
426
- // Object.assign compound component resolution
427
- // ---------------------------------------------------------------------------
428
-
429
- function resolveObjectAssign(
430
- checker: ts.TypeChecker,
431
- name: string,
432
- call: ts.CallExpression,
433
- sourceFile: ts.SourceFile,
434
- ): ResolvedComponent | null {
435
- if (call.arguments.length < 2) return null;
436
-
437
- // First arg is the root component
438
- const rootExpr = call.arguments[0];
439
- const rootResult = resolveFromExpression(checker, name, rootExpr, null, sourceFile);
440
-
441
- // Second arg is the object literal with sub-components
442
- const subsArg = call.arguments[1];
443
- const compoundParts = new Map<string, ts.Type>();
444
-
445
- if (ts.isObjectLiteralExpression(subsArg)) {
446
- for (const prop of subsArg.properties) {
447
- let subName: string | null = null;
448
- let subExpression: ts.Expression | null = null;
449
-
450
- if (ts.isShorthandPropertyAssignment(prop)) {
451
- subName = prop.name.text;
452
- // Resolve the identifier to its type
453
- const sym = checker.getSymbolAtLocation(prop.name);
454
- if (sym) {
455
- const subPropsType = extractPropsFromComponentSymbol(checker, sym);
456
- if (subPropsType) {
457
- compoundParts.set(subName, subPropsType);
458
- }
459
- }
460
- } else if (ts.isPropertyAssignment(prop) && ts.isIdentifier(prop.name)) {
461
- subName = prop.name.text;
462
- subExpression = prop.initializer;
463
- if (subExpression) {
464
- const subType = extractPropsFromExpression(checker, subExpression, sourceFile);
465
- if (subType) {
466
- compoundParts.set(subName, subType);
467
- }
468
- }
469
- }
470
- }
471
- }
472
-
473
- return {
474
- name,
475
- propsType: rootResult?.propsType ?? null,
476
- componentNode: rootResult?.componentNode ?? rootExpr,
477
- compoundParts: compoundParts.size > 0 ? compoundParts : null,
478
- };
479
- }
480
-
481
- /**
482
- * Extract props type from a component symbol (function, variable, etc.)
483
- */
484
- function extractPropsFromComponentSymbol(checker: ts.TypeChecker, symbol: ts.Symbol): ts.Type | null {
485
- // Follow aliases
486
- let sym = symbol;
487
- if (sym.flags & ts.SymbolFlags.Alias) {
488
- sym = checker.getAliasedSymbol(sym);
489
- }
490
-
491
- const decls = sym.getDeclarations();
492
- if (!decls || decls.length === 0) return null;
493
-
494
- const decl = decls[0];
495
-
496
- if (ts.isFunctionDeclaration(decl)) {
497
- return extractPropsFromFunctionLike(checker, decl);
498
- }
499
-
500
- if (ts.isVariableDeclaration(decl) && decl.initializer) {
501
- // forwardRef, memo, arrow function, etc.
502
- return extractPropsFromExpressionDeep(checker, decl);
503
- }
504
-
505
- return null;
506
- }
507
-
508
- /**
509
- * Extract props type from an arbitrary expression (used for Object.assign values).
510
- */
511
- function extractPropsFromExpression(checker: ts.TypeChecker, expr: ts.Expression, sourceFile: ts.SourceFile): ts.Type | null {
512
- expr = unwrapExpression(expr);
513
-
514
- if (ts.isIdentifier(expr)) {
515
- const sym = checker.getSymbolAtLocation(expr);
516
- if (sym) return extractPropsFromComponentSymbol(checker, sym);
517
- }
518
-
519
- if (ts.isArrowFunction(expr) || ts.isFunctionExpression(expr)) {
520
- return extractPropsFromFunctionLike(checker, expr);
521
- }
522
-
523
- if (ts.isCallExpression(expr)) {
524
- if (isForwardRefCall(expr.expression)) {
525
- const typeArg = expr.typeArguments?.[1];
526
- if (typeArg) return checker.getTypeFromTypeNode(typeArg);
527
- const innerArg = expr.arguments[0];
528
- if (innerArg && (ts.isArrowFunction(innerArg) || ts.isFunctionExpression(innerArg))) {
529
- return extractPropsFromFunctionLike(checker, innerArg);
530
- }
531
- }
532
- if (isMemoCall(expr.expression) && expr.arguments[0]) {
533
- return extractPropsFromExpression(checker, expr.arguments[0], sourceFile);
534
- }
535
- }
536
-
537
- return null;
538
- }
539
-
540
- /**
541
- * Deep extraction from a variable declaration.
542
- */
543
- function extractPropsFromExpressionDeep(checker: ts.TypeChecker, decl: ts.VariableDeclaration): ts.Type | null {
544
- if (!decl.initializer) return null;
545
- const expr = unwrapExpression(decl.initializer);
546
-
547
- if (ts.isArrowFunction(expr) || ts.isFunctionExpression(expr)) {
548
- return extractPropsFromFunctionLike(checker, expr);
549
- }
550
-
551
- if (ts.isCallExpression(expr)) {
552
- if (isForwardRefCall(expr.expression)) {
553
- const typeArg = expr.typeArguments?.[1];
554
- if (typeArg) return checker.getTypeFromTypeNode(typeArg);
555
- const innerArg = expr.arguments[0];
556
- if (innerArg && (ts.isArrowFunction(innerArg) || ts.isFunctionExpression(innerArg))) {
557
- return extractPropsFromFunctionLike(checker, innerArg);
558
- }
559
- }
560
- if (isMemoCall(expr.expression) && expr.arguments[0]) {
561
- return extractPropsFromExpressionDeep(checker, {
562
- ...decl,
563
- initializer: expr.arguments[0],
564
- } as ts.VariableDeclaration);
565
- }
566
- }
567
-
568
- return null;
569
- }
570
-
571
- // ---------------------------------------------------------------------------
572
- // forwardRef resolution
573
- // ---------------------------------------------------------------------------
574
-
575
- function resolveForwardRef(
576
- checker: ts.TypeChecker,
577
- name: string,
578
- call: ts.CallExpression,
579
- sourceFile: ts.SourceFile,
580
- ): ResolvedComponent | null {
581
- // Try type arguments first: forwardRef<Ref, Props>(...)
582
- const propsTypeArg = call.typeArguments?.[1];
583
- if (propsTypeArg) {
584
- const propsType = checker.getTypeFromTypeNode(propsTypeArg);
585
- const innerArg = call.arguments[0];
586
- const componentNode = innerArg && (ts.isArrowFunction(innerArg) || ts.isFunctionExpression(innerArg))
587
- ? innerArg : null;
588
- return { name, propsType, componentNode, compoundParts: null };
589
- }
590
-
591
- // Fall back to inner function's parameter type
592
- const innerArg = call.arguments[0];
593
- if (innerArg) {
594
- if (ts.isArrowFunction(innerArg) || ts.isFunctionExpression(innerArg)) {
595
- const propsType = extractPropsFromFunctionLike(checker, innerArg);
596
- return propsType ? { name, propsType, componentNode: innerArg, compoundParts: null } : null;
597
- }
598
- if (ts.isIdentifier(innerArg)) {
599
- const sym = checker.getSymbolAtLocation(innerArg);
600
- if (sym) {
601
- const propsType = extractPropsFromComponentSymbol(checker, sym);
602
- if (propsType) return { name, propsType, componentNode: innerArg, compoundParts: null };
603
- }
604
- }
605
- }
606
-
607
- return null;
608
- }
609
-
610
- // ---------------------------------------------------------------------------
611
- // Props extraction from function-like declarations
612
- // ---------------------------------------------------------------------------
613
-
614
- /**
615
- * Extract the props type from a function's first parameter.
616
- */
617
- function extractPropsFromFunctionLike(
618
- checker: ts.TypeChecker,
619
- func: ts.FunctionLikeDeclaration,
620
- ): ts.Type | null {
621
- const firstParam = func.parameters[0];
622
- if (!firstParam) return null;
623
-
624
- // If there's a type annotation, use it
625
- if (firstParam.type) {
626
- const directType = checker.getTypeFromTypeNode(firstParam.type);
627
- if (checker.getPropertiesOfType(directType).length > 0) {
628
- return directType;
629
- }
630
-
631
- const apparentType = checker.getApparentType(directType);
632
- if (checker.getPropertiesOfType(apparentType).length > 0) {
633
- return apparentType;
634
- }
635
-
636
- const paramType = checker.getTypeAtLocation(firstParam);
637
- if (checker.getPropertiesOfType(paramType).length > 0) {
638
- return paramType;
639
- }
640
-
641
- const constrainedType = checker.getBaseConstraintOfType(directType);
642
- if (constrainedType && checker.getPropertiesOfType(constrainedType).length > 0) {
643
- return constrainedType;
644
- }
645
-
646
- return directType;
647
- }
648
-
649
- // Otherwise try to infer from the parameter's symbol
650
- const paramSymbol = checker.getSymbolAtLocation(firstParam.name);
651
- if (paramSymbol) {
652
- return checker.getTypeOfSymbolAtLocation(paramSymbol, firstParam);
653
- }
654
-
655
- return null;
656
- }
657
-
658
- // ---------------------------------------------------------------------------
659
- // Build ComponentMeta from resolved component
660
- // ---------------------------------------------------------------------------
661
-
662
- function buildComponentMeta(
663
- checker: ts.TypeChecker,
664
- component: ResolvedComponent,
665
- filePath: string,
666
- sourceFile: ts.SourceFile,
667
- moduleExports: ts.Symbol[],
668
- ): ComponentMeta | null {
669
- const props: Record<string, PropMeta> = {};
670
- const sourceFilePath = toPosixPath(sourceFile.fileName);
671
-
672
- if (component.propsType) {
673
- extractPropsFromType(checker, component.propsType, props, sourceFilePath);
674
- if (Object.keys(props).length === 0) {
675
- const apparentPropsType = checker.getApparentType(component.propsType);
676
- if (apparentPropsType !== component.propsType) {
677
- extractPropsFromType(checker, apparentPropsType, props, sourceFilePath);
678
- }
679
- }
680
- if (Object.keys(props).length === 0) {
681
- const constrainedPropsType = checker.getBaseConstraintOfType(component.propsType);
682
- if (constrainedPropsType) {
683
- extractPropsFromType(checker, constrainedPropsType, props, sourceFilePath);
684
- }
685
- }
686
- }
687
-
688
- // Promote explicitly destructured props to "local" so wrapper components like
689
- // shadcn Input surface the props they actively customize from inherited DOM types.
690
- const destructuredProps = component.componentNode
691
- ? extractDestructuredPropNames(component.componentNode)
692
- : new Set<string>();
693
- for (const propName of destructuredProps) {
694
- if (props[propName]) {
695
- props[propName].source = 'local';
696
- }
697
- }
698
-
699
- // Extract defaults from the component function body
700
- const defaults = component.componentNode
701
- ? extractDefaultValues(component.componentNode)
702
- : {};
703
-
704
- // Apply defaults
705
- for (const [propName, defaultVal] of Object.entries(defaults)) {
706
- if (props[propName]) {
707
- props[propName].default = defaultVal;
708
- }
709
- }
710
-
711
- // Build composition meta
712
- let composition: CompositionMeta | null = null;
713
- if (component.compoundParts && component.compoundParts.size > 0) {
714
- const parts: PartMeta[] = [];
715
- for (const [partName, partType] of component.compoundParts) {
716
- const partProps: Record<string, PropMeta> = {};
717
- extractPropsFromType(checker, partType, partProps, sourceFilePath);
718
- parts.push({ name: partName, props: partProps });
719
- }
720
-
721
- composition = {
722
- pattern: 'compound',
723
- parts,
724
- required: [], // Could be inferred from usage patterns
725
- };
726
- }
727
-
728
- // Extract description from JSDoc
729
- let description = '';
730
- const componentSymbol = moduleExports.find(s => s.getName() === component.name);
731
- if (componentSymbol) {
732
- description = extractJSDocDescription(checker, componentSymbol);
733
- }
734
-
735
- // Collect export names
736
- const exportNames = moduleExports
737
- .filter(s => /^[A-Z]/.test(s.getName()))
738
- .map(s => s.getName());
739
-
740
- // Collect import dependencies (other components imported)
741
- const dependencies = extractDependencies(sourceFile);
742
-
743
- return {
744
- name: component.name,
745
- filePath,
746
- description,
747
- props,
748
- composition,
749
- exports: exportNames,
750
- dependencies,
751
- };
752
- }
753
-
754
- // ---------------------------------------------------------------------------
755
- // Type → PropMeta extraction
756
- // ---------------------------------------------------------------------------
757
-
758
- function extractPropsFromType(
759
- checker: ts.TypeChecker,
760
- propsType: ts.Type,
761
- result: Record<string, PropMeta>,
762
- sourceFilePath: string,
763
- ): void {
764
- for (const symbol of checker.getPropertiesOfType(propsType)) {
765
- const propName = symbol.getName();
766
-
767
- // Skip internal/private props
768
- if (propName.startsWith('_') || propName.startsWith('$')) continue;
769
-
770
- // Skip React internal props
771
- if (propName === 'key' || propName === 'ref') continue;
772
-
773
- const declarations = symbol.getDeclarations() ?? [];
774
-
775
- // Determine source: local vs inherited
776
- const isLocal = declarations.some(
777
- d => toPosixPath(d.getSourceFile().fileName) === sourceFilePath,
778
- );
779
-
780
- const referenceNode = declarations[0];
781
- if (!referenceNode) continue;
782
-
783
- const propType = checker.getTypeOfSymbolAtLocation(symbol, referenceNode);
784
- const serialized = serializePropType(checker, propType);
785
- const description = ts.displayPartsToString(symbol.getDocumentationComment(checker)).trim();
786
- const required = (symbol.flags & ts.SymbolFlags.Optional) === 0;
787
-
788
- // Extract @default from JSDoc tags
789
- let jsDocDefault: string | undefined;
790
- const jsDocTags = symbol.getJsDocTags(checker);
791
- const defaultTag = jsDocTags.find(t => t.name === 'default');
792
- if (defaultTag?.text) {
793
- jsDocDefault = ts.displayPartsToString(defaultTag.text).trim();
794
- }
795
-
796
- result[propName] = {
797
- name: propName,
798
- type: serialized.type,
799
- typeKind: serialized.typeKind,
800
- values: serialized.values,
801
- default: jsDocDefault,
802
- description: description || undefined,
803
- required,
804
- source: isLocal ? 'local' : 'inherited',
805
- };
806
- }
807
- }
808
-
809
- // ---------------------------------------------------------------------------
810
- // Type serializer: ts.Type → PropMeta shape
811
- // ---------------------------------------------------------------------------
812
-
813
- function serializePropType(
814
- checker: ts.TypeChecker,
815
- type: ts.Type,
816
- ): Pick<PropMeta, 'type' | 'typeKind' | 'values'> {
817
- // Check for ReactNode/ReactElement BEFORE union decomposition,
818
- // because ReactNode is defined as a large union in React's types.
819
- const aliasSymbol = type.aliasSymbol;
820
- if (aliasSymbol) {
821
- const aliasName = aliasSymbol.getName();
822
- if (aliasName === 'ReactNode') {
823
- return { type: 'ReactNode', typeKind: 'node' };
824
- }
825
- if (aliasName === 'ReactElement') {
826
- return { type: 'ReactElement', typeKind: 'element' };
827
- }
828
- }
829
-
830
- // Handle union types (including optional which adds undefined)
831
- if (type.isUnion()) {
832
- const nonNullableTypes = type.types.filter(
833
- t => !((t.flags & ts.TypeFlags.Undefined) || (t.flags & ts.TypeFlags.Null) || (t.flags & ts.TypeFlags.Void)),
834
- );
835
-
836
- // Single type after filtering nullable
837
- if (nonNullableTypes.length === 1) {
838
- return serializePropType(checker, nonNullableTypes[0]);
839
- }
840
-
841
- // All string literals → enum
842
- if (nonNullableTypes.length > 0 && nonNullableTypes.every(t => t.isStringLiteral())) {
843
- const values = nonNullableTypes.map(t => (t as ts.StringLiteralType).value);
844
- return {
845
- type: values.map(v => `"${v}"`).join(' | '),
846
- typeKind: 'enum',
847
- values,
848
- };
849
- }
850
-
851
- // All boolean-like
852
- if (nonNullableTypes.every(t => isBooleanLike(t))) {
853
- return { type: 'boolean', typeKind: 'boolean' };
854
- }
855
-
856
- // Mixed union
857
- const typeStr = checker.typeToString(type, undefined, ts.TypeFormatFlags.NoTruncation);
858
- return { type: typeStr, typeKind: 'union' };
859
- }
860
-
861
- // Check for ReactNode / ReactElement
862
- const typeStr = checker.typeToString(type, undefined, ts.TypeFormatFlags.NoTruncation);
863
- if (typeStr.includes('ReactNode')) {
864
- return { type: 'ReactNode', typeKind: 'node' };
865
- }
866
- if (typeStr.includes('ReactElement') || typeStr.includes('JSX.Element')) {
867
- return { type: 'ReactElement', typeKind: 'element' };
868
- }
869
-
870
- // Function types
871
- if (type.getCallSignatures().length > 0) {
872
- return { type: typeStr, typeKind: 'function' };
873
- }
874
-
875
- // Primitives
876
- if (type.flags & ts.TypeFlags.String) return { type: 'string', typeKind: 'string' };
877
- if (type.flags & ts.TypeFlags.Number) return { type: 'number', typeKind: 'number' };
878
- if (type.flags & ts.TypeFlags.Boolean || type.flags & ts.TypeFlags.BooleanLiteral) {
879
- return { type: 'boolean', typeKind: 'boolean' };
880
- }
881
-
882
- // String literal (not in union)
883
- if (type.isStringLiteral()) {
884
- return { type: `"${type.value}"`, typeKind: 'enum', values: [type.value] };
885
- }
886
-
887
- // Array
888
- if (checker.isArrayType(type) || checker.isTupleType(type)) {
889
- return { type: typeStr, typeKind: 'array' };
890
- }
891
-
892
- // Object
893
- if (type.flags & ts.TypeFlags.Object) {
894
- return { type: typeStr, typeKind: 'object' };
895
- }
896
-
897
- return { type: typeStr, typeKind: 'custom' };
898
- }
899
-
900
- // ---------------------------------------------------------------------------
901
- // Default value extraction
902
- // ---------------------------------------------------------------------------
903
-
904
- function extractDefaultValues(node: ts.Node): Record<string, string> {
905
- const defaults: Record<string, string> = {};
906
-
907
- // Find function-like node
908
- let funcNode: ts.FunctionLikeDeclaration | null = null;
909
- if (ts.isFunctionDeclaration(node) || ts.isArrowFunction(node) || ts.isFunctionExpression(node)) {
910
- funcNode = node;
911
- }
912
-
913
- if (!funcNode?.parameters?.length) return defaults;
914
-
915
- const firstParam = funcNode.parameters[0];
916
- if (!ts.isObjectBindingPattern(firstParam.name)) return defaults;
917
-
918
- for (const element of firstParam.name.elements) {
919
- let propName: string | null = null;
920
-
921
- if (element.propertyName) {
922
- if (ts.isIdentifier(element.propertyName) || ts.isStringLiteral(element.propertyName)) {
923
- propName = element.propertyName.text;
924
- }
925
- } else if (ts.isIdentifier(element.name)) {
926
- propName = element.name.text;
927
- }
928
-
929
- if (!propName || !element.initializer) continue;
930
-
931
- const value = readLiteralValue(element.initializer);
932
- if (value !== undefined) {
933
- defaults[propName] = value;
934
- }
935
- }
936
-
937
- return defaults;
938
- }
939
-
940
- function extractDestructuredPropNames(node: ts.Node): Set<string> {
941
- const names = new Set<string>();
942
-
943
- let funcNode: ts.FunctionLikeDeclaration | null = null;
944
- if (ts.isFunctionDeclaration(node) || ts.isArrowFunction(node) || ts.isFunctionExpression(node)) {
945
- funcNode = node;
946
- }
947
-
948
- if (!funcNode?.parameters?.length) return names;
949
-
950
- const firstParam = funcNode.parameters[0];
951
- if (!ts.isObjectBindingPattern(firstParam.name)) return names;
952
-
953
- for (const element of firstParam.name.elements) {
954
- if (element.dotDotDotToken || !ts.isIdentifier(element.name)) continue;
955
-
956
- const propName = element.propertyName
957
- ? element.propertyName.getText()
958
- : element.name.text;
959
-
960
- if (propName === 'className' || propName === 'children' || propName === 'ref') {
961
- continue;
962
- }
963
-
964
- names.add(propName);
965
- }
966
-
967
- return names;
968
- }
969
-
970
- function readLiteralValue(expression: ts.Expression): string | undefined {
971
- if (ts.isStringLiteral(expression) || ts.isNoSubstitutionTemplateLiteral(expression)) {
972
- return expression.text;
973
- }
974
- if (ts.isNumericLiteral(expression)) {
975
- return expression.text;
976
- }
977
- if (expression.kind === ts.SyntaxKind.TrueKeyword) return 'true';
978
- if (expression.kind === ts.SyntaxKind.FalseKeyword) return 'false';
979
- if (expression.kind === ts.SyntaxKind.NullKeyword) return 'null';
980
- if (
981
- ts.isPrefixUnaryExpression(expression) &&
982
- expression.operator === ts.SyntaxKind.MinusToken &&
983
- ts.isNumericLiteral(expression.operand)
984
- ) {
985
- return `-${expression.operand.text}`;
986
- }
987
-
988
- return undefined;
989
- }
990
-
991
- // ---------------------------------------------------------------------------
992
- // JSDoc extraction
993
- // ---------------------------------------------------------------------------
994
-
995
- function extractJSDocDescription(checker: ts.TypeChecker, symbol: ts.Symbol): string {
996
- // Follow aliases
997
- let sym = symbol;
998
- if (sym.flags & ts.SymbolFlags.Alias) {
999
- sym = checker.getAliasedSymbol(sym);
1000
- }
1001
-
1002
- const docComment = ts.displayPartsToString(sym.getDocumentationComment(checker)).trim();
1003
- if (docComment) return docComment;
1004
-
1005
- // Try to get from the Props interface
1006
- const decls = sym.getDeclarations();
1007
- if (decls) {
1008
- for (const decl of decls) {
1009
- // Look for the Props type
1010
- const sourceFile = decl.getSourceFile();
1011
- for (const stmt of sourceFile.statements) {
1012
- if (
1013
- (ts.isInterfaceDeclaration(stmt) || ts.isTypeAliasDeclaration(stmt)) &&
1014
- stmt.name.text === `${symbol.getName()}Props`
1015
- ) {
1016
- const propsDoc = ts.displayPartsToString(
1017
- checker.getSymbolAtLocation(stmt.name)?.getDocumentationComment(checker) ?? [],
1018
- ).trim();
1019
- if (propsDoc) return propsDoc;
1020
- }
1021
- }
1022
- }
1023
- }
1024
-
1025
- return '';
1026
- }
1027
-
1028
- // ---------------------------------------------------------------------------
1029
- // Import dependency extraction
1030
- // ---------------------------------------------------------------------------
1031
-
1032
- function extractDependencies(sourceFile: ts.SourceFile): string[] {
1033
- const deps: string[] = [];
1034
- for (const stmt of sourceFile.statements) {
1035
- if (!ts.isImportDeclaration(stmt)) continue;
1036
- if (!ts.isStringLiteral(stmt.moduleSpecifier)) continue;
1037
-
1038
- const modulePath = stmt.moduleSpecifier.text;
1039
- // Only relative imports to other components
1040
- if (!modulePath.startsWith('.') && !modulePath.startsWith('/')) continue;
1041
-
1042
- const clause = stmt.importClause;
1043
- if (!clause) continue;
1044
-
1045
- // Default import
1046
- if (clause.name && /^[A-Z]/.test(clause.name.text)) {
1047
- deps.push(clause.name.text);
1048
- }
1049
-
1050
- // Named imports
1051
- if (clause.namedBindings && ts.isNamedImports(clause.namedBindings)) {
1052
- for (const element of clause.namedBindings.elements) {
1053
- if (/^[A-Z]/.test(element.name.text)) {
1054
- deps.push(element.name.text);
1055
- }
1056
- }
1057
- }
1058
- }
1059
- return deps;
1060
- }
1061
-
1062
- // ---------------------------------------------------------------------------
1063
- // Helpers
1064
- // ---------------------------------------------------------------------------
1065
-
1066
- function unwrapExpression(expr: ts.Expression): ts.Expression {
1067
- while (true) {
1068
- if (ts.isParenthesizedExpression(expr)) {
1069
- expr = expr.expression;
1070
- } else if (ts.isAsExpression(expr) || ts.isTypeAssertionExpression(expr)) {
1071
- expr = expr.expression;
1072
- } else {
1073
- return expr;
1074
- }
1075
- }
1076
- }
1077
-
1078
- function isObjectAssignCall(callee: ts.Expression, sourceFile: ts.SourceFile): boolean {
1079
- if (
1080
- ts.isPropertyAccessExpression(callee) &&
1081
- ts.isIdentifier(callee.expression) &&
1082
- callee.expression.text === 'Object' &&
1083
- callee.name.text === 'assign'
1084
- ) {
1085
- return true;
1086
- }
1087
- return false;
1088
- }
1089
-
1090
- function isForwardRefCall(callee: ts.Expression): boolean {
1091
- // React.forwardRef(...)
1092
- if (ts.isPropertyAccessExpression(callee) && callee.name.text === 'forwardRef') {
1093
- return true;
1094
- }
1095
- // forwardRef(...)
1096
- if (ts.isIdentifier(callee) && callee.text === 'forwardRef') {
1097
- return true;
1098
- }
1099
- return false;
1100
- }
1101
-
1102
- function isMemoCall(callee: ts.Expression): boolean {
1103
- if (ts.isPropertyAccessExpression(callee) && callee.name.text === 'memo') {
1104
- return true;
1105
- }
1106
- if (ts.isIdentifier(callee) && callee.text === 'memo') {
1107
- return true;
1108
- }
1109
- return false;
1110
- }
1111
-
1112
- function isBooleanLike(type: ts.Type): boolean {
1113
- return (
1114
- (type.flags & ts.TypeFlags.BooleanLike) !== 0 ||
1115
- type.flags === ts.TypeFlags.BooleanLiteral
1116
- );
1117
- }
1118
-
1119
- function toPosixPath(filePath: string): string {
1120
- return filePath.replace(/\\/g, '/');
1121
- }