@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
|
@@ -1,397 +0,0 @@
|
|
|
1
|
-
import { Severity } from '../types';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Types of linting issues the component linter can detect.
|
|
5
|
-
*
|
|
6
|
-
* These are issues that TypeScript cannot catch - primarily style/pattern issues
|
|
7
|
-
* that are syntactically valid but violate Idealyst conventions.
|
|
8
|
-
*/
|
|
9
|
-
export type LintIssueType =
|
|
10
|
-
| 'hardcoded-color' // Using color strings like '#fff', 'red', 'rgb()'
|
|
11
|
-
| 'direct-platform-import'; // Importing directly from react-native in shared file
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* A single lint issue found during analysis
|
|
15
|
-
*/
|
|
16
|
-
export interface LintIssue {
|
|
17
|
-
/** Type of lint issue */
|
|
18
|
-
type: LintIssueType;
|
|
19
|
-
/** Severity level */
|
|
20
|
-
severity: Severity;
|
|
21
|
-
/** Line number where issue occurred */
|
|
22
|
-
line: number;
|
|
23
|
-
/** Column number where issue occurred */
|
|
24
|
-
column: number;
|
|
25
|
-
/** The problematic code snippet */
|
|
26
|
-
code: string;
|
|
27
|
-
/** Human-readable message describing the issue */
|
|
28
|
-
message: string;
|
|
29
|
-
/** Suggested fix (if available) */
|
|
30
|
-
suggestion?: string;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* Result of linting a component file
|
|
35
|
-
*/
|
|
36
|
-
export interface LintResult {
|
|
37
|
-
/** Path to the analyzed file */
|
|
38
|
-
filePath: string;
|
|
39
|
-
/** List of issues found */
|
|
40
|
-
issues: LintIssue[];
|
|
41
|
-
/** Whether the file passed linting (no errors) */
|
|
42
|
-
passed: boolean;
|
|
43
|
-
/** Count of issues by severity */
|
|
44
|
-
counts: {
|
|
45
|
-
error: number;
|
|
46
|
-
warning: number;
|
|
47
|
-
info: number;
|
|
48
|
-
};
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* Options for the component linter
|
|
53
|
-
*/
|
|
54
|
-
export interface ComponentLinterOptions {
|
|
55
|
-
/**
|
|
56
|
-
* Which rules to enable (all enabled by default)
|
|
57
|
-
*/
|
|
58
|
-
rules?: {
|
|
59
|
-
/** Detect hardcoded color values like '#fff', 'red', 'rgb()' */
|
|
60
|
-
hardcodedColors?: boolean | Severity;
|
|
61
|
-
/** Detect direct imports from 'react-native' in shared files */
|
|
62
|
-
directPlatformImports?: boolean | Severity;
|
|
63
|
-
};
|
|
64
|
-
|
|
65
|
-
/**
|
|
66
|
-
* Glob patterns for files to ignore
|
|
67
|
-
*/
|
|
68
|
-
ignoredPatterns?: string[];
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
* Color values that are allowed (e.g., 'transparent', 'inherit')
|
|
72
|
-
* @default ['transparent', 'inherit', 'currentColor']
|
|
73
|
-
*/
|
|
74
|
-
allowedColors?: string[];
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
// Common CSS color names that indicate hardcoded colors
|
|
78
|
-
const CSS_COLOR_NAMES = new Set([
|
|
79
|
-
'aliceblue', 'antiquewhite', 'aqua', 'aquamarine', 'azure', 'beige', 'bisque',
|
|
80
|
-
'black', 'blanchedalmond', 'blue', 'blueviolet', 'brown', 'burlywood', 'cadetblue',
|
|
81
|
-
'chartreuse', 'chocolate', 'coral', 'cornflowerblue', 'cornsilk', 'crimson', 'cyan',
|
|
82
|
-
'darkblue', 'darkcyan', 'darkgoldenrod', 'darkgray', 'darkgreen', 'darkgrey',
|
|
83
|
-
'darkkhaki', 'darkmagenta', 'darkolivegreen', 'darkorange', 'darkorchid', 'darkred',
|
|
84
|
-
'darksalmon', 'darkseagreen', 'darkslateblue', 'darkslategray', 'darkslategrey',
|
|
85
|
-
'darkturquoise', 'darkviolet', 'deeppink', 'deepskyblue', 'dimgray', 'dimgrey',
|
|
86
|
-
'dodgerblue', 'firebrick', 'floralwhite', 'forestgreen', 'fuchsia', 'gainsboro',
|
|
87
|
-
'ghostwhite', 'gold', 'goldenrod', 'gray', 'green', 'greenyellow', 'grey',
|
|
88
|
-
'honeydew', 'hotpink', 'indianred', 'indigo', 'ivory', 'khaki', 'lavender',
|
|
89
|
-
'lavenderblush', 'lawngreen', 'lemonchiffon', 'lightblue', 'lightcoral', 'lightcyan',
|
|
90
|
-
'lightgoldenrodyellow', 'lightgray', 'lightgreen', 'lightgrey', 'lightpink',
|
|
91
|
-
'lightsalmon', 'lightseagreen', 'lightskyblue', 'lightslategray', 'lightslategrey',
|
|
92
|
-
'lightsteelblue', 'lightyellow', 'lime', 'limegreen', 'linen', 'magenta', 'maroon',
|
|
93
|
-
'mediumaquamarine', 'mediumblue', 'mediumorchid', 'mediumpurple', 'mediumseagreen',
|
|
94
|
-
'mediumslateblue', 'mediumspringgreen', 'mediumturquoise', 'mediumvioletred',
|
|
95
|
-
'midnightblue', 'mintcream', 'mistyrose', 'moccasin', 'navajowhite', 'navy',
|
|
96
|
-
'oldlace', 'olive', 'olivedrab', 'orange', 'orangered', 'orchid', 'palegoldenrod',
|
|
97
|
-
'palegreen', 'paleturquoise', 'palevioletred', 'papayawhip', 'peachpuff', 'peru',
|
|
98
|
-
'pink', 'plum', 'powderblue', 'purple', 'rebeccapurple', 'red', 'rosybrown',
|
|
99
|
-
'royalblue', 'saddlebrown', 'salmon', 'sandybrown', 'seagreen', 'seashell', 'sienna',
|
|
100
|
-
'silver', 'skyblue', 'slateblue', 'slategray', 'slategrey', 'snow', 'springgreen',
|
|
101
|
-
'steelblue', 'tan', 'teal', 'thistle', 'tomato', 'turquoise', 'violet', 'wheat',
|
|
102
|
-
'white', 'whitesmoke', 'yellow', 'yellowgreen',
|
|
103
|
-
]);
|
|
104
|
-
|
|
105
|
-
// Colors that are generally safe to use
|
|
106
|
-
const DEFAULT_ALLOWED_COLORS = new Set([
|
|
107
|
-
'transparent',
|
|
108
|
-
'inherit',
|
|
109
|
-
'currentColor',
|
|
110
|
-
'currentcolor',
|
|
111
|
-
]);
|
|
112
|
-
|
|
113
|
-
// Style properties that typically use colors
|
|
114
|
-
const COLOR_PROPERTIES = [
|
|
115
|
-
'color',
|
|
116
|
-
'backgroundColor',
|
|
117
|
-
'borderColor',
|
|
118
|
-
'borderTopColor',
|
|
119
|
-
'borderRightColor',
|
|
120
|
-
'borderBottomColor',
|
|
121
|
-
'borderLeftColor',
|
|
122
|
-
'shadowColor',
|
|
123
|
-
'textDecorationColor',
|
|
124
|
-
'tintColor',
|
|
125
|
-
'overlayColor',
|
|
126
|
-
];
|
|
127
|
-
|
|
128
|
-
/**
|
|
129
|
-
* Check if a string is a hardcoded color value
|
|
130
|
-
*/
|
|
131
|
-
function isHardcodedColor(value: string, allowedColors: Set<string>): boolean {
|
|
132
|
-
const trimmed = value.trim().toLowerCase();
|
|
133
|
-
|
|
134
|
-
// Check if it's an allowed color
|
|
135
|
-
if (allowedColors.has(trimmed)) {
|
|
136
|
-
return false;
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
// Hex color: #fff, #ffffff, #ffffffff
|
|
140
|
-
if (/^#[0-9a-f]{3,8}$/i.test(trimmed)) {
|
|
141
|
-
return true;
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
// RGB/RGBA: rgb(0,0,0), rgba(0,0,0,0.5)
|
|
145
|
-
if (/^rgba?\s*\(/.test(trimmed)) {
|
|
146
|
-
return true;
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
// HSL/HSLA: hsl(0,0%,0%), hsla(0,0%,0%,0.5)
|
|
150
|
-
if (/^hsla?\s*\(/.test(trimmed)) {
|
|
151
|
-
return true;
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
// CSS named color
|
|
155
|
-
if (CSS_COLOR_NAMES.has(trimmed)) {
|
|
156
|
-
return true;
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
return false;
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
/**
|
|
163
|
-
* Get the severity for a rule, handling boolean and severity values
|
|
164
|
-
*/
|
|
165
|
-
function getRuleSeverity(
|
|
166
|
-
rule: boolean | Severity | undefined,
|
|
167
|
-
defaultSeverity: Severity
|
|
168
|
-
): Severity | null {
|
|
169
|
-
if (rule === false) return null;
|
|
170
|
-
if (rule === true || rule === undefined) return defaultSeverity;
|
|
171
|
-
return rule;
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
/**
|
|
175
|
-
* Parse source code and find line/column for a match index
|
|
176
|
-
*/
|
|
177
|
-
function getLineColumn(source: string, index: number): { line: number; column: number } {
|
|
178
|
-
const lines = source.substring(0, index).split('\n');
|
|
179
|
-
return {
|
|
180
|
-
line: lines.length,
|
|
181
|
-
column: lines[lines.length - 1].length + 1,
|
|
182
|
-
};
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
/**
|
|
186
|
-
* Lint a component file for Idealyst-specific issues.
|
|
187
|
-
*
|
|
188
|
-
* Detects issues that TypeScript cannot catch:
|
|
189
|
-
* - Hardcoded colors (TypeScript sees these as valid strings)
|
|
190
|
-
* - Direct react-native imports in shared files (valid TS, but breaks web)
|
|
191
|
-
*
|
|
192
|
-
* @param filePath - Path to the file being analyzed
|
|
193
|
-
* @param sourceCode - The source code content
|
|
194
|
-
* @param options - Linter options
|
|
195
|
-
* @returns Lint result with issues found
|
|
196
|
-
*
|
|
197
|
-
* @example
|
|
198
|
-
* ```typescript
|
|
199
|
-
* const result = lintComponent(
|
|
200
|
-
* 'src/components/MyButton.tsx',
|
|
201
|
-
* sourceCode,
|
|
202
|
-
* { rules: { hardcodedColors: 'error' } }
|
|
203
|
-
* );
|
|
204
|
-
*
|
|
205
|
-
* if (!result.passed) {
|
|
206
|
-
* for (const issue of result.issues) {
|
|
207
|
-
* console.error(`${issue.line}:${issue.column} - ${issue.message}`);
|
|
208
|
-
* }
|
|
209
|
-
* }
|
|
210
|
-
* ```
|
|
211
|
-
*/
|
|
212
|
-
export function lintComponent(
|
|
213
|
-
filePath: string,
|
|
214
|
-
sourceCode: string,
|
|
215
|
-
options: ComponentLinterOptions = {}
|
|
216
|
-
): LintResult {
|
|
217
|
-
const issues: LintIssue[] = [];
|
|
218
|
-
const rules = options.rules || {};
|
|
219
|
-
|
|
220
|
-
const allowedColors = new Set([
|
|
221
|
-
...DEFAULT_ALLOWED_COLORS,
|
|
222
|
-
...(options.allowedColors || []).map(c => c.toLowerCase()),
|
|
223
|
-
]);
|
|
224
|
-
|
|
225
|
-
// Rule: Hardcoded colors
|
|
226
|
-
const hardcodedColorSeverity = getRuleSeverity(rules.hardcodedColors, 'warning');
|
|
227
|
-
if (hardcodedColorSeverity) {
|
|
228
|
-
// Match color properties with string values
|
|
229
|
-
for (const prop of COLOR_PROPERTIES) {
|
|
230
|
-
// Match: backgroundColor: '#fff' or backgroundColor: "red"
|
|
231
|
-
const propRegex = new RegExp(`${prop}\\s*:\\s*['"]([^'"]+)['"]`, 'g');
|
|
232
|
-
let match;
|
|
233
|
-
while ((match = propRegex.exec(sourceCode)) !== null) {
|
|
234
|
-
const colorValue = match[1];
|
|
235
|
-
if (isHardcodedColor(colorValue, allowedColors)) {
|
|
236
|
-
const { line, column } = getLineColumn(sourceCode, match.index);
|
|
237
|
-
issues.push({
|
|
238
|
-
type: 'hardcoded-color',
|
|
239
|
-
severity: hardcodedColorSeverity,
|
|
240
|
-
line,
|
|
241
|
-
column,
|
|
242
|
-
code: match[0],
|
|
243
|
-
message: `Hardcoded color '${colorValue}' in ${prop}. Use theme colors instead.`,
|
|
244
|
-
suggestion: `Use theme.colors.*, theme.intents[intent].*, or pass color via props`,
|
|
245
|
-
});
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
// Also check for color/backgroundColor in template literals
|
|
251
|
-
const templateColorRegex = /(?:color|backgroundColor)\s*:\s*`[^`]*#[0-9a-fA-F]{3,8}[^`]*`/g;
|
|
252
|
-
let templateMatch;
|
|
253
|
-
while ((templateMatch = templateColorRegex.exec(sourceCode)) !== null) {
|
|
254
|
-
const { line, column } = getLineColumn(sourceCode, templateMatch.index);
|
|
255
|
-
issues.push({
|
|
256
|
-
type: 'hardcoded-color',
|
|
257
|
-
severity: hardcodedColorSeverity,
|
|
258
|
-
line,
|
|
259
|
-
column,
|
|
260
|
-
code: templateMatch[0],
|
|
261
|
-
message: `Hardcoded hex color in template literal. Use theme colors instead.`,
|
|
262
|
-
suggestion: `Use theme.colors.* or theme.intents[intent].*`,
|
|
263
|
-
});
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
// Rule: Direct platform imports in shared files
|
|
268
|
-
const directPlatformSeverity = getRuleSeverity(rules.directPlatformImports, 'warning');
|
|
269
|
-
if (directPlatformSeverity) {
|
|
270
|
-
// Only check shared files (not .web.tsx or .native.tsx)
|
|
271
|
-
const isSharedFile = !filePath.includes('.web.') && !filePath.includes('.native.');
|
|
272
|
-
if (isSharedFile) {
|
|
273
|
-
// Check for direct react-native imports
|
|
274
|
-
const rnImportRegex = /import\s+.*\s+from\s+['"]react-native['"]/g;
|
|
275
|
-
let match;
|
|
276
|
-
while ((match = rnImportRegex.exec(sourceCode)) !== null) {
|
|
277
|
-
const { line, column } = getLineColumn(sourceCode, match.index);
|
|
278
|
-
issues.push({
|
|
279
|
-
type: 'direct-platform-import',
|
|
280
|
-
severity: directPlatformSeverity,
|
|
281
|
-
line,
|
|
282
|
-
column,
|
|
283
|
-
code: match[0],
|
|
284
|
-
message: `Direct import from 'react-native' in shared file. Use @idealyst/components instead.`,
|
|
285
|
-
suggestion: `Import View, Text, etc. from '@idealyst/components'`,
|
|
286
|
-
});
|
|
287
|
-
}
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
// Calculate counts
|
|
292
|
-
const counts = {
|
|
293
|
-
error: issues.filter(i => i.severity === 'error').length,
|
|
294
|
-
warning: issues.filter(i => i.severity === 'warning').length,
|
|
295
|
-
info: issues.filter(i => i.severity === 'info').length,
|
|
296
|
-
};
|
|
297
|
-
|
|
298
|
-
return {
|
|
299
|
-
filePath,
|
|
300
|
-
issues,
|
|
301
|
-
passed: counts.error === 0,
|
|
302
|
-
counts,
|
|
303
|
-
};
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
/**
|
|
307
|
-
* Lint multiple component files
|
|
308
|
-
*
|
|
309
|
-
* @param files - Array of files to lint
|
|
310
|
-
* @param options - Linter options
|
|
311
|
-
* @returns Array of lint results
|
|
312
|
-
*/
|
|
313
|
-
export function lintComponents(
|
|
314
|
-
files: Array<{ path: string; content: string }>,
|
|
315
|
-
options: ComponentLinterOptions = {}
|
|
316
|
-
): LintResult[] {
|
|
317
|
-
return files.map(file => lintComponent(file.path, file.content, options));
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
/**
|
|
321
|
-
* Format a lint issue for console output
|
|
322
|
-
*/
|
|
323
|
-
export function formatLintIssue(issue: LintIssue, filePath: string): string {
|
|
324
|
-
const severityPrefix = issue.severity === 'error'
|
|
325
|
-
? 'ERROR'
|
|
326
|
-
: issue.severity === 'warning'
|
|
327
|
-
? 'WARN'
|
|
328
|
-
: 'INFO';
|
|
329
|
-
|
|
330
|
-
let output = `${severityPrefix}: ${filePath}:${issue.line}:${issue.column} - ${issue.message}`;
|
|
331
|
-
if (issue.suggestion) {
|
|
332
|
-
output += `\n Suggestion: ${issue.suggestion}`;
|
|
333
|
-
}
|
|
334
|
-
return output;
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
/**
|
|
338
|
-
* Format all lint results for console output
|
|
339
|
-
*/
|
|
340
|
-
export function formatLintResults(results: LintResult[]): string[] {
|
|
341
|
-
const lines: string[] = [];
|
|
342
|
-
|
|
343
|
-
for (const result of results) {
|
|
344
|
-
for (const issue of result.issues) {
|
|
345
|
-
lines.push(formatLintIssue(issue, result.filePath));
|
|
346
|
-
}
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
return lines;
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
/**
|
|
353
|
-
* Summary of lint results
|
|
354
|
-
*/
|
|
355
|
-
export interface LintSummary {
|
|
356
|
-
totalFiles: number;
|
|
357
|
-
passedFiles: number;
|
|
358
|
-
failedFiles: number;
|
|
359
|
-
totalIssues: number;
|
|
360
|
-
issuesByType: Record<LintIssueType, number>;
|
|
361
|
-
issuesBySeverity: Record<Severity, number>;
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
/**
|
|
365
|
-
* Summarize lint results
|
|
366
|
-
*/
|
|
367
|
-
export function summarizeLintResults(results: LintResult[]): LintSummary {
|
|
368
|
-
const issuesByType: Record<LintIssueType, number> = {
|
|
369
|
-
'hardcoded-color': 0,
|
|
370
|
-
'direct-platform-import': 0,
|
|
371
|
-
};
|
|
372
|
-
|
|
373
|
-
const issuesBySeverity: Record<Severity, number> = {
|
|
374
|
-
error: 0,
|
|
375
|
-
warning: 0,
|
|
376
|
-
info: 0,
|
|
377
|
-
};
|
|
378
|
-
|
|
379
|
-
let totalIssues = 0;
|
|
380
|
-
|
|
381
|
-
for (const result of results) {
|
|
382
|
-
for (const issue of result.issues) {
|
|
383
|
-
totalIssues++;
|
|
384
|
-
issuesByType[issue.type]++;
|
|
385
|
-
issuesBySeverity[issue.severity]++;
|
|
386
|
-
}
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
return {
|
|
390
|
-
totalFiles: results.length,
|
|
391
|
-
passedFiles: results.filter(r => r.passed).length,
|
|
392
|
-
failedFiles: results.filter(r => !r.passed).length,
|
|
393
|
-
totalIssues,
|
|
394
|
-
issuesByType,
|
|
395
|
-
issuesBySeverity,
|
|
396
|
-
};
|
|
397
|
-
}
|
package/src/analyzers/index.ts
DELETED