@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.
@@ -0,0 +1,1201 @@
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/analyzers/index.ts
31
+ var analyzers_exports = {};
32
+ __export(analyzers_exports, {
33
+ analyzeFiles: () => analyzeFiles,
34
+ analyzePlatformImports: () => analyzePlatformImports,
35
+ formatLintIssue: () => formatLintIssue,
36
+ formatLintResults: () => formatLintResults,
37
+ formatViolation: () => formatViolation,
38
+ formatViolations: () => formatViolations,
39
+ lintComponent: () => lintComponent,
40
+ lintComponents: () => lintComponents,
41
+ summarizeLintResults: () => summarizeLintResults,
42
+ summarizeResults: () => summarizeResults
43
+ });
44
+ module.exports = __toCommonJS(analyzers_exports);
45
+
46
+ // src/utils/fileClassifier.ts
47
+ var path = __toESM(require("path"), 1);
48
+ var EXTENSION_PATTERNS = [
49
+ // Platform-specific component files
50
+ { pattern: /\.web\.(tsx|jsx)$/, type: "web" },
51
+ { pattern: /\.native\.(tsx|jsx)$/, type: "native" },
52
+ { pattern: /\.ios\.(tsx|jsx)$/, type: "native" },
53
+ { pattern: /\.android\.(tsx|jsx)$/, type: "native" },
54
+ // Style files (can be .ts or .tsx)
55
+ { pattern: /\.styles?\.(tsx?|jsx?)$/, type: "styles" },
56
+ // Type definition files
57
+ { pattern: /\.types?\.(ts|tsx)$/, type: "types" },
58
+ { pattern: /types\.(ts|tsx)$/, type: "types" },
59
+ { pattern: /\.d\.ts$/, type: "types" },
60
+ // Shared component files (generic .tsx/.jsx without platform suffix)
61
+ { pattern: /\.(tsx|jsx)$/, type: "shared" }
62
+ ];
63
+ var EXCLUDED_PATTERNS = [
64
+ /\.test\.(tsx?|jsx?)$/,
65
+ /\.spec\.(tsx?|jsx?)$/,
66
+ /\.stories\.(tsx?|jsx?)$/,
67
+ /\.config\.(ts|js)$/,
68
+ /index\.(ts|tsx|js|jsx)$/
69
+ ];
70
+ function classifyFile(filePath) {
71
+ const fileName = path.basename(filePath);
72
+ for (const pattern of EXCLUDED_PATTERNS) {
73
+ if (pattern.test(fileName)) {
74
+ return "other";
75
+ }
76
+ }
77
+ for (const { pattern, type } of EXTENSION_PATTERNS) {
78
+ if (pattern.test(fileName)) {
79
+ return type;
80
+ }
81
+ }
82
+ return "other";
83
+ }
84
+
85
+ // src/utils/importParser.ts
86
+ var ts = __toESM(require("typescript"), 1);
87
+
88
+ // src/rules/reactNativePrimitives.ts
89
+ var REACT_NATIVE_SOURCES = [
90
+ "react-native",
91
+ "react-native-web",
92
+ "react-native-gesture-handler",
93
+ "react-native-reanimated",
94
+ "react-native-safe-area-context",
95
+ "react-native-screens",
96
+ "react-native-svg",
97
+ "@react-native-vector-icons/material-design-icons",
98
+ "@react-native-vector-icons/common",
99
+ "@react-native-community/async-storage",
100
+ "@react-native-picker/picker",
101
+ "expo",
102
+ "expo-constants",
103
+ "expo-linking",
104
+ "expo-status-bar"
105
+ ];
106
+ var CORE_PRIMITIVES = [
107
+ {
108
+ name: "View",
109
+ source: "react-native",
110
+ platform: "react-native",
111
+ description: "Basic container component"
112
+ },
113
+ {
114
+ name: "Text",
115
+ source: "react-native",
116
+ platform: "react-native",
117
+ description: "Text display component"
118
+ },
119
+ {
120
+ name: "Image",
121
+ source: "react-native",
122
+ platform: "react-native",
123
+ description: "Image display component"
124
+ },
125
+ {
126
+ name: "ImageBackground",
127
+ source: "react-native",
128
+ platform: "react-native",
129
+ description: "Background image container"
130
+ },
131
+ {
132
+ name: "ScrollView",
133
+ source: "react-native",
134
+ platform: "react-native",
135
+ description: "Scrollable container"
136
+ },
137
+ {
138
+ name: "FlatList",
139
+ source: "react-native",
140
+ platform: "react-native",
141
+ description: "Performant list component"
142
+ },
143
+ {
144
+ name: "SectionList",
145
+ source: "react-native",
146
+ platform: "react-native",
147
+ description: "Sectioned list component"
148
+ },
149
+ {
150
+ name: "VirtualizedList",
151
+ source: "react-native",
152
+ platform: "react-native",
153
+ description: "Base virtualized list"
154
+ }
155
+ ];
156
+ var INTERACTIVE_PRIMITIVES = [
157
+ {
158
+ name: "TouchableOpacity",
159
+ source: "react-native",
160
+ platform: "react-native",
161
+ description: "Touch with opacity feedback"
162
+ },
163
+ {
164
+ name: "TouchableHighlight",
165
+ source: "react-native",
166
+ platform: "react-native",
167
+ description: "Touch with highlight feedback"
168
+ },
169
+ {
170
+ name: "TouchableWithoutFeedback",
171
+ source: "react-native",
172
+ platform: "react-native",
173
+ description: "Touch without visual feedback"
174
+ },
175
+ {
176
+ name: "TouchableNativeFeedback",
177
+ source: "react-native",
178
+ platform: "react-native",
179
+ description: "Android ripple feedback"
180
+ },
181
+ {
182
+ name: "Pressable",
183
+ source: "react-native",
184
+ platform: "react-native",
185
+ description: "Modern press handler component"
186
+ },
187
+ {
188
+ name: "Button",
189
+ source: "react-native",
190
+ platform: "react-native",
191
+ description: "Basic button component"
192
+ }
193
+ ];
194
+ var INPUT_PRIMITIVES = [
195
+ {
196
+ name: "TextInput",
197
+ source: "react-native",
198
+ platform: "react-native",
199
+ description: "Text input field"
200
+ },
201
+ {
202
+ name: "Switch",
203
+ source: "react-native",
204
+ platform: "react-native",
205
+ description: "Toggle switch component"
206
+ },
207
+ {
208
+ name: "Slider",
209
+ source: "react-native",
210
+ platform: "react-native",
211
+ description: "Slider input (deprecated)"
212
+ }
213
+ ];
214
+ var MODAL_PRIMITIVES = [
215
+ {
216
+ name: "Modal",
217
+ source: "react-native",
218
+ platform: "react-native",
219
+ description: "Modal overlay component"
220
+ },
221
+ {
222
+ name: "Alert",
223
+ source: "react-native",
224
+ platform: "react-native",
225
+ description: "Native alert dialog"
226
+ },
227
+ {
228
+ name: "ActionSheetIOS",
229
+ source: "react-native",
230
+ platform: "react-native",
231
+ description: "iOS action sheet"
232
+ },
233
+ {
234
+ name: "StatusBar",
235
+ source: "react-native",
236
+ platform: "react-native",
237
+ description: "Status bar controller"
238
+ }
239
+ ];
240
+ var ANIMATION_PRIMITIVES = [
241
+ {
242
+ name: "Animated",
243
+ source: "react-native",
244
+ platform: "react-native",
245
+ description: "Animation library namespace"
246
+ },
247
+ {
248
+ name: "Easing",
249
+ source: "react-native",
250
+ platform: "react-native",
251
+ description: "Easing functions"
252
+ },
253
+ {
254
+ name: "LayoutAnimation",
255
+ source: "react-native",
256
+ platform: "react-native",
257
+ description: "Layout animation controller"
258
+ }
259
+ ];
260
+ var PLATFORM_PRIMITIVES = [
261
+ {
262
+ name: "Platform",
263
+ source: "react-native",
264
+ platform: "react-native",
265
+ description: "Platform detection utility"
266
+ },
267
+ {
268
+ name: "Dimensions",
269
+ source: "react-native",
270
+ platform: "react-native",
271
+ description: "Screen dimensions utility"
272
+ },
273
+ {
274
+ name: "PixelRatio",
275
+ source: "react-native",
276
+ platform: "react-native",
277
+ description: "Pixel ratio utility"
278
+ },
279
+ {
280
+ name: "Appearance",
281
+ source: "react-native",
282
+ platform: "react-native",
283
+ description: "Color scheme detection"
284
+ },
285
+ {
286
+ name: "useColorScheme",
287
+ source: "react-native",
288
+ platform: "react-native",
289
+ description: "Color scheme hook"
290
+ },
291
+ {
292
+ name: "useWindowDimensions",
293
+ source: "react-native",
294
+ platform: "react-native",
295
+ description: "Window dimensions hook"
296
+ }
297
+ ];
298
+ var EVENT_PRIMITIVES = [
299
+ {
300
+ name: "BackHandler",
301
+ source: "react-native",
302
+ platform: "react-native",
303
+ description: "Android back button handler"
304
+ },
305
+ {
306
+ name: "Keyboard",
307
+ source: "react-native",
308
+ platform: "react-native",
309
+ description: "Keyboard event handler"
310
+ },
311
+ {
312
+ name: "PanResponder",
313
+ source: "react-native",
314
+ platform: "react-native",
315
+ description: "Gesture responder system"
316
+ },
317
+ {
318
+ name: "Linking",
319
+ source: "react-native",
320
+ platform: "react-native",
321
+ description: "Deep linking utility"
322
+ },
323
+ {
324
+ name: "AppState",
325
+ source: "react-native",
326
+ platform: "react-native",
327
+ description: "App lifecycle state"
328
+ }
329
+ ];
330
+ var SAFETY_PRIMITIVES = [
331
+ {
332
+ name: "SafeAreaView",
333
+ source: "react-native",
334
+ platform: "react-native",
335
+ description: "Safe area inset container"
336
+ },
337
+ {
338
+ name: "KeyboardAvoidingView",
339
+ source: "react-native",
340
+ platform: "react-native",
341
+ description: "Keyboard avoidance container"
342
+ }
343
+ ];
344
+ var ACCESSIBILITY_PRIMITIVES = [
345
+ {
346
+ name: "AccessibilityInfo",
347
+ source: "react-native",
348
+ platform: "react-native",
349
+ description: "Accessibility information API"
350
+ }
351
+ ];
352
+ var STYLE_PRIMITIVES = [
353
+ {
354
+ name: "StyleSheet",
355
+ source: "react-native",
356
+ platform: "react-native",
357
+ description: "Style sheet creator"
358
+ }
359
+ ];
360
+ var REACT_NATIVE_PRIMITIVES = [
361
+ ...CORE_PRIMITIVES,
362
+ ...INTERACTIVE_PRIMITIVES,
363
+ ...INPUT_PRIMITIVES,
364
+ ...MODAL_PRIMITIVES,
365
+ ...ANIMATION_PRIMITIVES,
366
+ ...PLATFORM_PRIMITIVES,
367
+ ...EVENT_PRIMITIVES,
368
+ ...SAFETY_PRIMITIVES,
369
+ ...ACCESSIBILITY_PRIMITIVES,
370
+ ...STYLE_PRIMITIVES
371
+ ];
372
+ var REACT_NATIVE_PRIMITIVE_NAMES = new Set(
373
+ REACT_NATIVE_PRIMITIVES.map((p) => p.name)
374
+ );
375
+ var REACT_NATIVE_RULE_SET = {
376
+ platform: "react-native",
377
+ primitives: REACT_NATIVE_PRIMITIVES,
378
+ sources: [...REACT_NATIVE_SOURCES]
379
+ };
380
+ function isReactNativePrimitive(name) {
381
+ return REACT_NATIVE_PRIMITIVE_NAMES.has(name);
382
+ }
383
+
384
+ // src/rules/reactDomPrimitives.ts
385
+ var REACT_DOM_SOURCES = [
386
+ "react-dom",
387
+ "react-dom/client",
388
+ "react-dom/server"
389
+ ];
390
+ var DOM_API_PRIMITIVES = [
391
+ {
392
+ name: "createPortal",
393
+ source: "react-dom",
394
+ platform: "react-dom",
395
+ description: "Render children into a different DOM node"
396
+ },
397
+ {
398
+ name: "flushSync",
399
+ source: "react-dom",
400
+ platform: "react-dom",
401
+ description: "Force synchronous DOM updates"
402
+ },
403
+ {
404
+ name: "createRoot",
405
+ source: "react-dom/client",
406
+ platform: "react-dom",
407
+ description: "Create a React root for rendering"
408
+ },
409
+ {
410
+ name: "hydrateRoot",
411
+ source: "react-dom/client",
412
+ platform: "react-dom",
413
+ description: "Hydrate server-rendered content"
414
+ },
415
+ {
416
+ name: "render",
417
+ source: "react-dom",
418
+ platform: "react-dom",
419
+ description: "Legacy render function (deprecated)"
420
+ },
421
+ {
422
+ name: "hydrate",
423
+ source: "react-dom",
424
+ platform: "react-dom",
425
+ description: "Legacy hydrate function (deprecated)"
426
+ },
427
+ {
428
+ name: "unmountComponentAtNode",
429
+ source: "react-dom",
430
+ platform: "react-dom",
431
+ description: "Legacy unmount function (deprecated)"
432
+ },
433
+ {
434
+ name: "findDOMNode",
435
+ source: "react-dom",
436
+ platform: "react-dom",
437
+ description: "Find DOM node (deprecated)"
438
+ }
439
+ ];
440
+ var HTML_INTRINSIC_ELEMENTS = [
441
+ // Document structure
442
+ "html",
443
+ "head",
444
+ "body",
445
+ "main",
446
+ "header",
447
+ "footer",
448
+ "nav",
449
+ "aside",
450
+ "article",
451
+ "section",
452
+ // Content sectioning
453
+ "div",
454
+ "span",
455
+ "p",
456
+ "h1",
457
+ "h2",
458
+ "h3",
459
+ "h4",
460
+ "h5",
461
+ "h6",
462
+ // Text content
463
+ "a",
464
+ "strong",
465
+ "em",
466
+ "code",
467
+ "pre",
468
+ "blockquote",
469
+ "br",
470
+ "hr",
471
+ // Lists
472
+ "ul",
473
+ "ol",
474
+ "li",
475
+ "dl",
476
+ "dt",
477
+ "dd",
478
+ // Tables
479
+ "table",
480
+ "thead",
481
+ "tbody",
482
+ "tfoot",
483
+ "tr",
484
+ "th",
485
+ "td",
486
+ "caption",
487
+ "colgroup",
488
+ "col",
489
+ // Forms
490
+ "form",
491
+ "input",
492
+ "textarea",
493
+ "select",
494
+ "option",
495
+ "optgroup",
496
+ "button",
497
+ "label",
498
+ "fieldset",
499
+ "legend",
500
+ "datalist",
501
+ "output",
502
+ "progress",
503
+ "meter",
504
+ // Media
505
+ "img",
506
+ "video",
507
+ "audio",
508
+ "source",
509
+ "track",
510
+ "picture",
511
+ "figure",
512
+ "figcaption",
513
+ "canvas",
514
+ "svg",
515
+ "iframe",
516
+ "embed",
517
+ "object",
518
+ // Interactive
519
+ "details",
520
+ "summary",
521
+ "dialog",
522
+ "menu",
523
+ // Scripting
524
+ "script",
525
+ "noscript",
526
+ "template",
527
+ "slot"
528
+ ];
529
+ var REACT_DOM_PRIMITIVES = [...DOM_API_PRIMITIVES];
530
+ var REACT_DOM_PRIMITIVE_NAMES = new Set(
531
+ REACT_DOM_PRIMITIVES.map((p) => p.name)
532
+ );
533
+ var HTML_ELEMENT_NAMES = new Set(HTML_INTRINSIC_ELEMENTS);
534
+ var REACT_DOM_RULE_SET = {
535
+ platform: "react-dom",
536
+ primitives: REACT_DOM_PRIMITIVES,
537
+ sources: [...REACT_DOM_SOURCES]
538
+ };
539
+ function isReactDomPrimitive(name) {
540
+ return REACT_DOM_PRIMITIVE_NAMES.has(name);
541
+ }
542
+
543
+ // src/utils/importParser.ts
544
+ function getPlatformForSource(source, options) {
545
+ const nativeSources = /* @__PURE__ */ new Set([
546
+ ...REACT_NATIVE_SOURCES,
547
+ ...options?.additionalNativeSources ?? []
548
+ ]);
549
+ const domSources = /* @__PURE__ */ new Set([
550
+ ...REACT_DOM_SOURCES,
551
+ ...options?.additionalDomSources ?? []
552
+ ]);
553
+ if (nativeSources.has(source)) return "react-native";
554
+ if (domSources.has(source)) return "react-dom";
555
+ if (source.startsWith("react-native")) return "react-native";
556
+ if (source.startsWith("react-dom")) return "react-dom";
557
+ return "neutral";
558
+ }
559
+ function parseImports(sourceCode, filePath = "unknown.tsx", options) {
560
+ const imports = [];
561
+ const sourceFile = ts.createSourceFile(
562
+ filePath,
563
+ sourceCode,
564
+ ts.ScriptTarget.Latest,
565
+ true,
566
+ filePath.endsWith(".tsx") || filePath.endsWith(".jsx") ? ts.ScriptKind.TSX : ts.ScriptKind.TS
567
+ );
568
+ const visit = (node) => {
569
+ if (ts.isImportDeclaration(node)) {
570
+ const importInfo = parseImportDeclaration(node, sourceFile, options);
571
+ imports.push(...importInfo);
572
+ }
573
+ if (ts.isCallExpression(node) && ts.isIdentifier(node.expression) && node.expression.text === "require" && node.arguments.length === 1 && ts.isStringLiteral(node.arguments[0])) {
574
+ const source = node.arguments[0].text;
575
+ const { line, character } = sourceFile.getLineAndCharacterOfPosition(
576
+ node.getStart()
577
+ );
578
+ imports.push({
579
+ name: "require",
580
+ source,
581
+ platform: getPlatformForSource(source, options),
582
+ line: line + 1,
583
+ column: character + 1,
584
+ isDefault: false,
585
+ isNamespace: true,
586
+ isTypeOnly: false
587
+ });
588
+ }
589
+ ts.forEachChild(node, visit);
590
+ };
591
+ visit(sourceFile);
592
+ return imports;
593
+ }
594
+ function parseImportDeclaration(node, sourceFile, options) {
595
+ const imports = [];
596
+ if (!ts.isStringLiteral(node.moduleSpecifier)) {
597
+ return imports;
598
+ }
599
+ const source = node.moduleSpecifier.text;
600
+ const platform = getPlatformForSource(source, options);
601
+ const isTypeOnly = node.importClause?.isTypeOnly ?? false;
602
+ const importClause = node.importClause;
603
+ if (!importClause) {
604
+ return imports;
605
+ }
606
+ if (importClause.name) {
607
+ const { line, character } = sourceFile.getLineAndCharacterOfPosition(
608
+ importClause.name.getStart()
609
+ );
610
+ imports.push({
611
+ name: importClause.name.text,
612
+ source,
613
+ platform,
614
+ line: line + 1,
615
+ column: character + 1,
616
+ isDefault: true,
617
+ isNamespace: false,
618
+ isTypeOnly
619
+ });
620
+ }
621
+ const namedBindings = importClause.namedBindings;
622
+ if (namedBindings) {
623
+ if (ts.isNamespaceImport(namedBindings)) {
624
+ const { line, character } = sourceFile.getLineAndCharacterOfPosition(
625
+ namedBindings.name.getStart()
626
+ );
627
+ imports.push({
628
+ name: namedBindings.name.text,
629
+ source,
630
+ platform,
631
+ line: line + 1,
632
+ column: character + 1,
633
+ isDefault: false,
634
+ isNamespace: true,
635
+ isTypeOnly
636
+ });
637
+ } else if (ts.isNamedImports(namedBindings)) {
638
+ for (const element of namedBindings.elements) {
639
+ const { line, character } = sourceFile.getLineAndCharacterOfPosition(
640
+ element.name.getStart()
641
+ );
642
+ const importedName = element.propertyName?.text ?? element.name.text;
643
+ const localName = element.name.text;
644
+ imports.push({
645
+ name: localName,
646
+ originalName: element.propertyName ? importedName : void 0,
647
+ source,
648
+ platform,
649
+ line: line + 1,
650
+ column: character + 1,
651
+ isDefault: false,
652
+ isNamespace: false,
653
+ isTypeOnly: isTypeOnly || element.isTypeOnly
654
+ });
655
+ }
656
+ }
657
+ }
658
+ return imports;
659
+ }
660
+
661
+ // src/analyzers/platformImports.ts
662
+ var DEFAULT_OPTIONS = {
663
+ severity: "error",
664
+ additionalNativePrimitives: [],
665
+ additionalDomPrimitives: [],
666
+ ignoredPrimitives: [],
667
+ ignoredPatterns: [],
668
+ additionalNativeSources: [],
669
+ additionalDomSources: []
670
+ };
671
+ function matchesIgnoredPattern(filePath, patterns) {
672
+ if (patterns.length === 0) return false;
673
+ for (const pattern of patterns) {
674
+ const regexPattern = pattern.replace(/\*\*/g, "{{GLOBSTAR}}").replace(/\*/g, "[^/]*").replace(/{{GLOBSTAR}}/g, ".*").replace(/\?/g, ".");
675
+ const regex = new RegExp(regexPattern);
676
+ if (regex.test(filePath)) {
677
+ return true;
678
+ }
679
+ }
680
+ return false;
681
+ }
682
+ function createViolation(type, primitive, source, filePath, line, column, severity) {
683
+ const messages = {
684
+ "native-in-shared": `React Native primitive '${primitive}' from '${source}' should not be used in shared files. Use a .native.tsx file instead.`,
685
+ "dom-in-shared": `React DOM primitive '${primitive}' from '${source}' should not be used in shared files. Use a .web.tsx file instead.`,
686
+ "native-in-web": `React Native primitive '${primitive}' from '${source}' should not be used in web-specific files.`,
687
+ "dom-in-native": `React DOM primitive '${primitive}' from '${source}' should not be used in native-specific files.`
688
+ };
689
+ return {
690
+ type,
691
+ primitive,
692
+ source,
693
+ filePath,
694
+ line,
695
+ column,
696
+ message: messages[type],
697
+ severity
698
+ };
699
+ }
700
+ function isNativePrimitive(importInfo, additionalPrimitives) {
701
+ const name = importInfo.originalName ?? importInfo.name;
702
+ if (isReactNativePrimitive(name)) return true;
703
+ if (additionalPrimitives.includes(name)) return true;
704
+ const nativeSources = /* @__PURE__ */ new Set([...REACT_NATIVE_SOURCES]);
705
+ if (nativeSources.has(importInfo.source)) {
706
+ if (/^[A-Z]/.test(name)) return true;
707
+ }
708
+ return false;
709
+ }
710
+ function isDomPrimitive(importInfo, additionalPrimitives) {
711
+ const name = importInfo.originalName ?? importInfo.name;
712
+ if (isReactDomPrimitive(name)) return true;
713
+ if (additionalPrimitives.includes(name)) return true;
714
+ return false;
715
+ }
716
+ function analyzePlatformImports(filePath, sourceCode, options) {
717
+ const opts = { ...DEFAULT_OPTIONS, ...options };
718
+ const fileType = classifyFile(filePath);
719
+ const violations = [];
720
+ if (matchesIgnoredPattern(filePath, opts.ignoredPatterns)) {
721
+ return {
722
+ filePath,
723
+ fileType,
724
+ violations: [],
725
+ imports: [],
726
+ passed: true
727
+ };
728
+ }
729
+ if (fileType === "other" || fileType === "styles" || fileType === "types") {
730
+ return {
731
+ filePath,
732
+ fileType,
733
+ violations: [],
734
+ imports: [],
735
+ passed: true
736
+ };
737
+ }
738
+ const imports = parseImports(sourceCode, filePath, {
739
+ additionalNativeSources: opts.additionalNativeSources,
740
+ additionalDomSources: opts.additionalDomSources
741
+ });
742
+ const ignoredPrimitives = new Set(opts.ignoredPrimitives);
743
+ for (const imp of imports) {
744
+ if (imp.isTypeOnly) continue;
745
+ const primitiveName = imp.originalName ?? imp.name;
746
+ if (ignoredPrimitives.has(primitiveName)) continue;
747
+ switch (fileType) {
748
+ case "shared":
749
+ if (isNativePrimitive(imp, opts.additionalNativePrimitives)) {
750
+ violations.push(
751
+ createViolation(
752
+ "native-in-shared",
753
+ primitiveName,
754
+ imp.source,
755
+ filePath,
756
+ imp.line,
757
+ imp.column,
758
+ opts.severity
759
+ )
760
+ );
761
+ }
762
+ if (isDomPrimitive(imp, opts.additionalDomPrimitives)) {
763
+ violations.push(
764
+ createViolation(
765
+ "dom-in-shared",
766
+ primitiveName,
767
+ imp.source,
768
+ filePath,
769
+ imp.line,
770
+ imp.column,
771
+ opts.severity
772
+ )
773
+ );
774
+ }
775
+ break;
776
+ case "web":
777
+ if (isNativePrimitive(imp, opts.additionalNativePrimitives)) {
778
+ violations.push(
779
+ createViolation(
780
+ "native-in-web",
781
+ primitiveName,
782
+ imp.source,
783
+ filePath,
784
+ imp.line,
785
+ imp.column,
786
+ opts.severity
787
+ )
788
+ );
789
+ }
790
+ break;
791
+ case "native":
792
+ if (isDomPrimitive(imp, opts.additionalDomPrimitives)) {
793
+ violations.push(
794
+ createViolation(
795
+ "dom-in-native",
796
+ primitiveName,
797
+ imp.source,
798
+ filePath,
799
+ imp.line,
800
+ imp.column,
801
+ opts.severity
802
+ )
803
+ );
804
+ }
805
+ break;
806
+ }
807
+ }
808
+ return {
809
+ filePath,
810
+ fileType,
811
+ violations,
812
+ imports,
813
+ passed: violations.length === 0
814
+ };
815
+ }
816
+ function analyzeFiles(files, options) {
817
+ return files.map(
818
+ (file) => analyzePlatformImports(file.path, file.content, options)
819
+ );
820
+ }
821
+ function summarizeResults(results) {
822
+ const violationsByType = {
823
+ "native-in-shared": 0,
824
+ "dom-in-shared": 0,
825
+ "native-in-web": 0,
826
+ "dom-in-native": 0
827
+ };
828
+ const violationsBySeverity = {
829
+ error: 0,
830
+ warning: 0,
831
+ info: 0
832
+ };
833
+ let totalViolations = 0;
834
+ for (const result of results) {
835
+ for (const violation of result.violations) {
836
+ totalViolations++;
837
+ violationsByType[violation.type]++;
838
+ violationsBySeverity[violation.severity]++;
839
+ }
840
+ }
841
+ return {
842
+ totalFiles: results.length,
843
+ passedFiles: results.filter((r) => r.passed).length,
844
+ failedFiles: results.filter((r) => !r.passed).length,
845
+ totalViolations,
846
+ violationsByType,
847
+ violationsBySeverity
848
+ };
849
+ }
850
+ function formatViolation(violation) {
851
+ const severityPrefix = violation.severity === "error" ? "ERROR" : violation.severity === "warning" ? "WARN" : "INFO";
852
+ return `${severityPrefix}: ${violation.filePath}:${violation.line}:${violation.column} - ${violation.message}`;
853
+ }
854
+ function formatViolations(results) {
855
+ const lines = [];
856
+ for (const result of results) {
857
+ for (const violation of result.violations) {
858
+ lines.push(formatViolation(violation));
859
+ }
860
+ }
861
+ return lines;
862
+ }
863
+
864
+ // src/analyzers/componentLinter.ts
865
+ var CSS_COLOR_NAMES = /* @__PURE__ */ new Set([
866
+ "aliceblue",
867
+ "antiquewhite",
868
+ "aqua",
869
+ "aquamarine",
870
+ "azure",
871
+ "beige",
872
+ "bisque",
873
+ "black",
874
+ "blanchedalmond",
875
+ "blue",
876
+ "blueviolet",
877
+ "brown",
878
+ "burlywood",
879
+ "cadetblue",
880
+ "chartreuse",
881
+ "chocolate",
882
+ "coral",
883
+ "cornflowerblue",
884
+ "cornsilk",
885
+ "crimson",
886
+ "cyan",
887
+ "darkblue",
888
+ "darkcyan",
889
+ "darkgoldenrod",
890
+ "darkgray",
891
+ "darkgreen",
892
+ "darkgrey",
893
+ "darkkhaki",
894
+ "darkmagenta",
895
+ "darkolivegreen",
896
+ "darkorange",
897
+ "darkorchid",
898
+ "darkred",
899
+ "darksalmon",
900
+ "darkseagreen",
901
+ "darkslateblue",
902
+ "darkslategray",
903
+ "darkslategrey",
904
+ "darkturquoise",
905
+ "darkviolet",
906
+ "deeppink",
907
+ "deepskyblue",
908
+ "dimgray",
909
+ "dimgrey",
910
+ "dodgerblue",
911
+ "firebrick",
912
+ "floralwhite",
913
+ "forestgreen",
914
+ "fuchsia",
915
+ "gainsboro",
916
+ "ghostwhite",
917
+ "gold",
918
+ "goldenrod",
919
+ "gray",
920
+ "green",
921
+ "greenyellow",
922
+ "grey",
923
+ "honeydew",
924
+ "hotpink",
925
+ "indianred",
926
+ "indigo",
927
+ "ivory",
928
+ "khaki",
929
+ "lavender",
930
+ "lavenderblush",
931
+ "lawngreen",
932
+ "lemonchiffon",
933
+ "lightblue",
934
+ "lightcoral",
935
+ "lightcyan",
936
+ "lightgoldenrodyellow",
937
+ "lightgray",
938
+ "lightgreen",
939
+ "lightgrey",
940
+ "lightpink",
941
+ "lightsalmon",
942
+ "lightseagreen",
943
+ "lightskyblue",
944
+ "lightslategray",
945
+ "lightslategrey",
946
+ "lightsteelblue",
947
+ "lightyellow",
948
+ "lime",
949
+ "limegreen",
950
+ "linen",
951
+ "magenta",
952
+ "maroon",
953
+ "mediumaquamarine",
954
+ "mediumblue",
955
+ "mediumorchid",
956
+ "mediumpurple",
957
+ "mediumseagreen",
958
+ "mediumslateblue",
959
+ "mediumspringgreen",
960
+ "mediumturquoise",
961
+ "mediumvioletred",
962
+ "midnightblue",
963
+ "mintcream",
964
+ "mistyrose",
965
+ "moccasin",
966
+ "navajowhite",
967
+ "navy",
968
+ "oldlace",
969
+ "olive",
970
+ "olivedrab",
971
+ "orange",
972
+ "orangered",
973
+ "orchid",
974
+ "palegoldenrod",
975
+ "palegreen",
976
+ "paleturquoise",
977
+ "palevioletred",
978
+ "papayawhip",
979
+ "peachpuff",
980
+ "peru",
981
+ "pink",
982
+ "plum",
983
+ "powderblue",
984
+ "purple",
985
+ "rebeccapurple",
986
+ "red",
987
+ "rosybrown",
988
+ "royalblue",
989
+ "saddlebrown",
990
+ "salmon",
991
+ "sandybrown",
992
+ "seagreen",
993
+ "seashell",
994
+ "sienna",
995
+ "silver",
996
+ "skyblue",
997
+ "slateblue",
998
+ "slategray",
999
+ "slategrey",
1000
+ "snow",
1001
+ "springgreen",
1002
+ "steelblue",
1003
+ "tan",
1004
+ "teal",
1005
+ "thistle",
1006
+ "tomato",
1007
+ "turquoise",
1008
+ "violet",
1009
+ "wheat",
1010
+ "white",
1011
+ "whitesmoke",
1012
+ "yellow",
1013
+ "yellowgreen"
1014
+ ]);
1015
+ var DEFAULT_ALLOWED_COLORS = /* @__PURE__ */ new Set([
1016
+ "transparent",
1017
+ "inherit",
1018
+ "currentColor",
1019
+ "currentcolor"
1020
+ ]);
1021
+ var COLOR_PROPERTIES = [
1022
+ "color",
1023
+ "backgroundColor",
1024
+ "borderColor",
1025
+ "borderTopColor",
1026
+ "borderRightColor",
1027
+ "borderBottomColor",
1028
+ "borderLeftColor",
1029
+ "shadowColor",
1030
+ "textDecorationColor",
1031
+ "tintColor",
1032
+ "overlayColor"
1033
+ ];
1034
+ function isHardcodedColor(value, allowedColors) {
1035
+ const trimmed = value.trim().toLowerCase();
1036
+ if (allowedColors.has(trimmed)) {
1037
+ return false;
1038
+ }
1039
+ if (/^#[0-9a-f]{3,8}$/i.test(trimmed)) {
1040
+ return true;
1041
+ }
1042
+ if (/^rgba?\s*\(/.test(trimmed)) {
1043
+ return true;
1044
+ }
1045
+ if (/^hsla?\s*\(/.test(trimmed)) {
1046
+ return true;
1047
+ }
1048
+ if (CSS_COLOR_NAMES.has(trimmed)) {
1049
+ return true;
1050
+ }
1051
+ return false;
1052
+ }
1053
+ function getRuleSeverity(rule, defaultSeverity) {
1054
+ if (rule === false) return null;
1055
+ if (rule === true || rule === void 0) return defaultSeverity;
1056
+ return rule;
1057
+ }
1058
+ function getLineColumn(source, index) {
1059
+ const lines = source.substring(0, index).split("\n");
1060
+ return {
1061
+ line: lines.length,
1062
+ column: lines[lines.length - 1].length + 1
1063
+ };
1064
+ }
1065
+ function lintComponent(filePath, sourceCode, options = {}) {
1066
+ const issues = [];
1067
+ const rules = options.rules || {};
1068
+ const allowedColors = /* @__PURE__ */ new Set([
1069
+ ...DEFAULT_ALLOWED_COLORS,
1070
+ ...(options.allowedColors || []).map((c) => c.toLowerCase())
1071
+ ]);
1072
+ const hardcodedColorSeverity = getRuleSeverity(rules.hardcodedColors, "warning");
1073
+ if (hardcodedColorSeverity) {
1074
+ for (const prop of COLOR_PROPERTIES) {
1075
+ const propRegex = new RegExp(`${prop}\\s*:\\s*['"]([^'"]+)['"]`, "g");
1076
+ let match;
1077
+ while ((match = propRegex.exec(sourceCode)) !== null) {
1078
+ const colorValue = match[1];
1079
+ if (isHardcodedColor(colorValue, allowedColors)) {
1080
+ const { line, column } = getLineColumn(sourceCode, match.index);
1081
+ issues.push({
1082
+ type: "hardcoded-color",
1083
+ severity: hardcodedColorSeverity,
1084
+ line,
1085
+ column,
1086
+ code: match[0],
1087
+ message: `Hardcoded color '${colorValue}' in ${prop}. Use theme colors instead.`,
1088
+ suggestion: `Use theme.colors.*, theme.intents[intent].*, or pass color via props`
1089
+ });
1090
+ }
1091
+ }
1092
+ }
1093
+ const templateColorRegex = /(?:color|backgroundColor)\s*:\s*`[^`]*#[0-9a-fA-F]{3,8}[^`]*`/g;
1094
+ let templateMatch;
1095
+ while ((templateMatch = templateColorRegex.exec(sourceCode)) !== null) {
1096
+ const { line, column } = getLineColumn(sourceCode, templateMatch.index);
1097
+ issues.push({
1098
+ type: "hardcoded-color",
1099
+ severity: hardcodedColorSeverity,
1100
+ line,
1101
+ column,
1102
+ code: templateMatch[0],
1103
+ message: `Hardcoded hex color in template literal. Use theme colors instead.`,
1104
+ suggestion: `Use theme.colors.* or theme.intents[intent].*`
1105
+ });
1106
+ }
1107
+ }
1108
+ const directPlatformSeverity = getRuleSeverity(rules.directPlatformImports, "warning");
1109
+ if (directPlatformSeverity) {
1110
+ const isSharedFile = !filePath.includes(".web.") && !filePath.includes(".native.");
1111
+ if (isSharedFile) {
1112
+ const rnImportRegex = /import\s+.*\s+from\s+['"]react-native['"]/g;
1113
+ let match;
1114
+ while ((match = rnImportRegex.exec(sourceCode)) !== null) {
1115
+ const { line, column } = getLineColumn(sourceCode, match.index);
1116
+ issues.push({
1117
+ type: "direct-platform-import",
1118
+ severity: directPlatformSeverity,
1119
+ line,
1120
+ column,
1121
+ code: match[0],
1122
+ message: `Direct import from 'react-native' in shared file. Use @idealyst/components instead.`,
1123
+ suggestion: `Import View, Text, etc. from '@idealyst/components'`
1124
+ });
1125
+ }
1126
+ }
1127
+ }
1128
+ const counts = {
1129
+ error: issues.filter((i) => i.severity === "error").length,
1130
+ warning: issues.filter((i) => i.severity === "warning").length,
1131
+ info: issues.filter((i) => i.severity === "info").length
1132
+ };
1133
+ return {
1134
+ filePath,
1135
+ issues,
1136
+ passed: counts.error === 0,
1137
+ counts
1138
+ };
1139
+ }
1140
+ function lintComponents(files, options = {}) {
1141
+ return files.map((file) => lintComponent(file.path, file.content, options));
1142
+ }
1143
+ function formatLintIssue(issue, filePath) {
1144
+ const severityPrefix = issue.severity === "error" ? "ERROR" : issue.severity === "warning" ? "WARN" : "INFO";
1145
+ let output = `${severityPrefix}: ${filePath}:${issue.line}:${issue.column} - ${issue.message}`;
1146
+ if (issue.suggestion) {
1147
+ output += `
1148
+ Suggestion: ${issue.suggestion}`;
1149
+ }
1150
+ return output;
1151
+ }
1152
+ function formatLintResults(results) {
1153
+ const lines = [];
1154
+ for (const result of results) {
1155
+ for (const issue of result.issues) {
1156
+ lines.push(formatLintIssue(issue, result.filePath));
1157
+ }
1158
+ }
1159
+ return lines;
1160
+ }
1161
+ function summarizeLintResults(results) {
1162
+ const issuesByType = {
1163
+ "hardcoded-color": 0,
1164
+ "direct-platform-import": 0
1165
+ };
1166
+ const issuesBySeverity = {
1167
+ error: 0,
1168
+ warning: 0,
1169
+ info: 0
1170
+ };
1171
+ let totalIssues = 0;
1172
+ for (const result of results) {
1173
+ for (const issue of result.issues) {
1174
+ totalIssues++;
1175
+ issuesByType[issue.type]++;
1176
+ issuesBySeverity[issue.severity]++;
1177
+ }
1178
+ }
1179
+ return {
1180
+ totalFiles: results.length,
1181
+ passedFiles: results.filter((r) => r.passed).length,
1182
+ failedFiles: results.filter((r) => !r.passed).length,
1183
+ totalIssues,
1184
+ issuesByType,
1185
+ issuesBySeverity
1186
+ };
1187
+ }
1188
+ // Annotate the CommonJS export names for ESM import in node:
1189
+ 0 && (module.exports = {
1190
+ analyzeFiles,
1191
+ analyzePlatformImports,
1192
+ formatLintIssue,
1193
+ formatLintResults,
1194
+ formatViolation,
1195
+ formatViolations,
1196
+ lintComponent,
1197
+ lintComponents,
1198
+ summarizeLintResults,
1199
+ summarizeResults
1200
+ });
1201
+ //# sourceMappingURL=index.cjs.map