@idealyst/tooling 1.2.25 → 1.2.27
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.cjs +730 -0
- package/dist/analyzer/index.cjs.map +1 -0
- package/dist/analyzer/index.d.cts +66 -0
- package/dist/analyzer/index.d.ts +1 -0
- package/dist/analyzer/index.js +5 -2
- package/dist/analyzer/index.js.map +1 -1
- package/dist/analyzers/index.cjs +1201 -0
- package/dist/analyzers/index.cjs.map +1 -0
- package/dist/analyzers/index.d.cts +205 -0
- package/dist/index.cjs +2146 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +55 -0
- package/dist/index.js +5 -2
- package/dist/index.js.map +1 -1
- package/dist/rules/index.cjs +522 -0
- package/dist/rules/index.cjs.map +1 -0
- package/dist/rules/index.d.cts +69 -0
- package/dist/types-CrlxbLFJ.d.cts +121 -0
- package/dist/types-CvIlSIOV.d.cts +149 -0
- package/dist/utils/index.cjs +709 -0
- package/dist/utils/index.cjs.map +1 -0
- package/dist/utils/index.d.cts +94 -0
- package/dist/vite-plugin.cjs +802 -0
- package/dist/vite-plugin.cjs.map +1 -0
- package/dist/vite-plugin.d.cts +40 -0
- package/dist/vite-plugin.js +3 -1
- package/dist/vite-plugin.js.map +1 -1
- package/package.json +13 -7
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,2146 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/index.ts
|
|
31
|
+
var src_exports = {};
|
|
32
|
+
__export(src_exports, {
|
|
33
|
+
HTML_ELEMENT_NAMES: () => HTML_ELEMENT_NAMES,
|
|
34
|
+
HTML_INTRINSIC_ELEMENTS: () => HTML_INTRINSIC_ELEMENTS,
|
|
35
|
+
REACT_DOM_PRIMITIVES: () => REACT_DOM_PRIMITIVES,
|
|
36
|
+
REACT_DOM_PRIMITIVE_NAMES: () => REACT_DOM_PRIMITIVE_NAMES,
|
|
37
|
+
REACT_DOM_RULE_SET: () => REACT_DOM_RULE_SET,
|
|
38
|
+
REACT_DOM_SOURCES: () => REACT_DOM_SOURCES,
|
|
39
|
+
REACT_NATIVE_PRIMITIVES: () => REACT_NATIVE_PRIMITIVES,
|
|
40
|
+
REACT_NATIVE_PRIMITIVE_NAMES: () => REACT_NATIVE_PRIMITIVE_NAMES,
|
|
41
|
+
REACT_NATIVE_RULE_SET: () => REACT_NATIVE_RULE_SET,
|
|
42
|
+
REACT_NATIVE_SOURCES: () => REACT_NATIVE_SOURCES,
|
|
43
|
+
analyzeComponents: () => analyzeComponents,
|
|
44
|
+
analyzeFiles: () => analyzeFiles,
|
|
45
|
+
analyzePlatformImports: () => analyzePlatformImports,
|
|
46
|
+
analyzeTheme: () => analyzeTheme,
|
|
47
|
+
classifyFile: () => classifyFile,
|
|
48
|
+
componentNames: () => componentNames,
|
|
49
|
+
componentRegistry: () => componentRegistry,
|
|
50
|
+
filterPlatformImports: () => filterPlatformImports,
|
|
51
|
+
formatLintIssue: () => formatLintIssue,
|
|
52
|
+
formatLintResults: () => formatLintResults,
|
|
53
|
+
formatViolation: () => formatViolation,
|
|
54
|
+
formatViolations: () => formatViolations,
|
|
55
|
+
generateComponentRegistry: () => generateComponentRegistry,
|
|
56
|
+
getBaseName: () => getBaseName,
|
|
57
|
+
getComponentsByCategory: () => getComponentsByCategory,
|
|
58
|
+
getExpectedPlatform: () => getExpectedPlatform,
|
|
59
|
+
getPlatformForSource: () => getPlatformForSource,
|
|
60
|
+
getPropConfig: () => getPropConfig,
|
|
61
|
+
getReactDomPrimitive: () => getReactDomPrimitive,
|
|
62
|
+
getReactNativePrimitive: () => getReactNativePrimitive,
|
|
63
|
+
getUniqueSources: () => getUniqueSources,
|
|
64
|
+
groupImportsBySource: () => groupImportsBySource,
|
|
65
|
+
idealystDocsPlugin: () => idealystDocsPlugin,
|
|
66
|
+
isComponentFile: () => isComponentFile,
|
|
67
|
+
isHtmlElement: () => isHtmlElement,
|
|
68
|
+
isPlatformSpecificFile: () => isPlatformSpecificFile,
|
|
69
|
+
isReactDomPrimitive: () => isReactDomPrimitive,
|
|
70
|
+
isReactNativePrimitive: () => isReactNativePrimitive,
|
|
71
|
+
isSharedFile: () => isSharedFile,
|
|
72
|
+
lintComponent: () => lintComponent,
|
|
73
|
+
lintComponents: () => lintComponents,
|
|
74
|
+
loadThemeKeys: () => loadThemeKeys,
|
|
75
|
+
parseImports: () => parseImports,
|
|
76
|
+
resetThemeCache: () => resetThemeCache,
|
|
77
|
+
summarizeLintResults: () => summarizeLintResults,
|
|
78
|
+
summarizeResults: () => summarizeResults
|
|
79
|
+
});
|
|
80
|
+
module.exports = __toCommonJS(src_exports);
|
|
81
|
+
|
|
82
|
+
// src/analyzer/component-analyzer.ts
|
|
83
|
+
var ts2 = __toESM(require("typescript"), 1);
|
|
84
|
+
var fs2 = __toESM(require("fs"), 1);
|
|
85
|
+
var path2 = __toESM(require("path"), 1);
|
|
86
|
+
|
|
87
|
+
// src/analyzer/theme-analyzer.ts
|
|
88
|
+
var ts = __toESM(require("typescript"), 1);
|
|
89
|
+
var fs = __toESM(require("fs"), 1);
|
|
90
|
+
var path = __toESM(require("path"), 1);
|
|
91
|
+
function analyzeTheme(themePath, verbose = false) {
|
|
92
|
+
const resolvedPath = path.resolve(themePath);
|
|
93
|
+
if (!fs.existsSync(resolvedPath)) {
|
|
94
|
+
throw new Error(`Theme file not found: ${resolvedPath}`);
|
|
95
|
+
}
|
|
96
|
+
const log = (...args) => {
|
|
97
|
+
if (verbose) console.log("[theme-analyzer]", ...args);
|
|
98
|
+
};
|
|
99
|
+
log("Analyzing theme file:", resolvedPath);
|
|
100
|
+
const program = ts.createProgram([resolvedPath], {
|
|
101
|
+
target: ts.ScriptTarget.ES2020,
|
|
102
|
+
module: ts.ModuleKind.ESNext,
|
|
103
|
+
strict: true,
|
|
104
|
+
esModuleInterop: true,
|
|
105
|
+
skipLibCheck: true,
|
|
106
|
+
allowSyntheticDefaultImports: true
|
|
107
|
+
});
|
|
108
|
+
const sourceFile = program.getSourceFile(resolvedPath);
|
|
109
|
+
if (!sourceFile) {
|
|
110
|
+
throw new Error(`Failed to parse theme file: ${resolvedPath}`);
|
|
111
|
+
}
|
|
112
|
+
const ctx = {
|
|
113
|
+
program,
|
|
114
|
+
typeChecker: program.getTypeChecker(),
|
|
115
|
+
verbose
|
|
116
|
+
};
|
|
117
|
+
const values = {
|
|
118
|
+
intents: [],
|
|
119
|
+
sizes: {},
|
|
120
|
+
radii: [],
|
|
121
|
+
shadows: [],
|
|
122
|
+
breakpoints: [],
|
|
123
|
+
typography: [],
|
|
124
|
+
surfaceColors: [],
|
|
125
|
+
textColors: [],
|
|
126
|
+
borderColors: []
|
|
127
|
+
};
|
|
128
|
+
const imports = /* @__PURE__ */ new Map();
|
|
129
|
+
ts.forEachChild(sourceFile, (node) => {
|
|
130
|
+
if (ts.isImportDeclaration(node)) {
|
|
131
|
+
const source = node.moduleSpecifier.text;
|
|
132
|
+
const clause = node.importClause;
|
|
133
|
+
if (clause?.namedBindings && ts.isNamedImports(clause.namedBindings)) {
|
|
134
|
+
for (const element of clause.namedBindings.elements) {
|
|
135
|
+
const localName = element.name.text;
|
|
136
|
+
const importedName = element.propertyName?.text ?? localName;
|
|
137
|
+
imports.set(localName, { source, imported: importedName });
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
function processBuilderChain(node) {
|
|
143
|
+
if (!ts.isCallExpression(node)) return;
|
|
144
|
+
if (ts.isPropertyAccessExpression(node.expression)) {
|
|
145
|
+
const methodName = node.expression.name.text;
|
|
146
|
+
if (methodName === "build") {
|
|
147
|
+
const calls = traceBuilderCalls(node);
|
|
148
|
+
processCalls(calls);
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
ts.forEachChild(node, processBuilderChain);
|
|
153
|
+
}
|
|
154
|
+
function traceBuilderCalls(node, calls = []) {
|
|
155
|
+
if (!ts.isPropertyAccessExpression(node.expression)) {
|
|
156
|
+
if (ts.isCallExpression(node) && ts.isIdentifier(node.expression)) {
|
|
157
|
+
const fnName = node.expression.text;
|
|
158
|
+
if (fnName === "fromTheme" && node.arguments.length > 0) {
|
|
159
|
+
const arg = node.arguments[0];
|
|
160
|
+
if (ts.isIdentifier(arg)) {
|
|
161
|
+
analyzeBaseTheme(arg.text, imports, values, ctx);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
return calls;
|
|
166
|
+
}
|
|
167
|
+
const methodName = node.expression.name.text;
|
|
168
|
+
calls.unshift({ method: methodName, args: node.arguments });
|
|
169
|
+
const obj = node.expression.expression;
|
|
170
|
+
if (ts.isCallExpression(obj)) {
|
|
171
|
+
return traceBuilderCalls(obj, calls);
|
|
172
|
+
}
|
|
173
|
+
return calls;
|
|
174
|
+
}
|
|
175
|
+
function processCalls(calls) {
|
|
176
|
+
log("Processing", calls.length, "builder method calls");
|
|
177
|
+
for (const { method, args } of calls) {
|
|
178
|
+
switch (method) {
|
|
179
|
+
case "addIntent": {
|
|
180
|
+
const name = getStringValue(args[0]);
|
|
181
|
+
if (name && !values.intents.includes(name)) {
|
|
182
|
+
values.intents.push(name);
|
|
183
|
+
log(" Found intent:", name);
|
|
184
|
+
}
|
|
185
|
+
break;
|
|
186
|
+
}
|
|
187
|
+
case "addRadius": {
|
|
188
|
+
const name = getStringValue(args[0]);
|
|
189
|
+
if (name && !values.radii.includes(name)) {
|
|
190
|
+
values.radii.push(name);
|
|
191
|
+
log(" Found radius:", name);
|
|
192
|
+
}
|
|
193
|
+
break;
|
|
194
|
+
}
|
|
195
|
+
case "addShadow": {
|
|
196
|
+
const name = getStringValue(args[0]);
|
|
197
|
+
if (name && !values.shadows.includes(name)) {
|
|
198
|
+
values.shadows.push(name);
|
|
199
|
+
log(" Found shadow:", name);
|
|
200
|
+
}
|
|
201
|
+
break;
|
|
202
|
+
}
|
|
203
|
+
case "setSizes": {
|
|
204
|
+
if (args[0] && ts.isObjectLiteralExpression(args[0])) {
|
|
205
|
+
for (const prop of args[0].properties) {
|
|
206
|
+
if (ts.isPropertyAssignment(prop)) {
|
|
207
|
+
const componentName = getPropertyName(prop.name);
|
|
208
|
+
if (componentName && ts.isObjectLiteralExpression(prop.initializer)) {
|
|
209
|
+
values.sizes[componentName] = getObjectKeys(prop.initializer);
|
|
210
|
+
log(" Found sizes for", componentName + ":", values.sizes[componentName]);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
break;
|
|
216
|
+
}
|
|
217
|
+
case "setBreakpoints": {
|
|
218
|
+
if (args[0] && ts.isObjectLiteralExpression(args[0])) {
|
|
219
|
+
values.breakpoints = getObjectKeys(args[0]);
|
|
220
|
+
log(" Found breakpoints:", values.breakpoints);
|
|
221
|
+
}
|
|
222
|
+
break;
|
|
223
|
+
}
|
|
224
|
+
case "setColors": {
|
|
225
|
+
if (args[0] && ts.isObjectLiteralExpression(args[0])) {
|
|
226
|
+
for (const prop of args[0].properties) {
|
|
227
|
+
if (ts.isPropertyAssignment(prop)) {
|
|
228
|
+
const colorType = getPropertyName(prop.name);
|
|
229
|
+
if (ts.isObjectLiteralExpression(prop.initializer)) {
|
|
230
|
+
const keys = getObjectKeys(prop.initializer);
|
|
231
|
+
switch (colorType) {
|
|
232
|
+
case "surface":
|
|
233
|
+
values.surfaceColors = keys;
|
|
234
|
+
log(" Found surface colors:", keys);
|
|
235
|
+
break;
|
|
236
|
+
case "text":
|
|
237
|
+
values.textColors = keys;
|
|
238
|
+
log(" Found text colors:", keys);
|
|
239
|
+
break;
|
|
240
|
+
case "border":
|
|
241
|
+
values.borderColors = keys;
|
|
242
|
+
log(" Found border colors:", keys);
|
|
243
|
+
break;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
break;
|
|
250
|
+
}
|
|
251
|
+
case "build":
|
|
252
|
+
break;
|
|
253
|
+
default:
|
|
254
|
+
log(" Skipping unknown method:", method);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
ts.forEachChild(sourceFile, (node) => {
|
|
259
|
+
if (ts.isVariableStatement(node)) {
|
|
260
|
+
for (const decl of node.declarationList.declarations) {
|
|
261
|
+
if (decl.initializer) {
|
|
262
|
+
processBuilderChain(decl.initializer);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
if (ts.isExportAssignment(node)) {
|
|
267
|
+
processBuilderChain(node.expression);
|
|
268
|
+
}
|
|
269
|
+
});
|
|
270
|
+
if (values.sizes["typography"]) {
|
|
271
|
+
values.typography = values.sizes["typography"];
|
|
272
|
+
}
|
|
273
|
+
log("Extracted theme values:", values);
|
|
274
|
+
return values;
|
|
275
|
+
}
|
|
276
|
+
function analyzeBaseTheme(varName, imports, values, ctx) {
|
|
277
|
+
const log = (...args) => {
|
|
278
|
+
if (ctx.verbose) console.log("[theme-analyzer]", ...args);
|
|
279
|
+
};
|
|
280
|
+
const importInfo = imports.get(varName);
|
|
281
|
+
if (!importInfo) {
|
|
282
|
+
log("Could not find import for base theme:", varName);
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
log("Base theme", varName, "imported from", importInfo.source);
|
|
286
|
+
if (importInfo.source === "@idealyst/theme" || importInfo.source.includes("@idealyst/theme")) {
|
|
287
|
+
const defaultValues = getDefaultThemeValues();
|
|
288
|
+
mergeThemeValues(values, defaultValues);
|
|
289
|
+
log("Using default @idealyst/theme values");
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
log("Skipping base theme analysis for:", importInfo.source);
|
|
293
|
+
}
|
|
294
|
+
function getDefaultThemeValues() {
|
|
295
|
+
return {
|
|
296
|
+
intents: ["primary", "success", "error", "warning", "neutral", "info"],
|
|
297
|
+
sizes: {
|
|
298
|
+
button: ["xs", "sm", "md", "lg", "xl"],
|
|
299
|
+
chip: ["xs", "sm", "md", "lg", "xl"],
|
|
300
|
+
badge: ["xs", "sm", "md", "lg", "xl"],
|
|
301
|
+
icon: ["xs", "sm", "md", "lg", "xl"],
|
|
302
|
+
input: ["xs", "sm", "md", "lg", "xl"],
|
|
303
|
+
radioButton: ["xs", "sm", "md", "lg", "xl"],
|
|
304
|
+
select: ["xs", "sm", "md", "lg", "xl"],
|
|
305
|
+
slider: ["xs", "sm", "md", "lg", "xl"],
|
|
306
|
+
switch: ["xs", "sm", "md", "lg", "xl"],
|
|
307
|
+
textarea: ["xs", "sm", "md", "lg", "xl"],
|
|
308
|
+
avatar: ["xs", "sm", "md", "lg", "xl"],
|
|
309
|
+
progress: ["xs", "sm", "md", "lg", "xl"],
|
|
310
|
+
accordion: ["xs", "sm", "md", "lg", "xl"],
|
|
311
|
+
activityIndicator: ["xs", "sm", "md", "lg", "xl"],
|
|
312
|
+
breadcrumb: ["xs", "sm", "md", "lg", "xl"],
|
|
313
|
+
list: ["xs", "sm", "md", "lg", "xl"],
|
|
314
|
+
menu: ["xs", "sm", "md", "lg", "xl"],
|
|
315
|
+
text: ["xs", "sm", "md", "lg", "xl"],
|
|
316
|
+
tabBar: ["xs", "sm", "md", "lg", "xl"],
|
|
317
|
+
table: ["xs", "sm", "md", "lg", "xl"],
|
|
318
|
+
tooltip: ["xs", "sm", "md", "lg", "xl"],
|
|
319
|
+
view: ["xs", "sm", "md", "lg", "xl"],
|
|
320
|
+
// Typography sizes for Text component's $typography iterator
|
|
321
|
+
typography: ["h1", "h2", "h3", "h4", "h5", "h6", "subtitle1", "subtitle2", "body1", "body2", "caption"]
|
|
322
|
+
},
|
|
323
|
+
radii: ["none", "xs", "sm", "md", "lg", "xl"],
|
|
324
|
+
shadows: ["none", "sm", "md", "lg", "xl"],
|
|
325
|
+
breakpoints: ["xs", "sm", "md", "lg", "xl"],
|
|
326
|
+
typography: ["h1", "h2", "h3", "h4", "h5", "h6", "subtitle1", "subtitle2", "body1", "body2", "caption"],
|
|
327
|
+
surfaceColors: ["screen", "primary", "secondary", "tertiary", "inverse", "inverse-secondary", "inverse-tertiary"],
|
|
328
|
+
textColors: ["primary", "secondary", "tertiary", "inverse", "inverse-secondary", "inverse-tertiary"],
|
|
329
|
+
borderColors: ["primary", "secondary", "tertiary", "disabled"]
|
|
330
|
+
};
|
|
331
|
+
}
|
|
332
|
+
function mergeThemeValues(target, source) {
|
|
333
|
+
target.intents.push(...source.intents.filter((k) => !target.intents.includes(k)));
|
|
334
|
+
target.radii.push(...source.radii.filter((k) => !target.radii.includes(k)));
|
|
335
|
+
target.shadows.push(...source.shadows.filter((k) => !target.shadows.includes(k)));
|
|
336
|
+
target.breakpoints.push(...source.breakpoints.filter((k) => !target.breakpoints.includes(k)));
|
|
337
|
+
target.typography.push(...source.typography.filter((k) => !target.typography.includes(k)));
|
|
338
|
+
target.surfaceColors.push(...source.surfaceColors.filter((k) => !target.surfaceColors.includes(k)));
|
|
339
|
+
target.textColors.push(...source.textColors.filter((k) => !target.textColors.includes(k)));
|
|
340
|
+
target.borderColors.push(...source.borderColors.filter((k) => !target.borderColors.includes(k)));
|
|
341
|
+
for (const [comp, sizes] of Object.entries(source.sizes)) {
|
|
342
|
+
if (!target.sizes[comp]) {
|
|
343
|
+
target.sizes[comp] = sizes;
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
function getStringValue(node) {
|
|
348
|
+
if (!node) return null;
|
|
349
|
+
if (ts.isStringLiteral(node)) return node.text;
|
|
350
|
+
if (ts.isIdentifier(node)) return node.text;
|
|
351
|
+
return null;
|
|
352
|
+
}
|
|
353
|
+
function getPropertyName(node) {
|
|
354
|
+
if (ts.isIdentifier(node)) return node.text;
|
|
355
|
+
if (ts.isStringLiteral(node)) return node.text;
|
|
356
|
+
return null;
|
|
357
|
+
}
|
|
358
|
+
function getObjectKeys(node) {
|
|
359
|
+
return node.properties.filter(ts.isPropertyAssignment).map((prop) => getPropertyName(prop.name)).filter((k) => k !== null);
|
|
360
|
+
}
|
|
361
|
+
var themeKeysCache = null;
|
|
362
|
+
var themeLoadAttempted = false;
|
|
363
|
+
function loadThemeKeys(opts, rootDir, _babelTypes, verboseMode = false) {
|
|
364
|
+
if (themeLoadAttempted && themeKeysCache) {
|
|
365
|
+
return themeKeysCache;
|
|
366
|
+
}
|
|
367
|
+
themeLoadAttempted = true;
|
|
368
|
+
const themePath = opts.themePath;
|
|
369
|
+
if (!themePath) {
|
|
370
|
+
throw new Error(
|
|
371
|
+
'[idealyst-plugin] themePath is required!\nAdd it to your babel config:\n ["@idealyst/theme/plugin", { themePath: "./src/theme/styles.ts" }]'
|
|
372
|
+
);
|
|
373
|
+
}
|
|
374
|
+
const resolvedPath = themePath.startsWith(".") ? path.resolve(rootDir, themePath) : themePath;
|
|
375
|
+
if (verboseMode) {
|
|
376
|
+
console.log("[idealyst-plugin] Analyzing theme file via @idealyst/tooling:", resolvedPath);
|
|
377
|
+
}
|
|
378
|
+
const themeValues = analyzeTheme(resolvedPath, verboseMode);
|
|
379
|
+
themeKeysCache = {
|
|
380
|
+
intents: themeValues.intents,
|
|
381
|
+
sizes: themeValues.sizes,
|
|
382
|
+
radii: themeValues.radii,
|
|
383
|
+
shadows: themeValues.shadows,
|
|
384
|
+
typography: themeValues.typography
|
|
385
|
+
};
|
|
386
|
+
if (verboseMode) {
|
|
387
|
+
console.log("[idealyst-plugin] Extracted theme keys:");
|
|
388
|
+
console.log(" intents:", themeKeysCache.intents);
|
|
389
|
+
console.log(" radii:", themeKeysCache.radii);
|
|
390
|
+
console.log(" shadows:", themeKeysCache.shadows);
|
|
391
|
+
console.log(" sizes:");
|
|
392
|
+
for (const [component, sizes] of Object.entries(themeKeysCache.sizes)) {
|
|
393
|
+
console.log(` ${component}:`, sizes);
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
return themeKeysCache;
|
|
397
|
+
}
|
|
398
|
+
function resetThemeCache() {
|
|
399
|
+
themeKeysCache = null;
|
|
400
|
+
themeLoadAttempted = false;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
// src/analyzer/component-analyzer.ts
|
|
404
|
+
function analyzeComponents(options) {
|
|
405
|
+
const { componentPaths, themePath, include, exclude, includeInternal = false } = options;
|
|
406
|
+
const registry = {};
|
|
407
|
+
const themeValues = analyzeTheme(themePath, false);
|
|
408
|
+
for (const componentPath of componentPaths) {
|
|
409
|
+
const resolvedPath = path2.resolve(componentPath);
|
|
410
|
+
if (!fs2.existsSync(resolvedPath)) {
|
|
411
|
+
console.warn(`[component-analyzer] Path not found: ${resolvedPath}`);
|
|
412
|
+
continue;
|
|
413
|
+
}
|
|
414
|
+
const componentDirs = findComponentDirs(resolvedPath);
|
|
415
|
+
for (const dir of componentDirs) {
|
|
416
|
+
const componentName = path2.basename(dir);
|
|
417
|
+
if (include && !include.includes(componentName)) continue;
|
|
418
|
+
if (exclude && exclude.includes(componentName)) continue;
|
|
419
|
+
if (!includeInternal && componentName.startsWith("_")) continue;
|
|
420
|
+
const definition = analyzeComponentDir(dir, componentName, themeValues);
|
|
421
|
+
if (definition) {
|
|
422
|
+
registry[componentName] = definition;
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
return registry;
|
|
427
|
+
}
|
|
428
|
+
function findComponentDirs(basePath) {
|
|
429
|
+
const dirs = [];
|
|
430
|
+
const entries = fs2.readdirSync(basePath, { withFileTypes: true });
|
|
431
|
+
for (const entry of entries) {
|
|
432
|
+
if (!entry.isDirectory()) continue;
|
|
433
|
+
const dirPath = path2.join(basePath, entry.name);
|
|
434
|
+
const hasIndex = fs2.existsSync(path2.join(dirPath, "index.ts"));
|
|
435
|
+
const hasTypes = fs2.existsSync(path2.join(dirPath, "types.ts"));
|
|
436
|
+
if (hasIndex || hasTypes) {
|
|
437
|
+
dirs.push(dirPath);
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
return dirs;
|
|
441
|
+
}
|
|
442
|
+
function analyzeComponentDir(dir, componentName, themeValues) {
|
|
443
|
+
const tsFiles = fs2.readdirSync(dir).filter((f) => f.endsWith(".ts") || f.endsWith(".tsx")).map((f) => path2.join(dir, f));
|
|
444
|
+
if (tsFiles.length === 0) {
|
|
445
|
+
return null;
|
|
446
|
+
}
|
|
447
|
+
const program = ts2.createProgram(tsFiles, {
|
|
448
|
+
target: ts2.ScriptTarget.ES2020,
|
|
449
|
+
module: ts2.ModuleKind.ESNext,
|
|
450
|
+
jsx: ts2.JsxEmit.React,
|
|
451
|
+
strict: true,
|
|
452
|
+
esModuleInterop: true,
|
|
453
|
+
skipLibCheck: true
|
|
454
|
+
});
|
|
455
|
+
const typeChecker = program.getTypeChecker();
|
|
456
|
+
const propsInterfaceName = `${componentName}Props`;
|
|
457
|
+
const altNames = [`${componentName}ComponentProps`, "Props"];
|
|
458
|
+
let propsInterface = null;
|
|
459
|
+
let interfaceDescription;
|
|
460
|
+
for (const filePath of tsFiles) {
|
|
461
|
+
const sourceFile = program.getSourceFile(filePath);
|
|
462
|
+
if (!sourceFile) continue;
|
|
463
|
+
ts2.forEachChild(sourceFile, (node) => {
|
|
464
|
+
if (ts2.isInterfaceDeclaration(node) && node.name.text === propsInterfaceName) {
|
|
465
|
+
propsInterface = node;
|
|
466
|
+
interfaceDescription = getJSDocDescription(node);
|
|
467
|
+
}
|
|
468
|
+
if (ts2.isTypeAliasDeclaration(node) && node.name.text === propsInterfaceName) {
|
|
469
|
+
propsInterface = node;
|
|
470
|
+
interfaceDescription = getJSDocDescription(node);
|
|
471
|
+
}
|
|
472
|
+
});
|
|
473
|
+
if (propsInterface) break;
|
|
474
|
+
}
|
|
475
|
+
if (!propsInterface) {
|
|
476
|
+
for (const altName of altNames) {
|
|
477
|
+
for (const filePath of tsFiles) {
|
|
478
|
+
const sourceFile = program.getSourceFile(filePath);
|
|
479
|
+
if (!sourceFile) continue;
|
|
480
|
+
ts2.forEachChild(sourceFile, (node) => {
|
|
481
|
+
if ((ts2.isInterfaceDeclaration(node) || ts2.isTypeAliasDeclaration(node)) && node.name.text === altName) {
|
|
482
|
+
propsInterface = node;
|
|
483
|
+
interfaceDescription = getJSDocDescription(node);
|
|
484
|
+
}
|
|
485
|
+
});
|
|
486
|
+
if (propsInterface) break;
|
|
487
|
+
}
|
|
488
|
+
if (propsInterface) break;
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
if (!propsInterface) {
|
|
492
|
+
return null;
|
|
493
|
+
}
|
|
494
|
+
const props = {};
|
|
495
|
+
if (propsInterface) {
|
|
496
|
+
const type = typeChecker.getTypeAtLocation(propsInterface);
|
|
497
|
+
const properties = type.getProperties();
|
|
498
|
+
for (const prop of properties) {
|
|
499
|
+
const propDef = analyzeProperty(prop, typeChecker, themeValues);
|
|
500
|
+
if (propDef && !isInternalProp(propDef.name)) {
|
|
501
|
+
props[propDef.name] = propDef;
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
const description = interfaceDescription;
|
|
506
|
+
const category = inferCategory(componentName);
|
|
507
|
+
const sampleProps = extractSampleProps(dir);
|
|
508
|
+
return {
|
|
509
|
+
name: componentName,
|
|
510
|
+
description,
|
|
511
|
+
props,
|
|
512
|
+
category,
|
|
513
|
+
filePath: path2.relative(process.cwd(), dir),
|
|
514
|
+
sampleProps
|
|
515
|
+
};
|
|
516
|
+
}
|
|
517
|
+
function extractSampleProps(dir) {
|
|
518
|
+
const docsPath = path2.join(dir, "docs.ts");
|
|
519
|
+
if (!fs2.existsSync(docsPath)) {
|
|
520
|
+
return void 0;
|
|
521
|
+
}
|
|
522
|
+
try {
|
|
523
|
+
const content = fs2.readFileSync(docsPath, "utf-8");
|
|
524
|
+
const sourceFile = ts2.createSourceFile(
|
|
525
|
+
"docs.ts",
|
|
526
|
+
content,
|
|
527
|
+
ts2.ScriptTarget.ES2020,
|
|
528
|
+
true,
|
|
529
|
+
ts2.ScriptKind.TS
|
|
530
|
+
);
|
|
531
|
+
let samplePropsNode = null;
|
|
532
|
+
ts2.forEachChild(sourceFile, (node) => {
|
|
533
|
+
if (ts2.isVariableStatement(node)) {
|
|
534
|
+
const isExported = node.modifiers?.some((m) => m.kind === ts2.SyntaxKind.ExportKeyword);
|
|
535
|
+
if (isExported) {
|
|
536
|
+
for (const decl of node.declarationList.declarations) {
|
|
537
|
+
if (ts2.isIdentifier(decl.name) && decl.name.text === "sampleProps" && decl.initializer) {
|
|
538
|
+
if (ts2.isObjectLiteralExpression(decl.initializer)) {
|
|
539
|
+
samplePropsNode = decl.initializer;
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
});
|
|
546
|
+
if (!samplePropsNode) {
|
|
547
|
+
return void 0;
|
|
548
|
+
}
|
|
549
|
+
const result = {};
|
|
550
|
+
const propsNode = samplePropsNode;
|
|
551
|
+
for (const prop of propsNode.properties) {
|
|
552
|
+
if (ts2.isPropertyAssignment(prop) && ts2.isIdentifier(prop.name)) {
|
|
553
|
+
const propName = prop.name.text;
|
|
554
|
+
if (propName === "props" && ts2.isObjectLiteralExpression(prop.initializer)) {
|
|
555
|
+
result.props = extractObjectLiteral(prop.initializer, content);
|
|
556
|
+
} else if (propName === "children") {
|
|
557
|
+
result.children = prop.initializer.getText(sourceFile);
|
|
558
|
+
} else if (propName === "state" && ts2.isObjectLiteralExpression(prop.initializer)) {
|
|
559
|
+
result.state = extractObjectLiteral(prop.initializer, content);
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
return Object.keys(result).length > 0 ? result : void 0;
|
|
564
|
+
} catch (e) {
|
|
565
|
+
console.warn(`[component-analyzer] Error reading docs.ts in ${dir}:`, e);
|
|
566
|
+
return void 0;
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
function extractObjectLiteral(node, sourceContent) {
|
|
570
|
+
const result = {};
|
|
571
|
+
for (const prop of node.properties) {
|
|
572
|
+
if (ts2.isPropertyAssignment(prop) && ts2.isIdentifier(prop.name)) {
|
|
573
|
+
const key = prop.name.text;
|
|
574
|
+
const init = prop.initializer;
|
|
575
|
+
if (ts2.isStringLiteral(init)) {
|
|
576
|
+
result[key] = init.text;
|
|
577
|
+
} else if (ts2.isNumericLiteral(init)) {
|
|
578
|
+
result[key] = Number(init.text);
|
|
579
|
+
} else if (init.kind === ts2.SyntaxKind.TrueKeyword) {
|
|
580
|
+
result[key] = true;
|
|
581
|
+
} else if (init.kind === ts2.SyntaxKind.FalseKeyword) {
|
|
582
|
+
result[key] = false;
|
|
583
|
+
} else if (ts2.isArrayLiteralExpression(init)) {
|
|
584
|
+
result[key] = extractArrayLiteral(init, sourceContent);
|
|
585
|
+
} else if (ts2.isObjectLiteralExpression(init)) {
|
|
586
|
+
result[key] = extractObjectLiteral(init, sourceContent);
|
|
587
|
+
} else {
|
|
588
|
+
result[key] = init.getText();
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
return result;
|
|
593
|
+
}
|
|
594
|
+
function extractArrayLiteral(node, sourceContent) {
|
|
595
|
+
const result = [];
|
|
596
|
+
for (const element of node.elements) {
|
|
597
|
+
if (ts2.isStringLiteral(element)) {
|
|
598
|
+
result.push(element.text);
|
|
599
|
+
} else if (ts2.isNumericLiteral(element)) {
|
|
600
|
+
result.push(Number(element.text));
|
|
601
|
+
} else if (element.kind === ts2.SyntaxKind.TrueKeyword) {
|
|
602
|
+
result.push(true);
|
|
603
|
+
} else if (element.kind === ts2.SyntaxKind.FalseKeyword) {
|
|
604
|
+
result.push(false);
|
|
605
|
+
} else if (ts2.isObjectLiteralExpression(element)) {
|
|
606
|
+
result.push(extractObjectLiteral(element, sourceContent));
|
|
607
|
+
} else if (ts2.isArrayLiteralExpression(element)) {
|
|
608
|
+
result.push(extractArrayLiteral(element, sourceContent));
|
|
609
|
+
} else {
|
|
610
|
+
result.push(element.getText());
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
return result;
|
|
614
|
+
}
|
|
615
|
+
function analyzeProperty(symbol, typeChecker, themeValues) {
|
|
616
|
+
const name = symbol.getName();
|
|
617
|
+
const declarations = symbol.getDeclarations();
|
|
618
|
+
if (!declarations || declarations.length === 0) return null;
|
|
619
|
+
const declaration = declarations[0];
|
|
620
|
+
const type = typeChecker.getTypeOfSymbolAtLocation(symbol, declaration);
|
|
621
|
+
const typeString = typeChecker.typeToString(type);
|
|
622
|
+
const description = ts2.displayPartsToString(symbol.getDocumentationComment(typeChecker)) || void 0;
|
|
623
|
+
const required = !(symbol.flags & ts2.SymbolFlags.Optional);
|
|
624
|
+
const values = extractPropValues(type, typeString, typeChecker, themeValues);
|
|
625
|
+
const defaultValue = extractDefaultValue(symbol);
|
|
626
|
+
return {
|
|
627
|
+
name,
|
|
628
|
+
type: simplifyTypeName(typeString),
|
|
629
|
+
values: values.length > 0 ? values : void 0,
|
|
630
|
+
default: defaultValue,
|
|
631
|
+
description,
|
|
632
|
+
required
|
|
633
|
+
};
|
|
634
|
+
}
|
|
635
|
+
function extractPropValues(type, typeString, _typeChecker, themeValues) {
|
|
636
|
+
if (typeString === "Intent" || typeString.includes("Intent")) {
|
|
637
|
+
return themeValues.intents;
|
|
638
|
+
}
|
|
639
|
+
if (typeString === "Size" || typeString.includes("Size")) {
|
|
640
|
+
return ["xs", "sm", "md", "lg", "xl"];
|
|
641
|
+
}
|
|
642
|
+
if (type.isUnion()) {
|
|
643
|
+
const values = [];
|
|
644
|
+
for (const unionType of type.types) {
|
|
645
|
+
if (unionType.isStringLiteral()) {
|
|
646
|
+
values.push(unionType.value);
|
|
647
|
+
} else if (unionType.intrinsicName === "true") {
|
|
648
|
+
values.push("true");
|
|
649
|
+
} else if (unionType.intrinsicName === "false") {
|
|
650
|
+
values.push("false");
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
if (values.length > 0) return values;
|
|
654
|
+
}
|
|
655
|
+
if (typeString === "boolean") {
|
|
656
|
+
return ["true", "false"];
|
|
657
|
+
}
|
|
658
|
+
return [];
|
|
659
|
+
}
|
|
660
|
+
function extractDefaultValue(symbol) {
|
|
661
|
+
const tags = symbol.getJsDocTags();
|
|
662
|
+
for (const tag of tags) {
|
|
663
|
+
if (tag.name === "default" && tag.text) {
|
|
664
|
+
const value = ts2.displayPartsToString(tag.text);
|
|
665
|
+
try {
|
|
666
|
+
return JSON.parse(value);
|
|
667
|
+
} catch {
|
|
668
|
+
return value;
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
return void 0;
|
|
673
|
+
}
|
|
674
|
+
function getJSDocDescription(node) {
|
|
675
|
+
const jsDocs = node.jsDoc;
|
|
676
|
+
if (!jsDocs || jsDocs.length === 0) return void 0;
|
|
677
|
+
const firstDoc = jsDocs[0];
|
|
678
|
+
if (firstDoc.comment) {
|
|
679
|
+
if (typeof firstDoc.comment === "string") {
|
|
680
|
+
return firstDoc.comment;
|
|
681
|
+
}
|
|
682
|
+
return firstDoc.comment.map((c) => c.text || "").join("");
|
|
683
|
+
}
|
|
684
|
+
return void 0;
|
|
685
|
+
}
|
|
686
|
+
function simplifyTypeName(typeString) {
|
|
687
|
+
typeString = typeString.replace(/import\([^)]+\)\./g, "");
|
|
688
|
+
if (typeString.includes("ReactNode")) return "ReactNode";
|
|
689
|
+
if (typeString.includes("StyleProp")) return "Style";
|
|
690
|
+
return typeString;
|
|
691
|
+
}
|
|
692
|
+
function isInternalProp(name) {
|
|
693
|
+
const internalProps = [
|
|
694
|
+
"ref",
|
|
695
|
+
"key",
|
|
696
|
+
"children",
|
|
697
|
+
"style",
|
|
698
|
+
"testID",
|
|
699
|
+
"nativeID",
|
|
700
|
+
"accessible",
|
|
701
|
+
"accessibilityActions",
|
|
702
|
+
"accessibilityComponentType",
|
|
703
|
+
"accessibilityElementsHidden",
|
|
704
|
+
"accessibilityHint",
|
|
705
|
+
"accessibilityIgnoresInvertColors",
|
|
706
|
+
"accessibilityLabel",
|
|
707
|
+
"accessibilityLabelledBy",
|
|
708
|
+
"accessibilityLanguage",
|
|
709
|
+
"accessibilityLiveRegion",
|
|
710
|
+
"accessibilityRole",
|
|
711
|
+
"accessibilityState",
|
|
712
|
+
"accessibilityTraits",
|
|
713
|
+
"accessibilityValue",
|
|
714
|
+
"accessibilityViewIsModal",
|
|
715
|
+
"collapsable",
|
|
716
|
+
"focusable",
|
|
717
|
+
"hasTVPreferredFocus",
|
|
718
|
+
"hitSlop",
|
|
719
|
+
"importantForAccessibility",
|
|
720
|
+
"needsOffscreenAlphaCompositing",
|
|
721
|
+
"onAccessibilityAction",
|
|
722
|
+
"onAccessibilityEscape",
|
|
723
|
+
"onAccessibilityTap",
|
|
724
|
+
"onLayout",
|
|
725
|
+
"onMagicTap",
|
|
726
|
+
"onMoveShouldSetResponder",
|
|
727
|
+
"onMoveShouldSetResponderCapture",
|
|
728
|
+
"onResponderEnd",
|
|
729
|
+
"onResponderGrant",
|
|
730
|
+
"onResponderMove",
|
|
731
|
+
"onResponderReject",
|
|
732
|
+
"onResponderRelease",
|
|
733
|
+
"onResponderStart",
|
|
734
|
+
"onResponderTerminate",
|
|
735
|
+
"onResponderTerminationRequest",
|
|
736
|
+
"onStartShouldSetResponder",
|
|
737
|
+
"onStartShouldSetResponderCapture",
|
|
738
|
+
"pointerEvents",
|
|
739
|
+
"removeClippedSubviews",
|
|
740
|
+
"renderToHardwareTextureAndroid",
|
|
741
|
+
"shouldRasterizeIOS",
|
|
742
|
+
"tvParallaxMagnification",
|
|
743
|
+
"tvParallaxProperties",
|
|
744
|
+
"tvParallaxShiftDistanceX",
|
|
745
|
+
"tvParallaxShiftDistanceY",
|
|
746
|
+
"tvParallaxTiltAngle"
|
|
747
|
+
];
|
|
748
|
+
return internalProps.includes(name) || name.startsWith("accessibility");
|
|
749
|
+
}
|
|
750
|
+
function inferCategory(componentName) {
|
|
751
|
+
const formComponents = ["Button", "Input", "Checkbox", "Select", "Switch", "RadioButton", "Slider", "TextArea"];
|
|
752
|
+
const displayComponents = ["Text", "Card", "Badge", "Chip", "Avatar", "Icon", "Skeleton", "Alert", "Tooltip"];
|
|
753
|
+
const layoutComponents = ["View", "Screen", "Divider"];
|
|
754
|
+
const navigationComponents = ["TabBar", "Breadcrumb", "Menu", "List", "Link"];
|
|
755
|
+
const overlayComponents = ["Dialog", "Popover", "Modal"];
|
|
756
|
+
const dataComponents = ["Table", "Progress", "Accordion"];
|
|
757
|
+
if (formComponents.includes(componentName)) return "form";
|
|
758
|
+
if (displayComponents.includes(componentName)) return "display";
|
|
759
|
+
if (layoutComponents.includes(componentName)) return "layout";
|
|
760
|
+
if (navigationComponents.includes(componentName)) return "navigation";
|
|
761
|
+
if (overlayComponents.includes(componentName)) return "overlay";
|
|
762
|
+
if (dataComponents.includes(componentName)) return "data";
|
|
763
|
+
return "display";
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
// src/vite-plugin.ts
|
|
767
|
+
var fs3 = __toESM(require("fs"), 1);
|
|
768
|
+
var path3 = __toESM(require("path"), 1);
|
|
769
|
+
function idealystDocsPlugin(options) {
|
|
770
|
+
let registry = null;
|
|
771
|
+
const { debug = false, output, outputPath } = options;
|
|
772
|
+
const log = (...args) => {
|
|
773
|
+
if (debug) console.log("[idealyst-docs]", ...args);
|
|
774
|
+
};
|
|
775
|
+
function generateRegistry() {
|
|
776
|
+
log("Generating component registry...");
|
|
777
|
+
log("Component paths:", options.componentPaths);
|
|
778
|
+
log("Theme path:", options.themePath);
|
|
779
|
+
try {
|
|
780
|
+
registry = analyzeComponents(options);
|
|
781
|
+
log(`Generated registry with ${Object.keys(registry).length} components`);
|
|
782
|
+
if (debug) {
|
|
783
|
+
log("Components found:", Object.keys(registry));
|
|
784
|
+
}
|
|
785
|
+
if (output === "file" && outputPath) {
|
|
786
|
+
const outputDir = path3.dirname(outputPath);
|
|
787
|
+
if (!fs3.existsSync(outputDir)) {
|
|
788
|
+
fs3.mkdirSync(outputDir, { recursive: true });
|
|
789
|
+
}
|
|
790
|
+
fs3.writeFileSync(
|
|
791
|
+
outputPath,
|
|
792
|
+
`// Auto-generated by @idealyst/tooling - DO NOT EDIT
|
|
793
|
+
export const componentRegistry = ${JSON.stringify(registry, null, 2)} as const;
|
|
794
|
+
`
|
|
795
|
+
);
|
|
796
|
+
log(`Wrote registry to ${outputPath}`);
|
|
797
|
+
}
|
|
798
|
+
return registry;
|
|
799
|
+
} catch (error) {
|
|
800
|
+
console.error("[idealyst-docs] Error generating registry:", error);
|
|
801
|
+
return {};
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
return {
|
|
805
|
+
name: "idealyst-docs",
|
|
806
|
+
// Transform @idealyst/tooling to inject the actual registry
|
|
807
|
+
transform(code, id) {
|
|
808
|
+
const isToolingIndex = id.includes("@idealyst/tooling") && id.endsWith("index.ts");
|
|
809
|
+
const isToolingSrc = id.includes("/packages/tooling/src/index.ts");
|
|
810
|
+
if (isToolingIndex || isToolingSrc) {
|
|
811
|
+
console.log(`[idealyst-docs] Transforming tooling index: ${id}`);
|
|
812
|
+
if (!registry) {
|
|
813
|
+
registry = generateRegistry();
|
|
814
|
+
}
|
|
815
|
+
let transformed = code;
|
|
816
|
+
transformed = transformed.replace(
|
|
817
|
+
/export const componentRegistry\s*=\s*\{\s*\};?/,
|
|
818
|
+
`export const componentRegistry = ${JSON.stringify(registry, null, 2)};`
|
|
819
|
+
);
|
|
820
|
+
log(`Code transformed: ${code !== transformed}`);
|
|
821
|
+
transformed = transformed.replace(
|
|
822
|
+
/export const componentNames\s*=\s*\[\s*\];?/,
|
|
823
|
+
`export const componentNames = ${JSON.stringify(Object.keys(registry))};`
|
|
824
|
+
);
|
|
825
|
+
transformed = transformed.replace(
|
|
826
|
+
/export function getComponentsByCategory\(category\)\s*\{[\s\S]*?^\}/m,
|
|
827
|
+
`export function getComponentsByCategory(category) {
|
|
828
|
+
return Object.entries(componentRegistry)
|
|
829
|
+
.filter(([_, def]) => def.category === category)
|
|
830
|
+
.map(([name]) => name);
|
|
831
|
+
}`
|
|
832
|
+
);
|
|
833
|
+
transformed = transformed.replace(
|
|
834
|
+
/export function getPropConfig\(componentName\)\s*\{[\s\S]*?^\}/m,
|
|
835
|
+
`export function getPropConfig(componentName) {
|
|
836
|
+
const def = componentRegistry[componentName];
|
|
837
|
+
if (!def) return {};
|
|
838
|
+
return Object.entries(def.props).reduce((acc, [key, prop]) => {
|
|
839
|
+
if (prop.values && prop.values.length > 0) {
|
|
840
|
+
acc[key] = { type: 'select', options: prop.values, default: prop.default };
|
|
841
|
+
} else if (prop.type === 'boolean') {
|
|
842
|
+
acc[key] = { type: 'boolean', default: prop.default ?? false };
|
|
843
|
+
} else if (prop.type === 'string') {
|
|
844
|
+
acc[key] = { type: 'text', default: prop.default ?? '' };
|
|
845
|
+
} else if (prop.type === 'number') {
|
|
846
|
+
acc[key] = { type: 'number', default: prop.default ?? 0 };
|
|
847
|
+
}
|
|
848
|
+
return acc;
|
|
849
|
+
}, {});
|
|
850
|
+
}`
|
|
851
|
+
);
|
|
852
|
+
return {
|
|
853
|
+
code: transformed,
|
|
854
|
+
map: null
|
|
855
|
+
};
|
|
856
|
+
}
|
|
857
|
+
return null;
|
|
858
|
+
},
|
|
859
|
+
// Regenerate on component file changes
|
|
860
|
+
handleHotUpdate({ file, server }) {
|
|
861
|
+
const isComponentFile2 = options.componentPaths.some((p) => file.includes(path3.resolve(p))) && (file.endsWith(".ts") || file.endsWith(".tsx"));
|
|
862
|
+
const isThemeFile = file.includes(path3.resolve(options.themePath));
|
|
863
|
+
if (isComponentFile2 || isThemeFile) {
|
|
864
|
+
log(`File changed: ${file}, regenerating registry...`);
|
|
865
|
+
registry = null;
|
|
866
|
+
const toolingMods = Array.from(server.moduleGraph.idToModuleMap.values()).filter((m) => m.id && (m.id.includes("@idealyst/tooling") || m.id.includes("/packages/tooling/src/index")));
|
|
867
|
+
toolingMods.forEach((m) => server.moduleGraph.invalidateModule(m));
|
|
868
|
+
if (toolingMods.length > 0) {
|
|
869
|
+
return toolingMods;
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
},
|
|
873
|
+
// Generate on build
|
|
874
|
+
buildStart() {
|
|
875
|
+
registry = generateRegistry();
|
|
876
|
+
}
|
|
877
|
+
};
|
|
878
|
+
}
|
|
879
|
+
function generateComponentRegistry(options) {
|
|
880
|
+
return analyzeComponents(options);
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
// src/utils/fileClassifier.ts
|
|
884
|
+
var path4 = __toESM(require("path"), 1);
|
|
885
|
+
var EXTENSION_PATTERNS = [
|
|
886
|
+
// Platform-specific component files
|
|
887
|
+
{ pattern: /\.web\.(tsx|jsx)$/, type: "web" },
|
|
888
|
+
{ pattern: /\.native\.(tsx|jsx)$/, type: "native" },
|
|
889
|
+
{ pattern: /\.ios\.(tsx|jsx)$/, type: "native" },
|
|
890
|
+
{ pattern: /\.android\.(tsx|jsx)$/, type: "native" },
|
|
891
|
+
// Style files (can be .ts or .tsx)
|
|
892
|
+
{ pattern: /\.styles?\.(tsx?|jsx?)$/, type: "styles" },
|
|
893
|
+
// Type definition files
|
|
894
|
+
{ pattern: /\.types?\.(ts|tsx)$/, type: "types" },
|
|
895
|
+
{ pattern: /types\.(ts|tsx)$/, type: "types" },
|
|
896
|
+
{ pattern: /\.d\.ts$/, type: "types" },
|
|
897
|
+
// Shared component files (generic .tsx/.jsx without platform suffix)
|
|
898
|
+
{ pattern: /\.(tsx|jsx)$/, type: "shared" }
|
|
899
|
+
];
|
|
900
|
+
var EXCLUDED_PATTERNS = [
|
|
901
|
+
/\.test\.(tsx?|jsx?)$/,
|
|
902
|
+
/\.spec\.(tsx?|jsx?)$/,
|
|
903
|
+
/\.stories\.(tsx?|jsx?)$/,
|
|
904
|
+
/\.config\.(ts|js)$/,
|
|
905
|
+
/index\.(ts|tsx|js|jsx)$/
|
|
906
|
+
];
|
|
907
|
+
function classifyFile(filePath) {
|
|
908
|
+
const fileName = path4.basename(filePath);
|
|
909
|
+
for (const pattern of EXCLUDED_PATTERNS) {
|
|
910
|
+
if (pattern.test(fileName)) {
|
|
911
|
+
return "other";
|
|
912
|
+
}
|
|
913
|
+
}
|
|
914
|
+
for (const { pattern, type } of EXTENSION_PATTERNS) {
|
|
915
|
+
if (pattern.test(fileName)) {
|
|
916
|
+
return type;
|
|
917
|
+
}
|
|
918
|
+
}
|
|
919
|
+
return "other";
|
|
920
|
+
}
|
|
921
|
+
function isComponentFile(filePath) {
|
|
922
|
+
const fileType = classifyFile(filePath);
|
|
923
|
+
return fileType === "shared" || fileType === "web" || fileType === "native";
|
|
924
|
+
}
|
|
925
|
+
function isSharedFile(filePath) {
|
|
926
|
+
return classifyFile(filePath) === "shared";
|
|
927
|
+
}
|
|
928
|
+
function isPlatformSpecificFile(filePath) {
|
|
929
|
+
const fileType = classifyFile(filePath);
|
|
930
|
+
return fileType === "web" || fileType === "native";
|
|
931
|
+
}
|
|
932
|
+
function getExpectedPlatform(filePath) {
|
|
933
|
+
const fileType = classifyFile(filePath);
|
|
934
|
+
if (fileType === "web") return "web";
|
|
935
|
+
if (fileType === "native") return "native";
|
|
936
|
+
return null;
|
|
937
|
+
}
|
|
938
|
+
function getBaseName(filePath) {
|
|
939
|
+
const fileName = path4.basename(filePath);
|
|
940
|
+
return fileName.replace(/\.(web|native|ios|android)\.(tsx|jsx|ts|js)$/, "").replace(/\.styles?\.(tsx|jsx|ts|js)$/, "").replace(/\.types?\.(tsx|ts)$/, "").replace(/\.(tsx|jsx|ts|js)$/, "");
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
// src/utils/importParser.ts
|
|
944
|
+
var ts3 = __toESM(require("typescript"), 1);
|
|
945
|
+
|
|
946
|
+
// src/rules/reactNativePrimitives.ts
|
|
947
|
+
var REACT_NATIVE_SOURCES = [
|
|
948
|
+
"react-native",
|
|
949
|
+
"react-native-web",
|
|
950
|
+
"react-native-gesture-handler",
|
|
951
|
+
"react-native-reanimated",
|
|
952
|
+
"react-native-safe-area-context",
|
|
953
|
+
"react-native-screens",
|
|
954
|
+
"react-native-svg",
|
|
955
|
+
"@react-native-vector-icons/material-design-icons",
|
|
956
|
+
"@react-native-vector-icons/common",
|
|
957
|
+
"@react-native-community/async-storage",
|
|
958
|
+
"@react-native-picker/picker",
|
|
959
|
+
"expo",
|
|
960
|
+
"expo-constants",
|
|
961
|
+
"expo-linking",
|
|
962
|
+
"expo-status-bar"
|
|
963
|
+
];
|
|
964
|
+
var CORE_PRIMITIVES = [
|
|
965
|
+
{
|
|
966
|
+
name: "View",
|
|
967
|
+
source: "react-native",
|
|
968
|
+
platform: "react-native",
|
|
969
|
+
description: "Basic container component"
|
|
970
|
+
},
|
|
971
|
+
{
|
|
972
|
+
name: "Text",
|
|
973
|
+
source: "react-native",
|
|
974
|
+
platform: "react-native",
|
|
975
|
+
description: "Text display component"
|
|
976
|
+
},
|
|
977
|
+
{
|
|
978
|
+
name: "Image",
|
|
979
|
+
source: "react-native",
|
|
980
|
+
platform: "react-native",
|
|
981
|
+
description: "Image display component"
|
|
982
|
+
},
|
|
983
|
+
{
|
|
984
|
+
name: "ImageBackground",
|
|
985
|
+
source: "react-native",
|
|
986
|
+
platform: "react-native",
|
|
987
|
+
description: "Background image container"
|
|
988
|
+
},
|
|
989
|
+
{
|
|
990
|
+
name: "ScrollView",
|
|
991
|
+
source: "react-native",
|
|
992
|
+
platform: "react-native",
|
|
993
|
+
description: "Scrollable container"
|
|
994
|
+
},
|
|
995
|
+
{
|
|
996
|
+
name: "FlatList",
|
|
997
|
+
source: "react-native",
|
|
998
|
+
platform: "react-native",
|
|
999
|
+
description: "Performant list component"
|
|
1000
|
+
},
|
|
1001
|
+
{
|
|
1002
|
+
name: "SectionList",
|
|
1003
|
+
source: "react-native",
|
|
1004
|
+
platform: "react-native",
|
|
1005
|
+
description: "Sectioned list component"
|
|
1006
|
+
},
|
|
1007
|
+
{
|
|
1008
|
+
name: "VirtualizedList",
|
|
1009
|
+
source: "react-native",
|
|
1010
|
+
platform: "react-native",
|
|
1011
|
+
description: "Base virtualized list"
|
|
1012
|
+
}
|
|
1013
|
+
];
|
|
1014
|
+
var INTERACTIVE_PRIMITIVES = [
|
|
1015
|
+
{
|
|
1016
|
+
name: "TouchableOpacity",
|
|
1017
|
+
source: "react-native",
|
|
1018
|
+
platform: "react-native",
|
|
1019
|
+
description: "Touch with opacity feedback"
|
|
1020
|
+
},
|
|
1021
|
+
{
|
|
1022
|
+
name: "TouchableHighlight",
|
|
1023
|
+
source: "react-native",
|
|
1024
|
+
platform: "react-native",
|
|
1025
|
+
description: "Touch with highlight feedback"
|
|
1026
|
+
},
|
|
1027
|
+
{
|
|
1028
|
+
name: "TouchableWithoutFeedback",
|
|
1029
|
+
source: "react-native",
|
|
1030
|
+
platform: "react-native",
|
|
1031
|
+
description: "Touch without visual feedback"
|
|
1032
|
+
},
|
|
1033
|
+
{
|
|
1034
|
+
name: "TouchableNativeFeedback",
|
|
1035
|
+
source: "react-native",
|
|
1036
|
+
platform: "react-native",
|
|
1037
|
+
description: "Android ripple feedback"
|
|
1038
|
+
},
|
|
1039
|
+
{
|
|
1040
|
+
name: "Pressable",
|
|
1041
|
+
source: "react-native",
|
|
1042
|
+
platform: "react-native",
|
|
1043
|
+
description: "Modern press handler component"
|
|
1044
|
+
},
|
|
1045
|
+
{
|
|
1046
|
+
name: "Button",
|
|
1047
|
+
source: "react-native",
|
|
1048
|
+
platform: "react-native",
|
|
1049
|
+
description: "Basic button component"
|
|
1050
|
+
}
|
|
1051
|
+
];
|
|
1052
|
+
var INPUT_PRIMITIVES = [
|
|
1053
|
+
{
|
|
1054
|
+
name: "TextInput",
|
|
1055
|
+
source: "react-native",
|
|
1056
|
+
platform: "react-native",
|
|
1057
|
+
description: "Text input field"
|
|
1058
|
+
},
|
|
1059
|
+
{
|
|
1060
|
+
name: "Switch",
|
|
1061
|
+
source: "react-native",
|
|
1062
|
+
platform: "react-native",
|
|
1063
|
+
description: "Toggle switch component"
|
|
1064
|
+
},
|
|
1065
|
+
{
|
|
1066
|
+
name: "Slider",
|
|
1067
|
+
source: "react-native",
|
|
1068
|
+
platform: "react-native",
|
|
1069
|
+
description: "Slider input (deprecated)"
|
|
1070
|
+
}
|
|
1071
|
+
];
|
|
1072
|
+
var MODAL_PRIMITIVES = [
|
|
1073
|
+
{
|
|
1074
|
+
name: "Modal",
|
|
1075
|
+
source: "react-native",
|
|
1076
|
+
platform: "react-native",
|
|
1077
|
+
description: "Modal overlay component"
|
|
1078
|
+
},
|
|
1079
|
+
{
|
|
1080
|
+
name: "Alert",
|
|
1081
|
+
source: "react-native",
|
|
1082
|
+
platform: "react-native",
|
|
1083
|
+
description: "Native alert dialog"
|
|
1084
|
+
},
|
|
1085
|
+
{
|
|
1086
|
+
name: "ActionSheetIOS",
|
|
1087
|
+
source: "react-native",
|
|
1088
|
+
platform: "react-native",
|
|
1089
|
+
description: "iOS action sheet"
|
|
1090
|
+
},
|
|
1091
|
+
{
|
|
1092
|
+
name: "StatusBar",
|
|
1093
|
+
source: "react-native",
|
|
1094
|
+
platform: "react-native",
|
|
1095
|
+
description: "Status bar controller"
|
|
1096
|
+
}
|
|
1097
|
+
];
|
|
1098
|
+
var ANIMATION_PRIMITIVES = [
|
|
1099
|
+
{
|
|
1100
|
+
name: "Animated",
|
|
1101
|
+
source: "react-native",
|
|
1102
|
+
platform: "react-native",
|
|
1103
|
+
description: "Animation library namespace"
|
|
1104
|
+
},
|
|
1105
|
+
{
|
|
1106
|
+
name: "Easing",
|
|
1107
|
+
source: "react-native",
|
|
1108
|
+
platform: "react-native",
|
|
1109
|
+
description: "Easing functions"
|
|
1110
|
+
},
|
|
1111
|
+
{
|
|
1112
|
+
name: "LayoutAnimation",
|
|
1113
|
+
source: "react-native",
|
|
1114
|
+
platform: "react-native",
|
|
1115
|
+
description: "Layout animation controller"
|
|
1116
|
+
}
|
|
1117
|
+
];
|
|
1118
|
+
var PLATFORM_PRIMITIVES = [
|
|
1119
|
+
{
|
|
1120
|
+
name: "Platform",
|
|
1121
|
+
source: "react-native",
|
|
1122
|
+
platform: "react-native",
|
|
1123
|
+
description: "Platform detection utility"
|
|
1124
|
+
},
|
|
1125
|
+
{
|
|
1126
|
+
name: "Dimensions",
|
|
1127
|
+
source: "react-native",
|
|
1128
|
+
platform: "react-native",
|
|
1129
|
+
description: "Screen dimensions utility"
|
|
1130
|
+
},
|
|
1131
|
+
{
|
|
1132
|
+
name: "PixelRatio",
|
|
1133
|
+
source: "react-native",
|
|
1134
|
+
platform: "react-native",
|
|
1135
|
+
description: "Pixel ratio utility"
|
|
1136
|
+
},
|
|
1137
|
+
{
|
|
1138
|
+
name: "Appearance",
|
|
1139
|
+
source: "react-native",
|
|
1140
|
+
platform: "react-native",
|
|
1141
|
+
description: "Color scheme detection"
|
|
1142
|
+
},
|
|
1143
|
+
{
|
|
1144
|
+
name: "useColorScheme",
|
|
1145
|
+
source: "react-native",
|
|
1146
|
+
platform: "react-native",
|
|
1147
|
+
description: "Color scheme hook"
|
|
1148
|
+
},
|
|
1149
|
+
{
|
|
1150
|
+
name: "useWindowDimensions",
|
|
1151
|
+
source: "react-native",
|
|
1152
|
+
platform: "react-native",
|
|
1153
|
+
description: "Window dimensions hook"
|
|
1154
|
+
}
|
|
1155
|
+
];
|
|
1156
|
+
var EVENT_PRIMITIVES = [
|
|
1157
|
+
{
|
|
1158
|
+
name: "BackHandler",
|
|
1159
|
+
source: "react-native",
|
|
1160
|
+
platform: "react-native",
|
|
1161
|
+
description: "Android back button handler"
|
|
1162
|
+
},
|
|
1163
|
+
{
|
|
1164
|
+
name: "Keyboard",
|
|
1165
|
+
source: "react-native",
|
|
1166
|
+
platform: "react-native",
|
|
1167
|
+
description: "Keyboard event handler"
|
|
1168
|
+
},
|
|
1169
|
+
{
|
|
1170
|
+
name: "PanResponder",
|
|
1171
|
+
source: "react-native",
|
|
1172
|
+
platform: "react-native",
|
|
1173
|
+
description: "Gesture responder system"
|
|
1174
|
+
},
|
|
1175
|
+
{
|
|
1176
|
+
name: "Linking",
|
|
1177
|
+
source: "react-native",
|
|
1178
|
+
platform: "react-native",
|
|
1179
|
+
description: "Deep linking utility"
|
|
1180
|
+
},
|
|
1181
|
+
{
|
|
1182
|
+
name: "AppState",
|
|
1183
|
+
source: "react-native",
|
|
1184
|
+
platform: "react-native",
|
|
1185
|
+
description: "App lifecycle state"
|
|
1186
|
+
}
|
|
1187
|
+
];
|
|
1188
|
+
var SAFETY_PRIMITIVES = [
|
|
1189
|
+
{
|
|
1190
|
+
name: "SafeAreaView",
|
|
1191
|
+
source: "react-native",
|
|
1192
|
+
platform: "react-native",
|
|
1193
|
+
description: "Safe area inset container"
|
|
1194
|
+
},
|
|
1195
|
+
{
|
|
1196
|
+
name: "KeyboardAvoidingView",
|
|
1197
|
+
source: "react-native",
|
|
1198
|
+
platform: "react-native",
|
|
1199
|
+
description: "Keyboard avoidance container"
|
|
1200
|
+
}
|
|
1201
|
+
];
|
|
1202
|
+
var ACCESSIBILITY_PRIMITIVES = [
|
|
1203
|
+
{
|
|
1204
|
+
name: "AccessibilityInfo",
|
|
1205
|
+
source: "react-native",
|
|
1206
|
+
platform: "react-native",
|
|
1207
|
+
description: "Accessibility information API"
|
|
1208
|
+
}
|
|
1209
|
+
];
|
|
1210
|
+
var STYLE_PRIMITIVES = [
|
|
1211
|
+
{
|
|
1212
|
+
name: "StyleSheet",
|
|
1213
|
+
source: "react-native",
|
|
1214
|
+
platform: "react-native",
|
|
1215
|
+
description: "Style sheet creator"
|
|
1216
|
+
}
|
|
1217
|
+
];
|
|
1218
|
+
var REACT_NATIVE_PRIMITIVES = [
|
|
1219
|
+
...CORE_PRIMITIVES,
|
|
1220
|
+
...INTERACTIVE_PRIMITIVES,
|
|
1221
|
+
...INPUT_PRIMITIVES,
|
|
1222
|
+
...MODAL_PRIMITIVES,
|
|
1223
|
+
...ANIMATION_PRIMITIVES,
|
|
1224
|
+
...PLATFORM_PRIMITIVES,
|
|
1225
|
+
...EVENT_PRIMITIVES,
|
|
1226
|
+
...SAFETY_PRIMITIVES,
|
|
1227
|
+
...ACCESSIBILITY_PRIMITIVES,
|
|
1228
|
+
...STYLE_PRIMITIVES
|
|
1229
|
+
];
|
|
1230
|
+
var REACT_NATIVE_PRIMITIVE_NAMES = new Set(
|
|
1231
|
+
REACT_NATIVE_PRIMITIVES.map((p) => p.name)
|
|
1232
|
+
);
|
|
1233
|
+
var REACT_NATIVE_RULE_SET = {
|
|
1234
|
+
platform: "react-native",
|
|
1235
|
+
primitives: REACT_NATIVE_PRIMITIVES,
|
|
1236
|
+
sources: [...REACT_NATIVE_SOURCES]
|
|
1237
|
+
};
|
|
1238
|
+
function isReactNativePrimitive(name) {
|
|
1239
|
+
return REACT_NATIVE_PRIMITIVE_NAMES.has(name);
|
|
1240
|
+
}
|
|
1241
|
+
function getReactNativePrimitive(name) {
|
|
1242
|
+
return REACT_NATIVE_PRIMITIVES.find((p) => p.name === name);
|
|
1243
|
+
}
|
|
1244
|
+
|
|
1245
|
+
// src/rules/reactDomPrimitives.ts
|
|
1246
|
+
var REACT_DOM_SOURCES = [
|
|
1247
|
+
"react-dom",
|
|
1248
|
+
"react-dom/client",
|
|
1249
|
+
"react-dom/server"
|
|
1250
|
+
];
|
|
1251
|
+
var DOM_API_PRIMITIVES = [
|
|
1252
|
+
{
|
|
1253
|
+
name: "createPortal",
|
|
1254
|
+
source: "react-dom",
|
|
1255
|
+
platform: "react-dom",
|
|
1256
|
+
description: "Render children into a different DOM node"
|
|
1257
|
+
},
|
|
1258
|
+
{
|
|
1259
|
+
name: "flushSync",
|
|
1260
|
+
source: "react-dom",
|
|
1261
|
+
platform: "react-dom",
|
|
1262
|
+
description: "Force synchronous DOM updates"
|
|
1263
|
+
},
|
|
1264
|
+
{
|
|
1265
|
+
name: "createRoot",
|
|
1266
|
+
source: "react-dom/client",
|
|
1267
|
+
platform: "react-dom",
|
|
1268
|
+
description: "Create a React root for rendering"
|
|
1269
|
+
},
|
|
1270
|
+
{
|
|
1271
|
+
name: "hydrateRoot",
|
|
1272
|
+
source: "react-dom/client",
|
|
1273
|
+
platform: "react-dom",
|
|
1274
|
+
description: "Hydrate server-rendered content"
|
|
1275
|
+
},
|
|
1276
|
+
{
|
|
1277
|
+
name: "render",
|
|
1278
|
+
source: "react-dom",
|
|
1279
|
+
platform: "react-dom",
|
|
1280
|
+
description: "Legacy render function (deprecated)"
|
|
1281
|
+
},
|
|
1282
|
+
{
|
|
1283
|
+
name: "hydrate",
|
|
1284
|
+
source: "react-dom",
|
|
1285
|
+
platform: "react-dom",
|
|
1286
|
+
description: "Legacy hydrate function (deprecated)"
|
|
1287
|
+
},
|
|
1288
|
+
{
|
|
1289
|
+
name: "unmountComponentAtNode",
|
|
1290
|
+
source: "react-dom",
|
|
1291
|
+
platform: "react-dom",
|
|
1292
|
+
description: "Legacy unmount function (deprecated)"
|
|
1293
|
+
},
|
|
1294
|
+
{
|
|
1295
|
+
name: "findDOMNode",
|
|
1296
|
+
source: "react-dom",
|
|
1297
|
+
platform: "react-dom",
|
|
1298
|
+
description: "Find DOM node (deprecated)"
|
|
1299
|
+
}
|
|
1300
|
+
];
|
|
1301
|
+
var HTML_INTRINSIC_ELEMENTS = [
|
|
1302
|
+
// Document structure
|
|
1303
|
+
"html",
|
|
1304
|
+
"head",
|
|
1305
|
+
"body",
|
|
1306
|
+
"main",
|
|
1307
|
+
"header",
|
|
1308
|
+
"footer",
|
|
1309
|
+
"nav",
|
|
1310
|
+
"aside",
|
|
1311
|
+
"article",
|
|
1312
|
+
"section",
|
|
1313
|
+
// Content sectioning
|
|
1314
|
+
"div",
|
|
1315
|
+
"span",
|
|
1316
|
+
"p",
|
|
1317
|
+
"h1",
|
|
1318
|
+
"h2",
|
|
1319
|
+
"h3",
|
|
1320
|
+
"h4",
|
|
1321
|
+
"h5",
|
|
1322
|
+
"h6",
|
|
1323
|
+
// Text content
|
|
1324
|
+
"a",
|
|
1325
|
+
"strong",
|
|
1326
|
+
"em",
|
|
1327
|
+
"code",
|
|
1328
|
+
"pre",
|
|
1329
|
+
"blockquote",
|
|
1330
|
+
"br",
|
|
1331
|
+
"hr",
|
|
1332
|
+
// Lists
|
|
1333
|
+
"ul",
|
|
1334
|
+
"ol",
|
|
1335
|
+
"li",
|
|
1336
|
+
"dl",
|
|
1337
|
+
"dt",
|
|
1338
|
+
"dd",
|
|
1339
|
+
// Tables
|
|
1340
|
+
"table",
|
|
1341
|
+
"thead",
|
|
1342
|
+
"tbody",
|
|
1343
|
+
"tfoot",
|
|
1344
|
+
"tr",
|
|
1345
|
+
"th",
|
|
1346
|
+
"td",
|
|
1347
|
+
"caption",
|
|
1348
|
+
"colgroup",
|
|
1349
|
+
"col",
|
|
1350
|
+
// Forms
|
|
1351
|
+
"form",
|
|
1352
|
+
"input",
|
|
1353
|
+
"textarea",
|
|
1354
|
+
"select",
|
|
1355
|
+
"option",
|
|
1356
|
+
"optgroup",
|
|
1357
|
+
"button",
|
|
1358
|
+
"label",
|
|
1359
|
+
"fieldset",
|
|
1360
|
+
"legend",
|
|
1361
|
+
"datalist",
|
|
1362
|
+
"output",
|
|
1363
|
+
"progress",
|
|
1364
|
+
"meter",
|
|
1365
|
+
// Media
|
|
1366
|
+
"img",
|
|
1367
|
+
"video",
|
|
1368
|
+
"audio",
|
|
1369
|
+
"source",
|
|
1370
|
+
"track",
|
|
1371
|
+
"picture",
|
|
1372
|
+
"figure",
|
|
1373
|
+
"figcaption",
|
|
1374
|
+
"canvas",
|
|
1375
|
+
"svg",
|
|
1376
|
+
"iframe",
|
|
1377
|
+
"embed",
|
|
1378
|
+
"object",
|
|
1379
|
+
// Interactive
|
|
1380
|
+
"details",
|
|
1381
|
+
"summary",
|
|
1382
|
+
"dialog",
|
|
1383
|
+
"menu",
|
|
1384
|
+
// Scripting
|
|
1385
|
+
"script",
|
|
1386
|
+
"noscript",
|
|
1387
|
+
"template",
|
|
1388
|
+
"slot"
|
|
1389
|
+
];
|
|
1390
|
+
var REACT_DOM_PRIMITIVES = [...DOM_API_PRIMITIVES];
|
|
1391
|
+
var REACT_DOM_PRIMITIVE_NAMES = new Set(
|
|
1392
|
+
REACT_DOM_PRIMITIVES.map((p) => p.name)
|
|
1393
|
+
);
|
|
1394
|
+
var HTML_ELEMENT_NAMES = new Set(HTML_INTRINSIC_ELEMENTS);
|
|
1395
|
+
var REACT_DOM_RULE_SET = {
|
|
1396
|
+
platform: "react-dom",
|
|
1397
|
+
primitives: REACT_DOM_PRIMITIVES,
|
|
1398
|
+
sources: [...REACT_DOM_SOURCES]
|
|
1399
|
+
};
|
|
1400
|
+
function isReactDomPrimitive(name) {
|
|
1401
|
+
return REACT_DOM_PRIMITIVE_NAMES.has(name);
|
|
1402
|
+
}
|
|
1403
|
+
function isHtmlElement(name) {
|
|
1404
|
+
return HTML_ELEMENT_NAMES.has(name);
|
|
1405
|
+
}
|
|
1406
|
+
function getReactDomPrimitive(name) {
|
|
1407
|
+
return REACT_DOM_PRIMITIVES.find((p) => p.name === name);
|
|
1408
|
+
}
|
|
1409
|
+
|
|
1410
|
+
// src/utils/importParser.ts
|
|
1411
|
+
function getPlatformForSource(source, options) {
|
|
1412
|
+
const nativeSources = /* @__PURE__ */ new Set([
|
|
1413
|
+
...REACT_NATIVE_SOURCES,
|
|
1414
|
+
...options?.additionalNativeSources ?? []
|
|
1415
|
+
]);
|
|
1416
|
+
const domSources = /* @__PURE__ */ new Set([
|
|
1417
|
+
...REACT_DOM_SOURCES,
|
|
1418
|
+
...options?.additionalDomSources ?? []
|
|
1419
|
+
]);
|
|
1420
|
+
if (nativeSources.has(source)) return "react-native";
|
|
1421
|
+
if (domSources.has(source)) return "react-dom";
|
|
1422
|
+
if (source.startsWith("react-native")) return "react-native";
|
|
1423
|
+
if (source.startsWith("react-dom")) return "react-dom";
|
|
1424
|
+
return "neutral";
|
|
1425
|
+
}
|
|
1426
|
+
function parseImports(sourceCode, filePath = "unknown.tsx", options) {
|
|
1427
|
+
const imports = [];
|
|
1428
|
+
const sourceFile = ts3.createSourceFile(
|
|
1429
|
+
filePath,
|
|
1430
|
+
sourceCode,
|
|
1431
|
+
ts3.ScriptTarget.Latest,
|
|
1432
|
+
true,
|
|
1433
|
+
filePath.endsWith(".tsx") || filePath.endsWith(".jsx") ? ts3.ScriptKind.TSX : ts3.ScriptKind.TS
|
|
1434
|
+
);
|
|
1435
|
+
const visit = (node) => {
|
|
1436
|
+
if (ts3.isImportDeclaration(node)) {
|
|
1437
|
+
const importInfo = parseImportDeclaration(node, sourceFile, options);
|
|
1438
|
+
imports.push(...importInfo);
|
|
1439
|
+
}
|
|
1440
|
+
if (ts3.isCallExpression(node) && ts3.isIdentifier(node.expression) && node.expression.text === "require" && node.arguments.length === 1 && ts3.isStringLiteral(node.arguments[0])) {
|
|
1441
|
+
const source = node.arguments[0].text;
|
|
1442
|
+
const { line, character } = sourceFile.getLineAndCharacterOfPosition(
|
|
1443
|
+
node.getStart()
|
|
1444
|
+
);
|
|
1445
|
+
imports.push({
|
|
1446
|
+
name: "require",
|
|
1447
|
+
source,
|
|
1448
|
+
platform: getPlatformForSource(source, options),
|
|
1449
|
+
line: line + 1,
|
|
1450
|
+
column: character + 1,
|
|
1451
|
+
isDefault: false,
|
|
1452
|
+
isNamespace: true,
|
|
1453
|
+
isTypeOnly: false
|
|
1454
|
+
});
|
|
1455
|
+
}
|
|
1456
|
+
ts3.forEachChild(node, visit);
|
|
1457
|
+
};
|
|
1458
|
+
visit(sourceFile);
|
|
1459
|
+
return imports;
|
|
1460
|
+
}
|
|
1461
|
+
function parseImportDeclaration(node, sourceFile, options) {
|
|
1462
|
+
const imports = [];
|
|
1463
|
+
if (!ts3.isStringLiteral(node.moduleSpecifier)) {
|
|
1464
|
+
return imports;
|
|
1465
|
+
}
|
|
1466
|
+
const source = node.moduleSpecifier.text;
|
|
1467
|
+
const platform = getPlatformForSource(source, options);
|
|
1468
|
+
const isTypeOnly = node.importClause?.isTypeOnly ?? false;
|
|
1469
|
+
const importClause = node.importClause;
|
|
1470
|
+
if (!importClause) {
|
|
1471
|
+
return imports;
|
|
1472
|
+
}
|
|
1473
|
+
if (importClause.name) {
|
|
1474
|
+
const { line, character } = sourceFile.getLineAndCharacterOfPosition(
|
|
1475
|
+
importClause.name.getStart()
|
|
1476
|
+
);
|
|
1477
|
+
imports.push({
|
|
1478
|
+
name: importClause.name.text,
|
|
1479
|
+
source,
|
|
1480
|
+
platform,
|
|
1481
|
+
line: line + 1,
|
|
1482
|
+
column: character + 1,
|
|
1483
|
+
isDefault: true,
|
|
1484
|
+
isNamespace: false,
|
|
1485
|
+
isTypeOnly
|
|
1486
|
+
});
|
|
1487
|
+
}
|
|
1488
|
+
const namedBindings = importClause.namedBindings;
|
|
1489
|
+
if (namedBindings) {
|
|
1490
|
+
if (ts3.isNamespaceImport(namedBindings)) {
|
|
1491
|
+
const { line, character } = sourceFile.getLineAndCharacterOfPosition(
|
|
1492
|
+
namedBindings.name.getStart()
|
|
1493
|
+
);
|
|
1494
|
+
imports.push({
|
|
1495
|
+
name: namedBindings.name.text,
|
|
1496
|
+
source,
|
|
1497
|
+
platform,
|
|
1498
|
+
line: line + 1,
|
|
1499
|
+
column: character + 1,
|
|
1500
|
+
isDefault: false,
|
|
1501
|
+
isNamespace: true,
|
|
1502
|
+
isTypeOnly
|
|
1503
|
+
});
|
|
1504
|
+
} else if (ts3.isNamedImports(namedBindings)) {
|
|
1505
|
+
for (const element of namedBindings.elements) {
|
|
1506
|
+
const { line, character } = sourceFile.getLineAndCharacterOfPosition(
|
|
1507
|
+
element.name.getStart()
|
|
1508
|
+
);
|
|
1509
|
+
const importedName = element.propertyName?.text ?? element.name.text;
|
|
1510
|
+
const localName = element.name.text;
|
|
1511
|
+
imports.push({
|
|
1512
|
+
name: localName,
|
|
1513
|
+
originalName: element.propertyName ? importedName : void 0,
|
|
1514
|
+
source,
|
|
1515
|
+
platform,
|
|
1516
|
+
line: line + 1,
|
|
1517
|
+
column: character + 1,
|
|
1518
|
+
isDefault: false,
|
|
1519
|
+
isNamespace: false,
|
|
1520
|
+
isTypeOnly: isTypeOnly || element.isTypeOnly
|
|
1521
|
+
});
|
|
1522
|
+
}
|
|
1523
|
+
}
|
|
1524
|
+
}
|
|
1525
|
+
return imports;
|
|
1526
|
+
}
|
|
1527
|
+
function filterPlatformImports(imports, platform) {
|
|
1528
|
+
return imports.filter((imp) => {
|
|
1529
|
+
if (imp.platform === "neutral") return false;
|
|
1530
|
+
if (platform && imp.platform !== platform) return false;
|
|
1531
|
+
return true;
|
|
1532
|
+
});
|
|
1533
|
+
}
|
|
1534
|
+
function getUniqueSources(imports) {
|
|
1535
|
+
return [...new Set(imports.map((imp) => imp.source))];
|
|
1536
|
+
}
|
|
1537
|
+
function groupImportsBySource(imports) {
|
|
1538
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
1539
|
+
for (const imp of imports) {
|
|
1540
|
+
const existing = grouped.get(imp.source) ?? [];
|
|
1541
|
+
existing.push(imp);
|
|
1542
|
+
grouped.set(imp.source, existing);
|
|
1543
|
+
}
|
|
1544
|
+
return grouped;
|
|
1545
|
+
}
|
|
1546
|
+
|
|
1547
|
+
// src/analyzers/platformImports.ts
|
|
1548
|
+
var DEFAULT_OPTIONS = {
|
|
1549
|
+
severity: "error",
|
|
1550
|
+
additionalNativePrimitives: [],
|
|
1551
|
+
additionalDomPrimitives: [],
|
|
1552
|
+
ignoredPrimitives: [],
|
|
1553
|
+
ignoredPatterns: [],
|
|
1554
|
+
additionalNativeSources: [],
|
|
1555
|
+
additionalDomSources: []
|
|
1556
|
+
};
|
|
1557
|
+
function matchesIgnoredPattern(filePath, patterns) {
|
|
1558
|
+
if (patterns.length === 0) return false;
|
|
1559
|
+
for (const pattern of patterns) {
|
|
1560
|
+
const regexPattern = pattern.replace(/\*\*/g, "{{GLOBSTAR}}").replace(/\*/g, "[^/]*").replace(/{{GLOBSTAR}}/g, ".*").replace(/\?/g, ".");
|
|
1561
|
+
const regex = new RegExp(regexPattern);
|
|
1562
|
+
if (regex.test(filePath)) {
|
|
1563
|
+
return true;
|
|
1564
|
+
}
|
|
1565
|
+
}
|
|
1566
|
+
return false;
|
|
1567
|
+
}
|
|
1568
|
+
function createViolation(type, primitive, source, filePath, line, column, severity) {
|
|
1569
|
+
const messages = {
|
|
1570
|
+
"native-in-shared": `React Native primitive '${primitive}' from '${source}' should not be used in shared files. Use a .native.tsx file instead.`,
|
|
1571
|
+
"dom-in-shared": `React DOM primitive '${primitive}' from '${source}' should not be used in shared files. Use a .web.tsx file instead.`,
|
|
1572
|
+
"native-in-web": `React Native primitive '${primitive}' from '${source}' should not be used in web-specific files.`,
|
|
1573
|
+
"dom-in-native": `React DOM primitive '${primitive}' from '${source}' should not be used in native-specific files.`
|
|
1574
|
+
};
|
|
1575
|
+
return {
|
|
1576
|
+
type,
|
|
1577
|
+
primitive,
|
|
1578
|
+
source,
|
|
1579
|
+
filePath,
|
|
1580
|
+
line,
|
|
1581
|
+
column,
|
|
1582
|
+
message: messages[type],
|
|
1583
|
+
severity
|
|
1584
|
+
};
|
|
1585
|
+
}
|
|
1586
|
+
function isNativePrimitive(importInfo, additionalPrimitives) {
|
|
1587
|
+
const name = importInfo.originalName ?? importInfo.name;
|
|
1588
|
+
if (isReactNativePrimitive(name)) return true;
|
|
1589
|
+
if (additionalPrimitives.includes(name)) return true;
|
|
1590
|
+
const nativeSources = /* @__PURE__ */ new Set([...REACT_NATIVE_SOURCES]);
|
|
1591
|
+
if (nativeSources.has(importInfo.source)) {
|
|
1592
|
+
if (/^[A-Z]/.test(name)) return true;
|
|
1593
|
+
}
|
|
1594
|
+
return false;
|
|
1595
|
+
}
|
|
1596
|
+
function isDomPrimitive(importInfo, additionalPrimitives) {
|
|
1597
|
+
const name = importInfo.originalName ?? importInfo.name;
|
|
1598
|
+
if (isReactDomPrimitive(name)) return true;
|
|
1599
|
+
if (additionalPrimitives.includes(name)) return true;
|
|
1600
|
+
return false;
|
|
1601
|
+
}
|
|
1602
|
+
function analyzePlatformImports(filePath, sourceCode, options) {
|
|
1603
|
+
const opts = { ...DEFAULT_OPTIONS, ...options };
|
|
1604
|
+
const fileType = classifyFile(filePath);
|
|
1605
|
+
const violations = [];
|
|
1606
|
+
if (matchesIgnoredPattern(filePath, opts.ignoredPatterns)) {
|
|
1607
|
+
return {
|
|
1608
|
+
filePath,
|
|
1609
|
+
fileType,
|
|
1610
|
+
violations: [],
|
|
1611
|
+
imports: [],
|
|
1612
|
+
passed: true
|
|
1613
|
+
};
|
|
1614
|
+
}
|
|
1615
|
+
if (fileType === "other" || fileType === "styles" || fileType === "types") {
|
|
1616
|
+
return {
|
|
1617
|
+
filePath,
|
|
1618
|
+
fileType,
|
|
1619
|
+
violations: [],
|
|
1620
|
+
imports: [],
|
|
1621
|
+
passed: true
|
|
1622
|
+
};
|
|
1623
|
+
}
|
|
1624
|
+
const imports = parseImports(sourceCode, filePath, {
|
|
1625
|
+
additionalNativeSources: opts.additionalNativeSources,
|
|
1626
|
+
additionalDomSources: opts.additionalDomSources
|
|
1627
|
+
});
|
|
1628
|
+
const ignoredPrimitives = new Set(opts.ignoredPrimitives);
|
|
1629
|
+
for (const imp of imports) {
|
|
1630
|
+
if (imp.isTypeOnly) continue;
|
|
1631
|
+
const primitiveName = imp.originalName ?? imp.name;
|
|
1632
|
+
if (ignoredPrimitives.has(primitiveName)) continue;
|
|
1633
|
+
switch (fileType) {
|
|
1634
|
+
case "shared":
|
|
1635
|
+
if (isNativePrimitive(imp, opts.additionalNativePrimitives)) {
|
|
1636
|
+
violations.push(
|
|
1637
|
+
createViolation(
|
|
1638
|
+
"native-in-shared",
|
|
1639
|
+
primitiveName,
|
|
1640
|
+
imp.source,
|
|
1641
|
+
filePath,
|
|
1642
|
+
imp.line,
|
|
1643
|
+
imp.column,
|
|
1644
|
+
opts.severity
|
|
1645
|
+
)
|
|
1646
|
+
);
|
|
1647
|
+
}
|
|
1648
|
+
if (isDomPrimitive(imp, opts.additionalDomPrimitives)) {
|
|
1649
|
+
violations.push(
|
|
1650
|
+
createViolation(
|
|
1651
|
+
"dom-in-shared",
|
|
1652
|
+
primitiveName,
|
|
1653
|
+
imp.source,
|
|
1654
|
+
filePath,
|
|
1655
|
+
imp.line,
|
|
1656
|
+
imp.column,
|
|
1657
|
+
opts.severity
|
|
1658
|
+
)
|
|
1659
|
+
);
|
|
1660
|
+
}
|
|
1661
|
+
break;
|
|
1662
|
+
case "web":
|
|
1663
|
+
if (isNativePrimitive(imp, opts.additionalNativePrimitives)) {
|
|
1664
|
+
violations.push(
|
|
1665
|
+
createViolation(
|
|
1666
|
+
"native-in-web",
|
|
1667
|
+
primitiveName,
|
|
1668
|
+
imp.source,
|
|
1669
|
+
filePath,
|
|
1670
|
+
imp.line,
|
|
1671
|
+
imp.column,
|
|
1672
|
+
opts.severity
|
|
1673
|
+
)
|
|
1674
|
+
);
|
|
1675
|
+
}
|
|
1676
|
+
break;
|
|
1677
|
+
case "native":
|
|
1678
|
+
if (isDomPrimitive(imp, opts.additionalDomPrimitives)) {
|
|
1679
|
+
violations.push(
|
|
1680
|
+
createViolation(
|
|
1681
|
+
"dom-in-native",
|
|
1682
|
+
primitiveName,
|
|
1683
|
+
imp.source,
|
|
1684
|
+
filePath,
|
|
1685
|
+
imp.line,
|
|
1686
|
+
imp.column,
|
|
1687
|
+
opts.severity
|
|
1688
|
+
)
|
|
1689
|
+
);
|
|
1690
|
+
}
|
|
1691
|
+
break;
|
|
1692
|
+
}
|
|
1693
|
+
}
|
|
1694
|
+
return {
|
|
1695
|
+
filePath,
|
|
1696
|
+
fileType,
|
|
1697
|
+
violations,
|
|
1698
|
+
imports,
|
|
1699
|
+
passed: violations.length === 0
|
|
1700
|
+
};
|
|
1701
|
+
}
|
|
1702
|
+
function analyzeFiles(files, options) {
|
|
1703
|
+
return files.map(
|
|
1704
|
+
(file) => analyzePlatformImports(file.path, file.content, options)
|
|
1705
|
+
);
|
|
1706
|
+
}
|
|
1707
|
+
function summarizeResults(results) {
|
|
1708
|
+
const violationsByType = {
|
|
1709
|
+
"native-in-shared": 0,
|
|
1710
|
+
"dom-in-shared": 0,
|
|
1711
|
+
"native-in-web": 0,
|
|
1712
|
+
"dom-in-native": 0
|
|
1713
|
+
};
|
|
1714
|
+
const violationsBySeverity = {
|
|
1715
|
+
error: 0,
|
|
1716
|
+
warning: 0,
|
|
1717
|
+
info: 0
|
|
1718
|
+
};
|
|
1719
|
+
let totalViolations = 0;
|
|
1720
|
+
for (const result of results) {
|
|
1721
|
+
for (const violation of result.violations) {
|
|
1722
|
+
totalViolations++;
|
|
1723
|
+
violationsByType[violation.type]++;
|
|
1724
|
+
violationsBySeverity[violation.severity]++;
|
|
1725
|
+
}
|
|
1726
|
+
}
|
|
1727
|
+
return {
|
|
1728
|
+
totalFiles: results.length,
|
|
1729
|
+
passedFiles: results.filter((r) => r.passed).length,
|
|
1730
|
+
failedFiles: results.filter((r) => !r.passed).length,
|
|
1731
|
+
totalViolations,
|
|
1732
|
+
violationsByType,
|
|
1733
|
+
violationsBySeverity
|
|
1734
|
+
};
|
|
1735
|
+
}
|
|
1736
|
+
function formatViolation(violation) {
|
|
1737
|
+
const severityPrefix = violation.severity === "error" ? "ERROR" : violation.severity === "warning" ? "WARN" : "INFO";
|
|
1738
|
+
return `${severityPrefix}: ${violation.filePath}:${violation.line}:${violation.column} - ${violation.message}`;
|
|
1739
|
+
}
|
|
1740
|
+
function formatViolations(results) {
|
|
1741
|
+
const lines = [];
|
|
1742
|
+
for (const result of results) {
|
|
1743
|
+
for (const violation of result.violations) {
|
|
1744
|
+
lines.push(formatViolation(violation));
|
|
1745
|
+
}
|
|
1746
|
+
}
|
|
1747
|
+
return lines;
|
|
1748
|
+
}
|
|
1749
|
+
|
|
1750
|
+
// src/analyzers/componentLinter.ts
|
|
1751
|
+
var CSS_COLOR_NAMES = /* @__PURE__ */ new Set([
|
|
1752
|
+
"aliceblue",
|
|
1753
|
+
"antiquewhite",
|
|
1754
|
+
"aqua",
|
|
1755
|
+
"aquamarine",
|
|
1756
|
+
"azure",
|
|
1757
|
+
"beige",
|
|
1758
|
+
"bisque",
|
|
1759
|
+
"black",
|
|
1760
|
+
"blanchedalmond",
|
|
1761
|
+
"blue",
|
|
1762
|
+
"blueviolet",
|
|
1763
|
+
"brown",
|
|
1764
|
+
"burlywood",
|
|
1765
|
+
"cadetblue",
|
|
1766
|
+
"chartreuse",
|
|
1767
|
+
"chocolate",
|
|
1768
|
+
"coral",
|
|
1769
|
+
"cornflowerblue",
|
|
1770
|
+
"cornsilk",
|
|
1771
|
+
"crimson",
|
|
1772
|
+
"cyan",
|
|
1773
|
+
"darkblue",
|
|
1774
|
+
"darkcyan",
|
|
1775
|
+
"darkgoldenrod",
|
|
1776
|
+
"darkgray",
|
|
1777
|
+
"darkgreen",
|
|
1778
|
+
"darkgrey",
|
|
1779
|
+
"darkkhaki",
|
|
1780
|
+
"darkmagenta",
|
|
1781
|
+
"darkolivegreen",
|
|
1782
|
+
"darkorange",
|
|
1783
|
+
"darkorchid",
|
|
1784
|
+
"darkred",
|
|
1785
|
+
"darksalmon",
|
|
1786
|
+
"darkseagreen",
|
|
1787
|
+
"darkslateblue",
|
|
1788
|
+
"darkslategray",
|
|
1789
|
+
"darkslategrey",
|
|
1790
|
+
"darkturquoise",
|
|
1791
|
+
"darkviolet",
|
|
1792
|
+
"deeppink",
|
|
1793
|
+
"deepskyblue",
|
|
1794
|
+
"dimgray",
|
|
1795
|
+
"dimgrey",
|
|
1796
|
+
"dodgerblue",
|
|
1797
|
+
"firebrick",
|
|
1798
|
+
"floralwhite",
|
|
1799
|
+
"forestgreen",
|
|
1800
|
+
"fuchsia",
|
|
1801
|
+
"gainsboro",
|
|
1802
|
+
"ghostwhite",
|
|
1803
|
+
"gold",
|
|
1804
|
+
"goldenrod",
|
|
1805
|
+
"gray",
|
|
1806
|
+
"green",
|
|
1807
|
+
"greenyellow",
|
|
1808
|
+
"grey",
|
|
1809
|
+
"honeydew",
|
|
1810
|
+
"hotpink",
|
|
1811
|
+
"indianred",
|
|
1812
|
+
"indigo",
|
|
1813
|
+
"ivory",
|
|
1814
|
+
"khaki",
|
|
1815
|
+
"lavender",
|
|
1816
|
+
"lavenderblush",
|
|
1817
|
+
"lawngreen",
|
|
1818
|
+
"lemonchiffon",
|
|
1819
|
+
"lightblue",
|
|
1820
|
+
"lightcoral",
|
|
1821
|
+
"lightcyan",
|
|
1822
|
+
"lightgoldenrodyellow",
|
|
1823
|
+
"lightgray",
|
|
1824
|
+
"lightgreen",
|
|
1825
|
+
"lightgrey",
|
|
1826
|
+
"lightpink",
|
|
1827
|
+
"lightsalmon",
|
|
1828
|
+
"lightseagreen",
|
|
1829
|
+
"lightskyblue",
|
|
1830
|
+
"lightslategray",
|
|
1831
|
+
"lightslategrey",
|
|
1832
|
+
"lightsteelblue",
|
|
1833
|
+
"lightyellow",
|
|
1834
|
+
"lime",
|
|
1835
|
+
"limegreen",
|
|
1836
|
+
"linen",
|
|
1837
|
+
"magenta",
|
|
1838
|
+
"maroon",
|
|
1839
|
+
"mediumaquamarine",
|
|
1840
|
+
"mediumblue",
|
|
1841
|
+
"mediumorchid",
|
|
1842
|
+
"mediumpurple",
|
|
1843
|
+
"mediumseagreen",
|
|
1844
|
+
"mediumslateblue",
|
|
1845
|
+
"mediumspringgreen",
|
|
1846
|
+
"mediumturquoise",
|
|
1847
|
+
"mediumvioletred",
|
|
1848
|
+
"midnightblue",
|
|
1849
|
+
"mintcream",
|
|
1850
|
+
"mistyrose",
|
|
1851
|
+
"moccasin",
|
|
1852
|
+
"navajowhite",
|
|
1853
|
+
"navy",
|
|
1854
|
+
"oldlace",
|
|
1855
|
+
"olive",
|
|
1856
|
+
"olivedrab",
|
|
1857
|
+
"orange",
|
|
1858
|
+
"orangered",
|
|
1859
|
+
"orchid",
|
|
1860
|
+
"palegoldenrod",
|
|
1861
|
+
"palegreen",
|
|
1862
|
+
"paleturquoise",
|
|
1863
|
+
"palevioletred",
|
|
1864
|
+
"papayawhip",
|
|
1865
|
+
"peachpuff",
|
|
1866
|
+
"peru",
|
|
1867
|
+
"pink",
|
|
1868
|
+
"plum",
|
|
1869
|
+
"powderblue",
|
|
1870
|
+
"purple",
|
|
1871
|
+
"rebeccapurple",
|
|
1872
|
+
"red",
|
|
1873
|
+
"rosybrown",
|
|
1874
|
+
"royalblue",
|
|
1875
|
+
"saddlebrown",
|
|
1876
|
+
"salmon",
|
|
1877
|
+
"sandybrown",
|
|
1878
|
+
"seagreen",
|
|
1879
|
+
"seashell",
|
|
1880
|
+
"sienna",
|
|
1881
|
+
"silver",
|
|
1882
|
+
"skyblue",
|
|
1883
|
+
"slateblue",
|
|
1884
|
+
"slategray",
|
|
1885
|
+
"slategrey",
|
|
1886
|
+
"snow",
|
|
1887
|
+
"springgreen",
|
|
1888
|
+
"steelblue",
|
|
1889
|
+
"tan",
|
|
1890
|
+
"teal",
|
|
1891
|
+
"thistle",
|
|
1892
|
+
"tomato",
|
|
1893
|
+
"turquoise",
|
|
1894
|
+
"violet",
|
|
1895
|
+
"wheat",
|
|
1896
|
+
"white",
|
|
1897
|
+
"whitesmoke",
|
|
1898
|
+
"yellow",
|
|
1899
|
+
"yellowgreen"
|
|
1900
|
+
]);
|
|
1901
|
+
var DEFAULT_ALLOWED_COLORS = /* @__PURE__ */ new Set([
|
|
1902
|
+
"transparent",
|
|
1903
|
+
"inherit",
|
|
1904
|
+
"currentColor",
|
|
1905
|
+
"currentcolor"
|
|
1906
|
+
]);
|
|
1907
|
+
var COLOR_PROPERTIES = [
|
|
1908
|
+
"color",
|
|
1909
|
+
"backgroundColor",
|
|
1910
|
+
"borderColor",
|
|
1911
|
+
"borderTopColor",
|
|
1912
|
+
"borderRightColor",
|
|
1913
|
+
"borderBottomColor",
|
|
1914
|
+
"borderLeftColor",
|
|
1915
|
+
"shadowColor",
|
|
1916
|
+
"textDecorationColor",
|
|
1917
|
+
"tintColor",
|
|
1918
|
+
"overlayColor"
|
|
1919
|
+
];
|
|
1920
|
+
function isHardcodedColor(value, allowedColors) {
|
|
1921
|
+
const trimmed = value.trim().toLowerCase();
|
|
1922
|
+
if (allowedColors.has(trimmed)) {
|
|
1923
|
+
return false;
|
|
1924
|
+
}
|
|
1925
|
+
if (/^#[0-9a-f]{3,8}$/i.test(trimmed)) {
|
|
1926
|
+
return true;
|
|
1927
|
+
}
|
|
1928
|
+
if (/^rgba?\s*\(/.test(trimmed)) {
|
|
1929
|
+
return true;
|
|
1930
|
+
}
|
|
1931
|
+
if (/^hsla?\s*\(/.test(trimmed)) {
|
|
1932
|
+
return true;
|
|
1933
|
+
}
|
|
1934
|
+
if (CSS_COLOR_NAMES.has(trimmed)) {
|
|
1935
|
+
return true;
|
|
1936
|
+
}
|
|
1937
|
+
return false;
|
|
1938
|
+
}
|
|
1939
|
+
function getRuleSeverity(rule, defaultSeverity) {
|
|
1940
|
+
if (rule === false) return null;
|
|
1941
|
+
if (rule === true || rule === void 0) return defaultSeverity;
|
|
1942
|
+
return rule;
|
|
1943
|
+
}
|
|
1944
|
+
function getLineColumn(source, index) {
|
|
1945
|
+
const lines = source.substring(0, index).split("\n");
|
|
1946
|
+
return {
|
|
1947
|
+
line: lines.length,
|
|
1948
|
+
column: lines[lines.length - 1].length + 1
|
|
1949
|
+
};
|
|
1950
|
+
}
|
|
1951
|
+
function lintComponent(filePath, sourceCode, options = {}) {
|
|
1952
|
+
const issues = [];
|
|
1953
|
+
const rules = options.rules || {};
|
|
1954
|
+
const allowedColors = /* @__PURE__ */ new Set([
|
|
1955
|
+
...DEFAULT_ALLOWED_COLORS,
|
|
1956
|
+
...(options.allowedColors || []).map((c) => c.toLowerCase())
|
|
1957
|
+
]);
|
|
1958
|
+
const hardcodedColorSeverity = getRuleSeverity(rules.hardcodedColors, "warning");
|
|
1959
|
+
if (hardcodedColorSeverity) {
|
|
1960
|
+
for (const prop of COLOR_PROPERTIES) {
|
|
1961
|
+
const propRegex = new RegExp(`${prop}\\s*:\\s*['"]([^'"]+)['"]`, "g");
|
|
1962
|
+
let match;
|
|
1963
|
+
while ((match = propRegex.exec(sourceCode)) !== null) {
|
|
1964
|
+
const colorValue = match[1];
|
|
1965
|
+
if (isHardcodedColor(colorValue, allowedColors)) {
|
|
1966
|
+
const { line, column } = getLineColumn(sourceCode, match.index);
|
|
1967
|
+
issues.push({
|
|
1968
|
+
type: "hardcoded-color",
|
|
1969
|
+
severity: hardcodedColorSeverity,
|
|
1970
|
+
line,
|
|
1971
|
+
column,
|
|
1972
|
+
code: match[0],
|
|
1973
|
+
message: `Hardcoded color '${colorValue}' in ${prop}. Use theme colors instead.`,
|
|
1974
|
+
suggestion: `Use theme.colors.*, theme.intents[intent].*, or pass color via props`
|
|
1975
|
+
});
|
|
1976
|
+
}
|
|
1977
|
+
}
|
|
1978
|
+
}
|
|
1979
|
+
const templateColorRegex = /(?:color|backgroundColor)\s*:\s*`[^`]*#[0-9a-fA-F]{3,8}[^`]*`/g;
|
|
1980
|
+
let templateMatch;
|
|
1981
|
+
while ((templateMatch = templateColorRegex.exec(sourceCode)) !== null) {
|
|
1982
|
+
const { line, column } = getLineColumn(sourceCode, templateMatch.index);
|
|
1983
|
+
issues.push({
|
|
1984
|
+
type: "hardcoded-color",
|
|
1985
|
+
severity: hardcodedColorSeverity,
|
|
1986
|
+
line,
|
|
1987
|
+
column,
|
|
1988
|
+
code: templateMatch[0],
|
|
1989
|
+
message: `Hardcoded hex color in template literal. Use theme colors instead.`,
|
|
1990
|
+
suggestion: `Use theme.colors.* or theme.intents[intent].*`
|
|
1991
|
+
});
|
|
1992
|
+
}
|
|
1993
|
+
}
|
|
1994
|
+
const directPlatformSeverity = getRuleSeverity(rules.directPlatformImports, "warning");
|
|
1995
|
+
if (directPlatformSeverity) {
|
|
1996
|
+
const isSharedFile2 = !filePath.includes(".web.") && !filePath.includes(".native.");
|
|
1997
|
+
if (isSharedFile2) {
|
|
1998
|
+
const rnImportRegex = /import\s+.*\s+from\s+['"]react-native['"]/g;
|
|
1999
|
+
let match;
|
|
2000
|
+
while ((match = rnImportRegex.exec(sourceCode)) !== null) {
|
|
2001
|
+
const { line, column } = getLineColumn(sourceCode, match.index);
|
|
2002
|
+
issues.push({
|
|
2003
|
+
type: "direct-platform-import",
|
|
2004
|
+
severity: directPlatformSeverity,
|
|
2005
|
+
line,
|
|
2006
|
+
column,
|
|
2007
|
+
code: match[0],
|
|
2008
|
+
message: `Direct import from 'react-native' in shared file. Use @idealyst/components instead.`,
|
|
2009
|
+
suggestion: `Import View, Text, etc. from '@idealyst/components'`
|
|
2010
|
+
});
|
|
2011
|
+
}
|
|
2012
|
+
}
|
|
2013
|
+
}
|
|
2014
|
+
const counts = {
|
|
2015
|
+
error: issues.filter((i) => i.severity === "error").length,
|
|
2016
|
+
warning: issues.filter((i) => i.severity === "warning").length,
|
|
2017
|
+
info: issues.filter((i) => i.severity === "info").length
|
|
2018
|
+
};
|
|
2019
|
+
return {
|
|
2020
|
+
filePath,
|
|
2021
|
+
issues,
|
|
2022
|
+
passed: counts.error === 0,
|
|
2023
|
+
counts
|
|
2024
|
+
};
|
|
2025
|
+
}
|
|
2026
|
+
function lintComponents(files, options = {}) {
|
|
2027
|
+
return files.map((file) => lintComponent(file.path, file.content, options));
|
|
2028
|
+
}
|
|
2029
|
+
function formatLintIssue(issue, filePath) {
|
|
2030
|
+
const severityPrefix = issue.severity === "error" ? "ERROR" : issue.severity === "warning" ? "WARN" : "INFO";
|
|
2031
|
+
let output = `${severityPrefix}: ${filePath}:${issue.line}:${issue.column} - ${issue.message}`;
|
|
2032
|
+
if (issue.suggestion) {
|
|
2033
|
+
output += `
|
|
2034
|
+
Suggestion: ${issue.suggestion}`;
|
|
2035
|
+
}
|
|
2036
|
+
return output;
|
|
2037
|
+
}
|
|
2038
|
+
function formatLintResults(results) {
|
|
2039
|
+
const lines = [];
|
|
2040
|
+
for (const result of results) {
|
|
2041
|
+
for (const issue of result.issues) {
|
|
2042
|
+
lines.push(formatLintIssue(issue, result.filePath));
|
|
2043
|
+
}
|
|
2044
|
+
}
|
|
2045
|
+
return lines;
|
|
2046
|
+
}
|
|
2047
|
+
function summarizeLintResults(results) {
|
|
2048
|
+
const issuesByType = {
|
|
2049
|
+
"hardcoded-color": 0,
|
|
2050
|
+
"direct-platform-import": 0
|
|
2051
|
+
};
|
|
2052
|
+
const issuesBySeverity = {
|
|
2053
|
+
error: 0,
|
|
2054
|
+
warning: 0,
|
|
2055
|
+
info: 0
|
|
2056
|
+
};
|
|
2057
|
+
let totalIssues = 0;
|
|
2058
|
+
for (const result of results) {
|
|
2059
|
+
for (const issue of result.issues) {
|
|
2060
|
+
totalIssues++;
|
|
2061
|
+
issuesByType[issue.type]++;
|
|
2062
|
+
issuesBySeverity[issue.severity]++;
|
|
2063
|
+
}
|
|
2064
|
+
}
|
|
2065
|
+
return {
|
|
2066
|
+
totalFiles: results.length,
|
|
2067
|
+
passedFiles: results.filter((r) => r.passed).length,
|
|
2068
|
+
failedFiles: results.filter((r) => !r.passed).length,
|
|
2069
|
+
totalIssues,
|
|
2070
|
+
issuesByType,
|
|
2071
|
+
issuesBySeverity
|
|
2072
|
+
};
|
|
2073
|
+
}
|
|
2074
|
+
|
|
2075
|
+
// src/index.ts
|
|
2076
|
+
var componentRegistry = {};
|
|
2077
|
+
var componentNames = [];
|
|
2078
|
+
function getComponentsByCategory(category) {
|
|
2079
|
+
return Object.entries(componentRegistry).filter(([_, def]) => def.category === category).map(([name]) => name);
|
|
2080
|
+
}
|
|
2081
|
+
function getPropConfig(componentName) {
|
|
2082
|
+
const def = componentRegistry[componentName];
|
|
2083
|
+
if (!def) return {};
|
|
2084
|
+
return Object.entries(def.props).reduce((acc, [key, prop]) => {
|
|
2085
|
+
if (prop.values && prop.values.length > 0) {
|
|
2086
|
+
acc[key] = { type: "select", options: prop.values, default: prop.default };
|
|
2087
|
+
} else if (prop.type === "boolean") {
|
|
2088
|
+
acc[key] = { type: "boolean", default: prop.default ?? false };
|
|
2089
|
+
} else if (prop.type === "string") {
|
|
2090
|
+
acc[key] = { type: "text", default: prop.default ?? "" };
|
|
2091
|
+
} else if (prop.type === "number") {
|
|
2092
|
+
acc[key] = { type: "number", default: prop.default ?? 0 };
|
|
2093
|
+
}
|
|
2094
|
+
return acc;
|
|
2095
|
+
}, {});
|
|
2096
|
+
}
|
|
2097
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
2098
|
+
0 && (module.exports = {
|
|
2099
|
+
HTML_ELEMENT_NAMES,
|
|
2100
|
+
HTML_INTRINSIC_ELEMENTS,
|
|
2101
|
+
REACT_DOM_PRIMITIVES,
|
|
2102
|
+
REACT_DOM_PRIMITIVE_NAMES,
|
|
2103
|
+
REACT_DOM_RULE_SET,
|
|
2104
|
+
REACT_DOM_SOURCES,
|
|
2105
|
+
REACT_NATIVE_PRIMITIVES,
|
|
2106
|
+
REACT_NATIVE_PRIMITIVE_NAMES,
|
|
2107
|
+
REACT_NATIVE_RULE_SET,
|
|
2108
|
+
REACT_NATIVE_SOURCES,
|
|
2109
|
+
analyzeComponents,
|
|
2110
|
+
analyzeFiles,
|
|
2111
|
+
analyzePlatformImports,
|
|
2112
|
+
analyzeTheme,
|
|
2113
|
+
classifyFile,
|
|
2114
|
+
componentNames,
|
|
2115
|
+
componentRegistry,
|
|
2116
|
+
filterPlatformImports,
|
|
2117
|
+
formatLintIssue,
|
|
2118
|
+
formatLintResults,
|
|
2119
|
+
formatViolation,
|
|
2120
|
+
formatViolations,
|
|
2121
|
+
generateComponentRegistry,
|
|
2122
|
+
getBaseName,
|
|
2123
|
+
getComponentsByCategory,
|
|
2124
|
+
getExpectedPlatform,
|
|
2125
|
+
getPlatformForSource,
|
|
2126
|
+
getPropConfig,
|
|
2127
|
+
getReactDomPrimitive,
|
|
2128
|
+
getReactNativePrimitive,
|
|
2129
|
+
getUniqueSources,
|
|
2130
|
+
groupImportsBySource,
|
|
2131
|
+
idealystDocsPlugin,
|
|
2132
|
+
isComponentFile,
|
|
2133
|
+
isHtmlElement,
|
|
2134
|
+
isPlatformSpecificFile,
|
|
2135
|
+
isReactDomPrimitive,
|
|
2136
|
+
isReactNativePrimitive,
|
|
2137
|
+
isSharedFile,
|
|
2138
|
+
lintComponent,
|
|
2139
|
+
lintComponents,
|
|
2140
|
+
loadThemeKeys,
|
|
2141
|
+
parseImports,
|
|
2142
|
+
resetThemeCache,
|
|
2143
|
+
summarizeLintResults,
|
|
2144
|
+
summarizeResults
|
|
2145
|
+
});
|
|
2146
|
+
//# sourceMappingURL=index.cjs.map
|