@idealyst/tooling 1.2.23 → 1.2.25
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/analyzer/index.d.ts +65 -0
- package/dist/analyzer/index.js +687 -0
- package/dist/analyzer/index.js.map +1 -0
- package/dist/analyzers/index.d.ts +205 -0
- package/dist/analyzers/index.js +1155 -0
- package/dist/analyzers/index.js.map +1 -0
- package/dist/index.d.ts +55 -0
- package/dist/index.js +2061 -0
- package/dist/index.js.map +1 -0
- package/dist/rules/index.d.ts +69 -0
- package/dist/rules/index.js +481 -0
- package/dist/rules/index.js.map +1 -0
- package/dist/types-CrlxbLFJ.d.ts +121 -0
- package/dist/types-CvIlSIOV.d.ts +149 -0
- package/dist/utils/index.d.ts +94 -0
- package/dist/utils/index.js +662 -0
- package/dist/utils/index.js.map +1 -0
- package/dist/vite-plugin.d.ts +40 -0
- package/dist/vite-plugin.js +764 -0
- package/dist/vite-plugin.js.map +1 -0
- package/package.json +21 -24
- package/src/analyzer/component-analyzer.ts +0 -554
- package/src/analyzer/index.ts +0 -16
- package/src/analyzer/theme-analyzer.ts +0 -473
- package/src/analyzer/types.ts +0 -159
- package/src/analyzers/componentLinter.ts +0 -397
- package/src/analyzers/index.ts +0 -2
- package/src/analyzers/platformImports.ts +0 -391
- package/src/index.ts +0 -156
- package/src/rules/index.ts +0 -2
- package/src/rules/reactDomPrimitives.ts +0 -217
- package/src/rules/reactNativePrimitives.ts +0 -362
- package/src/types.ts +0 -173
- package/src/utils/fileClassifier.ts +0 -135
- package/src/utils/importParser.ts +0 -235
- package/src/utils/index.ts +0 -2
- package/src/vite-plugin.ts +0 -199
|
@@ -0,0 +1,687 @@
|
|
|
1
|
+
// src/analyzer/component-analyzer.ts
|
|
2
|
+
import * as ts2 from "typescript";
|
|
3
|
+
import * as fs2 from "fs";
|
|
4
|
+
import * as path2 from "path";
|
|
5
|
+
|
|
6
|
+
// src/analyzer/theme-analyzer.ts
|
|
7
|
+
import * as ts from "typescript";
|
|
8
|
+
import * as fs from "fs";
|
|
9
|
+
import * as path from "path";
|
|
10
|
+
function analyzeTheme(themePath, verbose = false) {
|
|
11
|
+
const resolvedPath = path.resolve(themePath);
|
|
12
|
+
if (!fs.existsSync(resolvedPath)) {
|
|
13
|
+
throw new Error(`Theme file not found: ${resolvedPath}`);
|
|
14
|
+
}
|
|
15
|
+
const log = (...args) => {
|
|
16
|
+
if (verbose) console.log("[theme-analyzer]", ...args);
|
|
17
|
+
};
|
|
18
|
+
log("Analyzing theme file:", resolvedPath);
|
|
19
|
+
const program = ts.createProgram([resolvedPath], {
|
|
20
|
+
target: ts.ScriptTarget.ES2020,
|
|
21
|
+
module: ts.ModuleKind.ESNext,
|
|
22
|
+
strict: true,
|
|
23
|
+
esModuleInterop: true,
|
|
24
|
+
skipLibCheck: true,
|
|
25
|
+
allowSyntheticDefaultImports: true
|
|
26
|
+
});
|
|
27
|
+
const sourceFile = program.getSourceFile(resolvedPath);
|
|
28
|
+
if (!sourceFile) {
|
|
29
|
+
throw new Error(`Failed to parse theme file: ${resolvedPath}`);
|
|
30
|
+
}
|
|
31
|
+
const ctx = {
|
|
32
|
+
program,
|
|
33
|
+
typeChecker: program.getTypeChecker(),
|
|
34
|
+
verbose
|
|
35
|
+
};
|
|
36
|
+
const values = {
|
|
37
|
+
intents: [],
|
|
38
|
+
sizes: {},
|
|
39
|
+
radii: [],
|
|
40
|
+
shadows: [],
|
|
41
|
+
breakpoints: [],
|
|
42
|
+
typography: [],
|
|
43
|
+
surfaceColors: [],
|
|
44
|
+
textColors: [],
|
|
45
|
+
borderColors: []
|
|
46
|
+
};
|
|
47
|
+
const imports = /* @__PURE__ */ new Map();
|
|
48
|
+
ts.forEachChild(sourceFile, (node) => {
|
|
49
|
+
if (ts.isImportDeclaration(node)) {
|
|
50
|
+
const source = node.moduleSpecifier.text;
|
|
51
|
+
const clause = node.importClause;
|
|
52
|
+
if (clause?.namedBindings && ts.isNamedImports(clause.namedBindings)) {
|
|
53
|
+
for (const element of clause.namedBindings.elements) {
|
|
54
|
+
const localName = element.name.text;
|
|
55
|
+
const importedName = element.propertyName?.text ?? localName;
|
|
56
|
+
imports.set(localName, { source, imported: importedName });
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
function processBuilderChain(node) {
|
|
62
|
+
if (!ts.isCallExpression(node)) return;
|
|
63
|
+
if (ts.isPropertyAccessExpression(node.expression)) {
|
|
64
|
+
const methodName = node.expression.name.text;
|
|
65
|
+
if (methodName === "build") {
|
|
66
|
+
const calls = traceBuilderCalls(node);
|
|
67
|
+
processCalls(calls);
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
ts.forEachChild(node, processBuilderChain);
|
|
72
|
+
}
|
|
73
|
+
function traceBuilderCalls(node, calls = []) {
|
|
74
|
+
if (!ts.isPropertyAccessExpression(node.expression)) {
|
|
75
|
+
if (ts.isCallExpression(node) && ts.isIdentifier(node.expression)) {
|
|
76
|
+
const fnName = node.expression.text;
|
|
77
|
+
if (fnName === "fromTheme" && node.arguments.length > 0) {
|
|
78
|
+
const arg = node.arguments[0];
|
|
79
|
+
if (ts.isIdentifier(arg)) {
|
|
80
|
+
analyzeBaseTheme(arg.text, imports, values, ctx);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return calls;
|
|
85
|
+
}
|
|
86
|
+
const methodName = node.expression.name.text;
|
|
87
|
+
calls.unshift({ method: methodName, args: node.arguments });
|
|
88
|
+
const obj = node.expression.expression;
|
|
89
|
+
if (ts.isCallExpression(obj)) {
|
|
90
|
+
return traceBuilderCalls(obj, calls);
|
|
91
|
+
}
|
|
92
|
+
return calls;
|
|
93
|
+
}
|
|
94
|
+
function processCalls(calls) {
|
|
95
|
+
log("Processing", calls.length, "builder method calls");
|
|
96
|
+
for (const { method, args } of calls) {
|
|
97
|
+
switch (method) {
|
|
98
|
+
case "addIntent": {
|
|
99
|
+
const name = getStringValue(args[0]);
|
|
100
|
+
if (name && !values.intents.includes(name)) {
|
|
101
|
+
values.intents.push(name);
|
|
102
|
+
log(" Found intent:", name);
|
|
103
|
+
}
|
|
104
|
+
break;
|
|
105
|
+
}
|
|
106
|
+
case "addRadius": {
|
|
107
|
+
const name = getStringValue(args[0]);
|
|
108
|
+
if (name && !values.radii.includes(name)) {
|
|
109
|
+
values.radii.push(name);
|
|
110
|
+
log(" Found radius:", name);
|
|
111
|
+
}
|
|
112
|
+
break;
|
|
113
|
+
}
|
|
114
|
+
case "addShadow": {
|
|
115
|
+
const name = getStringValue(args[0]);
|
|
116
|
+
if (name && !values.shadows.includes(name)) {
|
|
117
|
+
values.shadows.push(name);
|
|
118
|
+
log(" Found shadow:", name);
|
|
119
|
+
}
|
|
120
|
+
break;
|
|
121
|
+
}
|
|
122
|
+
case "setSizes": {
|
|
123
|
+
if (args[0] && ts.isObjectLiteralExpression(args[0])) {
|
|
124
|
+
for (const prop of args[0].properties) {
|
|
125
|
+
if (ts.isPropertyAssignment(prop)) {
|
|
126
|
+
const componentName = getPropertyName(prop.name);
|
|
127
|
+
if (componentName && ts.isObjectLiteralExpression(prop.initializer)) {
|
|
128
|
+
values.sizes[componentName] = getObjectKeys(prop.initializer);
|
|
129
|
+
log(" Found sizes for", componentName + ":", values.sizes[componentName]);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
break;
|
|
135
|
+
}
|
|
136
|
+
case "setBreakpoints": {
|
|
137
|
+
if (args[0] && ts.isObjectLiteralExpression(args[0])) {
|
|
138
|
+
values.breakpoints = getObjectKeys(args[0]);
|
|
139
|
+
log(" Found breakpoints:", values.breakpoints);
|
|
140
|
+
}
|
|
141
|
+
break;
|
|
142
|
+
}
|
|
143
|
+
case "setColors": {
|
|
144
|
+
if (args[0] && ts.isObjectLiteralExpression(args[0])) {
|
|
145
|
+
for (const prop of args[0].properties) {
|
|
146
|
+
if (ts.isPropertyAssignment(prop)) {
|
|
147
|
+
const colorType = getPropertyName(prop.name);
|
|
148
|
+
if (ts.isObjectLiteralExpression(prop.initializer)) {
|
|
149
|
+
const keys = getObjectKeys(prop.initializer);
|
|
150
|
+
switch (colorType) {
|
|
151
|
+
case "surface":
|
|
152
|
+
values.surfaceColors = keys;
|
|
153
|
+
log(" Found surface colors:", keys);
|
|
154
|
+
break;
|
|
155
|
+
case "text":
|
|
156
|
+
values.textColors = keys;
|
|
157
|
+
log(" Found text colors:", keys);
|
|
158
|
+
break;
|
|
159
|
+
case "border":
|
|
160
|
+
values.borderColors = keys;
|
|
161
|
+
log(" Found border colors:", keys);
|
|
162
|
+
break;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
break;
|
|
169
|
+
}
|
|
170
|
+
case "build":
|
|
171
|
+
break;
|
|
172
|
+
default:
|
|
173
|
+
log(" Skipping unknown method:", method);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
ts.forEachChild(sourceFile, (node) => {
|
|
178
|
+
if (ts.isVariableStatement(node)) {
|
|
179
|
+
for (const decl of node.declarationList.declarations) {
|
|
180
|
+
if (decl.initializer) {
|
|
181
|
+
processBuilderChain(decl.initializer);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
if (ts.isExportAssignment(node)) {
|
|
186
|
+
processBuilderChain(node.expression);
|
|
187
|
+
}
|
|
188
|
+
});
|
|
189
|
+
if (values.sizes["typography"]) {
|
|
190
|
+
values.typography = values.sizes["typography"];
|
|
191
|
+
}
|
|
192
|
+
log("Extracted theme values:", values);
|
|
193
|
+
return values;
|
|
194
|
+
}
|
|
195
|
+
function analyzeBaseTheme(varName, imports, values, ctx) {
|
|
196
|
+
const log = (...args) => {
|
|
197
|
+
if (ctx.verbose) console.log("[theme-analyzer]", ...args);
|
|
198
|
+
};
|
|
199
|
+
const importInfo = imports.get(varName);
|
|
200
|
+
if (!importInfo) {
|
|
201
|
+
log("Could not find import for base theme:", varName);
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
log("Base theme", varName, "imported from", importInfo.source);
|
|
205
|
+
if (importInfo.source === "@idealyst/theme" || importInfo.source.includes("@idealyst/theme")) {
|
|
206
|
+
const defaultValues = getDefaultThemeValues();
|
|
207
|
+
mergeThemeValues(values, defaultValues);
|
|
208
|
+
log("Using default @idealyst/theme values");
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
log("Skipping base theme analysis for:", importInfo.source);
|
|
212
|
+
}
|
|
213
|
+
function getDefaultThemeValues() {
|
|
214
|
+
return {
|
|
215
|
+
intents: ["primary", "success", "error", "warning", "neutral", "info"],
|
|
216
|
+
sizes: {
|
|
217
|
+
button: ["xs", "sm", "md", "lg", "xl"],
|
|
218
|
+
chip: ["xs", "sm", "md", "lg", "xl"],
|
|
219
|
+
badge: ["xs", "sm", "md", "lg", "xl"],
|
|
220
|
+
icon: ["xs", "sm", "md", "lg", "xl"],
|
|
221
|
+
input: ["xs", "sm", "md", "lg", "xl"],
|
|
222
|
+
radioButton: ["xs", "sm", "md", "lg", "xl"],
|
|
223
|
+
select: ["xs", "sm", "md", "lg", "xl"],
|
|
224
|
+
slider: ["xs", "sm", "md", "lg", "xl"],
|
|
225
|
+
switch: ["xs", "sm", "md", "lg", "xl"],
|
|
226
|
+
textarea: ["xs", "sm", "md", "lg", "xl"],
|
|
227
|
+
avatar: ["xs", "sm", "md", "lg", "xl"],
|
|
228
|
+
progress: ["xs", "sm", "md", "lg", "xl"],
|
|
229
|
+
accordion: ["xs", "sm", "md", "lg", "xl"],
|
|
230
|
+
activityIndicator: ["xs", "sm", "md", "lg", "xl"],
|
|
231
|
+
breadcrumb: ["xs", "sm", "md", "lg", "xl"],
|
|
232
|
+
list: ["xs", "sm", "md", "lg", "xl"],
|
|
233
|
+
menu: ["xs", "sm", "md", "lg", "xl"],
|
|
234
|
+
text: ["xs", "sm", "md", "lg", "xl"],
|
|
235
|
+
tabBar: ["xs", "sm", "md", "lg", "xl"],
|
|
236
|
+
table: ["xs", "sm", "md", "lg", "xl"],
|
|
237
|
+
tooltip: ["xs", "sm", "md", "lg", "xl"],
|
|
238
|
+
view: ["xs", "sm", "md", "lg", "xl"]
|
|
239
|
+
},
|
|
240
|
+
radii: ["none", "xs", "sm", "md", "lg", "xl"],
|
|
241
|
+
shadows: ["none", "sm", "md", "lg", "xl"],
|
|
242
|
+
breakpoints: ["xs", "sm", "md", "lg", "xl"],
|
|
243
|
+
typography: ["h1", "h2", "h3", "h4", "h5", "h6", "subtitle1", "subtitle2", "body1", "body2", "caption"],
|
|
244
|
+
surfaceColors: ["screen", "primary", "secondary", "tertiary", "inverse", "inverse-secondary", "inverse-tertiary"],
|
|
245
|
+
textColors: ["primary", "secondary", "tertiary", "inverse", "inverse-secondary", "inverse-tertiary"],
|
|
246
|
+
borderColors: ["primary", "secondary", "tertiary", "disabled"]
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
function mergeThemeValues(target, source) {
|
|
250
|
+
target.intents.push(...source.intents.filter((k) => !target.intents.includes(k)));
|
|
251
|
+
target.radii.push(...source.radii.filter((k) => !target.radii.includes(k)));
|
|
252
|
+
target.shadows.push(...source.shadows.filter((k) => !target.shadows.includes(k)));
|
|
253
|
+
target.breakpoints.push(...source.breakpoints.filter((k) => !target.breakpoints.includes(k)));
|
|
254
|
+
target.typography.push(...source.typography.filter((k) => !target.typography.includes(k)));
|
|
255
|
+
target.surfaceColors.push(...source.surfaceColors.filter((k) => !target.surfaceColors.includes(k)));
|
|
256
|
+
target.textColors.push(...source.textColors.filter((k) => !target.textColors.includes(k)));
|
|
257
|
+
target.borderColors.push(...source.borderColors.filter((k) => !target.borderColors.includes(k)));
|
|
258
|
+
for (const [comp, sizes] of Object.entries(source.sizes)) {
|
|
259
|
+
if (!target.sizes[comp]) {
|
|
260
|
+
target.sizes[comp] = sizes;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
function getStringValue(node) {
|
|
265
|
+
if (!node) return null;
|
|
266
|
+
if (ts.isStringLiteral(node)) return node.text;
|
|
267
|
+
if (ts.isIdentifier(node)) return node.text;
|
|
268
|
+
return null;
|
|
269
|
+
}
|
|
270
|
+
function getPropertyName(node) {
|
|
271
|
+
if (ts.isIdentifier(node)) return node.text;
|
|
272
|
+
if (ts.isStringLiteral(node)) return node.text;
|
|
273
|
+
return null;
|
|
274
|
+
}
|
|
275
|
+
function getObjectKeys(node) {
|
|
276
|
+
return node.properties.filter(ts.isPropertyAssignment).map((prop) => getPropertyName(prop.name)).filter((k) => k !== null);
|
|
277
|
+
}
|
|
278
|
+
var themeKeysCache = null;
|
|
279
|
+
var themeLoadAttempted = false;
|
|
280
|
+
function loadThemeKeys(opts, rootDir, _babelTypes, verboseMode = false) {
|
|
281
|
+
if (themeLoadAttempted && themeKeysCache) {
|
|
282
|
+
return themeKeysCache;
|
|
283
|
+
}
|
|
284
|
+
themeLoadAttempted = true;
|
|
285
|
+
const themePath = opts.themePath;
|
|
286
|
+
if (!themePath) {
|
|
287
|
+
throw new Error(
|
|
288
|
+
'[idealyst-plugin] themePath is required!\nAdd it to your babel config:\n ["@idealyst/theme/plugin", { themePath: "./src/theme/styles.ts" }]'
|
|
289
|
+
);
|
|
290
|
+
}
|
|
291
|
+
const resolvedPath = themePath.startsWith(".") ? path.resolve(rootDir, themePath) : themePath;
|
|
292
|
+
if (verboseMode) {
|
|
293
|
+
console.log("[idealyst-plugin] Analyzing theme file via @idealyst/tooling:", resolvedPath);
|
|
294
|
+
}
|
|
295
|
+
const themeValues = analyzeTheme(resolvedPath, verboseMode);
|
|
296
|
+
themeKeysCache = {
|
|
297
|
+
intents: themeValues.intents,
|
|
298
|
+
sizes: themeValues.sizes,
|
|
299
|
+
radii: themeValues.radii,
|
|
300
|
+
shadows: themeValues.shadows
|
|
301
|
+
};
|
|
302
|
+
if (verboseMode) {
|
|
303
|
+
console.log("[idealyst-plugin] Extracted theme keys:");
|
|
304
|
+
console.log(" intents:", themeKeysCache.intents);
|
|
305
|
+
console.log(" radii:", themeKeysCache.radii);
|
|
306
|
+
console.log(" shadows:", themeKeysCache.shadows);
|
|
307
|
+
console.log(" sizes:");
|
|
308
|
+
for (const [component, sizes] of Object.entries(themeKeysCache.sizes)) {
|
|
309
|
+
console.log(` ${component}:`, sizes);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
return themeKeysCache;
|
|
313
|
+
}
|
|
314
|
+
function resetThemeCache() {
|
|
315
|
+
themeKeysCache = null;
|
|
316
|
+
themeLoadAttempted = false;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// src/analyzer/component-analyzer.ts
|
|
320
|
+
function analyzeComponents(options) {
|
|
321
|
+
const { componentPaths, themePath, include, exclude, includeInternal = false } = options;
|
|
322
|
+
const registry = {};
|
|
323
|
+
const themeValues = analyzeTheme(themePath, false);
|
|
324
|
+
for (const componentPath of componentPaths) {
|
|
325
|
+
const resolvedPath = path2.resolve(componentPath);
|
|
326
|
+
if (!fs2.existsSync(resolvedPath)) {
|
|
327
|
+
console.warn(`[component-analyzer] Path not found: ${resolvedPath}`);
|
|
328
|
+
continue;
|
|
329
|
+
}
|
|
330
|
+
const componentDirs = findComponentDirs(resolvedPath);
|
|
331
|
+
for (const dir of componentDirs) {
|
|
332
|
+
const componentName = path2.basename(dir);
|
|
333
|
+
if (include && !include.includes(componentName)) continue;
|
|
334
|
+
if (exclude && exclude.includes(componentName)) continue;
|
|
335
|
+
if (!includeInternal && componentName.startsWith("_")) continue;
|
|
336
|
+
const definition = analyzeComponentDir(dir, componentName, themeValues);
|
|
337
|
+
if (definition) {
|
|
338
|
+
registry[componentName] = definition;
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
return registry;
|
|
343
|
+
}
|
|
344
|
+
function findComponentDirs(basePath) {
|
|
345
|
+
const dirs = [];
|
|
346
|
+
const entries = fs2.readdirSync(basePath, { withFileTypes: true });
|
|
347
|
+
for (const entry of entries) {
|
|
348
|
+
if (!entry.isDirectory()) continue;
|
|
349
|
+
const dirPath = path2.join(basePath, entry.name);
|
|
350
|
+
const hasIndex = fs2.existsSync(path2.join(dirPath, "index.ts"));
|
|
351
|
+
const hasTypes = fs2.existsSync(path2.join(dirPath, "types.ts"));
|
|
352
|
+
if (hasIndex || hasTypes) {
|
|
353
|
+
dirs.push(dirPath);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
return dirs;
|
|
357
|
+
}
|
|
358
|
+
function analyzeComponentDir(dir, componentName, themeValues) {
|
|
359
|
+
const tsFiles = fs2.readdirSync(dir).filter((f) => f.endsWith(".ts") || f.endsWith(".tsx")).map((f) => path2.join(dir, f));
|
|
360
|
+
if (tsFiles.length === 0) {
|
|
361
|
+
return null;
|
|
362
|
+
}
|
|
363
|
+
const program = ts2.createProgram(tsFiles, {
|
|
364
|
+
target: ts2.ScriptTarget.ES2020,
|
|
365
|
+
module: ts2.ModuleKind.ESNext,
|
|
366
|
+
jsx: ts2.JsxEmit.React,
|
|
367
|
+
strict: true,
|
|
368
|
+
esModuleInterop: true,
|
|
369
|
+
skipLibCheck: true
|
|
370
|
+
});
|
|
371
|
+
const typeChecker = program.getTypeChecker();
|
|
372
|
+
const propsInterfaceName = `${componentName}Props`;
|
|
373
|
+
const altNames = [`${componentName}ComponentProps`, "Props"];
|
|
374
|
+
let propsInterface = null;
|
|
375
|
+
let interfaceDescription;
|
|
376
|
+
for (const filePath of tsFiles) {
|
|
377
|
+
const sourceFile = program.getSourceFile(filePath);
|
|
378
|
+
if (!sourceFile) continue;
|
|
379
|
+
ts2.forEachChild(sourceFile, (node) => {
|
|
380
|
+
if (ts2.isInterfaceDeclaration(node) && node.name.text === propsInterfaceName) {
|
|
381
|
+
propsInterface = node;
|
|
382
|
+
interfaceDescription = getJSDocDescription(node);
|
|
383
|
+
}
|
|
384
|
+
if (ts2.isTypeAliasDeclaration(node) && node.name.text === propsInterfaceName) {
|
|
385
|
+
propsInterface = node;
|
|
386
|
+
interfaceDescription = getJSDocDescription(node);
|
|
387
|
+
}
|
|
388
|
+
});
|
|
389
|
+
if (propsInterface) break;
|
|
390
|
+
}
|
|
391
|
+
if (!propsInterface) {
|
|
392
|
+
for (const altName of altNames) {
|
|
393
|
+
for (const filePath of tsFiles) {
|
|
394
|
+
const sourceFile = program.getSourceFile(filePath);
|
|
395
|
+
if (!sourceFile) continue;
|
|
396
|
+
ts2.forEachChild(sourceFile, (node) => {
|
|
397
|
+
if ((ts2.isInterfaceDeclaration(node) || ts2.isTypeAliasDeclaration(node)) && node.name.text === altName) {
|
|
398
|
+
propsInterface = node;
|
|
399
|
+
interfaceDescription = getJSDocDescription(node);
|
|
400
|
+
}
|
|
401
|
+
});
|
|
402
|
+
if (propsInterface) break;
|
|
403
|
+
}
|
|
404
|
+
if (propsInterface) break;
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
if (!propsInterface) {
|
|
408
|
+
return null;
|
|
409
|
+
}
|
|
410
|
+
const props = {};
|
|
411
|
+
if (propsInterface) {
|
|
412
|
+
const type = typeChecker.getTypeAtLocation(propsInterface);
|
|
413
|
+
const properties = type.getProperties();
|
|
414
|
+
for (const prop of properties) {
|
|
415
|
+
const propDef = analyzeProperty(prop, typeChecker, themeValues);
|
|
416
|
+
if (propDef && !isInternalProp(propDef.name)) {
|
|
417
|
+
props[propDef.name] = propDef;
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
const description = interfaceDescription;
|
|
422
|
+
const category = inferCategory(componentName);
|
|
423
|
+
const sampleProps = extractSampleProps(dir);
|
|
424
|
+
return {
|
|
425
|
+
name: componentName,
|
|
426
|
+
description,
|
|
427
|
+
props,
|
|
428
|
+
category,
|
|
429
|
+
filePath: path2.relative(process.cwd(), dir),
|
|
430
|
+
sampleProps
|
|
431
|
+
};
|
|
432
|
+
}
|
|
433
|
+
function extractSampleProps(dir) {
|
|
434
|
+
const docsPath = path2.join(dir, "docs.ts");
|
|
435
|
+
if (!fs2.existsSync(docsPath)) {
|
|
436
|
+
return void 0;
|
|
437
|
+
}
|
|
438
|
+
try {
|
|
439
|
+
const content = fs2.readFileSync(docsPath, "utf-8");
|
|
440
|
+
const sourceFile = ts2.createSourceFile(
|
|
441
|
+
"docs.ts",
|
|
442
|
+
content,
|
|
443
|
+
ts2.ScriptTarget.ES2020,
|
|
444
|
+
true,
|
|
445
|
+
ts2.ScriptKind.TS
|
|
446
|
+
);
|
|
447
|
+
let samplePropsNode = null;
|
|
448
|
+
ts2.forEachChild(sourceFile, (node) => {
|
|
449
|
+
if (ts2.isVariableStatement(node)) {
|
|
450
|
+
const isExported = node.modifiers?.some((m) => m.kind === ts2.SyntaxKind.ExportKeyword);
|
|
451
|
+
if (isExported) {
|
|
452
|
+
for (const decl of node.declarationList.declarations) {
|
|
453
|
+
if (ts2.isIdentifier(decl.name) && decl.name.text === "sampleProps" && decl.initializer) {
|
|
454
|
+
if (ts2.isObjectLiteralExpression(decl.initializer)) {
|
|
455
|
+
samplePropsNode = decl.initializer;
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
});
|
|
462
|
+
if (!samplePropsNode) {
|
|
463
|
+
return void 0;
|
|
464
|
+
}
|
|
465
|
+
const result = {};
|
|
466
|
+
const propsNode = samplePropsNode;
|
|
467
|
+
for (const prop of propsNode.properties) {
|
|
468
|
+
if (ts2.isPropertyAssignment(prop) && ts2.isIdentifier(prop.name)) {
|
|
469
|
+
const propName = prop.name.text;
|
|
470
|
+
if (propName === "props" && ts2.isObjectLiteralExpression(prop.initializer)) {
|
|
471
|
+
result.props = extractObjectLiteral(prop.initializer, content);
|
|
472
|
+
} else if (propName === "children") {
|
|
473
|
+
result.children = prop.initializer.getText(sourceFile);
|
|
474
|
+
} else if (propName === "state" && ts2.isObjectLiteralExpression(prop.initializer)) {
|
|
475
|
+
result.state = extractObjectLiteral(prop.initializer, content);
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
return Object.keys(result).length > 0 ? result : void 0;
|
|
480
|
+
} catch (e) {
|
|
481
|
+
console.warn(`[component-analyzer] Error reading docs.ts in ${dir}:`, e);
|
|
482
|
+
return void 0;
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
function extractObjectLiteral(node, sourceContent) {
|
|
486
|
+
const result = {};
|
|
487
|
+
for (const prop of node.properties) {
|
|
488
|
+
if (ts2.isPropertyAssignment(prop) && ts2.isIdentifier(prop.name)) {
|
|
489
|
+
const key = prop.name.text;
|
|
490
|
+
const init = prop.initializer;
|
|
491
|
+
if (ts2.isStringLiteral(init)) {
|
|
492
|
+
result[key] = init.text;
|
|
493
|
+
} else if (ts2.isNumericLiteral(init)) {
|
|
494
|
+
result[key] = Number(init.text);
|
|
495
|
+
} else if (init.kind === ts2.SyntaxKind.TrueKeyword) {
|
|
496
|
+
result[key] = true;
|
|
497
|
+
} else if (init.kind === ts2.SyntaxKind.FalseKeyword) {
|
|
498
|
+
result[key] = false;
|
|
499
|
+
} else if (ts2.isArrayLiteralExpression(init)) {
|
|
500
|
+
result[key] = extractArrayLiteral(init, sourceContent);
|
|
501
|
+
} else if (ts2.isObjectLiteralExpression(init)) {
|
|
502
|
+
result[key] = extractObjectLiteral(init, sourceContent);
|
|
503
|
+
} else {
|
|
504
|
+
result[key] = init.getText();
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
return result;
|
|
509
|
+
}
|
|
510
|
+
function extractArrayLiteral(node, sourceContent) {
|
|
511
|
+
const result = [];
|
|
512
|
+
for (const element of node.elements) {
|
|
513
|
+
if (ts2.isStringLiteral(element)) {
|
|
514
|
+
result.push(element.text);
|
|
515
|
+
} else if (ts2.isNumericLiteral(element)) {
|
|
516
|
+
result.push(Number(element.text));
|
|
517
|
+
} else if (element.kind === ts2.SyntaxKind.TrueKeyword) {
|
|
518
|
+
result.push(true);
|
|
519
|
+
} else if (element.kind === ts2.SyntaxKind.FalseKeyword) {
|
|
520
|
+
result.push(false);
|
|
521
|
+
} else if (ts2.isObjectLiteralExpression(element)) {
|
|
522
|
+
result.push(extractObjectLiteral(element, sourceContent));
|
|
523
|
+
} else if (ts2.isArrayLiteralExpression(element)) {
|
|
524
|
+
result.push(extractArrayLiteral(element, sourceContent));
|
|
525
|
+
} else {
|
|
526
|
+
result.push(element.getText());
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
return result;
|
|
530
|
+
}
|
|
531
|
+
function analyzeProperty(symbol, typeChecker, themeValues) {
|
|
532
|
+
const name = symbol.getName();
|
|
533
|
+
const declarations = symbol.getDeclarations();
|
|
534
|
+
if (!declarations || declarations.length === 0) return null;
|
|
535
|
+
const declaration = declarations[0];
|
|
536
|
+
const type = typeChecker.getTypeOfSymbolAtLocation(symbol, declaration);
|
|
537
|
+
const typeString = typeChecker.typeToString(type);
|
|
538
|
+
const description = ts2.displayPartsToString(symbol.getDocumentationComment(typeChecker)) || void 0;
|
|
539
|
+
const required = !(symbol.flags & ts2.SymbolFlags.Optional);
|
|
540
|
+
const values = extractPropValues(type, typeString, typeChecker, themeValues);
|
|
541
|
+
const defaultValue = extractDefaultValue(symbol);
|
|
542
|
+
return {
|
|
543
|
+
name,
|
|
544
|
+
type: simplifyTypeName(typeString),
|
|
545
|
+
values: values.length > 0 ? values : void 0,
|
|
546
|
+
default: defaultValue,
|
|
547
|
+
description,
|
|
548
|
+
required
|
|
549
|
+
};
|
|
550
|
+
}
|
|
551
|
+
function extractPropValues(type, typeString, _typeChecker, themeValues) {
|
|
552
|
+
if (typeString === "Intent" || typeString.includes("Intent")) {
|
|
553
|
+
return themeValues.intents;
|
|
554
|
+
}
|
|
555
|
+
if (typeString === "Size" || typeString.includes("Size")) {
|
|
556
|
+
return ["xs", "sm", "md", "lg", "xl"];
|
|
557
|
+
}
|
|
558
|
+
if (type.isUnion()) {
|
|
559
|
+
const values = [];
|
|
560
|
+
for (const unionType of type.types) {
|
|
561
|
+
if (unionType.isStringLiteral()) {
|
|
562
|
+
values.push(unionType.value);
|
|
563
|
+
} else if (unionType.intrinsicName === "true") {
|
|
564
|
+
values.push("true");
|
|
565
|
+
} else if (unionType.intrinsicName === "false") {
|
|
566
|
+
values.push("false");
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
if (values.length > 0) return values;
|
|
570
|
+
}
|
|
571
|
+
if (typeString === "boolean") {
|
|
572
|
+
return ["true", "false"];
|
|
573
|
+
}
|
|
574
|
+
return [];
|
|
575
|
+
}
|
|
576
|
+
function extractDefaultValue(symbol) {
|
|
577
|
+
const tags = symbol.getJsDocTags();
|
|
578
|
+
for (const tag of tags) {
|
|
579
|
+
if (tag.name === "default" && tag.text) {
|
|
580
|
+
const value = ts2.displayPartsToString(tag.text);
|
|
581
|
+
try {
|
|
582
|
+
return JSON.parse(value);
|
|
583
|
+
} catch {
|
|
584
|
+
return value;
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
return void 0;
|
|
589
|
+
}
|
|
590
|
+
function getJSDocDescription(node) {
|
|
591
|
+
const jsDocs = node.jsDoc;
|
|
592
|
+
if (!jsDocs || jsDocs.length === 0) return void 0;
|
|
593
|
+
const firstDoc = jsDocs[0];
|
|
594
|
+
if (firstDoc.comment) {
|
|
595
|
+
if (typeof firstDoc.comment === "string") {
|
|
596
|
+
return firstDoc.comment;
|
|
597
|
+
}
|
|
598
|
+
return firstDoc.comment.map((c) => c.text || "").join("");
|
|
599
|
+
}
|
|
600
|
+
return void 0;
|
|
601
|
+
}
|
|
602
|
+
function simplifyTypeName(typeString) {
|
|
603
|
+
typeString = typeString.replace(/import\([^)]+\)\./g, "");
|
|
604
|
+
if (typeString.includes("ReactNode")) return "ReactNode";
|
|
605
|
+
if (typeString.includes("StyleProp")) return "Style";
|
|
606
|
+
return typeString;
|
|
607
|
+
}
|
|
608
|
+
function isInternalProp(name) {
|
|
609
|
+
const internalProps = [
|
|
610
|
+
"ref",
|
|
611
|
+
"key",
|
|
612
|
+
"children",
|
|
613
|
+
"style",
|
|
614
|
+
"testID",
|
|
615
|
+
"nativeID",
|
|
616
|
+
"accessible",
|
|
617
|
+
"accessibilityActions",
|
|
618
|
+
"accessibilityComponentType",
|
|
619
|
+
"accessibilityElementsHidden",
|
|
620
|
+
"accessibilityHint",
|
|
621
|
+
"accessibilityIgnoresInvertColors",
|
|
622
|
+
"accessibilityLabel",
|
|
623
|
+
"accessibilityLabelledBy",
|
|
624
|
+
"accessibilityLanguage",
|
|
625
|
+
"accessibilityLiveRegion",
|
|
626
|
+
"accessibilityRole",
|
|
627
|
+
"accessibilityState",
|
|
628
|
+
"accessibilityTraits",
|
|
629
|
+
"accessibilityValue",
|
|
630
|
+
"accessibilityViewIsModal",
|
|
631
|
+
"collapsable",
|
|
632
|
+
"focusable",
|
|
633
|
+
"hasTVPreferredFocus",
|
|
634
|
+
"hitSlop",
|
|
635
|
+
"importantForAccessibility",
|
|
636
|
+
"needsOffscreenAlphaCompositing",
|
|
637
|
+
"onAccessibilityAction",
|
|
638
|
+
"onAccessibilityEscape",
|
|
639
|
+
"onAccessibilityTap",
|
|
640
|
+
"onLayout",
|
|
641
|
+
"onMagicTap",
|
|
642
|
+
"onMoveShouldSetResponder",
|
|
643
|
+
"onMoveShouldSetResponderCapture",
|
|
644
|
+
"onResponderEnd",
|
|
645
|
+
"onResponderGrant",
|
|
646
|
+
"onResponderMove",
|
|
647
|
+
"onResponderReject",
|
|
648
|
+
"onResponderRelease",
|
|
649
|
+
"onResponderStart",
|
|
650
|
+
"onResponderTerminate",
|
|
651
|
+
"onResponderTerminationRequest",
|
|
652
|
+
"onStartShouldSetResponder",
|
|
653
|
+
"onStartShouldSetResponderCapture",
|
|
654
|
+
"pointerEvents",
|
|
655
|
+
"removeClippedSubviews",
|
|
656
|
+
"renderToHardwareTextureAndroid",
|
|
657
|
+
"shouldRasterizeIOS",
|
|
658
|
+
"tvParallaxMagnification",
|
|
659
|
+
"tvParallaxProperties",
|
|
660
|
+
"tvParallaxShiftDistanceX",
|
|
661
|
+
"tvParallaxShiftDistanceY",
|
|
662
|
+
"tvParallaxTiltAngle"
|
|
663
|
+
];
|
|
664
|
+
return internalProps.includes(name) || name.startsWith("accessibility");
|
|
665
|
+
}
|
|
666
|
+
function inferCategory(componentName) {
|
|
667
|
+
const formComponents = ["Button", "Input", "Checkbox", "Select", "Switch", "RadioButton", "Slider", "TextArea"];
|
|
668
|
+
const displayComponents = ["Text", "Card", "Badge", "Chip", "Avatar", "Icon", "Skeleton", "Alert", "Tooltip"];
|
|
669
|
+
const layoutComponents = ["View", "Screen", "Divider"];
|
|
670
|
+
const navigationComponents = ["TabBar", "Breadcrumb", "Menu", "List", "Link"];
|
|
671
|
+
const overlayComponents = ["Dialog", "Popover", "Modal"];
|
|
672
|
+
const dataComponents = ["Table", "Progress", "Accordion"];
|
|
673
|
+
if (formComponents.includes(componentName)) return "form";
|
|
674
|
+
if (displayComponents.includes(componentName)) return "display";
|
|
675
|
+
if (layoutComponents.includes(componentName)) return "layout";
|
|
676
|
+
if (navigationComponents.includes(componentName)) return "navigation";
|
|
677
|
+
if (overlayComponents.includes(componentName)) return "overlay";
|
|
678
|
+
if (dataComponents.includes(componentName)) return "data";
|
|
679
|
+
return "display";
|
|
680
|
+
}
|
|
681
|
+
export {
|
|
682
|
+
analyzeComponents,
|
|
683
|
+
analyzeTheme,
|
|
684
|
+
loadThemeKeys,
|
|
685
|
+
resetThemeCache
|
|
686
|
+
};
|
|
687
|
+
//# sourceMappingURL=index.js.map
|