@idealyst/tooling 1.2.24 → 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.
@@ -0,0 +1,662 @@
1
+ // src/utils/fileClassifier.ts
2
+ import * as path from "path";
3
+ var EXTENSION_PATTERNS = [
4
+ // Platform-specific component files
5
+ { pattern: /\.web\.(tsx|jsx)$/, type: "web" },
6
+ { pattern: /\.native\.(tsx|jsx)$/, type: "native" },
7
+ { pattern: /\.ios\.(tsx|jsx)$/, type: "native" },
8
+ { pattern: /\.android\.(tsx|jsx)$/, type: "native" },
9
+ // Style files (can be .ts or .tsx)
10
+ { pattern: /\.styles?\.(tsx?|jsx?)$/, type: "styles" },
11
+ // Type definition files
12
+ { pattern: /\.types?\.(ts|tsx)$/, type: "types" },
13
+ { pattern: /types\.(ts|tsx)$/, type: "types" },
14
+ { pattern: /\.d\.ts$/, type: "types" },
15
+ // Shared component files (generic .tsx/.jsx without platform suffix)
16
+ { pattern: /\.(tsx|jsx)$/, type: "shared" }
17
+ ];
18
+ var EXCLUDED_PATTERNS = [
19
+ /\.test\.(tsx?|jsx?)$/,
20
+ /\.spec\.(tsx?|jsx?)$/,
21
+ /\.stories\.(tsx?|jsx?)$/,
22
+ /\.config\.(ts|js)$/,
23
+ /index\.(ts|tsx|js|jsx)$/
24
+ ];
25
+ function classifyFile(filePath) {
26
+ const fileName = path.basename(filePath);
27
+ for (const pattern of EXCLUDED_PATTERNS) {
28
+ if (pattern.test(fileName)) {
29
+ return "other";
30
+ }
31
+ }
32
+ for (const { pattern, type } of EXTENSION_PATTERNS) {
33
+ if (pattern.test(fileName)) {
34
+ return type;
35
+ }
36
+ }
37
+ return "other";
38
+ }
39
+ function isComponentFile(filePath) {
40
+ const fileType = classifyFile(filePath);
41
+ return fileType === "shared" || fileType === "web" || fileType === "native";
42
+ }
43
+ function isSharedFile(filePath) {
44
+ return classifyFile(filePath) === "shared";
45
+ }
46
+ function isPlatformSpecificFile(filePath) {
47
+ const fileType = classifyFile(filePath);
48
+ return fileType === "web" || fileType === "native";
49
+ }
50
+ function getExpectedPlatform(filePath) {
51
+ const fileType = classifyFile(filePath);
52
+ if (fileType === "web") return "web";
53
+ if (fileType === "native") return "native";
54
+ return null;
55
+ }
56
+ function getBaseName(filePath) {
57
+ const fileName = path.basename(filePath);
58
+ 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)$/, "");
59
+ }
60
+
61
+ // src/utils/importParser.ts
62
+ import * as ts from "typescript";
63
+
64
+ // src/rules/reactNativePrimitives.ts
65
+ var REACT_NATIVE_SOURCES = [
66
+ "react-native",
67
+ "react-native-web",
68
+ "react-native-gesture-handler",
69
+ "react-native-reanimated",
70
+ "react-native-safe-area-context",
71
+ "react-native-screens",
72
+ "react-native-svg",
73
+ "@react-native-vector-icons/material-design-icons",
74
+ "@react-native-vector-icons/common",
75
+ "@react-native-community/async-storage",
76
+ "@react-native-picker/picker",
77
+ "expo",
78
+ "expo-constants",
79
+ "expo-linking",
80
+ "expo-status-bar"
81
+ ];
82
+ var CORE_PRIMITIVES = [
83
+ {
84
+ name: "View",
85
+ source: "react-native",
86
+ platform: "react-native",
87
+ description: "Basic container component"
88
+ },
89
+ {
90
+ name: "Text",
91
+ source: "react-native",
92
+ platform: "react-native",
93
+ description: "Text display component"
94
+ },
95
+ {
96
+ name: "Image",
97
+ source: "react-native",
98
+ platform: "react-native",
99
+ description: "Image display component"
100
+ },
101
+ {
102
+ name: "ImageBackground",
103
+ source: "react-native",
104
+ platform: "react-native",
105
+ description: "Background image container"
106
+ },
107
+ {
108
+ name: "ScrollView",
109
+ source: "react-native",
110
+ platform: "react-native",
111
+ description: "Scrollable container"
112
+ },
113
+ {
114
+ name: "FlatList",
115
+ source: "react-native",
116
+ platform: "react-native",
117
+ description: "Performant list component"
118
+ },
119
+ {
120
+ name: "SectionList",
121
+ source: "react-native",
122
+ platform: "react-native",
123
+ description: "Sectioned list component"
124
+ },
125
+ {
126
+ name: "VirtualizedList",
127
+ source: "react-native",
128
+ platform: "react-native",
129
+ description: "Base virtualized list"
130
+ }
131
+ ];
132
+ var INTERACTIVE_PRIMITIVES = [
133
+ {
134
+ name: "TouchableOpacity",
135
+ source: "react-native",
136
+ platform: "react-native",
137
+ description: "Touch with opacity feedback"
138
+ },
139
+ {
140
+ name: "TouchableHighlight",
141
+ source: "react-native",
142
+ platform: "react-native",
143
+ description: "Touch with highlight feedback"
144
+ },
145
+ {
146
+ name: "TouchableWithoutFeedback",
147
+ source: "react-native",
148
+ platform: "react-native",
149
+ description: "Touch without visual feedback"
150
+ },
151
+ {
152
+ name: "TouchableNativeFeedback",
153
+ source: "react-native",
154
+ platform: "react-native",
155
+ description: "Android ripple feedback"
156
+ },
157
+ {
158
+ name: "Pressable",
159
+ source: "react-native",
160
+ platform: "react-native",
161
+ description: "Modern press handler component"
162
+ },
163
+ {
164
+ name: "Button",
165
+ source: "react-native",
166
+ platform: "react-native",
167
+ description: "Basic button component"
168
+ }
169
+ ];
170
+ var INPUT_PRIMITIVES = [
171
+ {
172
+ name: "TextInput",
173
+ source: "react-native",
174
+ platform: "react-native",
175
+ description: "Text input field"
176
+ },
177
+ {
178
+ name: "Switch",
179
+ source: "react-native",
180
+ platform: "react-native",
181
+ description: "Toggle switch component"
182
+ },
183
+ {
184
+ name: "Slider",
185
+ source: "react-native",
186
+ platform: "react-native",
187
+ description: "Slider input (deprecated)"
188
+ }
189
+ ];
190
+ var MODAL_PRIMITIVES = [
191
+ {
192
+ name: "Modal",
193
+ source: "react-native",
194
+ platform: "react-native",
195
+ description: "Modal overlay component"
196
+ },
197
+ {
198
+ name: "Alert",
199
+ source: "react-native",
200
+ platform: "react-native",
201
+ description: "Native alert dialog"
202
+ },
203
+ {
204
+ name: "ActionSheetIOS",
205
+ source: "react-native",
206
+ platform: "react-native",
207
+ description: "iOS action sheet"
208
+ },
209
+ {
210
+ name: "StatusBar",
211
+ source: "react-native",
212
+ platform: "react-native",
213
+ description: "Status bar controller"
214
+ }
215
+ ];
216
+ var ANIMATION_PRIMITIVES = [
217
+ {
218
+ name: "Animated",
219
+ source: "react-native",
220
+ platform: "react-native",
221
+ description: "Animation library namespace"
222
+ },
223
+ {
224
+ name: "Easing",
225
+ source: "react-native",
226
+ platform: "react-native",
227
+ description: "Easing functions"
228
+ },
229
+ {
230
+ name: "LayoutAnimation",
231
+ source: "react-native",
232
+ platform: "react-native",
233
+ description: "Layout animation controller"
234
+ }
235
+ ];
236
+ var PLATFORM_PRIMITIVES = [
237
+ {
238
+ name: "Platform",
239
+ source: "react-native",
240
+ platform: "react-native",
241
+ description: "Platform detection utility"
242
+ },
243
+ {
244
+ name: "Dimensions",
245
+ source: "react-native",
246
+ platform: "react-native",
247
+ description: "Screen dimensions utility"
248
+ },
249
+ {
250
+ name: "PixelRatio",
251
+ source: "react-native",
252
+ platform: "react-native",
253
+ description: "Pixel ratio utility"
254
+ },
255
+ {
256
+ name: "Appearance",
257
+ source: "react-native",
258
+ platform: "react-native",
259
+ description: "Color scheme detection"
260
+ },
261
+ {
262
+ name: "useColorScheme",
263
+ source: "react-native",
264
+ platform: "react-native",
265
+ description: "Color scheme hook"
266
+ },
267
+ {
268
+ name: "useWindowDimensions",
269
+ source: "react-native",
270
+ platform: "react-native",
271
+ description: "Window dimensions hook"
272
+ }
273
+ ];
274
+ var EVENT_PRIMITIVES = [
275
+ {
276
+ name: "BackHandler",
277
+ source: "react-native",
278
+ platform: "react-native",
279
+ description: "Android back button handler"
280
+ },
281
+ {
282
+ name: "Keyboard",
283
+ source: "react-native",
284
+ platform: "react-native",
285
+ description: "Keyboard event handler"
286
+ },
287
+ {
288
+ name: "PanResponder",
289
+ source: "react-native",
290
+ platform: "react-native",
291
+ description: "Gesture responder system"
292
+ },
293
+ {
294
+ name: "Linking",
295
+ source: "react-native",
296
+ platform: "react-native",
297
+ description: "Deep linking utility"
298
+ },
299
+ {
300
+ name: "AppState",
301
+ source: "react-native",
302
+ platform: "react-native",
303
+ description: "App lifecycle state"
304
+ }
305
+ ];
306
+ var SAFETY_PRIMITIVES = [
307
+ {
308
+ name: "SafeAreaView",
309
+ source: "react-native",
310
+ platform: "react-native",
311
+ description: "Safe area inset container"
312
+ },
313
+ {
314
+ name: "KeyboardAvoidingView",
315
+ source: "react-native",
316
+ platform: "react-native",
317
+ description: "Keyboard avoidance container"
318
+ }
319
+ ];
320
+ var ACCESSIBILITY_PRIMITIVES = [
321
+ {
322
+ name: "AccessibilityInfo",
323
+ source: "react-native",
324
+ platform: "react-native",
325
+ description: "Accessibility information API"
326
+ }
327
+ ];
328
+ var STYLE_PRIMITIVES = [
329
+ {
330
+ name: "StyleSheet",
331
+ source: "react-native",
332
+ platform: "react-native",
333
+ description: "Style sheet creator"
334
+ }
335
+ ];
336
+ var REACT_NATIVE_PRIMITIVES = [
337
+ ...CORE_PRIMITIVES,
338
+ ...INTERACTIVE_PRIMITIVES,
339
+ ...INPUT_PRIMITIVES,
340
+ ...MODAL_PRIMITIVES,
341
+ ...ANIMATION_PRIMITIVES,
342
+ ...PLATFORM_PRIMITIVES,
343
+ ...EVENT_PRIMITIVES,
344
+ ...SAFETY_PRIMITIVES,
345
+ ...ACCESSIBILITY_PRIMITIVES,
346
+ ...STYLE_PRIMITIVES
347
+ ];
348
+ var REACT_NATIVE_PRIMITIVE_NAMES = new Set(
349
+ REACT_NATIVE_PRIMITIVES.map((p) => p.name)
350
+ );
351
+ var REACT_NATIVE_RULE_SET = {
352
+ platform: "react-native",
353
+ primitives: REACT_NATIVE_PRIMITIVES,
354
+ sources: [...REACT_NATIVE_SOURCES]
355
+ };
356
+
357
+ // src/rules/reactDomPrimitives.ts
358
+ var REACT_DOM_SOURCES = [
359
+ "react-dom",
360
+ "react-dom/client",
361
+ "react-dom/server"
362
+ ];
363
+ var DOM_API_PRIMITIVES = [
364
+ {
365
+ name: "createPortal",
366
+ source: "react-dom",
367
+ platform: "react-dom",
368
+ description: "Render children into a different DOM node"
369
+ },
370
+ {
371
+ name: "flushSync",
372
+ source: "react-dom",
373
+ platform: "react-dom",
374
+ description: "Force synchronous DOM updates"
375
+ },
376
+ {
377
+ name: "createRoot",
378
+ source: "react-dom/client",
379
+ platform: "react-dom",
380
+ description: "Create a React root for rendering"
381
+ },
382
+ {
383
+ name: "hydrateRoot",
384
+ source: "react-dom/client",
385
+ platform: "react-dom",
386
+ description: "Hydrate server-rendered content"
387
+ },
388
+ {
389
+ name: "render",
390
+ source: "react-dom",
391
+ platform: "react-dom",
392
+ description: "Legacy render function (deprecated)"
393
+ },
394
+ {
395
+ name: "hydrate",
396
+ source: "react-dom",
397
+ platform: "react-dom",
398
+ description: "Legacy hydrate function (deprecated)"
399
+ },
400
+ {
401
+ name: "unmountComponentAtNode",
402
+ source: "react-dom",
403
+ platform: "react-dom",
404
+ description: "Legacy unmount function (deprecated)"
405
+ },
406
+ {
407
+ name: "findDOMNode",
408
+ source: "react-dom",
409
+ platform: "react-dom",
410
+ description: "Find DOM node (deprecated)"
411
+ }
412
+ ];
413
+ var HTML_INTRINSIC_ELEMENTS = [
414
+ // Document structure
415
+ "html",
416
+ "head",
417
+ "body",
418
+ "main",
419
+ "header",
420
+ "footer",
421
+ "nav",
422
+ "aside",
423
+ "article",
424
+ "section",
425
+ // Content sectioning
426
+ "div",
427
+ "span",
428
+ "p",
429
+ "h1",
430
+ "h2",
431
+ "h3",
432
+ "h4",
433
+ "h5",
434
+ "h6",
435
+ // Text content
436
+ "a",
437
+ "strong",
438
+ "em",
439
+ "code",
440
+ "pre",
441
+ "blockquote",
442
+ "br",
443
+ "hr",
444
+ // Lists
445
+ "ul",
446
+ "ol",
447
+ "li",
448
+ "dl",
449
+ "dt",
450
+ "dd",
451
+ // Tables
452
+ "table",
453
+ "thead",
454
+ "tbody",
455
+ "tfoot",
456
+ "tr",
457
+ "th",
458
+ "td",
459
+ "caption",
460
+ "colgroup",
461
+ "col",
462
+ // Forms
463
+ "form",
464
+ "input",
465
+ "textarea",
466
+ "select",
467
+ "option",
468
+ "optgroup",
469
+ "button",
470
+ "label",
471
+ "fieldset",
472
+ "legend",
473
+ "datalist",
474
+ "output",
475
+ "progress",
476
+ "meter",
477
+ // Media
478
+ "img",
479
+ "video",
480
+ "audio",
481
+ "source",
482
+ "track",
483
+ "picture",
484
+ "figure",
485
+ "figcaption",
486
+ "canvas",
487
+ "svg",
488
+ "iframe",
489
+ "embed",
490
+ "object",
491
+ // Interactive
492
+ "details",
493
+ "summary",
494
+ "dialog",
495
+ "menu",
496
+ // Scripting
497
+ "script",
498
+ "noscript",
499
+ "template",
500
+ "slot"
501
+ ];
502
+ var REACT_DOM_PRIMITIVES = [...DOM_API_PRIMITIVES];
503
+ var REACT_DOM_PRIMITIVE_NAMES = new Set(
504
+ REACT_DOM_PRIMITIVES.map((p) => p.name)
505
+ );
506
+ var HTML_ELEMENT_NAMES = new Set(HTML_INTRINSIC_ELEMENTS);
507
+ var REACT_DOM_RULE_SET = {
508
+ platform: "react-dom",
509
+ primitives: REACT_DOM_PRIMITIVES,
510
+ sources: [...REACT_DOM_SOURCES]
511
+ };
512
+
513
+ // src/utils/importParser.ts
514
+ function getPlatformForSource(source, options) {
515
+ const nativeSources = /* @__PURE__ */ new Set([
516
+ ...REACT_NATIVE_SOURCES,
517
+ ...options?.additionalNativeSources ?? []
518
+ ]);
519
+ const domSources = /* @__PURE__ */ new Set([
520
+ ...REACT_DOM_SOURCES,
521
+ ...options?.additionalDomSources ?? []
522
+ ]);
523
+ if (nativeSources.has(source)) return "react-native";
524
+ if (domSources.has(source)) return "react-dom";
525
+ if (source.startsWith("react-native")) return "react-native";
526
+ if (source.startsWith("react-dom")) return "react-dom";
527
+ return "neutral";
528
+ }
529
+ function parseImports(sourceCode, filePath = "unknown.tsx", options) {
530
+ const imports = [];
531
+ const sourceFile = ts.createSourceFile(
532
+ filePath,
533
+ sourceCode,
534
+ ts.ScriptTarget.Latest,
535
+ true,
536
+ filePath.endsWith(".tsx") || filePath.endsWith(".jsx") ? ts.ScriptKind.TSX : ts.ScriptKind.TS
537
+ );
538
+ const visit = (node) => {
539
+ if (ts.isImportDeclaration(node)) {
540
+ const importInfo = parseImportDeclaration(node, sourceFile, options);
541
+ imports.push(...importInfo);
542
+ }
543
+ if (ts.isCallExpression(node) && ts.isIdentifier(node.expression) && node.expression.text === "require" && node.arguments.length === 1 && ts.isStringLiteral(node.arguments[0])) {
544
+ const source = node.arguments[0].text;
545
+ const { line, character } = sourceFile.getLineAndCharacterOfPosition(
546
+ node.getStart()
547
+ );
548
+ imports.push({
549
+ name: "require",
550
+ source,
551
+ platform: getPlatformForSource(source, options),
552
+ line: line + 1,
553
+ column: character + 1,
554
+ isDefault: false,
555
+ isNamespace: true,
556
+ isTypeOnly: false
557
+ });
558
+ }
559
+ ts.forEachChild(node, visit);
560
+ };
561
+ visit(sourceFile);
562
+ return imports;
563
+ }
564
+ function parseImportDeclaration(node, sourceFile, options) {
565
+ const imports = [];
566
+ if (!ts.isStringLiteral(node.moduleSpecifier)) {
567
+ return imports;
568
+ }
569
+ const source = node.moduleSpecifier.text;
570
+ const platform = getPlatformForSource(source, options);
571
+ const isTypeOnly = node.importClause?.isTypeOnly ?? false;
572
+ const importClause = node.importClause;
573
+ if (!importClause) {
574
+ return imports;
575
+ }
576
+ if (importClause.name) {
577
+ const { line, character } = sourceFile.getLineAndCharacterOfPosition(
578
+ importClause.name.getStart()
579
+ );
580
+ imports.push({
581
+ name: importClause.name.text,
582
+ source,
583
+ platform,
584
+ line: line + 1,
585
+ column: character + 1,
586
+ isDefault: true,
587
+ isNamespace: false,
588
+ isTypeOnly
589
+ });
590
+ }
591
+ const namedBindings = importClause.namedBindings;
592
+ if (namedBindings) {
593
+ if (ts.isNamespaceImport(namedBindings)) {
594
+ const { line, character } = sourceFile.getLineAndCharacterOfPosition(
595
+ namedBindings.name.getStart()
596
+ );
597
+ imports.push({
598
+ name: namedBindings.name.text,
599
+ source,
600
+ platform,
601
+ line: line + 1,
602
+ column: character + 1,
603
+ isDefault: false,
604
+ isNamespace: true,
605
+ isTypeOnly
606
+ });
607
+ } else if (ts.isNamedImports(namedBindings)) {
608
+ for (const element of namedBindings.elements) {
609
+ const { line, character } = sourceFile.getLineAndCharacterOfPosition(
610
+ element.name.getStart()
611
+ );
612
+ const importedName = element.propertyName?.text ?? element.name.text;
613
+ const localName = element.name.text;
614
+ imports.push({
615
+ name: localName,
616
+ originalName: element.propertyName ? importedName : void 0,
617
+ source,
618
+ platform,
619
+ line: line + 1,
620
+ column: character + 1,
621
+ isDefault: false,
622
+ isNamespace: false,
623
+ isTypeOnly: isTypeOnly || element.isTypeOnly
624
+ });
625
+ }
626
+ }
627
+ }
628
+ return imports;
629
+ }
630
+ function filterPlatformImports(imports, platform) {
631
+ return imports.filter((imp) => {
632
+ if (imp.platform === "neutral") return false;
633
+ if (platform && imp.platform !== platform) return false;
634
+ return true;
635
+ });
636
+ }
637
+ function getUniqueSources(imports) {
638
+ return [...new Set(imports.map((imp) => imp.source))];
639
+ }
640
+ function groupImportsBySource(imports) {
641
+ const grouped = /* @__PURE__ */ new Map();
642
+ for (const imp of imports) {
643
+ const existing = grouped.get(imp.source) ?? [];
644
+ existing.push(imp);
645
+ grouped.set(imp.source, existing);
646
+ }
647
+ return grouped;
648
+ }
649
+ export {
650
+ classifyFile,
651
+ filterPlatformImports,
652
+ getBaseName,
653
+ getExpectedPlatform,
654
+ getPlatformForSource,
655
+ getUniqueSources,
656
+ groupImportsBySource,
657
+ isComponentFile,
658
+ isPlatformSpecificFile,
659
+ isSharedFile,
660
+ parseImports
661
+ };
662
+ //# sourceMappingURL=index.js.map