@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.
- package/dist/checks/api/integration-schema-consistency.d.ts +14 -0
- package/dist/checks/api/integration-schema-consistency.d.ts.map +1 -0
- package/dist/checks/api/integration-schema-consistency.js +230 -0
- package/dist/checks/api/integration-schema-consistency.js.map +1 -0
- package/dist/checks/react/state-update-during-render.d.ts +13 -0
- package/dist/checks/react/state-update-during-render.d.ts.map +1 -0
- package/dist/checks/react/state-update-during-render.js +229 -0
- package/dist/checks/react/state-update-during-render.js.map +1 -0
- package/dist/checks/ui/consistent-section-spacing.d.ts +13 -0
- package/dist/checks/ui/consistent-section-spacing.d.ts.map +1 -0
- package/dist/checks/ui/consistent-section-spacing.js +246 -0
- package/dist/checks/ui/consistent-section-spacing.js.map +1 -0
- package/dist/checks/ui/empty-wrapper-spacing.d.ts +13 -0
- package/dist/checks/ui/empty-wrapper-spacing.d.ts.map +1 -0
- package/dist/checks/ui/empty-wrapper-spacing.js +289 -0
- package/dist/checks/ui/empty-wrapper-spacing.js.map +1 -0
- package/package.json +1 -1
|
@@ -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":";;;;;;;AAsJA,kBA6FC;AAlPD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACH,4CAAoB;AACpB,gDAAwB;AACxB,+BAA4B;AAE5B,6DAAiE;AAEjE,kDAAkD;AACrC,QAAA,EAAE,GAAG,kCAAkC,CAAC;AACxC,QAAA,IAAI,GAAG,sCAAsC,CAAC;AAC9C,QAAA,WAAW,GAAG,yDAAyD,CAAC;AACxE,QAAA,QAAQ,GAAG,OAAO,CAAC;AACnB,QAAA,QAAQ,GAAG,IAAI,CAAC,CAAC,+BAA+B;AAChD,QAAA,IAAI,GAAG,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,QAAQ,CAAC,CAAC;AAE1D;;GAEG;AACH,MAAM,aAAa,GAAG;IACpB,cAAc;IACd,qBAAqB;CACtB,CAAC;AAEF;;GAEG;AACH,MAAM,gBAAgB,GAAG;IACvB,iBAAiB;IACjB,eAAe;IACf,eAAe;IACf,kBAAkB;CACnB,CAAC;AASF;;GAEG;AACH,MAAM,aAAa,GAAG;IACpB,iCAAiC;IACjC,wCAAwC;IACxC,+BAA+B;IAC/B,uCAAuC;IACvC,kBAAkB;IAClB,mBAAmB;IACnB,mBAAmB;IACnB,iBAAiB;IACjB,kBAAkB;IAClB,uBAAuB;IACvB,aAAa;IACb,cAAc;IACd,kBAAkB;IAClB,yBAAyB;IACzB,wBAAwB;CACzB,CAAC;AAEF;;GAEG;AACH,MAAM,iBAAiB,GAAG,uBAAuB,CAAC;AAElD;;GAEG;AACH,SAAS,eAAe,CAAC,OAAe,EAAE,QAAgB;IACxD,iCAAiC;IACjC,MAAM,MAAM,GAAG,OAAO,CAAC,SAAS,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,QAAQ,GAAG,GAAG,CAAC,EAAE,QAAQ,CAAC,CAAC;IAExE,oCAAoC;IACpC,IAAI,UAAU,GAAG,CAAC,CAAC;IACnB,KAAK,IAAI,CAAC,GAAG,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;QAC5C,IAAI,MAAM,CAAC,CAAC,CAAC,KAAK,GAAG;YAAE,UAAU,EAAE,CAAC;QACpC,IAAI,MAAM,CAAC,CAAC,CAAC,KAAK,GAAG;YAAE,UAAU,EAAE,CAAC;QAEpC,0DAA0D;QAC1D,IAAI,UAAU,GAAG,CAAC,EAAE,CAAC;YACnB,MAAM,aAAa,GAAG,MAAM,CAAC,SAAS,CAAC,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;YACjD,OAAO,aAAa,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC;QACpE,CAAC;IACH,CAAC;IAED,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;GAEG;AACH,SAAS,kBAAkB,CAAC,OAAe,EAAE,QAAgB;IAC3D,MAAM,KAAK,GAAG,OAAO,CAAC,SAAS,CAAC,QAAQ,EAAE,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,MAAM,EAAE,QAAQ,GAAG,GAAG,CAAC,CAAC,CAAC;IACpF,4DAA4D;IAC5D,OAAO,8BAA8B,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;AACpD,CAAC;AAED;;GAEG;AACH,SAAS,aAAa,CAAC,OAAe,EAAE,QAAgB;IACtD,OAAO,OAAO,CAAC,SAAS,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC;AAC3D,CAAC;AAED;;GAEG;AACH,SAAS,cAAc,CAAC,OAAe,EAAE,QAAgB;IACvD,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAClC,MAAM,OAAO,GAAG,aAAa,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;IACjD,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,OAAO,GAAG,CAAC,CAAC,CAAC;IACvC,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,EAAE,OAAO,GAAG,CAAC,CAAC,CAAC;IAChD,OAAO,KAAK,CAAC,KAAK,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,CAAC;AACnD,CAAC;AAEM,KAAK,UAAU,GAAG;IACvB,OAAO,CAAC,GAAG,CAAC,KAAK,qBAAK,CAAC,UAAU,uCAAuC,CAAC,CAAC;IAC1E,OAAO,CAAC,GAAG,CAAC,IAAA,6BAAa,EAAC,EAAE,EAAE,OAAO,CAAC,CAAC,CAAC;IAExC,MAAM,MAAM,GAAY,EAAE,CAAC;IAE3B,qBAAqB;IACrB,MAAM,QAAQ,GAAa,EAAE,CAAC;IAC9B,KAAK,MAAM,OAAO,IAAI,aAAa,EAAE,CAAC;QACpC,MAAM,OAAO,GAAG,MAAM,IAAA,WAAI,EAAC,OAAO,EAAE;YAClC,GAAG,EAAE,OAAO,CAAC,GAAG,EAAE;YAClB,MAAM,EAAE,gBAAgB;SACzB,CAAC,CAAC;QACH,QAAQ,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,CAAC;IAC5B,CAAC;IAED,MAAM,WAAW,GAAG,CAAC,GAAG,IAAI,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC;IAC3C,OAAO,CAAC,GAAG,CAAC,KAAK,qBAAK,CAAC,MAAM,aAAa,WAAW,CAAC,MAAM,eAAe,CAAC,CAAC;IAE7E,KAAK,MAAM,YAAY,IAAI,WAAW,EAAE,CAAC;QACvC,MAAM,QAAQ,GAAG,cAAI,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,YAAY,CAAC,CAAC;QAExD,IAAI,CAAC,YAAE,CAAC,UAAU,CAAC,QAAQ,CAAC;YAAE,SAAS;QAEvC,MAAM,OAAO,GAAG,YAAE,CAAC,YAAY,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;QAEnD,0BAA0B;QAC1B,IAAI,KAAK,CAAC;QACV,OAAO,CAAC,KAAK,GAAG,iBAAiB,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC;YAC1D,MAAM,QAAQ,GAAG,KAAK,CAAC,KAAK,CAAC;YAC7B,MAAM,MAAM,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;YAExB,4BAA4B;YAC5B,IAAI,eAAe,CAAC,OAAO,EAAE,QAAQ,CAAC,EAAE,CAAC;gBACvC,SAAS;YACX,CAAC;YAED,sDAAsD;YACtD,kDAAkD;YAClD,IAAI,kBAAkB,CAAC,OAAO,EAAE,QAAQ,CAAC,EAAE,CAAC;gBAC1C,MAAM,OAAO,GAAG,aAAa,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;gBAEjD,qCAAqC;gBACrC,MAAM,SAAS,GAAG,OAAO,CAAC,WAAW,CAAC,IAAI,EAAE,QAAQ,CAAC,GAAG,CAAC,CAAC;gBAC1D,MAAM,UAAU,GAAG,OAAO,CAAC,SAAS,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,SAAS,GAAG,GAAG,CAAC,EAAE,SAAS,CAAC,CAAC;gBAC9E,IAAI,kBAAkB,CAAC,IAAI,CAAC,UAAU,CAAC,EAAE,CAAC;oBACxC,SAAS;gBACX,CAAC;gBAED,MAAM,CAAC,IAAI,CAAC;oBACV,IAAI,EAAE,YAAY;oBAClB,IAAI,EAAE,OAAO;oBACb,MAAM;oBACN,OAAO,EAAE,cAAc,CAAC,OAAO,EAAE,QAAQ,CAAC;iBAC3C,CAAC,CAAC;YACL,CAAC;QACH,CAAC;IACH,CAAC;IAED,UAAU;IACV,OAAO,CAAC,GAAG,CAAC,KAAK,qBAAK,CAAC,KAAK,WAAW,CAAC,CAAC;IACzC,OAAO,CAAC,GAAG,CAAC,qBAAqB,WAAW,CAAC,MAAM,EAAE,CAAC,CAAC;IACvD,OAAO,CAAC,GAAG,CAAC,8BAA8B,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC;IAE3D,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACxB,OAAO,CAAC,GAAG,CAAC,KAAK,qBAAK,CAAC,OAAO,0CAA0C,CAAC,CAAC;QAC1E,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,CAAC;IACnD,CAAC;IAED,OAAO,CAAC,GAAG,CAAC,KAAK,qBAAK,CAAC,KAAK,yCAAyC,CAAC,CAAC;IACvE,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QAC3B,OAAO,CAAC,GAAG,CAAC,QAAQ,KAAK,CAAC,IAAI,IAAI,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC;QAChD,OAAO,CAAC,GAAG,CAAC,cAAc,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC;QAC1C,OAAO,CAAC,GAAG,CAAC,MAAM,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;QACpC,MAAM,YAAY,GAAG,KAAK,CAAC,OAAO;aAC/B,KAAK,CAAC,IAAI,CAAC;aACX,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,QAAQ,IAAI,EAAE,CAAC;aAC3B,IAAI,CAAC,IAAI,CAAC,CAAC;QACd,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC;IAC5B,CAAC;IAED,OAAO,CAAC,GAAG,CAAC,KAAK,qBAAK,CAAC,IAAI,sCAAsC,CAAC,CAAC;IACnE,OAAO,CAAC,GAAG,CAAC,kDAAkD,CAAC,CAAC;IAChE,OAAO,CAAC,GAAG,CAAC,oDAAoD,CAAC,CAAC;IAClE,OAAO,CAAC,GAAG,CAAC,gBAAgB,CAAC,CAAC;IAC9B,OAAO,CAAC,GAAG,CAAC,yBAAyB,CAAC,CAAC;IACvC,OAAO,CAAC,GAAG,CAAC,0BAA0B,CAAC,CAAC;IACxC,OAAO,CAAC,GAAG,CAAC,+BAA+B,CAAC,CAAC;IAC7C,OAAO,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;IACzB,OAAO,CAAC,GAAG,CAAC,wBAAwB,CAAC,CAAC;IAEtC,OAAO,CAAC,GAAG,CAAC,KAAK,qBAAK,CAAC,KAAK,0CAA0C,CAAC,CAAC;IACxE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,QAAQ,EAAE,CAAC,EAAE,CAAC;AAChE,CAAC;AAED,yBAAyB;AACzB,IAAI,OAAO,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;IAC5B,GAAG,EAAE;SACF,IAAI,CAAC,CAAC,MAAM,EAAE,EAAE;QACf,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACvC,CAAC,CAAC;SACD,KAAK,CAAC,CAAC,GAAU,EAAE,EAAE;QACpB,OAAO,CAAC,KAAK,CAAC,GAAG,qBAAK,CAAC,KAAK,oBAAoB,EAAE,GAAG,CAAC,CAAC;QACvD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC,CAAC,CAAC;AACP,CAAC"}
|
|
@@ -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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"}
|