@empline/preflight 1.1.28 → 1.1.30
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 +155 -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/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/dist/checks/ui/select-option-styling.d.ts +17 -0
- package/dist/checks/ui/select-option-styling.d.ts.map +1 -0
- package/dist/checks/ui/select-option-styling.js +149 -0
- package/dist/checks/ui/select-option-styling.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":";AAkBA,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;AA+ChD,wBAAsB,GAAG,IAAI,OAAO,CAAC;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,CAoG3F"}
|
|
@@ -0,0 +1,155 @@
|
|
|
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
|
+
*
|
|
18
|
+
* This helps prevent silent data loss where UI sends fields that get stripped by validation.
|
|
19
|
+
*/
|
|
20
|
+
const fs_1 = __importDefault(require("fs"));
|
|
21
|
+
const path_1 = __importDefault(require("path"));
|
|
22
|
+
const console_chars_1 = require("../../utils/console-chars");
|
|
23
|
+
// METADATA - Required for plugin loader discovery
|
|
24
|
+
exports.id = "api/integration-schema-consistency";
|
|
25
|
+
exports.name = "Integration Schema Consistency";
|
|
26
|
+
exports.description = "Detects mismatches between UI types and API schemas";
|
|
27
|
+
exports.category = "api";
|
|
28
|
+
exports.blocking = true; // This is a critical issue that can cause data loss
|
|
29
|
+
exports.tags = ["api", "schema", "types", "integration"];
|
|
30
|
+
exports.requires = ["trading-card-system"];
|
|
31
|
+
/**
|
|
32
|
+
* Integration endpoints to check
|
|
33
|
+
*/
|
|
34
|
+
const INTEGRATION_CHECKS = [
|
|
35
|
+
{
|
|
36
|
+
name: "WooCommerce",
|
|
37
|
+
uiTypePath: "components/woocommerce/types.ts",
|
|
38
|
+
apiRoutePath: "app/api/store/integrations/woocommerce/route.ts",
|
|
39
|
+
fieldMappings: [
|
|
40
|
+
{ uiField: "wooField", apiField: "sourceField" },
|
|
41
|
+
{ uiField: "supercatchField", apiField: "targetField" },
|
|
42
|
+
{ uiField: "linkProducts", apiField: "linkProducts" },
|
|
43
|
+
{ uiField: "importProducts", apiField: "importProducts" },
|
|
44
|
+
{ uiField: "syncInventory", apiField: "syncInventory" },
|
|
45
|
+
{ uiField: "syncPrices", apiField: "syncPrices" },
|
|
46
|
+
{ uiField: "syncOrders", apiField: "syncOrders" },
|
|
47
|
+
],
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
name: "Shopify",
|
|
51
|
+
uiTypePath: "components/shopify/types.ts",
|
|
52
|
+
apiRoutePath: "app/api/store/integrations/shopify/route.ts",
|
|
53
|
+
fieldMappings: [
|
|
54
|
+
{ uiField: "importProducts", apiField: "importProducts" },
|
|
55
|
+
{ uiField: "syncInventory", apiField: "syncInventory" },
|
|
56
|
+
{ uiField: "syncPrices", apiField: "syncPrices" },
|
|
57
|
+
],
|
|
58
|
+
},
|
|
59
|
+
];
|
|
60
|
+
async function run() {
|
|
61
|
+
console.log(`\n${console_chars_1.emoji.check} INTEGRATION SCHEMA CONSISTENCY`);
|
|
62
|
+
console.log((0, console_chars_1.createDivider)(65, "heavy"));
|
|
63
|
+
const issues = [];
|
|
64
|
+
for (const check of INTEGRATION_CHECKS) {
|
|
65
|
+
const uiPath = path_1.default.join(process.cwd(), check.uiTypePath);
|
|
66
|
+
const apiPath = path_1.default.join(process.cwd(), check.apiRoutePath);
|
|
67
|
+
// Check if files exist
|
|
68
|
+
if (!fs_1.default.existsSync(uiPath)) {
|
|
69
|
+
console.log(` Skipping ${check.name}: UI types file not found`);
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
if (!fs_1.default.existsSync(apiPath)) {
|
|
73
|
+
console.log(` Skipping ${check.name}: API route file not found`);
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
const uiContent = fs_1.default.readFileSync(uiPath, "utf-8");
|
|
77
|
+
const apiContent = fs_1.default.readFileSync(apiPath, "utf-8");
|
|
78
|
+
console.log(`\n Checking ${check.name}...`);
|
|
79
|
+
// Check field mappings
|
|
80
|
+
for (const mapping of check.fieldMappings) {
|
|
81
|
+
const uiHasField = new RegExp(`\\b${mapping.uiField}\\b`).test(uiContent);
|
|
82
|
+
const apiHasField = new RegExp(`\\b${mapping.apiField}\\b`).test(apiContent);
|
|
83
|
+
if (uiHasField && !apiHasField) {
|
|
84
|
+
issues.push({
|
|
85
|
+
integration: check.name,
|
|
86
|
+
type: "missing_in_api",
|
|
87
|
+
uiField: mapping.uiField,
|
|
88
|
+
apiField: mapping.apiField,
|
|
89
|
+
description: `UI uses '${mapping.uiField}' but API schema doesn't accept '${mapping.apiField}'`,
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
// Check for field name mismatch (UI sends different name than API expects)
|
|
93
|
+
if (mapping.uiField !== mapping.apiField) {
|
|
94
|
+
const apiAcceptsBoth = new RegExp(`\\b${mapping.uiField}\\b`).test(apiContent) ||
|
|
95
|
+
new RegExp(`\\b${mapping.apiField}\\b`).test(apiContent);
|
|
96
|
+
if (uiHasField && !apiAcceptsBoth) {
|
|
97
|
+
issues.push({
|
|
98
|
+
integration: check.name,
|
|
99
|
+
type: "field_mismatch",
|
|
100
|
+
uiField: mapping.uiField,
|
|
101
|
+
apiField: mapping.apiField,
|
|
102
|
+
description: `UI sends '${mapping.uiField}' but API may only accept '${mapping.apiField}' - ensure API handles both`,
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
// Check for exportListings vs linkProducts mismatch specifically
|
|
108
|
+
const uiHasLinkProducts = /\blinkProducts\b/.test(uiContent);
|
|
109
|
+
const apiHasLinkProducts = /\blinkProducts\b/.test(apiContent);
|
|
110
|
+
const apiHasExportListings = /\bexportListings\b/.test(apiContent);
|
|
111
|
+
if (uiHasLinkProducts && apiHasExportListings && !apiHasLinkProducts) {
|
|
112
|
+
issues.push({
|
|
113
|
+
integration: check.name,
|
|
114
|
+
type: "field_mismatch",
|
|
115
|
+
uiField: "linkProducts",
|
|
116
|
+
apiField: "exportListings",
|
|
117
|
+
description: `UI uses 'linkProducts' but API schema uses 'exportListings' - data will be silently dropped`,
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
// Summary
|
|
122
|
+
console.log(`\n${console_chars_1.emoji.chart} Summary:`);
|
|
123
|
+
console.log(` Integrations checked: ${INTEGRATION_CHECKS.length}`);
|
|
124
|
+
console.log(` Issues found: ${issues.length}`);
|
|
125
|
+
if (issues.length === 0) {
|
|
126
|
+
console.log(`\n${console_chars_1.emoji.success} INTEGRATION SCHEMA CONSISTENCY PASSED`);
|
|
127
|
+
return { success: true, errors: 0, warnings: 0 };
|
|
128
|
+
}
|
|
129
|
+
console.log(`\n${console_chars_1.emoji.error} Schema mismatches found:`);
|
|
130
|
+
for (const issue of issues) {
|
|
131
|
+
console.log(`\n ${issue.integration}:`);
|
|
132
|
+
console.log(` Type: ${issue.type}`);
|
|
133
|
+
console.log(` UI Field: ${issue.uiField}`);
|
|
134
|
+
console.log(` API Field: ${issue.apiField}`);
|
|
135
|
+
console.log(` ${issue.description}`);
|
|
136
|
+
}
|
|
137
|
+
console.log(`\n${console_chars_1.emoji.info} To fix schema mismatches:`);
|
|
138
|
+
console.log(` 1. Ensure API schema accepts the field names UI sends`);
|
|
139
|
+
console.log(` 2. Use Zod transform() to normalize field names if needed`);
|
|
140
|
+
console.log(` 3. Update UI types to match API expectations`);
|
|
141
|
+
console.log(`\n${console_chars_1.emoji.error} INTEGRATION SCHEMA CONSISTENCY FAILED`);
|
|
142
|
+
return { success: false, errors: issues.length, warnings: 0 };
|
|
143
|
+
}
|
|
144
|
+
// Allow direct execution
|
|
145
|
+
if (require.main === module) {
|
|
146
|
+
run()
|
|
147
|
+
.then((result) => {
|
|
148
|
+
process.exit(result.success ? 0 : 1);
|
|
149
|
+
})
|
|
150
|
+
.catch((err) => {
|
|
151
|
+
console.error(`${console_chars_1.emoji.error} Preflight failed:`, err);
|
|
152
|
+
process.exit(1);
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
//# 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/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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"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
#!/usr/bin/env tsx
|
|
2
|
+
/**
|
|
3
|
+
* Select Option Styling Preflight
|
|
4
|
+
*
|
|
5
|
+
* Detects native <option> elements inside Select components which break
|
|
6
|
+
* the shared component styling. MUI/custom Select components should use
|
|
7
|
+
* MenuItem instead of native option elements for consistent styling.
|
|
8
|
+
*
|
|
9
|
+
* @blocking false - Advisory check for UI consistency
|
|
10
|
+
*/
|
|
11
|
+
export declare const id = "ui/select-option-styling";
|
|
12
|
+
export declare const name = "Select Option Styling";
|
|
13
|
+
export declare const category = "ui";
|
|
14
|
+
export declare const blocking = false;
|
|
15
|
+
export declare const description = "Detects native <option> elements inside Select components that should use MenuItem for proper styling";
|
|
16
|
+
export declare const tags: string[];
|
|
17
|
+
//# sourceMappingURL=select-option-styling.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"select-option-styling.d.ts","sourceRoot":"","sources":["../../../src/checks/ui/select-option-styling.ts"],"names":[],"mappings":";AACA;;;;;;;;GAQG;AAQH,eAAO,MAAM,EAAE,6BAA6B,CAAC;AAC7C,eAAO,MAAM,IAAI,0BAA0B,CAAC;AAC5C,eAAO,MAAM,QAAQ,OAAO,CAAC;AAC7B,eAAO,MAAM,QAAQ,QAAQ,CAAC;AAC9B,eAAO,MAAM,WAAW,0GACiF,CAAC;AAC1G,eAAO,MAAM,IAAI,UAAsC,CAAC"}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
#!/usr/bin/env tsx
|
|
2
|
+
"use strict";
|
|
3
|
+
/**
|
|
4
|
+
* Select Option Styling Preflight
|
|
5
|
+
*
|
|
6
|
+
* Detects native <option> elements inside Select components which break
|
|
7
|
+
* the shared component styling. MUI/custom Select components should use
|
|
8
|
+
* MenuItem instead of native option elements for consistent styling.
|
|
9
|
+
*
|
|
10
|
+
* @blocking false - Advisory check for UI consistency
|
|
11
|
+
*/
|
|
12
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
13
|
+
if (k2 === undefined) k2 = k;
|
|
14
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
15
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
16
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
17
|
+
}
|
|
18
|
+
Object.defineProperty(o, k2, desc);
|
|
19
|
+
}) : (function(o, m, k, k2) {
|
|
20
|
+
if (k2 === undefined) k2 = k;
|
|
21
|
+
o[k2] = m[k];
|
|
22
|
+
}));
|
|
23
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
24
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
25
|
+
}) : function(o, v) {
|
|
26
|
+
o["default"] = v;
|
|
27
|
+
});
|
|
28
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
29
|
+
var ownKeys = function(o) {
|
|
30
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
31
|
+
var ar = [];
|
|
32
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
33
|
+
return ar;
|
|
34
|
+
};
|
|
35
|
+
return ownKeys(o);
|
|
36
|
+
};
|
|
37
|
+
return function (mod) {
|
|
38
|
+
if (mod && mod.__esModule) return mod;
|
|
39
|
+
var result = {};
|
|
40
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
41
|
+
__setModuleDefault(result, mod);
|
|
42
|
+
return result;
|
|
43
|
+
};
|
|
44
|
+
})();
|
|
45
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
46
|
+
exports.tags = exports.description = exports.blocking = exports.category = exports.name = exports.id = void 0;
|
|
47
|
+
const fs = __importStar(require("fs"));
|
|
48
|
+
const glob_1 = require("glob");
|
|
49
|
+
const path = __importStar(require("path"));
|
|
50
|
+
const console_chars_1 = require("../../utils/console-chars");
|
|
51
|
+
// Check metadata
|
|
52
|
+
exports.id = "ui/select-option-styling";
|
|
53
|
+
exports.name = "Select Option Styling";
|
|
54
|
+
exports.category = "ui";
|
|
55
|
+
exports.blocking = false;
|
|
56
|
+
exports.description = "Detects native <option> elements inside Select components that should use MenuItem for proper styling";
|
|
57
|
+
exports.tags = ["ui", "consistency", "components"];
|
|
58
|
+
const ROOT_DIR = process.cwd();
|
|
59
|
+
function getFiles(patterns) {
|
|
60
|
+
const allFiles = [];
|
|
61
|
+
for (const pattern of patterns) {
|
|
62
|
+
const matches = glob_1.glob.sync(pattern, {
|
|
63
|
+
cwd: ROOT_DIR,
|
|
64
|
+
absolute: true,
|
|
65
|
+
ignore: ["**/node_modules/**", "**/.next/**", "**/dist/**"],
|
|
66
|
+
});
|
|
67
|
+
allFiles.push(...matches);
|
|
68
|
+
}
|
|
69
|
+
return [...new Set(allFiles)];
|
|
70
|
+
}
|
|
71
|
+
function readFile(filePath) {
|
|
72
|
+
try {
|
|
73
|
+
return fs.readFileSync(filePath, "utf-8");
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
async function main() {
|
|
80
|
+
console.log(`\n${console_chars_1.emoji.palette} Select Option Styling Check\n`);
|
|
81
|
+
const issues = [];
|
|
82
|
+
// Find all TSX files that might have Select components
|
|
83
|
+
const files = getFiles(["components/**/*.tsx", "app/**/*.tsx"]);
|
|
84
|
+
// Pattern to detect <option> within context of Select usage
|
|
85
|
+
// We look for files that:
|
|
86
|
+
// 1. Import Select from @/components/ui
|
|
87
|
+
// 2. Contain <option> elements
|
|
88
|
+
for (const filePath of files) {
|
|
89
|
+
const content = readFile(filePath);
|
|
90
|
+
if (!content)
|
|
91
|
+
continue;
|
|
92
|
+
const relativePath = path.relative(ROOT_DIR, filePath);
|
|
93
|
+
// Check if file imports Select from @/components/ui
|
|
94
|
+
const hasSelectImport = content.includes("Select") &&
|
|
95
|
+
(content.includes("@/components/ui") || content.includes("components/ui"));
|
|
96
|
+
if (!hasSelectImport)
|
|
97
|
+
continue;
|
|
98
|
+
// Check for native <option> elements
|
|
99
|
+
const lines = content.split("\n");
|
|
100
|
+
for (let i = 0; i < lines.length; i++) {
|
|
101
|
+
const line = lines[i];
|
|
102
|
+
if (line && /<option\s+value=/.test(line)) {
|
|
103
|
+
issues.push({
|
|
104
|
+
file: relativePath,
|
|
105
|
+
line: i + 1,
|
|
106
|
+
content: line.trim().substring(0, 80),
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
// Report findings
|
|
112
|
+
if (issues.length === 0) {
|
|
113
|
+
console.log(`${console_chars_1.emoji.success} No native <option> elements found in Select components!\n`);
|
|
114
|
+
console.log("All dropdowns are using MenuItem for consistent shared styling.\n");
|
|
115
|
+
process.exit(0);
|
|
116
|
+
}
|
|
117
|
+
console.log(`${console_chars_1.emoji.warning} Found ${issues.length} native <option> elements that should use MenuItem\n`);
|
|
118
|
+
console.log("Native <option> elements don't receive shared component styling.");
|
|
119
|
+
console.log("Replace with MenuItem from @/components/ui for consistent UI.\n");
|
|
120
|
+
console.log("─".repeat(70));
|
|
121
|
+
console.log("");
|
|
122
|
+
// Group by file
|
|
123
|
+
const byFile = issues.reduce((acc, issue) => {
|
|
124
|
+
acc[issue.file] ||= [];
|
|
125
|
+
acc[issue.file].push(issue);
|
|
126
|
+
return acc;
|
|
127
|
+
}, {});
|
|
128
|
+
for (const [file, fileIssues] of Object.entries(byFile)) {
|
|
129
|
+
console.log(`${console_chars_1.emoji.file} ${file}`);
|
|
130
|
+
for (const issue of fileIssues) {
|
|
131
|
+
console.log(` Line ${issue.line}: ${issue.content}`);
|
|
132
|
+
}
|
|
133
|
+
console.log("");
|
|
134
|
+
}
|
|
135
|
+
console.log("─".repeat(70));
|
|
136
|
+
console.log("");
|
|
137
|
+
console.log(`${console_chars_1.emoji.info} Fix: Replace <option value="...">...</option>`);
|
|
138
|
+
console.log(` With: <MenuItem value="...">...</MenuItem>`);
|
|
139
|
+
console.log("");
|
|
140
|
+
console.log(` Import: import { MenuItem } from "@/components/ui";`);
|
|
141
|
+
console.log("");
|
|
142
|
+
// Non-blocking, just advisory
|
|
143
|
+
process.exit(0);
|
|
144
|
+
}
|
|
145
|
+
main().catch((err) => {
|
|
146
|
+
console.error(`${console_chars_1.emoji.error} Select option styling check crashed:`, err);
|
|
147
|
+
process.exit(1);
|
|
148
|
+
});
|
|
149
|
+
//# sourceMappingURL=select-option-styling.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"select-option-styling.js","sourceRoot":"","sources":["../../../src/checks/ui/select-option-styling.ts"],"names":[],"mappings":"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"}
|