@empline/preflight 1.1.43 → 1.1.45
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/react/state-update-during-render.d.ts +3 -5
- package/dist/checks/react/state-update-during-render.d.ts.map +1 -1
- package/dist/checks/react/state-update-during-render.js +77 -144
- package/dist/checks/react/state-update-during-render.js.map +1 -1
- package/dist/checks/system/preflight-metadata-validator.d.ts.map +1 -1
- package/dist/checks/system/preflight-metadata-validator.js +97 -2
- package/dist/checks/system/preflight-metadata-validator.js.map +1 -1
- package/dist/checks/ui/layout-overflow-containment.d.ts +23 -0
- package/dist/checks/ui/layout-overflow-containment.d.ts.map +1 -0
- package/dist/checks/ui/layout-overflow-containment.js +240 -0
- package/dist/checks/ui/layout-overflow-containment.js.map +1 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +19 -4
- package/dist/index.js.map +1 -1
- package/dist/runner.d.ts +1 -0
- package/dist/runner.d.ts.map +1 -1
- package/dist/runner.js +36 -0
- package/dist/runner.js.map +1 -1
- package/dist/shared/concurrency-config.d.ts +2 -0
- package/dist/shared/concurrency-config.d.ts.map +1 -1
- package/dist/shared/concurrency-config.js +2 -0
- package/dist/shared/concurrency-config.js.map +1 -1
- package/dist/shared/consolidated-check-base.d.ts +112 -0
- package/dist/shared/consolidated-check-base.d.ts.map +1 -0
- package/dist/shared/consolidated-check-base.js +247 -0
- package/dist/shared/consolidated-check-base.js.map +1 -0
- package/dist/shared/exclusions.d.ts +31 -0
- package/dist/shared/exclusions.d.ts.map +1 -1
- package/dist/shared/exclusions.js +108 -0
- package/dist/shared/exclusions.js.map +1 -1
- package/dist/shared/performance-tracker.d.ts +103 -0
- package/dist/shared/performance-tracker.d.ts.map +1 -0
- package/dist/shared/performance-tracker.js +267 -0
- package/dist/shared/performance-tracker.js.map +1 -0
- package/package.json +1 -1
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { PreflightCheckResult } from "../../core/types";
|
|
2
3
|
export declare const id = "react/state-update-during-render";
|
|
3
4
|
export declare const name = "State Update During Render Detection";
|
|
4
5
|
export declare const description = "Detects setState calls outside of effects and callbacks";
|
|
5
6
|
export declare const category = "react";
|
|
6
7
|
export declare const blocking = true;
|
|
7
8
|
export declare const tags: string[];
|
|
8
|
-
export declare
|
|
9
|
-
|
|
10
|
-
errors: number;
|
|
11
|
-
warnings: number;
|
|
12
|
-
}>;
|
|
9
|
+
export declare const requires: string[];
|
|
10
|
+
export declare function run(): Promise<PreflightCheckResult>;
|
|
13
11
|
//# sourceMappingURL=state-update-during-render.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"state-update-during-render.d.ts","sourceRoot":"","sources":["../../../src/checks/react/state-update-during-render.ts"],"names":[],"mappings":";
|
|
1
|
+
{"version":3,"file":"state-update-during-render.d.ts","sourceRoot":"","sources":["../../../src/checks/react/state-update-during-render.ts"],"names":[],"mappings":";AAeA,OAAO,EAAE,oBAAoB,EAAoB,MAAM,kBAAkB,CAAC;AAI1E,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,UAAuD,CAAC;AACzE,eAAO,MAAM,QAAQ,UAAY,CAAC;AA2ClC,wBAAsB,GAAG,IAAI,OAAO,CAAC,oBAAoB,CAAC,CA+CzD"}
|
|
@@ -1,10 +1,40 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
"use strict";
|
|
3
|
-
var
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
4
|
+
if (k2 === undefined) k2 = k;
|
|
5
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
6
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
7
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
8
|
+
}
|
|
9
|
+
Object.defineProperty(o, k2, desc);
|
|
10
|
+
}) : (function(o, m, k, k2) {
|
|
11
|
+
if (k2 === undefined) k2 = k;
|
|
12
|
+
o[k2] = m[k];
|
|
13
|
+
}));
|
|
14
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
15
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
16
|
+
}) : function(o, v) {
|
|
17
|
+
o["default"] = v;
|
|
18
|
+
});
|
|
19
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
20
|
+
var ownKeys = function(o) {
|
|
21
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
22
|
+
var ar = [];
|
|
23
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
24
|
+
return ar;
|
|
25
|
+
};
|
|
26
|
+
return ownKeys(o);
|
|
27
|
+
};
|
|
28
|
+
return function (mod) {
|
|
29
|
+
if (mod && mod.__esModule) return mod;
|
|
30
|
+
var result = {};
|
|
31
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
32
|
+
__setModuleDefault(result, mod);
|
|
33
|
+
return result;
|
|
34
|
+
};
|
|
35
|
+
})();
|
|
6
36
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
-
exports.tags = exports.blocking = exports.category = exports.description = exports.name = exports.id = void 0;
|
|
37
|
+
exports.requires = exports.tags = exports.blocking = exports.category = exports.description = exports.name = exports.id = void 0;
|
|
8
38
|
exports.run = run;
|
|
9
39
|
/**
|
|
10
40
|
* State Update During Render Preflight
|
|
@@ -15,96 +45,43 @@ exports.run = run;
|
|
|
15
45
|
* - UI flickering
|
|
16
46
|
* - Unpredictable behavior
|
|
17
47
|
*
|
|
18
|
-
*
|
|
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
|
-
* ```
|
|
48
|
+
* BLOCKING: Yes - This can cause infinite render loops
|
|
42
49
|
*/
|
|
43
|
-
const
|
|
44
|
-
const
|
|
50
|
+
const fs = __importStar(require("node:fs"));
|
|
51
|
+
const path = __importStar(require("node:path"));
|
|
45
52
|
const glob_1 = require("glob");
|
|
46
53
|
const console_chars_1 = require("../../utils/console-chars");
|
|
47
|
-
// METADATA
|
|
54
|
+
// METADATA
|
|
48
55
|
exports.id = "react/state-update-during-render";
|
|
49
56
|
exports.name = "State Update During Render Detection";
|
|
50
57
|
exports.description = "Detects setState calls outside of effects and callbacks";
|
|
51
58
|
exports.category = "react";
|
|
52
|
-
exports.blocking = true;
|
|
53
|
-
exports.tags = ["react", "state", "hooks", "render"];
|
|
54
|
-
|
|
55
|
-
|
|
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
|
-
*/
|
|
59
|
+
exports.blocking = true;
|
|
60
|
+
exports.tags = ["react", "state", "hooks", "render", "performance"];
|
|
61
|
+
exports.requires = ["react"];
|
|
62
|
+
const FILE_PATTERNS = ["app/**/*.tsx", "components/**/*.tsx", "src/**/*.tsx", "pages/**/*.tsx"];
|
|
63
|
+
const EXCLUDE_PATTERNS = ["**/node_modules/**", "**/*.test.tsx", "**/*.spec.tsx", "**/*.stories.tsx"];
|
|
73
64
|
const SAFE_CONTEXTS = [
|
|
74
65
|
/useEffect\s*\(\s*\(\)\s*=>\s*\{/,
|
|
75
66
|
/useCallback\s*\(\s*\([^)]*\)\s*=>\s*\{/,
|
|
76
67
|
/useMemo\s*\(\s*\(\)\s*=>\s*\{/,
|
|
77
68
|
/useLayoutEffect\s*\(\s*\(\)\s*=>\s*\{/,
|
|
78
|
-
/
|
|
79
|
-
/onChange\s*=\s*\{/,
|
|
80
|
-
/onSubmit\s*=\s*\{/,
|
|
81
|
-
/onBlur\s*=\s*\{/,
|
|
82
|
-
/onFocus\s*=\s*\{/,
|
|
69
|
+
/on\w+\s*=\s*\{/,
|
|
83
70
|
/addEventListener\s*\(/,
|
|
84
71
|
/\.then\s*\(/,
|
|
85
72
|
/\.catch\s*\(/,
|
|
86
73
|
/async\s+function/,
|
|
87
74
|
/const\s+\w+\s*=\s*async/,
|
|
88
|
-
/=>\s*\{[\s\S]*?await\b/,
|
|
89
75
|
];
|
|
90
|
-
/**
|
|
91
|
-
* Patterns for setState calls
|
|
92
|
-
*/
|
|
93
76
|
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
77
|
function isInSafeContext(content, position) {
|
|
98
|
-
// Look backwards to find context
|
|
99
78
|
const before = content.substring(Math.max(0, position - 500), position);
|
|
100
|
-
// Count braces to determine nesting
|
|
101
79
|
let braceCount = 0;
|
|
102
80
|
for (let i = before.length - 1; i >= 0; i--) {
|
|
103
81
|
if (before[i] === "}")
|
|
104
82
|
braceCount++;
|
|
105
83
|
if (before[i] === "{")
|
|
106
84
|
braceCount--;
|
|
107
|
-
// If we've exited the current block, check what opened it
|
|
108
85
|
if (braceCount < 0) {
|
|
109
86
|
const contextBefore = before.substring(0, i + 1);
|
|
110
87
|
return SAFE_CONTEXTS.some(pattern => pattern.test(contextBefore));
|
|
@@ -112,116 +89,72 @@ function isInSafeContext(content, position) {
|
|
|
112
89
|
}
|
|
113
90
|
return false;
|
|
114
91
|
}
|
|
115
|
-
/**
|
|
116
|
-
* Check if the setState is immediately followed by return (common problematic pattern)
|
|
117
|
-
*/
|
|
118
92
|
function isFollowedByReturn(content, position) {
|
|
119
93
|
const after = content.substring(position, Math.min(content.length, position + 100));
|
|
120
|
-
// Pattern: setState(...); return or setState(...)\n return
|
|
121
94
|
return /^\([^)]*\);\s*\n?\s*return\b/.test(after);
|
|
122
95
|
}
|
|
123
|
-
/**
|
|
124
|
-
* Get line number from position
|
|
125
|
-
*/
|
|
126
96
|
function getLineNumber(content, position) {
|
|
127
97
|
return content.substring(0, position).split("\n").length;
|
|
128
98
|
}
|
|
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
99
|
async function run() {
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
const issues = [];
|
|
143
|
-
// Find all TSX files
|
|
100
|
+
const startTime = Date.now();
|
|
101
|
+
const findings = [];
|
|
144
102
|
const allFiles = [];
|
|
145
103
|
for (const pattern of FILE_PATTERNS) {
|
|
146
|
-
const matches = await (0, glob_1.glob)(pattern, {
|
|
147
|
-
cwd: process.cwd(),
|
|
148
|
-
ignore: EXCLUDE_PATTERNS,
|
|
149
|
-
});
|
|
104
|
+
const matches = await (0, glob_1.glob)(pattern, { cwd: process.cwd(), ignore: EXCLUDE_PATTERNS });
|
|
150
105
|
allFiles.push(...matches);
|
|
151
106
|
}
|
|
152
|
-
const
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
const filePath = path_1.default.join(process.cwd(), relativePath);
|
|
156
|
-
if (!fs_1.default.existsSync(filePath))
|
|
107
|
+
for (const relativePath of [...new Set(allFiles)]) {
|
|
108
|
+
const filePath = path.join(process.cwd(), relativePath);
|
|
109
|
+
if (!fs.existsSync(filePath))
|
|
157
110
|
continue;
|
|
158
|
-
const content =
|
|
159
|
-
|
|
111
|
+
const content = fs.readFileSync(filePath, "utf-8");
|
|
112
|
+
const pattern = new RegExp(SET_STATE_PATTERN.source, SET_STATE_PATTERN.flags);
|
|
160
113
|
let match;
|
|
161
|
-
while ((match =
|
|
114
|
+
while ((match = pattern.exec(content)) !== null) {
|
|
162
115
|
const position = match.index;
|
|
163
116
|
const setter = match[1];
|
|
164
|
-
|
|
165
|
-
if (isInSafeContext(content, position)) {
|
|
117
|
+
if (isInSafeContext(content, position))
|
|
166
118
|
continue;
|
|
167
|
-
}
|
|
168
|
-
// Check if this looks like a render-time state update
|
|
169
|
-
// (followed by return, which is a common pattern)
|
|
170
119
|
if (isFollowedByReturn(content, position)) {
|
|
171
120
|
const lineNum = getLineNumber(content, position);
|
|
172
|
-
// Check for preflight-ignore comment
|
|
173
121
|
const lineStart = content.lastIndexOf('\n', position) + 1;
|
|
174
122
|
const lineBefore = content.substring(Math.max(0, lineStart - 100), lineStart);
|
|
175
|
-
if (/preflight-ignore/.test(lineBefore))
|
|
123
|
+
if (/preflight-ignore/.test(lineBefore))
|
|
176
124
|
continue;
|
|
177
|
-
|
|
178
|
-
|
|
125
|
+
findings.push({
|
|
126
|
+
level: "error",
|
|
127
|
+
message: `setState (${setter}) called during render, followed by return. Move to useEffect.`,
|
|
179
128
|
file: relativePath,
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
129
|
+
startLine: lineNum,
|
|
130
|
+
ruleId: "state-update-during-render",
|
|
131
|
+
suggestion: `Wrap in useEffect: useEffect(() => { if (condition) { ${setter}(...); } }, [deps]);`,
|
|
183
132
|
});
|
|
184
133
|
}
|
|
185
134
|
}
|
|
186
135
|
}
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
136
|
+
return {
|
|
137
|
+
passed: findings.filter(f => f.level === "error").length === 0,
|
|
138
|
+
findings,
|
|
139
|
+
duration: Date.now() - startTime,
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
async function main() {
|
|
143
|
+
console.log(`\n⚛️ STATE UPDATE DURING RENDER DETECTION`);
|
|
144
|
+
console.log((0, console_chars_1.createDivider)(65, "heavy"));
|
|
145
|
+
const result = await run();
|
|
146
|
+
if (result.passed) {
|
|
192
147
|
console.log(`\n${console_chars_1.emoji.success} STATE UPDATE DURING RENDER CHECK PASSED`);
|
|
193
|
-
|
|
148
|
+
process.exit(0);
|
|
194
149
|
}
|
|
195
150
|
console.log(`\n${console_chars_1.emoji.error} Potential state updates during render:`);
|
|
196
|
-
for (const
|
|
197
|
-
console.log(
|
|
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);
|
|
151
|
+
for (const finding of result.findings || []) {
|
|
152
|
+
console.log(` ${finding.file}:${finding.startLine} - ${finding.message}`);
|
|
205
153
|
}
|
|
206
|
-
|
|
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 };
|
|
154
|
+
process.exit(1);
|
|
217
155
|
}
|
|
218
|
-
// Allow direct execution
|
|
219
156
|
if (require.main === module) {
|
|
220
|
-
|
|
221
|
-
.then((result) => {
|
|
222
|
-
process.exit(result.success ? 0 : 1);
|
|
223
|
-
})
|
|
224
|
-
.catch((err) => {
|
|
157
|
+
main().catch((err) => {
|
|
225
158
|
console.error(`${console_chars_1.emoji.error} Preflight failed:`, err);
|
|
226
159
|
process.exit(1);
|
|
227
160
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"state-update-during-render.js","sourceRoot":"","sources":["../../../src/checks/react/state-update-during-render.ts"],"names":[],"mappings":"
|
|
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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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"preflight-metadata-validator.d.ts","sourceRoot":"","sources":["../../../src/checks/system/preflight-metadata-validator.ts"],"names":[],"mappings":";AACA;;;;;;;;;;;;;;;;;GAiBG;AAKH,OAAO,EAAE,oBAAoB,EAAoB,MAAM,kBAAkB,CAAC;AAQ1E,eAAO,MAAM,EAAE,wCAAwC,CAAC;AACxD,eAAO,MAAM,IAAI,iCAAiC,CAAC;AACnD,eAAO,MAAM,WAAW,mFAAmF,CAAC;AAC5G,eAAO,MAAM,QAAQ,WAAW,CAAC;AACjC,eAAO,MAAM,QAAQ,OAAO,CAAC;AAC7B,eAAO,MAAM,IAAI,UAAoD,CAAC;
|
|
1
|
+
{"version":3,"file":"preflight-metadata-validator.d.ts","sourceRoot":"","sources":["../../../src/checks/system/preflight-metadata-validator.ts"],"names":[],"mappings":";AACA;;;;;;;;;;;;;;;;;GAiBG;AAKH,OAAO,EAAE,oBAAoB,EAAoB,MAAM,kBAAkB,CAAC;AAQ1E,eAAO,MAAM,EAAE,wCAAwC,CAAC;AACxD,eAAO,MAAM,IAAI,iCAAiC,CAAC;AACnD,eAAO,MAAM,WAAW,mFAAmF,CAAC;AAC5G,eAAO,MAAM,QAAQ,WAAW,CAAC;AACjC,eAAO,MAAM,QAAQ,OAAO,CAAC;AAC7B,eAAO,MAAM,IAAI,UAAoD,CAAC;AAiNtE,wBAAsB,GAAG,IAAI,OAAO,CAAC,oBAAoB,CAAC,CAkDzD"}
|
|
@@ -72,7 +72,8 @@ exports.tags = ["system", "meta", "validation", "plugin-loader"];
|
|
|
72
72
|
// CONSTANTS
|
|
73
73
|
// =============================================================================
|
|
74
74
|
const REQUIRED_EXPORTS = ["id", "name", "category", "blocking"];
|
|
75
|
-
const RECOMMENDED_EXPORTS = ["description", "tags"];
|
|
75
|
+
const RECOMMENDED_EXPORTS = ["description", "tags", "requires"];
|
|
76
|
+
const OPTIONAL_EXPORTS = ["ciOnly"];
|
|
76
77
|
// Directories to scan for app-specific preflights
|
|
77
78
|
const PREFLIGHT_DIRS = [
|
|
78
79
|
"scripts/preflights",
|
|
@@ -94,6 +95,87 @@ function extractExports(content) {
|
|
|
94
95
|
}
|
|
95
96
|
return exports;
|
|
96
97
|
}
|
|
98
|
+
function extractMetadataValues(content) {
|
|
99
|
+
const metadata = {};
|
|
100
|
+
// Extract string values
|
|
101
|
+
const extractString = (key) => {
|
|
102
|
+
const patterns = [
|
|
103
|
+
new RegExp(`export\\s+const\\s+${key}\\s*=\\s*["']([^"']+)["']`),
|
|
104
|
+
new RegExp(`exports\\.${key}\\s*=\\s*["']([^"']+)["']`),
|
|
105
|
+
];
|
|
106
|
+
for (const pattern of patterns) {
|
|
107
|
+
const match = content.match(pattern);
|
|
108
|
+
if (match)
|
|
109
|
+
return match[1];
|
|
110
|
+
}
|
|
111
|
+
return undefined;
|
|
112
|
+
};
|
|
113
|
+
// Extract boolean values
|
|
114
|
+
const extractBoolean = (key) => {
|
|
115
|
+
const patterns = [
|
|
116
|
+
new RegExp(`export\\s+const\\s+${key}\\s*=\\s*(true|false)`),
|
|
117
|
+
new RegExp(`exports\\.${key}\\s*=\\s*(true|false)`),
|
|
118
|
+
];
|
|
119
|
+
for (const pattern of patterns) {
|
|
120
|
+
const match = content.match(pattern);
|
|
121
|
+
if (match)
|
|
122
|
+
return match[1] === "true";
|
|
123
|
+
}
|
|
124
|
+
return undefined;
|
|
125
|
+
};
|
|
126
|
+
// Extract array values
|
|
127
|
+
const extractArray = (key) => {
|
|
128
|
+
const patterns = [
|
|
129
|
+
new RegExp(`export\\s+const\\s+${key}\\s*=\\s*\\[([^\\]]*?)\\]`),
|
|
130
|
+
new RegExp(`exports\\.${key}\\s*=\\s*\\[([^\\]]*?)\\]`),
|
|
131
|
+
];
|
|
132
|
+
for (const pattern of patterns) {
|
|
133
|
+
const match = content.match(pattern);
|
|
134
|
+
if (match) {
|
|
135
|
+
const arrayContent = match[1].trim();
|
|
136
|
+
if (!arrayContent)
|
|
137
|
+
return [];
|
|
138
|
+
const strings = arrayContent.match(/["']([^"']+)["']/g);
|
|
139
|
+
if (strings) {
|
|
140
|
+
return strings.map(s => s.replace(/["']/g, ""));
|
|
141
|
+
}
|
|
142
|
+
return [];
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
return undefined;
|
|
146
|
+
};
|
|
147
|
+
metadata.id = extractString("id");
|
|
148
|
+
metadata.name = extractString("name");
|
|
149
|
+
metadata.category = extractString("category");
|
|
150
|
+
metadata.blocking = extractBoolean("blocking");
|
|
151
|
+
metadata.requires = extractArray("requires");
|
|
152
|
+
return metadata;
|
|
153
|
+
}
|
|
154
|
+
function validateMetadataFormat(metadata) {
|
|
155
|
+
const errors = [];
|
|
156
|
+
// Validate ID format: should be "category/check-name"
|
|
157
|
+
if (metadata.id) {
|
|
158
|
+
if (!metadata.id.includes("/")) {
|
|
159
|
+
errors.push(`Invalid id format "${metadata.id}": should be "category/check-name"`);
|
|
160
|
+
}
|
|
161
|
+
else {
|
|
162
|
+
const [idCategory] = metadata.id.split("/");
|
|
163
|
+
// Check if category in id matches the category field
|
|
164
|
+
if (metadata.category && idCategory !== metadata.category) {
|
|
165
|
+
errors.push(`Category mismatch: id starts with "${idCategory}" but category is "${metadata.category}"`);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
// Validate requires format: should be an array of strings
|
|
170
|
+
if (metadata.requires) {
|
|
171
|
+
for (const req of metadata.requires) {
|
|
172
|
+
if (typeof req !== "string" || req.trim() === "") {
|
|
173
|
+
errors.push(`Invalid requires entry: "${req}" should be a non-empty string`);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
return errors;
|
|
178
|
+
}
|
|
97
179
|
function isPreflightFile(filePath, content) {
|
|
98
180
|
// Check if file looks like a preflight check
|
|
99
181
|
const indicators = [
|
|
@@ -132,11 +214,15 @@ async function scanDirectory(dirPath) {
|
|
|
132
214
|
const exports = extractExports(content);
|
|
133
215
|
const missing = REQUIRED_EXPORTS.filter((exp) => !exports.has(exp));
|
|
134
216
|
const recommended = RECOMMENDED_EXPORTS.filter((exp) => !exports.has(exp));
|
|
135
|
-
|
|
217
|
+
// Extract and validate metadata values
|
|
218
|
+
const metadata = extractMetadataValues(content);
|
|
219
|
+
const formatErrors = validateMetadataFormat(metadata);
|
|
220
|
+
if (missing.length > 0 || recommended.length > 0 || formatErrors.length > 0) {
|
|
136
221
|
issues.push({
|
|
137
222
|
file: path.relative(process.cwd(), fullPath),
|
|
138
223
|
missing,
|
|
139
224
|
recommended,
|
|
225
|
+
formatErrors,
|
|
140
226
|
});
|
|
141
227
|
}
|
|
142
228
|
}
|
|
@@ -165,6 +251,15 @@ async function run() {
|
|
|
165
251
|
file: issue.file,
|
|
166
252
|
});
|
|
167
253
|
}
|
|
254
|
+
if (issue.formatErrors.length > 0) {
|
|
255
|
+
for (const formatError of issue.formatErrors) {
|
|
256
|
+
findings.push({
|
|
257
|
+
level: "error",
|
|
258
|
+
message: formatError,
|
|
259
|
+
file: issue.file,
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
}
|
|
168
263
|
if (issue.recommended.length > 0) {
|
|
169
264
|
findings.push({
|
|
170
265
|
level: "warning",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"preflight-metadata-validator.js","sourceRoot":"","sources":["../../../src/checks/system/preflight-metadata-validator.ts"],"names":[],"mappings":";;AACA;;;;;;;;;;;;;;;;;GAiBG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"preflight-metadata-validator.js","sourceRoot":"","sources":["../../../src/checks/system/preflight-metadata-validator.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
#!/usr/bin/env tsx
|
|
2
|
+
/**
|
|
3
|
+
* Layout Overflow Containment Preflight
|
|
4
|
+
*
|
|
5
|
+
* Detects dashboard/page layouts where the main content area lacks proper
|
|
6
|
+
* overflow containment, which can cause horizontal page scrollbars.
|
|
7
|
+
*
|
|
8
|
+
* When a flex container has `minWidth: 0` (to allow shrinking) but no
|
|
9
|
+
* `overflow: hidden`, scrollable children (like TableContainer) won't
|
|
10
|
+
* properly trigger internal scrolling - instead the content can expand
|
|
11
|
+
* and create page-level horizontal scrollbars.
|
|
12
|
+
*
|
|
13
|
+
* BLOCKING: Yes - horizontal page scrollbars are a significant UX issue
|
|
14
|
+
*/
|
|
15
|
+
import { PreflightCheckResult } from "../../core/types";
|
|
16
|
+
export declare const id = "ui/layout-overflow-containment";
|
|
17
|
+
export declare const name = "Layout Overflow Containment";
|
|
18
|
+
export declare const description = "Ensures dashboard layouts contain overflow to prevent page-level scrollbars";
|
|
19
|
+
export declare const category = "ui";
|
|
20
|
+
export declare const blocking = true;
|
|
21
|
+
export declare const tags: string[];
|
|
22
|
+
export declare function run(): Promise<PreflightCheckResult>;
|
|
23
|
+
//# sourceMappingURL=layout-overflow-containment.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"layout-overflow-containment.d.ts","sourceRoot":"","sources":["../../../src/checks/ui/layout-overflow-containment.ts"],"names":[],"mappings":";AACA;;;;;;;;;;;;GAYG;AAKH,OAAO,EAAE,oBAAoB,EAAoB,MAAM,kBAAkB,CAAC;AAI1E,eAAO,MAAM,EAAE,mCAAmC,CAAC;AACnD,eAAO,MAAM,IAAI,gCAAgC,CAAC;AAClD,eAAO,MAAM,WAAW,gFAAgF,CAAC;AACzG,eAAO,MAAM,QAAQ,OAAO,CAAC;AAC7B,eAAO,MAAM,QAAQ,OAAO,CAAC;AAC7B,eAAO,MAAM,IAAI,UAA6C,CAAC;AA0I/D,wBAAsB,GAAG,IAAI,OAAO,CAAC,oBAAoB,CAAC,CA2DzD"}
|