@webpieces/code-rules 0.4.716 → 0.4.717
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@webpieces/code-rules",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.717",
|
|
4
4
|
"description": "Standalone code validation rules extracted from architecture-validators, no Nx dependency required",
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"main": "./src/index.js",
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
"directory": "packages/tooling/code-rules"
|
|
17
17
|
},
|
|
18
18
|
"dependencies": {
|
|
19
|
-
"@webpieces/rules-config": "0.4.
|
|
19
|
+
"@webpieces/rules-config": "0.4.717",
|
|
20
20
|
"@inversifyjs/binding-decorators": "1.1.5",
|
|
21
21
|
"inversify": "7.10.4",
|
|
22
22
|
"reflect-metadata": "0.2.2"
|
|
@@ -12,8 +12,35 @@
|
|
|
12
12
|
* Format: // webpieces-disable max-lines-modified-files 2025/01/15 -- [reason]
|
|
13
13
|
* The disable expires after 1 month from the date specified.
|
|
14
14
|
*/
|
|
15
|
-
import { MaxFileLinesConfig } from '@webpieces/rules-config';
|
|
15
|
+
import { MaxFileLinesConfig, RuleFailError } from '@webpieces/rules-config';
|
|
16
16
|
import { CodeValidator, ExecutorResult } from './code-validator';
|
|
17
|
+
/** One over-limit file. A class, not an interface — findViolations is exported for its tests. */
|
|
18
|
+
export declare class FileViolation {
|
|
19
|
+
readonly file: string;
|
|
20
|
+
readonly lines: number;
|
|
21
|
+
readonly expiredDisable: boolean;
|
|
22
|
+
readonly expiredDate: string | undefined;
|
|
23
|
+
constructor(file: string, lines: number, expiredDisable?: boolean, expiredDate?: string);
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* The complete exempt-path list for one run: the machine-generated trees that are exempt with no
|
|
27
|
+
* configuration at all, PLUS whatever the repo listed in `max-file-lines.allowedPaths`. The config
|
|
28
|
+
* ADDS to the floor rather than replacing it, so exempting one of your own trees can never silently
|
|
29
|
+
* cost you the generated-code exemption (see GENERATED_CODE_PATHS for why that matters).
|
|
30
|
+
*/
|
|
31
|
+
export declare function exemptPathsFor(config: MaxFileLinesConfig): readonly string[];
|
|
32
|
+
/**
|
|
33
|
+
* Count lines in a file and check for violations
|
|
34
|
+
*/
|
|
35
|
+
export declare function findViolations(workspaceRoot: string, changedFiles: string[], limit: number, disableAllowed: boolean, exemptPaths: readonly string[]): FileViolation[];
|
|
36
|
+
/**
|
|
37
|
+
* The ONE failure value for this rule: a `RuleFailError` carrying the prose for a human and the cures
|
|
38
|
+
* as `Option[]`, which {@link RuleReporter} renders (it owns the "Fix Option N:" / "(preferred)"
|
|
39
|
+
* labels, so nothing here hand-numbers a cure). This used to be a `console.error` paragraph beside a
|
|
40
|
+
* `{ success: false }` return — a second spelling of "this validator failed" that the edit-time half of
|
|
41
|
+
* the SAME rule never used.
|
|
42
|
+
*/
|
|
43
|
+
export declare function violationsError(violations: readonly FileViolation[], limit: number, disableAllowed: boolean): RuleFailError;
|
|
17
44
|
export declare class MaxFileLinesValidator extends CodeValidator<MaxFileLinesConfig> {
|
|
18
45
|
constructor(config: MaxFileLinesConfig);
|
|
19
46
|
run(workspaceRoot: string): Promise<ExecutorResult>;
|
|
@@ -14,7 +14,10 @@
|
|
|
14
14
|
* The disable expires after 1 month from the date specified.
|
|
15
15
|
*/
|
|
16
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
-
exports.MaxFileLinesValidator = void 0;
|
|
17
|
+
exports.MaxFileLinesValidator = exports.FileViolation = void 0;
|
|
18
|
+
exports.exemptPathsFor = exemptPathsFor;
|
|
19
|
+
exports.findViolations = findViolations;
|
|
20
|
+
exports.violationsError = violationsError;
|
|
18
21
|
const tslib_1 = require("tslib");
|
|
19
22
|
const fs = tslib_1.__importStar(require("fs"));
|
|
20
23
|
const path = tslib_1.__importStar(require("path"));
|
|
@@ -22,6 +25,20 @@ const rules_config_1 = require("@webpieces/rules-config");
|
|
|
22
25
|
const code_validator_1 = require("./code-validator");
|
|
23
26
|
const inversify_1 = require("inversify");
|
|
24
27
|
const resolve_mode_1 = require("./resolve-mode");
|
|
28
|
+
/** One over-limit file. A class, not an interface — findViolations is exported for its tests. */
|
|
29
|
+
class FileViolation {
|
|
30
|
+
file;
|
|
31
|
+
lines;
|
|
32
|
+
expiredDisable;
|
|
33
|
+
expiredDate;
|
|
34
|
+
constructor(file, lines, expiredDisable = false, expiredDate) {
|
|
35
|
+
this.file = file;
|
|
36
|
+
this.lines = lines;
|
|
37
|
+
this.expiredDisable = expiredDisable;
|
|
38
|
+
this.expiredDate = expiredDate;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
exports.FileViolation = FileViolation;
|
|
25
42
|
const TMP_MD_FILE = 'webpieces.filesize.md';
|
|
26
43
|
/**
|
|
27
44
|
* Write the instructions documentation to .webpieces/instruct-ai/.
|
|
@@ -93,14 +110,28 @@ function checkDisableComment(content) {
|
|
|
93
110
|
}
|
|
94
111
|
return { hasDisable: false, isValid: false, isExpired: false };
|
|
95
112
|
}
|
|
113
|
+
/**
|
|
114
|
+
* The complete exempt-path list for one run: the machine-generated trees that are exempt with no
|
|
115
|
+
* configuration at all, PLUS whatever the repo listed in `max-file-lines.allowedPaths`. The config
|
|
116
|
+
* ADDS to the floor rather than replacing it, so exempting one of your own trees can never silently
|
|
117
|
+
* cost you the generated-code exemption (see GENERATED_CODE_PATHS for why that matters).
|
|
118
|
+
*/
|
|
119
|
+
// webpieces-disable no-function-outside-class -- pure list join, sibling of the file-scoped helpers here
|
|
120
|
+
function exemptPathsFor(config) {
|
|
121
|
+
return [...rules_config_1.GENERATED_CODE_PATHS, ...(config.allowedPaths ?? [])];
|
|
122
|
+
}
|
|
96
123
|
/**
|
|
97
124
|
* Count lines in a file and check for violations
|
|
98
125
|
*/
|
|
99
126
|
// webpieces-disable max-lines-new-methods -- File iteration with disable checking logic
|
|
100
|
-
function
|
|
127
|
+
// webpieces-disable no-function-outside-class -- pure scan over (files, limit, exemptPaths), sibling of the file-scoped helpers here
|
|
128
|
+
function findViolations(workspaceRoot, changedFiles, limit, disableAllowed, exemptPaths) {
|
|
101
129
|
const violations = [];
|
|
102
130
|
for (const file of changedFiles) {
|
|
103
131
|
const fullPath = path.join(workspaceRoot, file);
|
|
132
|
+
// Machine-generated / explicitly allowed trees: the file's size is not the repo's to fix.
|
|
133
|
+
if ((0, rules_config_1.isPathExcluded)(file, exemptPaths))
|
|
134
|
+
continue;
|
|
104
135
|
if (!fs.existsSync(fullPath))
|
|
105
136
|
continue;
|
|
106
137
|
const content = fs.readFileSync(fullPath, 'utf-8');
|
|
@@ -110,7 +141,7 @@ function findViolations(workspaceRoot, changedFiles, limit, disableAllowed) {
|
|
|
110
141
|
continue;
|
|
111
142
|
// When disableAllowed is false, ignore all disable comments
|
|
112
143
|
if (!disableAllowed) {
|
|
113
|
-
violations.push(
|
|
144
|
+
violations.push(new FileViolation(file, lineCount));
|
|
114
145
|
continue;
|
|
115
146
|
}
|
|
116
147
|
// Check for disable comment
|
|
@@ -122,20 +153,12 @@ function findViolations(workspaceRoot, changedFiles, limit, disableAllowed) {
|
|
|
122
153
|
}
|
|
123
154
|
if (disableStatus.isExpired) {
|
|
124
155
|
// Expired disable - report as violation with expired info
|
|
125
|
-
violations.push(
|
|
126
|
-
file,
|
|
127
|
-
lines: lineCount,
|
|
128
|
-
expiredDisable: true,
|
|
129
|
-
expiredDate: disableStatus.date,
|
|
130
|
-
});
|
|
156
|
+
violations.push(new FileViolation(file, lineCount, true, disableStatus.date));
|
|
131
157
|
continue;
|
|
132
158
|
}
|
|
133
159
|
// Invalid disable (missing/bad date) - fall through to report as violation
|
|
134
160
|
}
|
|
135
|
-
violations.push(
|
|
136
|
-
file,
|
|
137
|
-
lines: lineCount,
|
|
138
|
-
});
|
|
161
|
+
violations.push(new FileViolation(file, lineCount));
|
|
139
162
|
}
|
|
140
163
|
return violations;
|
|
141
164
|
}
|
|
@@ -150,58 +173,47 @@ function getTodayDateString() {
|
|
|
150
173
|
return `${year}/${month}/${day}`;
|
|
151
174
|
}
|
|
152
175
|
/**
|
|
153
|
-
*
|
|
176
|
+
* The ONE failure value for this rule: a `RuleFailError` carrying the prose for a human and the cures
|
|
177
|
+
* as `Option[]`, which {@link RuleReporter} renders (it owns the "Fix Option N:" / "(preferred)"
|
|
178
|
+
* labels, so nothing here hand-numbers a cure). This used to be a `console.error` paragraph beside a
|
|
179
|
+
* `{ success: false }` return — a second spelling of "this validator failed" that the edit-time half of
|
|
180
|
+
* the SAME rule never used.
|
|
154
181
|
*/
|
|
155
|
-
// webpieces-disable max-lines-new-methods --
|
|
156
|
-
function
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
console.error(` \u274c ${v.file} (${v.lines} lines, max: ${limit})`);
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
console.error('');
|
|
180
|
-
// Only show escape hatch instructions when disableAllowed is true
|
|
182
|
+
// webpieces-disable max-lines-new-methods -- one message value assembled from several sections
|
|
183
|
+
// webpieces-disable no-function-outside-class -- pure value builder, sibling of the file-scoped helpers here
|
|
184
|
+
function violationsError(violations, limit, disableAllowed) {
|
|
185
|
+
const listed = violations.map((v) => v.expiredDisable
|
|
186
|
+
? ` ${v.file} (${v.lines} lines, max: ${limit})\n EXPIRED DISABLE: the disable comment dated ${v.expiredDate ?? '?'} is over a month old — FIX the file or update the date for another month.`
|
|
187
|
+
: ` ${v.file} (${v.lines} lines, max: ${limit})`).join('\n');
|
|
188
|
+
const message = [
|
|
189
|
+
`YOU MUST FIX THIS AND NOT be more than ${limit} lines of code per file — it slows down IDEs and is VERY VERY EASY to refactor.`,
|
|
190
|
+
'',
|
|
191
|
+
'With stateless systems + dependency injection, refactor is trivial: pick a method or a few and',
|
|
192
|
+
'move them to a new class XXXXX, inject XXXXX into all users of those methods via the constructor,',
|
|
193
|
+
`then delete those methods from the original class. 99% of files can be less than ${limit} lines.`,
|
|
194
|
+
'',
|
|
195
|
+
listed,
|
|
196
|
+
'',
|
|
197
|
+
`disableAllowed is ${disableAllowed}${disableAllowed ? '' : ' — inline disable comments are IGNORED for this rule'}.`,
|
|
198
|
+
].join('\n');
|
|
199
|
+
const cures = [
|
|
200
|
+
new rules_config_1.Option(`Refactor the file under ${limit} lines — READ .webpieces/instruct-ai/${TMP_MD_FILE} for step-by-step guidance.`, true),
|
|
201
|
+
new rules_config_1.Option('Is the file MACHINE-GENERATED (codegen output, a schema dump)? Its size is not yours to fix, and turning the rule off is the wrong cure — that stops it on hand-written files too. Add its tree to rules."max-file-lines".allowedPaths in webpieces.config.json, e.g. "allowedPaths": ["**/__generated__/**"]. Never list hand-written code there.'),
|
|
202
|
+
];
|
|
181
203
|
if (disableAllowed) {
|
|
182
|
-
|
|
183
|
-
console.error(' since 99% of files can be less than ' + limit + ' lines of code.');
|
|
184
|
-
console.error('');
|
|
185
|
-
console.error(' Use escape with DATE (expires in 1 month):');
|
|
186
|
-
console.error(` // webpieces-disable max-lines-modified-files ${getTodayDateString()} -- [your reason]`);
|
|
187
|
-
console.error('');
|
|
204
|
+
cures.push(new rules_config_1.Option(`Put this on the FIRST 5 lines of the file (it expires in 1 month): // webpieces-disable max-lines-modified-files ${getTodayDateString()} -- [your reason]`));
|
|
188
205
|
}
|
|
189
206
|
else {
|
|
190
|
-
|
|
191
|
-
console.error(' This rule must be met and cannot be disabled since nx.json disableAllowed is set to false.');
|
|
192
|
-
console.error(' You MUST refactor to reduce file size.');
|
|
193
|
-
console.error('');
|
|
194
|
-
console.error(' For a major refactor, a human can add "turnOffRuleUntilEpoch" to nx.json validate-code options.');
|
|
195
|
-
console.error(' This is an expiry timestamp (epoch ms) for when we start forcing everyone to meet size rules again.');
|
|
196
|
-
console.error(' Sometimes for speed, we allow files to expand during a refactor and over time,');
|
|
197
|
-
console.error(' each PR reduces files as they get touched.');
|
|
198
|
-
console.error(' AI agents should NOT add turnOffRuleUntilEpoch - ask a human to do it.');
|
|
199
|
-
console.error('');
|
|
207
|
+
cures.push(new rules_config_1.Option('For a major refactor a HUMAN — never an AI agent — can set "turnOffRuleUntilEpoch" (an expiry, in epoch seconds) on the max-file-lines entry in webpieces.config.json, so files may expand during the refactor and each PR shrinks them as it touches them.'));
|
|
200
208
|
}
|
|
209
|
+
return new rules_config_1.RuleFailError('max-file-lines', message, undefined, undefined, cures);
|
|
201
210
|
}
|
|
202
211
|
async function runValidatorImpl(options, workspaceRoot) {
|
|
203
212
|
const limit = options.limit ?? 900;
|
|
204
213
|
const disableAllowed = options.disableAllowed ?? true;
|
|
214
|
+
// Machine-generated trees + the repo's own allowedPaths. A line-count rule firing on a file nobody
|
|
215
|
+
// authored is never a useful signal, so the generated half needs no configuration.
|
|
216
|
+
const exemptPaths = exemptPathsFor(options);
|
|
205
217
|
const rawMode = options.mode ?? 'NEW_AND_MODIFIED_FILES';
|
|
206
218
|
const skip = rawMode !== 'OFF' ? (0, resolve_mode_1.shouldSkipRule)(options.turnOffRuleUntilEpoch, (options.turnOffRuleWhileOnBranch ?? undefined)) : new resolve_mode_1.SkipRuleResult(false);
|
|
207
219
|
const mode = skip.skip ? 'OFF' : rawMode;
|
|
@@ -233,7 +245,9 @@ async function runValidatorImpl(options, workspaceRoot) {
|
|
|
233
245
|
console.log(` Mode: ${mode}`);
|
|
234
246
|
console.log(` Max lines for modified files: ${limit}`);
|
|
235
247
|
console.log(` Disable allowed: ${disableAllowed}${!disableAllowed ? ' (no escape hatch)' : ''}`);
|
|
248
|
+
console.log(` Exempt paths: ${exemptPaths.join(', ')}`);
|
|
236
249
|
console.log('');
|
|
250
|
+
let violations;
|
|
237
251
|
// eslint-disable-next-line @webpieces/no-unmanaged-exceptions
|
|
238
252
|
try {
|
|
239
253
|
const changedFiles = (0, rules_config_1.getChangedFiles)(workspaceRoot, base, head);
|
|
@@ -242,14 +256,7 @@ async function runValidatorImpl(options, workspaceRoot) {
|
|
|
242
256
|
return { success: true };
|
|
243
257
|
}
|
|
244
258
|
console.log(`\ud83d\udcc2 Checking ${changedFiles.length} changed file(s)...`);
|
|
245
|
-
|
|
246
|
-
if (violations.length === 0) {
|
|
247
|
-
console.log('\u2705 All modified files are under ' + limit + ' lines');
|
|
248
|
-
return { success: true };
|
|
249
|
-
}
|
|
250
|
-
writeTmpInstructions(workspaceRoot);
|
|
251
|
-
reportViolations(violations, limit, disableAllowed);
|
|
252
|
-
return { success: false };
|
|
259
|
+
violations = findViolations(workspaceRoot, changedFiles, limit, disableAllowed, exemptPaths);
|
|
253
260
|
}
|
|
254
261
|
catch (err) {
|
|
255
262
|
//const error = toError(err);
|
|
@@ -257,6 +264,13 @@ async function runValidatorImpl(options, workspaceRoot) {
|
|
|
257
264
|
console.error('\u274c Modified files validation failed:', error.message);
|
|
258
265
|
return { success: false };
|
|
259
266
|
}
|
|
267
|
+
if (violations.length === 0) {
|
|
268
|
+
console.log('\u2705 All modified files are under ' + limit + ' lines');
|
|
269
|
+
return { success: true };
|
|
270
|
+
}
|
|
271
|
+
// The ONE failure path: throw, so RuleReporter renders the message and its cures for this audience.
|
|
272
|
+
writeTmpInstructions(workspaceRoot);
|
|
273
|
+
throw violationsError(violations, limit, disableAllowed);
|
|
260
274
|
}
|
|
261
275
|
let MaxFileLinesValidator = class MaxFileLinesValidator extends code_validator_1.CodeValidator {
|
|
262
276
|
constructor(config) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validate-modified-files.js","sourceRoot":"","sources":["../../../../../packages/tooling/code-rules/src/validate-modified-files.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Validate Modified Files Executor\n *\n * Validates that modified files don't exceed a maximum line count (default 900).\n * This encourages keeping files small and focused - when you touch a file,\n * you must bring it under the limit.\n *\n * Usage:\n * nx affected --target=validate-modified-files --base=origin/main\n *\n * Escape hatch: Add webpieces-disable max-lines-modified-files comment with date and justification\n * Format: // webpieces-disable max-lines-modified-files 2025/01/15 -- [reason]\n * The disable expires after 1 month from the date specified.\n */\n\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport { writeTemplate, hasDisable, RULE_NAMES, MaxFileLinesConfig, FileLimitMode, detectBase, getChangedFiles } from '@webpieces/rules-config';\nimport { CodeValidator, ExecutorResult } from './code-validator';\nimport { injectable, bindingScopeValues } from 'inversify';\nimport { shouldSkipRule, SkipRuleResult } from './resolve-mode';\n\ninterface FileViolation {\n file: string;\n lines: number;\n expiredDisable?: boolean;\n expiredDate?: string;\n}\n\nconst TMP_MD_FILE = 'webpieces.filesize.md';\n\n\n/**\n * Write the instructions documentation to .webpieces/instruct-ai/.\n * Sourced from @webpieces/rules-config.\n */\nfunction writeTmpInstructions(workspaceRoot: string): string {\n return writeTemplate(workspaceRoot, TMP_MD_FILE);\n}\n\n/**\n * Parse a date string in yyyy/mm/dd format and return a Date object.\n * Returns null if the format is invalid.\n */\nfunction parseDisableDate(dateStr: string): Date | null {\n // Match yyyy/mm/dd format\n const match = dateStr.match(/^(\\d{4})\\/(\\d{2})\\/(\\d{2})$/);\n if (!match) return null;\n\n const year = parseInt(match[1], 10);\n const month = parseInt(match[2], 10) - 1; // JS months are 0-indexed\n const day = parseInt(match[3], 10);\n\n const date = new Date(year, month, day);\n\n // Validate the date is valid (e.g., not Feb 30)\n if (date.getFullYear() !== year || date.getMonth() !== month || date.getDate() !== day) {\n return null;\n }\n\n return date;\n}\n\n/**\n * Check if a date is within the last month (not expired).\n */\nfunction isDateWithinMonth(date: Date): boolean {\n const now = new Date();\n const oneMonthAgo = new Date(now.getFullYear(), now.getMonth() - 1, now.getDate());\n return date >= oneMonthAgo;\n}\n\ninterface DisableStatus {\n hasDisable: boolean;\n isValid: boolean;\n isExpired: boolean;\n date?: string;\n}\n\n/**\n * Check if a file has a valid, non-expired disable comment at the top (within first 5 lines).\n * Returns status object with details about the disable comment.\n */\n// webpieces-disable max-lines-new-methods -- Date validation logic requires checking multiple conditions\nfunction checkDisableComment(content: string): DisableStatus {\n const lines = content.split('\\n').slice(0, 5);\n\n for (const line of lines) {\n if (hasDisable(line, RULE_NAMES.MAX_LINES_MODIFIED_FILES)) {\n // Found disable comment, now check for date\n // Format: // webpieces-disable max-lines-modified-files yyyy/mm/dd -- reason\n const dateMatch = line.match(/max-lines-modified-files\\s+(\\d{4}\\/\\d{2}\\/\\d{2}|XXXX\\/XX\\/XX)/);\n\n if (!dateMatch) {\n // No date found - invalid disable comment\n return { hasDisable: true, isValid: false, isExpired: false };\n }\n\n const dateStr = dateMatch[1];\n\n // Secret permanent disable\n if (dateStr === 'XXXX/XX/XX') {\n return { hasDisable: true, isValid: true, isExpired: false, date: dateStr };\n }\n\n const date = parseDisableDate(dateStr);\n if (!date) {\n // Invalid date format\n return { hasDisable: true, isValid: false, isExpired: false, date: dateStr };\n }\n\n if (!isDateWithinMonth(date)) {\n // Date is expired (older than 1 month)\n return { hasDisable: true, isValid: true, isExpired: true, date: dateStr };\n }\n\n // Valid and not expired\n return { hasDisable: true, isValid: true, isExpired: false, date: dateStr };\n }\n }\n\n return { hasDisable: false, isValid: false, isExpired: false };\n}\n\n/**\n * Count lines in a file and check for violations\n */\n// webpieces-disable max-lines-new-methods -- File iteration with disable checking logic\nfunction findViolations(workspaceRoot: string, changedFiles: string[], limit: number, disableAllowed: boolean): FileViolation[] {\n const violations: FileViolation[] = [];\n\n for (const file of changedFiles) {\n const fullPath = path.join(workspaceRoot, file);\n\n if (!fs.existsSync(fullPath)) continue;\n\n const content = fs.readFileSync(fullPath, 'utf-8');\n const lineCount = content.split('\\n').length;\n\n // Skip files under the limit\n if (lineCount <= limit) continue;\n\n // When disableAllowed is false, ignore all disable comments\n if (!disableAllowed) {\n violations.push({ file, lines: lineCount });\n continue;\n }\n\n // Check for disable comment\n const disableStatus = checkDisableComment(content);\n\n if (disableStatus.hasDisable) {\n if (disableStatus.isValid && !disableStatus.isExpired) {\n // Valid, non-expired disable - skip this file\n continue;\n }\n\n if (disableStatus.isExpired) {\n // Expired disable - report as violation with expired info\n violations.push({\n file,\n lines: lineCount,\n expiredDisable: true,\n expiredDate: disableStatus.date,\n });\n continue;\n }\n\n // Invalid disable (missing/bad date) - fall through to report as violation\n }\n\n violations.push({\n file,\n lines: lineCount,\n });\n }\n\n return violations;\n}\n\n/**\n * Get today's date in yyyy/mm/dd format for error messages\n */\nfunction getTodayDateString(): string {\n const now = new Date();\n const year = now.getFullYear();\n const month = String(now.getMonth() + 1).padStart(2, '0');\n const day = String(now.getDate()).padStart(2, '0');\n return `${year}/${month}/${day}`;\n}\n\n/**\n * Report violations to console\n */\n// webpieces-disable max-lines-new-methods -- Error output formatting with multiple message sections\nfunction reportViolations(violations: FileViolation[], limit: number, disableAllowed: boolean): void {\n console.error('');\n console.error('\\u274c YOU MUST FIX THIS AND NOT be more than ' + limit + ' lines of code per file');\n console.error(' as it slows down IDEs AND is VERY VERY EASY to refactor.');\n console.error('');\n console.error('\\ud83d\\udcda With stateless systems + dependency injection, refactor is trivial:');\n console.error(' Pick a method or a few and move to new class XXXXX, then inject XXXXX');\n console.error(' into all users of those methods via the constructor.');\n console.error(' Delete those methods from original class.');\n console.error(' 99% of files can be less than ' + limit + ' lines of code.');\n console.error('');\n console.error('\\u26a0\\ufe0f *** READ .webpieces/instruct-ai/webpieces.filesize.md for detailed guidance on how to fix this easily *** \\u26a0\\ufe0f');\n console.error('');\n\n for (const v of violations) {\n if (v.expiredDisable) {\n console.error(` \\u274c ${v.file} (${v.lines} lines, max: ${limit})`);\n console.error(` \\u23f0 EXPIRED DISABLE: Your disable comment dated ${v.expiredDate} has expired (>1 month old).`);\n console.error(` You must either FIX the file or UPDATE the date to get another month.`);\n } else {\n console.error(` \\u274c ${v.file} (${v.lines} lines, max: ${limit})`);\n }\n }\n console.error('');\n\n // Only show escape hatch instructions when disableAllowed is true\n if (disableAllowed) {\n console.error(' You can disable this error, but you will be forced to fix again in 1 month');\n console.error(' since 99% of files can be less than ' + limit + ' lines of code.');\n console.error('');\n console.error(' Use escape with DATE (expires in 1 month):');\n console.error(` // webpieces-disable max-lines-modified-files ${getTodayDateString()} -- [your reason]`);\n console.error('');\n } else {\n console.error(' \\u26a0\\ufe0f disableAllowed is false - disable comments are NOT allowed.');\n console.error(' This rule must be met and cannot be disabled since nx.json disableAllowed is set to false.');\n console.error(' You MUST refactor to reduce file size.');\n console.error('');\n console.error(' For a major refactor, a human can add \"turnOffRuleUntilEpoch\" to nx.json validate-code options.');\n console.error(' This is an expiry timestamp (epoch ms) for when we start forcing everyone to meet size rules again.');\n console.error(' Sometimes for speed, we allow files to expand during a refactor and over time,');\n console.error(' each PR reduces files as they get touched.');\n console.error(' AI agents should NOT add turnOffRuleUntilEpoch - ask a human to do it.');\n console.error('');\n }\n}\n\nasync function runValidatorImpl(\n options: MaxFileLinesConfig,\n workspaceRoot: string\n): Promise<ExecutorResult> {\n const limit = options.limit ?? 900;\n const disableAllowed = options.disableAllowed ?? true;\n\n const rawMode: FileLimitMode = options.mode ?? 'NEW_AND_MODIFIED_FILES';\n const skip = rawMode !== 'OFF' ? shouldSkipRule(options.turnOffRuleUntilEpoch, (options.turnOffRuleWhileOnBranch ?? undefined)) : new SkipRuleResult(false);\n const mode: FileLimitMode = skip.skip ? 'OFF' : rawMode;\n\n // Skip validation entirely if mode is OFF\n if (mode === 'OFF') {\n const reason = skip.skip ? skip.reason : 'mode: OFF';\n console.log(`\\n\\u23ed\\ufe0f Skipping modified files validation (${reason})`);\n console.log('');\n return { success: true };\n }\n\n // If NX_HEAD is set (via nx affected --head=X), use it; otherwise compare to working tree\n let base = process.env['NX_BASE'];\n const head = process.env['NX_HEAD'];\n\n if (!base) {\n base = detectBase(workspaceRoot) ?? undefined;\n\n if (!base) {\n console.log('\\n\\u23ed\\ufe0f Skipping modified files validation (could not detect base branch)');\n console.log(' To run explicitly: nx affected --target=validate-modified-files --base=origin/main');\n console.log('');\n return { success: true };\n }\n\n console.log('\\n\\ud83d\\udccf Validating Modified File Sizes (auto-detected base)\\n');\n } else {\n console.log('\\n\\ud83d\\udccf Validating Modified File Sizes\\n');\n }\n\n console.log(` Base: ${base}`);\n console.log(` Head: ${head ?? 'working tree (includes uncommitted changes)'}`);\n console.log(` Mode: ${mode}`);\n console.log(` Max lines for modified files: ${limit}`);\n console.log(` Disable allowed: ${disableAllowed}${!disableAllowed ? ' (no escape hatch)' : ''}`);\n console.log('');\n\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n const changedFiles = getChangedFiles(workspaceRoot, base, head);\n\n if (changedFiles.length === 0) {\n console.log('\\u2705 No TypeScript files changed');\n return { success: true };\n }\n\n console.log(`\\ud83d\\udcc2 Checking ${changedFiles.length} changed file(s)...`);\n\n const violations = findViolations(workspaceRoot, changedFiles, limit, disableAllowed);\n\n if (violations.length === 0) {\n console.log('\\u2705 All modified files are under ' + limit + ' lines');\n return { success: true };\n }\n\n writeTmpInstructions(workspaceRoot);\n reportViolations(violations, limit, disableAllowed);\n return { success: false };\n } catch (err: unknown) {\n //const error = toError(err);\n const error = err instanceof Error ? err : new Error(String(err));\n console.error('\\u274c Modified files validation failed:', error.message);\n return { success: false };\n }\n}\n\n@injectable(bindingScopeValues.Singleton)\nexport class MaxFileLinesValidator extends CodeValidator<MaxFileLinesConfig> {\n constructor(config: MaxFileLinesConfig) {\n super(config, 'max-file-lines', 'max-file-lines');\n }\n\n async run(workspaceRoot: string): Promise<ExecutorResult> {\n return runValidatorImpl(this.config, workspaceRoot);\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"validate-modified-files.js","sourceRoot":"","sources":["../../../../../packages/tooling/code-rules/src/validate-modified-files.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Validate Modified Files Executor\n *\n * Validates that modified files don't exceed a maximum line count (default 900).\n * This encourages keeping files small and focused - when you touch a file,\n * you must bring it under the limit.\n *\n * Usage:\n * nx affected --target=validate-modified-files --base=origin/main\n *\n * Escape hatch: Add webpieces-disable max-lines-modified-files comment with date and justification\n * Format: // webpieces-disable max-lines-modified-files 2025/01/15 -- [reason]\n * The disable expires after 1 month from the date specified.\n */\n\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport { writeTemplate, hasDisable, RULE_NAMES, MaxFileLinesConfig, FileLimitMode, detectBase, getChangedFiles, isPathExcluded, GENERATED_CODE_PATHS, RuleFailError, Option } from '@webpieces/rules-config';\nimport { CodeValidator, ExecutorResult } from './code-validator';\nimport { injectable, bindingScopeValues } from 'inversify';\nimport { shouldSkipRule, SkipRuleResult } from './resolve-mode';\n\n/** One over-limit file. A class, not an interface — findViolations is exported for its tests. */\nexport class FileViolation {\n readonly file: string;\n readonly lines: number;\n readonly expiredDisable: boolean;\n readonly expiredDate: string | undefined;\n\n constructor(file: string, lines: number, expiredDisable = false, expiredDate?: string) {\n this.file = file;\n this.lines = lines;\n this.expiredDisable = expiredDisable;\n this.expiredDate = expiredDate;\n }\n}\n\nconst TMP_MD_FILE = 'webpieces.filesize.md';\n\n\n/**\n * Write the instructions documentation to .webpieces/instruct-ai/.\n * Sourced from @webpieces/rules-config.\n */\nfunction writeTmpInstructions(workspaceRoot: string): string {\n return writeTemplate(workspaceRoot, TMP_MD_FILE);\n}\n\n/**\n * Parse a date string in yyyy/mm/dd format and return a Date object.\n * Returns null if the format is invalid.\n */\nfunction parseDisableDate(dateStr: string): Date | null {\n // Match yyyy/mm/dd format\n const match = dateStr.match(/^(\\d{4})\\/(\\d{2})\\/(\\d{2})$/);\n if (!match) return null;\n\n const year = parseInt(match[1], 10);\n const month = parseInt(match[2], 10) - 1; // JS months are 0-indexed\n const day = parseInt(match[3], 10);\n\n const date = new Date(year, month, day);\n\n // Validate the date is valid (e.g., not Feb 30)\n if (date.getFullYear() !== year || date.getMonth() !== month || date.getDate() !== day) {\n return null;\n }\n\n return date;\n}\n\n/**\n * Check if a date is within the last month (not expired).\n */\nfunction isDateWithinMonth(date: Date): boolean {\n const now = new Date();\n const oneMonthAgo = new Date(now.getFullYear(), now.getMonth() - 1, now.getDate());\n return date >= oneMonthAgo;\n}\n\ninterface DisableStatus {\n hasDisable: boolean;\n isValid: boolean;\n isExpired: boolean;\n date?: string;\n}\n\n/**\n * Check if a file has a valid, non-expired disable comment at the top (within first 5 lines).\n * Returns status object with details about the disable comment.\n */\n// webpieces-disable max-lines-new-methods -- Date validation logic requires checking multiple conditions\nfunction checkDisableComment(content: string): DisableStatus {\n const lines = content.split('\\n').slice(0, 5);\n\n for (const line of lines) {\n if (hasDisable(line, RULE_NAMES.MAX_LINES_MODIFIED_FILES)) {\n // Found disable comment, now check for date\n // Format: // webpieces-disable max-lines-modified-files yyyy/mm/dd -- reason\n const dateMatch = line.match(/max-lines-modified-files\\s+(\\d{4}\\/\\d{2}\\/\\d{2}|XXXX\\/XX\\/XX)/);\n\n if (!dateMatch) {\n // No date found - invalid disable comment\n return { hasDisable: true, isValid: false, isExpired: false };\n }\n\n const dateStr = dateMatch[1];\n\n // Secret permanent disable\n if (dateStr === 'XXXX/XX/XX') {\n return { hasDisable: true, isValid: true, isExpired: false, date: dateStr };\n }\n\n const date = parseDisableDate(dateStr);\n if (!date) {\n // Invalid date format\n return { hasDisable: true, isValid: false, isExpired: false, date: dateStr };\n }\n\n if (!isDateWithinMonth(date)) {\n // Date is expired (older than 1 month)\n return { hasDisable: true, isValid: true, isExpired: true, date: dateStr };\n }\n\n // Valid and not expired\n return { hasDisable: true, isValid: true, isExpired: false, date: dateStr };\n }\n }\n\n return { hasDisable: false, isValid: false, isExpired: false };\n}\n\n/**\n * The complete exempt-path list for one run: the machine-generated trees that are exempt with no\n * configuration at all, PLUS whatever the repo listed in `max-file-lines.allowedPaths`. The config\n * ADDS to the floor rather than replacing it, so exempting one of your own trees can never silently\n * cost you the generated-code exemption (see GENERATED_CODE_PATHS for why that matters).\n */\n// webpieces-disable no-function-outside-class -- pure list join, sibling of the file-scoped helpers here\nexport function exemptPathsFor(config: MaxFileLinesConfig): readonly string[] {\n return [...GENERATED_CODE_PATHS, ...(config.allowedPaths ?? [])];\n}\n\n/**\n * Count lines in a file and check for violations\n */\n// webpieces-disable max-lines-new-methods -- File iteration with disable checking logic\n// webpieces-disable no-function-outside-class -- pure scan over (files, limit, exemptPaths), sibling of the file-scoped helpers here\nexport function findViolations(workspaceRoot: string, changedFiles: string[], limit: number, disableAllowed: boolean, exemptPaths: readonly string[]): FileViolation[] {\n const violations: FileViolation[] = [];\n\n for (const file of changedFiles) {\n const fullPath = path.join(workspaceRoot, file);\n\n // Machine-generated / explicitly allowed trees: the file's size is not the repo's to fix.\n if (isPathExcluded(file, exemptPaths)) continue;\n\n if (!fs.existsSync(fullPath)) continue;\n\n const content = fs.readFileSync(fullPath, 'utf-8');\n const lineCount = content.split('\\n').length;\n\n // Skip files under the limit\n if (lineCount <= limit) continue;\n\n // When disableAllowed is false, ignore all disable comments\n if (!disableAllowed) {\n violations.push(new FileViolation(file, lineCount));\n continue;\n }\n\n // Check for disable comment\n const disableStatus = checkDisableComment(content);\n\n if (disableStatus.hasDisable) {\n if (disableStatus.isValid && !disableStatus.isExpired) {\n // Valid, non-expired disable - skip this file\n continue;\n }\n\n if (disableStatus.isExpired) {\n // Expired disable - report as violation with expired info\n violations.push(new FileViolation(file, lineCount, true, disableStatus.date));\n continue;\n }\n\n // Invalid disable (missing/bad date) - fall through to report as violation\n }\n\n violations.push(new FileViolation(file, lineCount));\n }\n\n return violations;\n}\n\n/**\n * Get today's date in yyyy/mm/dd format for error messages\n */\nfunction getTodayDateString(): string {\n const now = new Date();\n const year = now.getFullYear();\n const month = String(now.getMonth() + 1).padStart(2, '0');\n const day = String(now.getDate()).padStart(2, '0');\n return `${year}/${month}/${day}`;\n}\n\n/**\n * The ONE failure value for this rule: a `RuleFailError` carrying the prose for a human and the cures\n * as `Option[]`, which {@link RuleReporter} renders (it owns the \"Fix Option N:\" / \"(preferred)\"\n * labels, so nothing here hand-numbers a cure). This used to be a `console.error` paragraph beside a\n * `{ success: false }` return — a second spelling of \"this validator failed\" that the edit-time half of\n * the SAME rule never used.\n */\n// webpieces-disable max-lines-new-methods -- one message value assembled from several sections\n// webpieces-disable no-function-outside-class -- pure value builder, sibling of the file-scoped helpers here\nexport function violationsError(violations: readonly FileViolation[], limit: number, disableAllowed: boolean): RuleFailError {\n const listed = violations.map((v: FileViolation): string => v.expiredDisable\n ? ` ${v.file} (${v.lines} lines, max: ${limit})\\n EXPIRED DISABLE: the disable comment dated ${v.expiredDate ?? '?'} is over a month old — FIX the file or update the date for another month.`\n : ` ${v.file} (${v.lines} lines, max: ${limit})`).join('\\n');\n\n const message = [\n `YOU MUST FIX THIS AND NOT be more than ${limit} lines of code per file — it slows down IDEs and is VERY VERY EASY to refactor.`,\n '',\n 'With stateless systems + dependency injection, refactor is trivial: pick a method or a few and',\n 'move them to a new class XXXXX, inject XXXXX into all users of those methods via the constructor,',\n `then delete those methods from the original class. 99% of files can be less than ${limit} lines.`,\n '',\n listed,\n '',\n `disableAllowed is ${disableAllowed}${disableAllowed ? '' : ' — inline disable comments are IGNORED for this rule'}.`,\n ].join('\\n');\n\n const cures: Option[] = [\n new Option(`Refactor the file under ${limit} lines — READ .webpieces/instruct-ai/${TMP_MD_FILE} for step-by-step guidance.`, true),\n new Option('Is the file MACHINE-GENERATED (codegen output, a schema dump)? Its size is not yours to fix, and turning the rule off is the wrong cure — that stops it on hand-written files too. Add its tree to rules.\"max-file-lines\".allowedPaths in webpieces.config.json, e.g. \"allowedPaths\": [\"**/__generated__/**\"]. Never list hand-written code there.'),\n ];\n if (disableAllowed) {\n cures.push(new Option(`Put this on the FIRST 5 lines of the file (it expires in 1 month): // webpieces-disable max-lines-modified-files ${getTodayDateString()} -- [your reason]`));\n } else {\n cures.push(new Option('For a major refactor a HUMAN — never an AI agent — can set \"turnOffRuleUntilEpoch\" (an expiry, in epoch seconds) on the max-file-lines entry in webpieces.config.json, so files may expand during the refactor and each PR shrinks them as it touches them.'));\n }\n\n return new RuleFailError('max-file-lines', message, undefined, undefined, cures);\n}\n\nasync function runValidatorImpl(\n options: MaxFileLinesConfig,\n workspaceRoot: string\n): Promise<ExecutorResult> {\n const limit = options.limit ?? 900;\n const disableAllowed = options.disableAllowed ?? true;\n // Machine-generated trees + the repo's own allowedPaths. A line-count rule firing on a file nobody\n // authored is never a useful signal, so the generated half needs no configuration.\n const exemptPaths = exemptPathsFor(options);\n\n const rawMode: FileLimitMode = options.mode ?? 'NEW_AND_MODIFIED_FILES';\n const skip = rawMode !== 'OFF' ? shouldSkipRule(options.turnOffRuleUntilEpoch, (options.turnOffRuleWhileOnBranch ?? undefined)) : new SkipRuleResult(false);\n const mode: FileLimitMode = skip.skip ? 'OFF' : rawMode;\n\n // Skip validation entirely if mode is OFF\n if (mode === 'OFF') {\n const reason = skip.skip ? skip.reason : 'mode: OFF';\n console.log(`\\n\\u23ed\\ufe0f Skipping modified files validation (${reason})`);\n console.log('');\n return { success: true };\n }\n\n // If NX_HEAD is set (via nx affected --head=X), use it; otherwise compare to working tree\n let base = process.env['NX_BASE'];\n const head = process.env['NX_HEAD'];\n\n if (!base) {\n base = detectBase(workspaceRoot) ?? undefined;\n\n if (!base) {\n console.log('\\n\\u23ed\\ufe0f Skipping modified files validation (could not detect base branch)');\n console.log(' To run explicitly: nx affected --target=validate-modified-files --base=origin/main');\n console.log('');\n return { success: true };\n }\n\n console.log('\\n\\ud83d\\udccf Validating Modified File Sizes (auto-detected base)\\n');\n } else {\n console.log('\\n\\ud83d\\udccf Validating Modified File Sizes\\n');\n }\n\n console.log(` Base: ${base}`);\n console.log(` Head: ${head ?? 'working tree (includes uncommitted changes)'}`);\n console.log(` Mode: ${mode}`);\n console.log(` Max lines for modified files: ${limit}`);\n console.log(` Disable allowed: ${disableAllowed}${!disableAllowed ? ' (no escape hatch)' : ''}`);\n console.log(` Exempt paths: ${exemptPaths.join(', ')}`);\n console.log('');\n\n let violations: FileViolation[];\n\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n const changedFiles = getChangedFiles(workspaceRoot, base, head);\n\n if (changedFiles.length === 0) {\n console.log('\\u2705 No TypeScript files changed');\n return { success: true };\n }\n\n console.log(`\\ud83d\\udcc2 Checking ${changedFiles.length} changed file(s)...`);\n\n violations = findViolations(workspaceRoot, changedFiles, limit, disableAllowed, exemptPaths);\n } catch (err: unknown) {\n //const error = toError(err);\n const error = err instanceof Error ? err : new Error(String(err));\n console.error('\\u274c Modified files validation failed:', error.message);\n return { success: false };\n }\n\n if (violations.length === 0) {\n console.log('\\u2705 All modified files are under ' + limit + ' lines');\n return { success: true };\n }\n\n // The ONE failure path: throw, so RuleReporter renders the message and its cures for this audience.\n writeTmpInstructions(workspaceRoot);\n throw violationsError(violations, limit, disableAllowed);\n}\n\n@injectable(bindingScopeValues.Singleton)\nexport class MaxFileLinesValidator extends CodeValidator<MaxFileLinesConfig> {\n constructor(config: MaxFileLinesConfig) {\n super(config, 'max-file-lines', 'max-file-lines');\n }\n\n async run(workspaceRoot: string): Promise<ExecutorResult> {\n return runValidatorImpl(this.config, workspaceRoot);\n }\n}\n"]}
|