@empline/preflight 1.1.29 → 1.1.31

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,14 @@
1
+ #!/usr/bin/env node
2
+ export declare const id = "api/integration-schema-consistency";
3
+ export declare const name = "Integration Schema Consistency";
4
+ export declare const description = "Detects mismatches between UI types and API schemas";
5
+ export declare const category = "api";
6
+ export declare const blocking = true;
7
+ export declare const tags: string[];
8
+ export declare const requires: string[];
9
+ export declare function run(): Promise<{
10
+ success: boolean;
11
+ errors: number;
12
+ warnings: number;
13
+ }>;
14
+ //# sourceMappingURL=integration-schema-consistency.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"integration-schema-consistency.d.ts","sourceRoot":"","sources":["../../../src/checks/api/integration-schema-consistency.ts"],"names":[],"mappings":";AAoBA,eAAO,MAAM,EAAE,uCAAuC,CAAC;AACvD,eAAO,MAAM,IAAI,mCAAmC,CAAC;AACrD,eAAO,MAAM,WAAW,wDAAwD,CAAC;AACjF,eAAO,MAAM,QAAQ,QAAQ,CAAC;AAC9B,eAAO,MAAM,QAAQ,OAAO,CAAC;AAC7B,eAAO,MAAM,IAAI,UAA4C,CAAC;AAC9D,eAAO,MAAM,QAAQ,UAA0B,CAAC;AA6FhD,wBAAsB,GAAG,IAAI,OAAO,CAAC;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,CA4I3F"}
@@ -0,0 +1,230 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ var __importDefault = (this && this.__importDefault) || function (mod) {
4
+ return (mod && mod.__esModule) ? mod : { "default": mod };
5
+ };
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.requires = exports.tags = exports.blocking = exports.category = exports.description = exports.name = exports.id = void 0;
8
+ exports.run = run;
9
+ /**
10
+ * Integration Schema Consistency Preflight
11
+ *
12
+ * Detects mismatches between UI type definitions and API schemas for integration endpoints.
13
+ * Common issues this catches:
14
+ * - Field name mismatches (e.g., UI uses `wooField` but API expects `sourceField`)
15
+ * - Feature flag mismatches (e.g., UI uses `linkProducts` but API expects `exportListings`)
16
+ * - Missing fields in API schemas that UI depends on
17
+ * - Zod transform() calls that drop fields needed by import APIs or UI
18
+ * - Inconsistency between save endpoint schema and import endpoint schema
19
+ *
20
+ * This helps prevent silent data loss where UI sends fields that get stripped by validation.
21
+ */
22
+ const fs_1 = __importDefault(require("fs"));
23
+ const path_1 = __importDefault(require("path"));
24
+ const console_chars_1 = require("../../utils/console-chars");
25
+ // METADATA - Required for plugin loader discovery
26
+ exports.id = "api/integration-schema-consistency";
27
+ exports.name = "Integration Schema Consistency";
28
+ exports.description = "Detects mismatches between UI types and API schemas";
29
+ exports.category = "api";
30
+ exports.blocking = true; // This is a critical issue that can cause data loss
31
+ exports.tags = ["api", "schema", "types", "integration"];
32
+ exports.requires = ["trading-card-system"];
33
+ /**
34
+ * Integration endpoints to check
35
+ */
36
+ const INTEGRATION_CHECKS = [
37
+ {
38
+ name: "WooCommerce",
39
+ uiTypePath: "components/woocommerce/types.ts",
40
+ apiRoutePath: "app/api/store/integrations/woocommerce/route.ts",
41
+ importRoutePath: "app/api/store/integrations/woocommerce/import/route.ts",
42
+ fieldMappings: [
43
+ { uiField: "wooField", apiField: "wooField" },
44
+ { uiField: "supercatchField", apiField: "supercatchField" },
45
+ { uiField: "required", apiField: "required" },
46
+ { uiField: "linkProducts", apiField: "linkProducts" },
47
+ { uiField: "importProducts", apiField: "importProducts" },
48
+ { uiField: "syncInventory", apiField: "syncInventory" },
49
+ { uiField: "syncPrices", apiField: "syncPrices" },
50
+ { uiField: "syncOrders", apiField: "syncOrders" },
51
+ ],
52
+ // These fields must be preserved in any Zod transform, not dropped
53
+ requiredPreservedFields: ["wooField", "supercatchField", "required", "id", "transform"],
54
+ },
55
+ {
56
+ name: "Shopify",
57
+ uiTypePath: "components/shopify/types.ts",
58
+ apiRoutePath: "app/api/store/integrations/shopify/route.ts",
59
+ fieldMappings: [
60
+ { uiField: "importProducts", apiField: "importProducts" },
61
+ { uiField: "syncInventory", apiField: "syncInventory" },
62
+ { uiField: "syncPrices", apiField: "syncPrices" },
63
+ ],
64
+ },
65
+ ];
66
+ /**
67
+ * Check if a Zod schema transform preserves a field
68
+ * Looks for patterns like `.transform((data) => ({ fieldName: ... }))`
69
+ */
70
+ function checkTransformPreservesField(content, fieldName) {
71
+ // Look for transform output that includes the field
72
+ // Pattern: transform((data) => ({ ... fieldName: ... }))
73
+ const transformPattern = /\.transform\s*\(\s*\([^)]*\)\s*=>\s*\(\{([\s\S]*?)\}\)\s*\)/g;
74
+ let match;
75
+ while ((match = transformPattern.exec(content)) !== null) {
76
+ const transformBody = match[1];
77
+ // Check if field is in the transform output
78
+ const fieldPattern = new RegExp(`\\b${fieldName}\\s*:`);
79
+ if (fieldPattern.test(transformBody || "")) {
80
+ return true;
81
+ }
82
+ }
83
+ // If no transform found, assume field is preserved (passthrough)
84
+ if (!content.includes(".transform(")) {
85
+ return true;
86
+ }
87
+ // Check if the field is at least accepted in the schema input
88
+ const acceptsField = new RegExp(`\\b${fieldName}\\s*:`).test(content);
89
+ return acceptsField;
90
+ }
91
+ /**
92
+ * Check if import route expects a field
93
+ */
94
+ function importRouteExpectsField(importContent, fieldName) {
95
+ // Look for fieldName in Zod schema definitions
96
+ const schemaPattern = new RegExp(`\\b${fieldName}\\s*:\\s*z\\.`);
97
+ return schemaPattern.test(importContent);
98
+ }
99
+ async function run() {
100
+ console.log(`\n${console_chars_1.emoji.check} INTEGRATION SCHEMA CONSISTENCY`);
101
+ console.log((0, console_chars_1.createDivider)(65, "heavy"));
102
+ const issues = [];
103
+ for (const check of INTEGRATION_CHECKS) {
104
+ const uiPath = path_1.default.join(process.cwd(), check.uiTypePath);
105
+ const apiPath = path_1.default.join(process.cwd(), check.apiRoutePath);
106
+ const importPath = check.importRoutePath ? path_1.default.join(process.cwd(), check.importRoutePath) : null;
107
+ // Check if files exist
108
+ if (!fs_1.default.existsSync(uiPath)) {
109
+ console.log(` Skipping ${check.name}: UI types file not found`);
110
+ continue;
111
+ }
112
+ if (!fs_1.default.existsSync(apiPath)) {
113
+ console.log(` Skipping ${check.name}: API route file not found`);
114
+ continue;
115
+ }
116
+ const uiContent = fs_1.default.readFileSync(uiPath, "utf-8");
117
+ const apiContent = fs_1.default.readFileSync(apiPath, "utf-8");
118
+ const importContent = importPath && fs_1.default.existsSync(importPath)
119
+ ? fs_1.default.readFileSync(importPath, "utf-8")
120
+ : null;
121
+ console.log(`\n Checking ${check.name}...`);
122
+ // Check field mappings
123
+ for (const mapping of check.fieldMappings) {
124
+ const uiHasField = new RegExp(`\\b${mapping.uiField}\\b`).test(uiContent);
125
+ const apiHasField = new RegExp(`\\b${mapping.apiField}\\b`).test(apiContent);
126
+ if (uiHasField && !apiHasField) {
127
+ issues.push({
128
+ integration: check.name,
129
+ type: "missing_in_api",
130
+ uiField: mapping.uiField,
131
+ apiField: mapping.apiField,
132
+ description: `UI uses '${mapping.uiField}' but API schema doesn't accept '${mapping.apiField}'`,
133
+ });
134
+ }
135
+ // Check for field name mismatch (UI sends different name than API expects)
136
+ if (mapping.uiField !== mapping.apiField) {
137
+ const apiAcceptsBoth = new RegExp(`\\b${mapping.uiField}\\b`).test(apiContent) ||
138
+ new RegExp(`\\b${mapping.apiField}\\b`).test(apiContent);
139
+ if (uiHasField && !apiAcceptsBoth) {
140
+ issues.push({
141
+ integration: check.name,
142
+ type: "field_mismatch",
143
+ uiField: mapping.uiField,
144
+ apiField: mapping.apiField,
145
+ description: `UI sends '${mapping.uiField}' but API may only accept '${mapping.apiField}' - ensure API handles both`,
146
+ });
147
+ }
148
+ }
149
+ }
150
+ // Check for exportListings vs linkProducts mismatch specifically
151
+ const uiHasLinkProducts = /\blinkProducts\b/.test(uiContent);
152
+ const apiHasLinkProducts = /\blinkProducts\b/.test(apiContent);
153
+ const apiHasExportListings = /\bexportListings\b/.test(apiContent);
154
+ if (uiHasLinkProducts && apiHasExportListings && !apiHasLinkProducts) {
155
+ issues.push({
156
+ integration: check.name,
157
+ type: "field_mismatch",
158
+ uiField: "linkProducts",
159
+ apiField: "exportListings",
160
+ description: `UI uses 'linkProducts' but API schema uses 'exportListings' - data will be silently dropped`,
161
+ });
162
+ }
163
+ // Check if required fields are preserved in Zod transforms
164
+ if (check.requiredPreservedFields) {
165
+ for (const field of check.requiredPreservedFields) {
166
+ const uiDefinesField = new RegExp(`\\b${field}\\s*[:\\?]`).test(uiContent);
167
+ if (uiDefinesField && !checkTransformPreservesField(apiContent, field)) {
168
+ issues.push({
169
+ integration: check.name,
170
+ type: "field_dropped_by_transform",
171
+ uiField: field,
172
+ apiField: field,
173
+ description: `Field '${field}' is defined in UI types but may be dropped by Zod transform() in API schema`,
174
+ });
175
+ }
176
+ }
177
+ }
178
+ // Check consistency between save endpoint and import endpoint
179
+ if (importContent && check.requiredPreservedFields) {
180
+ for (const field of check.requiredPreservedFields) {
181
+ const importExpects = importRouteExpectsField(importContent, field);
182
+ const apiPreserves = checkTransformPreservesField(apiContent, field);
183
+ if (importExpects && !apiPreserves) {
184
+ issues.push({
185
+ integration: check.name,
186
+ type: "import_api_mismatch",
187
+ uiField: field,
188
+ apiField: field,
189
+ description: `Import API expects '${field}' but save endpoint may drop it - data saved won't work when imported`,
190
+ });
191
+ }
192
+ }
193
+ }
194
+ }
195
+ // Summary
196
+ console.log(`\n${console_chars_1.emoji.chart} Summary:`);
197
+ console.log(` Integrations checked: ${INTEGRATION_CHECKS.length}`);
198
+ console.log(` Issues found: ${issues.length}`);
199
+ if (issues.length === 0) {
200
+ console.log(`\n${console_chars_1.emoji.success} INTEGRATION SCHEMA CONSISTENCY PASSED`);
201
+ return { success: true, errors: 0, warnings: 0 };
202
+ }
203
+ console.log(`\n${console_chars_1.emoji.error} Schema mismatches found:`);
204
+ for (const issue of issues) {
205
+ console.log(`\n ${issue.integration}:`);
206
+ console.log(` Type: ${issue.type}`);
207
+ console.log(` UI Field: ${issue.uiField}`);
208
+ console.log(` API Field: ${issue.apiField}`);
209
+ console.log(` ${issue.description}`);
210
+ }
211
+ console.log(`\n${console_chars_1.emoji.info} To fix schema mismatches:`);
212
+ console.log(` 1. Ensure API schema accepts the field names UI sends`);
213
+ console.log(` 2. When using Zod transform(), preserve ALL fields needed by UI and import APIs`);
214
+ console.log(` 3. Check that save endpoint schema output matches import endpoint schema input`);
215
+ console.log(` 4. Update UI types to match API expectations`);
216
+ console.log(`\n${console_chars_1.emoji.error} INTEGRATION SCHEMA CONSISTENCY FAILED`);
217
+ return { success: false, errors: issues.length, warnings: 0 };
218
+ }
219
+ // Allow direct execution
220
+ if (require.main === module) {
221
+ run()
222
+ .then((result) => {
223
+ process.exit(result.success ? 0 : 1);
224
+ })
225
+ .catch((err) => {
226
+ console.error(`${console_chars_1.emoji.error} Preflight failed:`, err);
227
+ process.exit(1);
228
+ });
229
+ }
230
+ //# sourceMappingURL=integration-schema-consistency.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"integration-schema-consistency.js","sourceRoot":"","sources":["../../../src/checks/api/integration-schema-consistency.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ #!/usr/bin/env node
2
+ export declare const id = "react/state-update-during-render";
3
+ export declare const name = "State Update During Render Detection";
4
+ export declare const description = "Detects setState calls outside of effects and callbacks";
5
+ export declare const category = "react";
6
+ export declare const blocking = true;
7
+ export declare const tags: string[];
8
+ export declare function run(): Promise<{
9
+ success: boolean;
10
+ errors: number;
11
+ warnings: number;
12
+ }>;
13
+ //# sourceMappingURL=state-update-during-render.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"state-update-during-render.d.ts","sourceRoot":"","sources":["../../../src/checks/react/state-update-during-render.ts"],"names":[],"mappings":";AA0CA,eAAO,MAAM,EAAE,qCAAqC,CAAC;AACrD,eAAO,MAAM,IAAI,yCAAyC,CAAC;AAC3D,eAAO,MAAM,WAAW,4DAA4D,CAAC;AACrF,eAAO,MAAM,QAAQ,UAAU,CAAC;AAChC,eAAO,MAAM,QAAQ,OAAO,CAAC;AAC7B,eAAO,MAAM,IAAI,UAAwC,CAAC;AAuG1D,wBAAsB,GAAG,IAAI,OAAO,CAAC;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,CA6F3F"}
@@ -0,0 +1,229 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ var __importDefault = (this && this.__importDefault) || function (mod) {
4
+ return (mod && mod.__esModule) ? mod : { "default": mod };
5
+ };
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.tags = exports.blocking = exports.category = exports.description = exports.name = exports.id = void 0;
8
+ exports.run = run;
9
+ /**
10
+ * State Update During Render Preflight
11
+ *
12
+ * Detects React anti-pattern of calling setState during render.
13
+ * This can cause:
14
+ * - Infinite render loops
15
+ * - UI flickering
16
+ * - Unpredictable behavior
17
+ *
18
+ * Common problematic patterns:
19
+ * ```tsx
20
+ * function Component() {
21
+ * if (condition) {
22
+ * setState(newValue); // BAD: called during render
23
+ * return null;
24
+ * }
25
+ * }
26
+ * ```
27
+ *
28
+ * Correct pattern:
29
+ * ```tsx
30
+ * function Component() {
31
+ * useEffect(() => {
32
+ * if (condition) {
33
+ * setState(newValue); // GOOD: called in effect
34
+ * }
35
+ * }, [condition]);
36
+ *
37
+ * if (condition) {
38
+ * return null;
39
+ * }
40
+ * }
41
+ * ```
42
+ */
43
+ const fs_1 = __importDefault(require("fs"));
44
+ const path_1 = __importDefault(require("path"));
45
+ const glob_1 = require("glob");
46
+ const console_chars_1 = require("../../utils/console-chars");
47
+ // METADATA - Required for plugin loader discovery
48
+ exports.id = "react/state-update-during-render";
49
+ exports.name = "State Update During Render Detection";
50
+ exports.description = "Detects setState calls outside of effects and callbacks";
51
+ exports.category = "react";
52
+ exports.blocking = true; // This is a critical React bug
53
+ exports.tags = ["react", "state", "hooks", "render"];
54
+ /**
55
+ * File patterns to check
56
+ */
57
+ const FILE_PATTERNS = [
58
+ "app/**/*.tsx",
59
+ "components/**/*.tsx",
60
+ ];
61
+ /**
62
+ * Files/directories to exclude
63
+ */
64
+ const EXCLUDE_PATTERNS = [
65
+ "node_modules/**",
66
+ "**/*.test.tsx",
67
+ "**/*.spec.tsx",
68
+ "**/*.stories.tsx",
69
+ ];
70
+ /**
71
+ * Patterns that indicate a setState call is inside a safe context
72
+ */
73
+ const SAFE_CONTEXTS = [
74
+ /useEffect\s*\(\s*\(\)\s*=>\s*\{/,
75
+ /useCallback\s*\(\s*\([^)]*\)\s*=>\s*\{/,
76
+ /useMemo\s*\(\s*\(\)\s*=>\s*\{/,
77
+ /useLayoutEffect\s*\(\s*\(\)\s*=>\s*\{/,
78
+ /onClick\s*=\s*\{/,
79
+ /onChange\s*=\s*\{/,
80
+ /onSubmit\s*=\s*\{/,
81
+ /onBlur\s*=\s*\{/,
82
+ /onFocus\s*=\s*\{/,
83
+ /addEventListener\s*\(/,
84
+ /\.then\s*\(/,
85
+ /\.catch\s*\(/,
86
+ /async\s+function/,
87
+ /const\s+\w+\s*=\s*async/,
88
+ /=>\s*\{[\s\S]*?await\b/,
89
+ ];
90
+ /**
91
+ * Patterns for setState calls
92
+ */
93
+ const SET_STATE_PATTERN = /\b(set[A-Z]\w+)\s*\(/g;
94
+ /**
95
+ * Check if position is inside a safe context (effect, callback, handler)
96
+ */
97
+ function isInSafeContext(content, position) {
98
+ // Look backwards to find context
99
+ const before = content.substring(Math.max(0, position - 500), position);
100
+ // Count braces to determine nesting
101
+ let braceCount = 0;
102
+ for (let i = before.length - 1; i >= 0; i--) {
103
+ if (before[i] === "}")
104
+ braceCount++;
105
+ if (before[i] === "{")
106
+ braceCount--;
107
+ // If we've exited the current block, check what opened it
108
+ if (braceCount < 0) {
109
+ const contextBefore = before.substring(0, i + 1);
110
+ return SAFE_CONTEXTS.some(pattern => pattern.test(contextBefore));
111
+ }
112
+ }
113
+ return false;
114
+ }
115
+ /**
116
+ * Check if the setState is immediately followed by return (common problematic pattern)
117
+ */
118
+ function isFollowedByReturn(content, position) {
119
+ const after = content.substring(position, Math.min(content.length, position + 100));
120
+ // Pattern: setState(...); return or setState(...)\n return
121
+ return /^\([^)]*\);\s*\n?\s*return\b/.test(after);
122
+ }
123
+ /**
124
+ * Get line number from position
125
+ */
126
+ function getLineNumber(content, position) {
127
+ return content.substring(0, position).split("\n").length;
128
+ }
129
+ /**
130
+ * Get surrounding code context
131
+ */
132
+ function getCodeContext(content, position) {
133
+ const lines = content.split("\n");
134
+ const lineNum = getLineNumber(content, position);
135
+ const start = Math.max(0, lineNum - 2);
136
+ const end = Math.min(lines.length, lineNum + 2);
137
+ return lines.slice(start, end).join("\n").trim();
138
+ }
139
+ async function run() {
140
+ console.log(`\n${console_chars_1.emoji.components} STATE UPDATE DURING RENDER DETECTION`);
141
+ console.log((0, console_chars_1.createDivider)(65, "heavy"));
142
+ const issues = [];
143
+ // Find all TSX files
144
+ const allFiles = [];
145
+ for (const pattern of FILE_PATTERNS) {
146
+ const matches = await (0, glob_1.glob)(pattern, {
147
+ cwd: process.cwd(),
148
+ ignore: EXCLUDE_PATTERNS,
149
+ });
150
+ allFiles.push(...matches);
151
+ }
152
+ const uniqueFiles = [...new Set(allFiles)];
153
+ console.log(`\n${console_chars_1.emoji.search} Scanning ${uniqueFiles.length} TSX files...`);
154
+ for (const relativePath of uniqueFiles) {
155
+ const filePath = path_1.default.join(process.cwd(), relativePath);
156
+ if (!fs_1.default.existsSync(filePath))
157
+ continue;
158
+ const content = fs_1.default.readFileSync(filePath, "utf-8");
159
+ // Find all setState calls
160
+ let match;
161
+ while ((match = SET_STATE_PATTERN.exec(content)) !== null) {
162
+ const position = match.index;
163
+ const setter = match[1];
164
+ // Skip if in a safe context
165
+ if (isInSafeContext(content, position)) {
166
+ continue;
167
+ }
168
+ // Check if this looks like a render-time state update
169
+ // (followed by return, which is a common pattern)
170
+ if (isFollowedByReturn(content, position)) {
171
+ const lineNum = getLineNumber(content, position);
172
+ // Check for preflight-ignore comment
173
+ const lineStart = content.lastIndexOf('\n', position) + 1;
174
+ const lineBefore = content.substring(Math.max(0, lineStart - 100), lineStart);
175
+ if (/preflight-ignore/.test(lineBefore)) {
176
+ continue;
177
+ }
178
+ issues.push({
179
+ file: relativePath,
180
+ line: lineNum,
181
+ setter,
182
+ context: getCodeContext(content, position),
183
+ });
184
+ }
185
+ }
186
+ }
187
+ // Summary
188
+ console.log(`\n${console_chars_1.emoji.chart} Summary:`);
189
+ console.log(` Files scanned: ${uniqueFiles.length}`);
190
+ console.log(` Potential issues found: ${issues.length}`);
191
+ if (issues.length === 0) {
192
+ console.log(`\n${console_chars_1.emoji.success} STATE UPDATE DURING RENDER CHECK PASSED`);
193
+ return { success: true, errors: 0, warnings: 0 };
194
+ }
195
+ console.log(`\n${console_chars_1.emoji.error} Potential state updates during render:`);
196
+ for (const issue of issues) {
197
+ console.log(`\n ${issue.file}:${issue.line}`);
198
+ console.log(` Setter: ${issue.setter}`);
199
+ console.log(` ${"-".repeat(50)}`);
200
+ const indentedCode = issue.context
201
+ .split("\n")
202
+ .map(line => ` ${line}`)
203
+ .join("\n");
204
+ console.log(indentedCode);
205
+ }
206
+ console.log(`\n${console_chars_1.emoji.info} To fix state updates during render:`);
207
+ console.log(` 1. Move the setState call to a useEffect hook`);
208
+ console.log(` 2. Make sure the effect has proper dependencies`);
209
+ console.log(` 3. Example:`);
210
+ console.log(` useEffect(() => {`);
211
+ console.log(` if (condition) {`);
212
+ console.log(` setState(newValue);`);
213
+ console.log(` }`);
214
+ console.log(` }, [condition]);`);
215
+ console.log(`\n${console_chars_1.emoji.error} STATE UPDATE DURING RENDER CHECK FAILED`);
216
+ return { success: false, errors: issues.length, warnings: 0 };
217
+ }
218
+ // Allow direct execution
219
+ if (require.main === module) {
220
+ run()
221
+ .then((result) => {
222
+ process.exit(result.success ? 0 : 1);
223
+ })
224
+ .catch((err) => {
225
+ console.error(`${console_chars_1.emoji.error} Preflight failed:`, err);
226
+ process.exit(1);
227
+ });
228
+ }
229
+ //# sourceMappingURL=state-update-during-render.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"state-update-during-render.js","sourceRoot":"","sources":["../../../src/checks/react/state-update-during-render.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ #!/usr/bin/env node
2
+ export declare const id = "ui/consistent-section-spacing";
3
+ export declare const name = "Consistent Section Spacing";
4
+ export declare const description = "Detects excessive margin/spacing values between UI sections";
5
+ export declare const category = "ui";
6
+ export declare const blocking = false;
7
+ export declare const tags: string[];
8
+ export declare function run(): Promise<{
9
+ success: boolean;
10
+ errors: number;
11
+ warnings: number;
12
+ }>;
13
+ //# sourceMappingURL=consistent-section-spacing.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"consistent-section-spacing.d.ts","sourceRoot":"","sources":["../../../src/checks/ui/consistent-section-spacing.ts"],"names":[],"mappings":";AAmCA,eAAO,MAAM,EAAE,kCAAkC,CAAC;AAClD,eAAO,MAAM,IAAI,+BAA+B,CAAC;AACjD,eAAO,MAAM,WAAW,gEAAgE,CAAC;AACzF,eAAO,MAAM,QAAQ,OAAO,CAAC;AAC7B,eAAO,MAAM,QAAQ,QAAQ,CAAC;AAC9B,eAAO,MAAM,IAAI,UAAqD,CAAC;AAqHvE,wBAAsB,GAAG,IAAI,OAAO,CAAC;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,CAmH3F"}
@@ -0,0 +1,246 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ var __importDefault = (this && this.__importDefault) || function (mod) {
4
+ return (mod && mod.__esModule) ? mod : { "default": mod };
5
+ };
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.tags = exports.blocking = exports.category = exports.description = exports.name = exports.id = void 0;
8
+ exports.run = run;
9
+ /**
10
+ * Consistent Section Spacing Preflight
11
+ *
12
+ * Detects excessive margin/spacing values between UI sections that can cause
13
+ * visual gaps. This enforces consistent spacing standards across the codebase.
14
+ *
15
+ * Common problematic patterns:
16
+ * ```tsx
17
+ * <Card className="mb-6"> // Too large - should be mb-3 or mb-4
18
+ * <Tabs>...</Tabs>
19
+ * </Card>
20
+ * <CardSection>...</CardSection>
21
+ * ```
22
+ *
23
+ * Correct pattern:
24
+ * ```tsx
25
+ * <Card className="mb-3"> // Consistent with design system
26
+ * <Tabs>...</Tabs>
27
+ * </Card>
28
+ * <CardSection>...</CardSection>
29
+ * ```
30
+ *
31
+ * Rules:
32
+ * - Tab navigation containers should use mb-3 or mb-4 max
33
+ * - Cards followed by CardSection should use mb-3 or smaller
34
+ * - space-y-* containers should use space-y-3 or space-y-4 max
35
+ */
36
+ const fs_1 = __importDefault(require("fs"));
37
+ const path_1 = __importDefault(require("path"));
38
+ const glob_1 = require("glob");
39
+ const console_chars_1 = require("../../utils/console-chars");
40
+ // METADATA - Required for plugin loader discovery
41
+ exports.id = "ui/consistent-section-spacing";
42
+ exports.name = "Consistent Section Spacing";
43
+ exports.description = "Detects excessive margin/spacing values between UI sections";
44
+ exports.category = "ui";
45
+ exports.blocking = false; // Warning level - requires review
46
+ exports.tags = ["ui", "spacing", "tailwind", "layout", "margins"];
47
+ /**
48
+ * File patterns to check
49
+ */
50
+ const FILE_PATTERNS = [
51
+ "app/**/*.tsx",
52
+ "components/**/*.tsx",
53
+ ];
54
+ /**
55
+ * Files/directories to exclude
56
+ */
57
+ const EXCLUDE_PATTERNS = [
58
+ "node_modules/**",
59
+ "**/*.test.tsx",
60
+ "**/*.spec.tsx",
61
+ "**/*.stories.tsx",
62
+ ".storybook/**",
63
+ ];
64
+ /**
65
+ * Patterns that indicate excessive spacing
66
+ * [pattern, description, suggestion, context-check]
67
+ */
68
+ const EXCESSIVE_SPACING_PATTERNS = [
69
+ {
70
+ // Card with mb-6 or larger followed by content
71
+ pattern: /<Card\s+className="[^"]*\bmb-[6-9]\b[^"]*"/g,
72
+ description: "Card with mb-6 or larger margin - may cause excessive gap",
73
+ suggestion: "Consider using mb-3 or mb-4 for tighter section spacing",
74
+ contextCheck: (content, position) => {
75
+ // Check if this Card contains Tabs (common pattern)
76
+ const after = content.substring(position, Math.min(content.length, position + 300));
77
+ return /<Tabs\b/.test(after);
78
+ },
79
+ },
80
+ {
81
+ // Box/div with mb-6 or larger
82
+ pattern: /<(?:Box|div)\s+className="[^"]*\bmb-[6-9]\b[^"]*">/g,
83
+ description: "Container with mb-6 or larger margin",
84
+ suggestion: "Consider using mb-3 or mb-4 for consistent section spacing",
85
+ },
86
+ {
87
+ // space-y-6 or larger on admin pages
88
+ pattern: /className="[^"]*\bspace-y-[6-9]\b[^"]*"/g,
89
+ description: "Container with space-y-6 or larger - may cause gaps between children",
90
+ suggestion: "Consider using space-y-3 or space-y-4 for tighter layout",
91
+ contextCheck: (content, _position) => {
92
+ // Only flag if it's in an admin page or component
93
+ return /\/admin\//.test(content) || /Admin/.test(content);
94
+ },
95
+ },
96
+ {
97
+ // mb-10 or larger (almost always too much)
98
+ pattern: /<[A-Z][a-zA-Z]*\s+className="[^"]*\bmb-(?:10|11|12|14|16|20)\b[^"]*"/g,
99
+ description: "Component with very large bottom margin (mb-10+)",
100
+ suggestion: "Large margins are usually a sign of layout issues - consider restructuring",
101
+ },
102
+ {
103
+ // gap-6 or larger in flex/grid containers
104
+ pattern: /className="[^"]*\b(?:flex|grid)[^"]*\bgap-[6-9]\b[^"]*"/g,
105
+ description: "Flex/grid container with gap-6 or larger",
106
+ suggestion: "Consider using gap-3 or gap-4 for tighter element spacing",
107
+ },
108
+ ];
109
+ /**
110
+ * Patterns that indicate this is NOT a spacing issue
111
+ */
112
+ const FALSE_POSITIVE_PATTERNS = [
113
+ // Page-level wrappers (spacing might be intentional)
114
+ /PageLayout|Layout>/,
115
+ // Main content areas with intentional large spacing
116
+ /main\s+className/,
117
+ // Full page containers
118
+ /className="[^"]*min-h-screen[^"]*"/,
119
+ // Modal/dialog content (often needs more spacing)
120
+ /Modal|Dialog|Sheet/,
121
+ // Hero sections (intentional large spacing)
122
+ /Hero|hero/,
123
+ // Footer areas
124
+ /footer|Footer/,
125
+ ];
126
+ /**
127
+ * Extract line number from content and position
128
+ */
129
+ function getLineNumber(content, position) {
130
+ return content.substring(0, position).split("\n").length;
131
+ }
132
+ /**
133
+ * Get surrounding code context
134
+ */
135
+ function getCodeContext(content, position, lines = 2) {
136
+ const allLines = content.split("\n");
137
+ const lineNum = getLineNumber(content, position);
138
+ const start = Math.max(0, lineNum - 1);
139
+ const end = Math.min(allLines.length, lineNum + lines);
140
+ return allLines.slice(start, end).join("\n").trim();
141
+ }
142
+ async function run() {
143
+ console.log(`\n${console_chars_1.emoji.ruler} CONSISTENT SECTION SPACING CHECK`);
144
+ console.log((0, console_chars_1.createDivider)(65, "heavy"));
145
+ const issues = [];
146
+ // Find all TSX files
147
+ const allFiles = [];
148
+ for (const pattern of FILE_PATTERNS) {
149
+ const matches = await (0, glob_1.glob)(pattern, {
150
+ cwd: process.cwd(),
151
+ ignore: EXCLUDE_PATTERNS,
152
+ });
153
+ allFiles.push(...matches);
154
+ }
155
+ const uniqueFiles = [...new Set(allFiles)];
156
+ console.log(`\n${console_chars_1.emoji.search} Scanning ${uniqueFiles.length} TSX files...`);
157
+ for (const relativePath of uniqueFiles) {
158
+ const filePath = path_1.default.join(process.cwd(), relativePath);
159
+ if (!fs_1.default.existsSync(filePath))
160
+ continue;
161
+ const content = fs_1.default.readFileSync(filePath, "utf-8");
162
+ // Check each excessive spacing pattern
163
+ for (const { pattern, description, suggestion, contextCheck } of EXCESSIVE_SPACING_PATTERNS) {
164
+ let match;
165
+ // Reset regex lastIndex
166
+ pattern.lastIndex = 0;
167
+ while ((match = pattern.exec(content)) !== null) {
168
+ const position = match.index;
169
+ const lineNum = getLineNumber(content, position);
170
+ // Check for preflight-ignore comment
171
+ const nearbyContent = content.substring(Math.max(0, position - 150), position + 50);
172
+ if (/preflight-ignore/.test(nearbyContent)) {
173
+ continue;
174
+ }
175
+ // Check for false positive patterns
176
+ const contextArea = content.substring(Math.max(0, position - 200), Math.min(content.length, position + 200));
177
+ const isFalsePositive = FALSE_POSITIVE_PATTERNS.some((fp) => fp.test(contextArea));
178
+ if (isFalsePositive) {
179
+ continue;
180
+ }
181
+ // Check context-specific conditions
182
+ if (contextCheck && !contextCheck(content, position)) {
183
+ continue;
184
+ }
185
+ issues.push({
186
+ file: relativePath,
187
+ line: lineNum,
188
+ description,
189
+ code: getCodeContext(content, position),
190
+ suggestion,
191
+ severity: "warning",
192
+ });
193
+ }
194
+ }
195
+ }
196
+ // Summary
197
+ console.log(`\n${console_chars_1.emoji.chart} Summary:`);
198
+ console.log(` Files scanned: ${uniqueFiles.length}`);
199
+ console.log(` Spacing issues found: ${issues.length}`);
200
+ if (issues.length === 0) {
201
+ console.log(`\n${console_chars_1.emoji.success} CONSISTENT SECTION SPACING CHECK PASSED`);
202
+ console.log(`\nNo excessive spacing issues detected.`);
203
+ return { success: true, errors: 0, warnings: 0 };
204
+ }
205
+ // Group issues by file
206
+ const issuesByFile = new Map();
207
+ for (const issue of issues) {
208
+ const fileIssues = issuesByFile.get(issue.file) || [];
209
+ fileIssues.push(issue);
210
+ issuesByFile.set(issue.file, fileIssues);
211
+ }
212
+ console.log(`\n${console_chars_1.emoji.warning} Spacing issues found:`);
213
+ for (const [file, fileIssues] of issuesByFile) {
214
+ console.log(`\n ${file}:`);
215
+ for (const issue of fileIssues) {
216
+ console.log(` Line ${issue.line}: ${issue.description}`);
217
+ console.log(` Suggestion: ${issue.suggestion}`);
218
+ console.log(` ${"-".repeat(50)}`);
219
+ const indentedCode = issue.code
220
+ .split("\n")
221
+ .map((line) => ` ${line}`)
222
+ .join("\n");
223
+ console.log(indentedCode);
224
+ }
225
+ }
226
+ console.log(`\n${console_chars_1.emoji.info} Spacing guidelines:`);
227
+ console.log(` - Tab navigation containers: use mb-3 or mb-4`);
228
+ console.log(` - Between Card and CardSection: use mb-3`);
229
+ console.log(` - Parent containers: use space-y-3 or space-y-4`);
230
+ console.log(` - Flex/grid gaps: use gap-3 or gap-4`);
231
+ console.log(` - Add /* preflight-ignore */ comment if large spacing is intentional`);
232
+ console.log(`\n${console_chars_1.emoji.warning} CONSISTENT SECTION SPACING CHECK COMPLETED WITH WARNINGS`);
233
+ return { success: true, errors: 0, warnings: issues.length }; // Non-blocking - requires review
234
+ }
235
+ // Allow direct execution
236
+ if (require.main === module) {
237
+ run()
238
+ .then((result) => {
239
+ process.exit(result.success ? 0 : 1);
240
+ })
241
+ .catch((err) => {
242
+ console.error(`${console_chars_1.emoji.error} Preflight failed:`, err);
243
+ process.exit(1);
244
+ });
245
+ }
246
+ //# sourceMappingURL=consistent-section-spacing.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"consistent-section-spacing.js","sourceRoot":"","sources":["../../../src/checks/ui/consistent-section-spacing.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ #!/usr/bin/env node
2
+ export declare const id = "ui/empty-wrapper-spacing";
3
+ export declare const name = "Empty Wrapper Spacing Detection";
4
+ export declare const description = "Detects potential spacing issues from conditionally-rendered wrapper elements";
5
+ export declare const category = "ui";
6
+ export declare const blocking = false;
7
+ export declare const tags: string[];
8
+ export declare function run(): Promise<{
9
+ success: boolean;
10
+ errors: number;
11
+ warnings: number;
12
+ }>;
13
+ //# sourceMappingURL=empty-wrapper-spacing.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"empty-wrapper-spacing.d.ts","sourceRoot":"","sources":["../../../src/checks/ui/empty-wrapper-spacing.ts"],"names":[],"mappings":";AAwCA,eAAO,MAAM,EAAE,6BAA6B,CAAC;AAC7C,eAAO,MAAM,IAAI,oCAAoC,CAAC;AACtD,eAAO,MAAM,WAAW,kFAAkF,CAAC;AAC3G,eAAO,MAAM,QAAQ,OAAO,CAAC;AAC7B,eAAO,MAAM,QAAQ,QAAQ,CAAC;AAC9B,eAAO,MAAM,IAAI,UAAyD,CAAC;AAqI3E,wBAAsB,GAAG,IAAI,OAAO,CAAC;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,CA0I3F"}
@@ -0,0 +1,289 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ var __importDefault = (this && this.__importDefault) || function (mod) {
4
+ return (mod && mod.__esModule) ? mod : { "default": mod };
5
+ };
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.tags = exports.blocking = exports.category = exports.description = exports.name = exports.id = void 0;
8
+ exports.run = run;
9
+ /**
10
+ * Empty Wrapper Spacing Preflight
11
+ *
12
+ * Detects potential spacing issues caused by conditionally-rendered wrapper
13
+ * elements inside space-y-* or gap-* containers. When a wrapper element
14
+ * renders with no visible children, it still takes up space due to the
15
+ * parent's spacing utilities.
16
+ *
17
+ * Common problematic pattern:
18
+ * ```tsx
19
+ * <div className="space-y-6">
20
+ * <Box className="..."> // Always rendered
21
+ * {condition && <Button />} // Conditionally rendered
22
+ * </Box> // When condition is false, Box is empty but takes up space
23
+ * <Card>...</Card>
24
+ * </div>
25
+ * ```
26
+ *
27
+ * Better pattern:
28
+ * ```tsx
29
+ * <div className="space-y-6">
30
+ * {condition && (
31
+ * <Box className="...">
32
+ * <Button />
33
+ * </Box>
34
+ * )}
35
+ * <Card>...</Card>
36
+ * </div>
37
+ * ```
38
+ *
39
+ * This check flags potential issues for manual review.
40
+ */
41
+ const fs_1 = __importDefault(require("fs"));
42
+ const path_1 = __importDefault(require("path"));
43
+ const glob_1 = require("glob");
44
+ const console_chars_1 = require("../../utils/console-chars");
45
+ // METADATA - Required for plugin loader discovery
46
+ exports.id = "ui/empty-wrapper-spacing";
47
+ exports.name = "Empty Wrapper Spacing Detection";
48
+ exports.description = "Detects potential spacing issues from conditionally-rendered wrapper elements";
49
+ exports.category = "ui";
50
+ exports.blocking = false; // Warning level - requires manual review
51
+ exports.tags = ["ui", "spacing", "tailwind", "conditional-rendering"];
52
+ /**
53
+ * File patterns to check
54
+ */
55
+ const FILE_PATTERNS = [
56
+ "app/**/*.tsx",
57
+ "components/**/*.tsx",
58
+ ];
59
+ /**
60
+ * Files/directories to exclude
61
+ */
62
+ const EXCLUDE_PATTERNS = [
63
+ "node_modules/**",
64
+ "**/*.test.tsx",
65
+ "**/*.spec.tsx",
66
+ "**/*.stories.tsx",
67
+ ".storybook/**",
68
+ ];
69
+ /**
70
+ * Spacing container patterns (Tailwind utilities that create gaps between children)
71
+ */
72
+ const SPACING_CONTAINER_PATTERNS = [
73
+ /className="[^"]*space-y-[1-9][0-6]?[^"]*"/,
74
+ /className="[^"]*space-x-[1-9][0-6]?[^"]*"/,
75
+ /className="[^"]*gap-[1-9][0-6]?[^"]*"/,
76
+ /className=\{[^}]*space-y-[1-9][0-6]?[^}]*\}/,
77
+ /className=\{[^}]*space-x-[1-9][0-6]?[^}]*\}/,
78
+ /className=\{[^}]*gap-[1-9][0-6]?[^}]*\}/,
79
+ ];
80
+ /**
81
+ * Patterns for wrapper elements that might render empty
82
+ * These are elements with conditional children that don't have the condition at the wrapper level
83
+ *
84
+ * We specifically look for:
85
+ * - Simple conditional: {condition && <Element />}
86
+ *
87
+ * We exclude:
88
+ * - .map() calls (will produce content if array has items)
89
+ * - Ternary expressions (usually have content in both branches)
90
+ */
91
+ const EMPTY_WRAPPER_PATTERNS = [
92
+ // Box/div with conditional children (NOT .map or ternary)
93
+ // Must be followed by && but NOT by .map( or ?
94
+ /<(?:Box|div)\s+className="[^"]*">\s*\{[^}]+\s*&&\s*[^?]/,
95
+ // Stack with conditional children
96
+ /<Stack[^>]*>\s*\{[^}]+\s*&&\s*[^?]/,
97
+ ];
98
+ /**
99
+ * Patterns that indicate this is NOT an empty wrapper issue
100
+ */
101
+ const FALSE_POSITIVE_PATTERNS = [
102
+ // .map() calls will produce content when array has items
103
+ /\.map\s*\(/,
104
+ // Ternary expressions usually have content in both branches
105
+ /\?\s*\(/,
106
+ /\?\s*</,
107
+ // Object.entries/keys/values mapping
108
+ /Object\.(?:entries|keys|values)\s*\(/,
109
+ // Array spread or filter followed by map
110
+ /\)\s*\.map\s*\(/,
111
+ // Fixed dimensions - won't collapse when empty
112
+ /className="[^"]*(?:w-\d+|h-\d+)[^"]*"/,
113
+ // Absolutely positioned - not in document flow
114
+ /className="[^"]*absolute[^"]*"/,
115
+ // Has a Switch, Button, or other always-rendered element nearby
116
+ /<Switch\s/,
117
+ /<Button\s[^>]*>/,
118
+ // TableCell context - spacing typically not an issue
119
+ /<TableCell/,
120
+ // Tab panel content - one tab always shows
121
+ /tabValue\s*===\s*\d+\s*&&/,
122
+ // Has a Tooltip that always renders
123
+ /<Tooltip\s/,
124
+ // UI library components (forwardRef) - flexible by design
125
+ /forwardRef</,
126
+ // Has {children} which receives content from parent
127
+ /\{children\}/,
128
+ // Flex-1 - takes remaining space, not causing spacing issues
129
+ /className="[^"]*flex-1[^"]*"/,
130
+ // Non-conditional sibling after conditional (closing )} followed by element)
131
+ /\)\}\s*\n\s*<[A-Z]/,
132
+ // Card always follows (common pattern)
133
+ /<Card[\s>]/,
134
+ // Alert followed by other content
135
+ /<Alert[\s\S]*?<Card/,
136
+ // LoadingButton followed by Button (common pattern in actions)
137
+ /<LoadingButton[\s\S]*?<Button\s/,
138
+ // Early return pattern - component returns null when empty
139
+ /if\s*\([^)]*===\s*0[^)]*\)\s*\{\s*return\s+null/,
140
+ // Early return with multiple conditions
141
+ /return\s+null;\s*\}\s*\n\s*return\s*\(/,
142
+ ];
143
+ /**
144
+ * Extract line number from content and position
145
+ */
146
+ function getLineNumber(content, position) {
147
+ return content.substring(0, position).split("\n").length;
148
+ }
149
+ /**
150
+ * Get surrounding code context
151
+ */
152
+ function getCodeContext(content, position, lines = 3) {
153
+ const allLines = content.split("\n");
154
+ const lineNum = getLineNumber(content, position);
155
+ const start = Math.max(0, lineNum - 2);
156
+ const end = Math.min(allLines.length, lineNum + lines);
157
+ return allLines.slice(start, end).join("\n").trim();
158
+ }
159
+ /**
160
+ * Check if position is inside a conditionally-rendered block
161
+ */
162
+ function isInsideConditionalWrapper(content, position) {
163
+ // Look backwards for pattern like { condition && (
164
+ const before = content.substring(Math.max(0, position - 200), position);
165
+ return /\{[^{}]+&&\s*\(\s*$/.test(before);
166
+ }
167
+ async function run() {
168
+ console.log(`\n${console_chars_1.emoji.ruler} EMPTY WRAPPER SPACING DETECTION`);
169
+ console.log((0, console_chars_1.createDivider)(65, "heavy"));
170
+ const issues = [];
171
+ // Find all TSX files
172
+ const allFiles = [];
173
+ for (const pattern of FILE_PATTERNS) {
174
+ const matches = await (0, glob_1.glob)(pattern, {
175
+ cwd: process.cwd(),
176
+ ignore: EXCLUDE_PATTERNS,
177
+ });
178
+ allFiles.push(...matches);
179
+ }
180
+ const uniqueFiles = [...new Set(allFiles)];
181
+ console.log(`\n${console_chars_1.emoji.search} Scanning ${uniqueFiles.length} TSX files...`);
182
+ let filesWithSpacingContainers = 0;
183
+ let potentialIssuesFound = 0;
184
+ for (const relativePath of uniqueFiles) {
185
+ const filePath = path_1.default.join(process.cwd(), relativePath);
186
+ if (!fs_1.default.existsSync(filePath))
187
+ continue;
188
+ const content = fs_1.default.readFileSync(filePath, "utf-8");
189
+ // Check if file has spacing containers
190
+ const hasSpacingContainer = SPACING_CONTAINER_PATTERNS.some((pattern) => pattern.test(content));
191
+ if (!hasSpacingContainer)
192
+ continue;
193
+ filesWithSpacingContainers++;
194
+ // Find potential empty wrapper issues
195
+ for (const pattern of EMPTY_WRAPPER_PATTERNS) {
196
+ let match;
197
+ const regex = new RegExp(pattern.source, "g");
198
+ while ((match = regex.exec(content)) !== null) {
199
+ const position = match.index;
200
+ const lineNum = getLineNumber(content, position);
201
+ // Get context around the match for false positive detection
202
+ const contextAfter = content.substring(position, Math.min(content.length, position + 500));
203
+ // Check for false positive patterns (map, ternary, etc.)
204
+ const isFalsePositive = FALSE_POSITIVE_PATTERNS.some((fp) => fp.test(contextAfter));
205
+ if (isFalsePositive) {
206
+ continue;
207
+ }
208
+ // Check if this is already inside a conditional wrapper (good pattern)
209
+ if (isInsideConditionalWrapper(content, position)) {
210
+ continue;
211
+ }
212
+ // Check if there's a preflight-ignore comment nearby (JS or JSX style)
213
+ const nearbyContent = content.substring(Math.max(0, position - 200), position);
214
+ if (/preflight-ignore/.test(nearbyContent)) {
215
+ continue;
216
+ }
217
+ // Also check for JSX comments on the line with the element
218
+ const lineStart = content.lastIndexOf('\n', position) + 1;
219
+ const lineContent = content.substring(lineStart, position + 200);
220
+ if (/\{\/\*\s*preflight-ignore/.test(lineContent) || /\/\/\s*preflight-ignore/.test(lineContent)) {
221
+ continue;
222
+ }
223
+ // Check if the wrapper itself is conditionally rendered
224
+ const before50 = content.substring(Math.max(0, position - 50), position);
225
+ if (/\{\s*\w+\s*&&\s*\(?\s*$/.test(before50)) {
226
+ continue; // Good - wrapper is conditional
227
+ }
228
+ // This is a potential issue
229
+ potentialIssuesFound++;
230
+ issues.push({
231
+ file: relativePath,
232
+ line: lineNum,
233
+ description: "Wrapper element with only conditional children may cause empty spacing",
234
+ code: getCodeContext(content, position),
235
+ severity: "warning",
236
+ });
237
+ }
238
+ }
239
+ }
240
+ // Summary
241
+ console.log(`\n${console_chars_1.emoji.chart} Summary:`);
242
+ console.log(` Files with spacing containers: ${filesWithSpacingContainers}`);
243
+ console.log(` Potential issues found: ${potentialIssuesFound}`);
244
+ if (issues.length === 0) {
245
+ console.log(`\n${console_chars_1.emoji.success} EMPTY WRAPPER SPACING VALIDATION PASSED`);
246
+ console.log(`\nNo potential empty wrapper spacing issues detected.`);
247
+ return { success: true, errors: 0, warnings: 0 };
248
+ }
249
+ // Group issues by file
250
+ const issuesByFile = new Map();
251
+ for (const issue of issues) {
252
+ const fileIssues = issuesByFile.get(issue.file) || [];
253
+ fileIssues.push(issue);
254
+ issuesByFile.set(issue.file, fileIssues);
255
+ }
256
+ console.log(`\n${console_chars_1.emoji.warning} Potential issues found:`);
257
+ for (const [file, fileIssues] of issuesByFile) {
258
+ console.log(`\n ${file}:`);
259
+ for (const issue of fileIssues) {
260
+ console.log(` Line ${issue.line}: ${issue.description}`);
261
+ console.log(` ${"-".repeat(50)}`);
262
+ const indentedCode = issue.code
263
+ .split("\n")
264
+ .map((line) => ` ${line}`)
265
+ .join("\n");
266
+ console.log(indentedCode);
267
+ }
268
+ }
269
+ console.log(`\n${console_chars_1.emoji.info} To fix empty wrapper spacing issues:`);
270
+ console.log(` 1. Move the condition to wrap the entire element:`);
271
+ console.log(` BAD: <Box className="...">\\{condition && <Button />}\\</Box>`);
272
+ console.log(` GOOD: {condition && <Box className="..."><Button /></Box>}`);
273
+ console.log(` 2. Or use a smaller spacing value (e.g., space-y-3 instead of space-y-6)`);
274
+ console.log(` 3. Or add a /* preflight-ignore */ comment if the spacing is intentional`);
275
+ console.log(`\n${console_chars_1.emoji.warning} EMPTY WRAPPER SPACING VALIDATION COMPLETED WITH WARNINGS`);
276
+ return { success: true, errors: 0, warnings: issues.length }; // Non-blocking - requires manual review
277
+ }
278
+ // Allow direct execution
279
+ if (require.main === module) {
280
+ run()
281
+ .then((result) => {
282
+ process.exit(result.success ? 0 : 1);
283
+ })
284
+ .catch((err) => {
285
+ console.error(`${console_chars_1.emoji.error} Preflight failed:`, err);
286
+ process.exit(1);
287
+ });
288
+ }
289
+ //# sourceMappingURL=empty-wrapper-spacing.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"empty-wrapper-spacing.js","sourceRoot":"","sources":["../../../src/checks/ui/empty-wrapper-spacing.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@empline/preflight",
3
- "version": "1.1.29",
3
+ "version": "1.1.31",
4
4
  "description": "Distributable preflight validation system with app-specific plugin support",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",