@webpieces/dev-config 0.2.95 → 0.2.98
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/config/eslint/base.mjs +1 -1
- package/executors.json +6 -91
- package/package.json +6 -19
- package/{executors → src/executors}/help/executor.d.ts +4 -2
- package/src/executors/help/executor.js.map +1 -0
- package/src/executors/validate-eslint-sync/executor.js.map +1 -0
- package/{executors → src/executors}/validate-versions-locked/executor.js +2 -1
- package/src/executors/validate-versions-locked/executor.js.map +1 -0
- package/src/index.d.ts +1 -1
- package/src/index.js +1 -1
- package/src/index.js.map +1 -1
- package/src/plugin.d.ts +86 -0
- package/{plugin.js → src/plugin.js} +31 -15
- package/src/plugin.js.map +1 -0
- package/src/toError.d.ts +5 -0
- package/src/toError.js +37 -0
- package/src/toError.js.map +1 -0
- package/templates/eslint.webpieces.config.mjs +1 -1
- package/architecture/executors/diff-utils.d.ts +0 -24
- package/architecture/executors/diff-utils.js +0 -119
- package/architecture/executors/diff-utils.js.map +0 -1
- package/architecture/executors/diff-utils.ts +0 -127
- package/architecture/executors/generate/executor.d.ts +0 -16
- package/architecture/executors/generate/executor.js +0 -44
- package/architecture/executors/generate/executor.js.map +0 -1
- package/architecture/executors/generate/executor.ts +0 -59
- package/architecture/executors/generate/schema.json +0 -14
- package/architecture/executors/validate-architecture-unchanged/executor.d.ts +0 -17
- package/architecture/executors/validate-architecture-unchanged/executor.js +0 -229
- package/architecture/executors/validate-architecture-unchanged/executor.js.map +0 -1
- package/architecture/executors/validate-architecture-unchanged/executor.ts +0 -251
- package/architecture/executors/validate-architecture-unchanged/schema.json +0 -14
- package/architecture/executors/validate-code/executor.d.ts +0 -78
- package/architecture/executors/validate-code/executor.js +0 -243
- package/architecture/executors/validate-code/executor.js.map +0 -1
- package/architecture/executors/validate-code/executor.ts +0 -406
- package/architecture/executors/validate-code/schema.json +0 -227
- package/architecture/executors/validate-dtos/executor.d.ts +0 -42
- package/architecture/executors/validate-dtos/executor.js +0 -561
- package/architecture/executors/validate-dtos/executor.js.map +0 -1
- package/architecture/executors/validate-dtos/executor.ts +0 -689
- package/architecture/executors/validate-dtos/schema.json +0 -33
- package/architecture/executors/validate-modified-files/executor.d.ts +0 -25
- package/architecture/executors/validate-modified-files/executor.js +0 -501
- package/architecture/executors/validate-modified-files/executor.js.map +0 -1
- package/architecture/executors/validate-modified-files/executor.ts +0 -571
- package/architecture/executors/validate-modified-files/schema.json +0 -25
- package/architecture/executors/validate-modified-methods/executor.d.ts +0 -31
- package/architecture/executors/validate-modified-methods/executor.js +0 -694
- package/architecture/executors/validate-modified-methods/executor.js.map +0 -1
- package/architecture/executors/validate-modified-methods/executor.ts +0 -797
- package/architecture/executors/validate-modified-methods/schema.json +0 -25
- package/architecture/executors/validate-new-methods/executor.d.ts +0 -28
- package/architecture/executors/validate-new-methods/executor.js +0 -513
- package/architecture/executors/validate-new-methods/executor.js.map +0 -1
- package/architecture/executors/validate-new-methods/executor.ts +0 -584
- package/architecture/executors/validate-new-methods/schema.json +0 -25
- package/architecture/executors/validate-no-any-unknown/executor.d.ts +0 -42
- package/architecture/executors/validate-no-any-unknown/executor.js +0 -462
- package/architecture/executors/validate-no-any-unknown/executor.js.map +0 -1
- package/architecture/executors/validate-no-any-unknown/executor.ts +0 -540
- package/architecture/executors/validate-no-any-unknown/schema.json +0 -24
- package/architecture/executors/validate-no-architecture-cycles/executor.d.ts +0 -16
- package/architecture/executors/validate-no-architecture-cycles/executor.js +0 -48
- package/architecture/executors/validate-no-architecture-cycles/executor.js.map +0 -1
- package/architecture/executors/validate-no-architecture-cycles/executor.ts +0 -60
- package/architecture/executors/validate-no-architecture-cycles/schema.json +0 -8
- package/architecture/executors/validate-no-destructure/executor.d.ts +0 -52
- package/architecture/executors/validate-no-destructure/executor.js +0 -491
- package/architecture/executors/validate-no-destructure/executor.js.map +0 -1
- package/architecture/executors/validate-no-destructure/executor.ts +0 -578
- package/architecture/executors/validate-no-destructure/schema.json +0 -24
- package/architecture/executors/validate-no-direct-api-resolver/executor.d.ts +0 -47
- package/architecture/executors/validate-no-direct-api-resolver/executor.js +0 -566
- package/architecture/executors/validate-no-direct-api-resolver/executor.js.map +0 -1
- package/architecture/executors/validate-no-direct-api-resolver/executor.ts +0 -666
- package/architecture/executors/validate-no-direct-api-resolver/schema.json +0 -29
- package/architecture/executors/validate-no-inline-types/executor.d.ts +0 -91
- package/architecture/executors/validate-no-inline-types/executor.js +0 -669
- package/architecture/executors/validate-no-inline-types/executor.js.map +0 -1
- package/architecture/executors/validate-no-inline-types/executor.ts +0 -775
- package/architecture/executors/validate-no-inline-types/schema.json +0 -24
- package/architecture/executors/validate-no-skiplevel-deps/executor.d.ts +0 -19
- package/architecture/executors/validate-no-skiplevel-deps/executor.js +0 -227
- package/architecture/executors/validate-no-skiplevel-deps/executor.js.map +0 -1
- package/architecture/executors/validate-no-skiplevel-deps/executor.ts +0 -267
- package/architecture/executors/validate-no-skiplevel-deps/schema.json +0 -8
- package/architecture/executors/validate-packagejson/executor.d.ts +0 -16
- package/architecture/executors/validate-packagejson/executor.js +0 -57
- package/architecture/executors/validate-packagejson/executor.js.map +0 -1
- package/architecture/executors/validate-packagejson/executor.ts +0 -74
- package/architecture/executors/validate-packagejson/schema.json +0 -8
- package/architecture/executors/validate-prisma-converters/executor.d.ts +0 -60
- package/architecture/executors/validate-prisma-converters/executor.js +0 -634
- package/architecture/executors/validate-prisma-converters/executor.js.map +0 -1
- package/architecture/executors/validate-prisma-converters/executor.ts +0 -822
- package/architecture/executors/validate-prisma-converters/schema.json +0 -38
- package/architecture/executors/validate-return-types/executor.d.ts +0 -29
- package/architecture/executors/validate-return-types/executor.js +0 -439
- package/architecture/executors/validate-return-types/executor.js.map +0 -1
- package/architecture/executors/validate-return-types/executor.ts +0 -524
- package/architecture/executors/validate-return-types/schema.json +0 -24
- package/architecture/executors/visualize/executor.d.ts +0 -17
- package/architecture/executors/visualize/executor.js +0 -49
- package/architecture/executors/visualize/executor.js.map +0 -1
- package/architecture/executors/visualize/executor.ts +0 -63
- package/architecture/executors/visualize/schema.json +0 -14
- package/architecture/index.d.ts +0 -19
- package/architecture/index.js +0 -23
- package/architecture/index.js.map +0 -1
- package/architecture/index.ts +0 -20
- package/architecture/lib/graph-comparator.d.ts +0 -39
- package/architecture/lib/graph-comparator.js +0 -100
- package/architecture/lib/graph-comparator.js.map +0 -1
- package/architecture/lib/graph-comparator.ts +0 -141
- package/architecture/lib/graph-generator.d.ts +0 -19
- package/architecture/lib/graph-generator.js +0 -84
- package/architecture/lib/graph-generator.js.map +0 -1
- package/architecture/lib/graph-generator.ts +0 -97
- package/architecture/lib/graph-loader.d.ts +0 -31
- package/architecture/lib/graph-loader.js +0 -98
- package/architecture/lib/graph-loader.js.map +0 -1
- package/architecture/lib/graph-loader.ts +0 -116
- package/architecture/lib/graph-sorter.d.ts +0 -37
- package/architecture/lib/graph-sorter.js +0 -110
- package/architecture/lib/graph-sorter.js.map +0 -1
- package/architecture/lib/graph-sorter.ts +0 -137
- package/architecture/lib/graph-visualizer.d.ts +0 -29
- package/architecture/lib/graph-visualizer.js +0 -217
- package/architecture/lib/graph-visualizer.js.map +0 -1
- package/architecture/lib/graph-visualizer.ts +0 -231
- package/architecture/lib/package-validator.d.ts +0 -38
- package/architecture/lib/package-validator.js +0 -126
- package/architecture/lib/package-validator.js.map +0 -1
- package/architecture/lib/package-validator.ts +0 -170
- package/eslint-plugin/__tests__/catch-error-pattern.test.ts +0 -374
- package/eslint-plugin/__tests__/max-file-lines.test.ts +0 -207
- package/eslint-plugin/__tests__/max-method-lines.test.ts +0 -258
- package/eslint-plugin/__tests__/no-unmanaged-exceptions.test.ts +0 -359
- package/eslint-plugin/index.d.ts +0 -23
- package/eslint-plugin/index.js +0 -30
- package/eslint-plugin/index.js.map +0 -1
- package/eslint-plugin/index.ts +0 -29
- package/eslint-plugin/rules/catch-error-pattern.d.ts +0 -11
- package/eslint-plugin/rules/catch-error-pattern.js +0 -143
- package/eslint-plugin/rules/catch-error-pattern.js.map +0 -1
- package/eslint-plugin/rules/catch-error-pattern.ts +0 -246
- package/eslint-plugin/rules/enforce-architecture.d.ts +0 -15
- package/eslint-plugin/rules/enforce-architecture.js +0 -476
- package/eslint-plugin/rules/enforce-architecture.js.map +0 -1
- package/eslint-plugin/rules/enforce-architecture.ts +0 -543
- package/eslint-plugin/rules/max-file-lines.d.ts +0 -12
- package/eslint-plugin/rules/max-file-lines.js +0 -257
- package/eslint-plugin/rules/max-file-lines.js.map +0 -1
- package/eslint-plugin/rules/max-file-lines.ts +0 -272
- package/eslint-plugin/rules/max-method-lines.d.ts +0 -12
- package/eslint-plugin/rules/max-method-lines.js +0 -240
- package/eslint-plugin/rules/max-method-lines.js.map +0 -1
- package/eslint-plugin/rules/max-method-lines.ts +0 -287
- package/eslint-plugin/rules/no-unmanaged-exceptions.d.ts +0 -22
- package/eslint-plugin/rules/no-unmanaged-exceptions.js +0 -160
- package/eslint-plugin/rules/no-unmanaged-exceptions.js.map +0 -1
- package/eslint-plugin/rules/no-unmanaged-exceptions.ts +0 -179
- package/executors/help/executor.js.map +0 -1
- package/executors/help/executor.ts +0 -61
- package/executors/validate-eslint-sync/executor.js.map +0 -1
- package/executors/validate-eslint-sync/executor.ts +0 -87
- package/executors/validate-versions-locked/executor.js.map +0 -1
- package/executors/validate-versions-locked/executor.ts +0 -368
- package/plugin/README.md +0 -243
- package/plugin/index.d.ts +0 -4
- package/plugin/index.js +0 -8
- package/plugin/index.js.map +0 -1
- package/plugin/index.ts +0 -4
- /package/{executors → src/executors}/help/executor.js +0 -0
- /package/{executors → src/executors}/help/schema.json +0 -0
- /package/{executors → src/executors}/validate-eslint-sync/executor.d.ts +0 -0
- /package/{executors → src/executors}/validate-eslint-sync/executor.js +0 -0
- /package/{executors → src/executors}/validate-eslint-sync/schema.json +0 -0
- /package/{executors → src/executors}/validate-versions-locked/executor.d.ts +0 -0
- /package/{executors → src/executors}/validate-versions-locked/schema.json +0 -0
|
@@ -1,584 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Validate New Methods Executor
|
|
3
|
-
*
|
|
4
|
-
* Validates that newly added methods don't exceed a maximum line count.
|
|
5
|
-
* Runs in affected mode when:
|
|
6
|
-
* 1. NX_BASE environment variable is set (via nx affected), OR
|
|
7
|
-
* 2. Auto-detects base by finding merge-base with origin/main
|
|
8
|
-
*
|
|
9
|
-
* This validator encourages writing methods that read like a "table of contents"
|
|
10
|
-
* where each method call describes a larger piece of work.
|
|
11
|
-
*
|
|
12
|
-
* Usage:
|
|
13
|
-
* nx affected --target=validate-new-methods --base=origin/main
|
|
14
|
-
* OR: runs automatically via build's architecture:validate-complete dependency
|
|
15
|
-
*
|
|
16
|
-
* Escape hatch: Add webpieces-disable max-lines-new-methods comment with justification
|
|
17
|
-
*/
|
|
18
|
-
|
|
19
|
-
import type { ExecutorContext } from '@nx/devkit';
|
|
20
|
-
import { execSync } from 'child_process';
|
|
21
|
-
import * as fs from 'fs';
|
|
22
|
-
import * as path from 'path';
|
|
23
|
-
import * as ts from 'typescript';
|
|
24
|
-
|
|
25
|
-
export type MethodMaxLimitMode = 'OFF' | 'NEW_METHODS' | 'NEW_AND_MODIFIED_METHODS' | 'MODIFIED_FILES';
|
|
26
|
-
|
|
27
|
-
export interface ValidateNewMethodsOptions {
|
|
28
|
-
limit?: number;
|
|
29
|
-
mode?: MethodMaxLimitMode;
|
|
30
|
-
disableAllowed?: boolean;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export interface ExecutorResult {
|
|
34
|
-
success: boolean;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
interface MethodViolation {
|
|
38
|
-
file: string;
|
|
39
|
-
methodName: string;
|
|
40
|
-
line: number;
|
|
41
|
-
lines: number;
|
|
42
|
-
isNew: boolean;
|
|
43
|
-
limit: number;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
interface MethodInfo {
|
|
47
|
-
name: string;
|
|
48
|
-
line: number;
|
|
49
|
-
lines: number;
|
|
50
|
-
hasDisableComment: boolean;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
const TMP_DIR = 'tmp/webpieces';
|
|
54
|
-
const TMP_MD_FILE = 'webpieces.methodsize.md';
|
|
55
|
-
|
|
56
|
-
const METHODSIZE_DOC_CONTENT = `# Instructions: New Method Too Long
|
|
57
|
-
|
|
58
|
-
## Requirement
|
|
59
|
-
|
|
60
|
-
**~99% of the time**, you can stay under the \`limit\` from nx.json
|
|
61
|
-
by extracting logical units into well-named methods.
|
|
62
|
-
Nearly all software can be written with methods under this size.
|
|
63
|
-
|
|
64
|
-
## The "Table of Contents" Principle
|
|
65
|
-
|
|
66
|
-
Good code reads like a book's table of contents:
|
|
67
|
-
- Chapter titles (method names) tell you WHAT happens
|
|
68
|
-
- Reading chapter titles gives you the full story
|
|
69
|
-
- You can dive into chapters (implementations) for details
|
|
70
|
-
|
|
71
|
-
## Why Limit New Methods?
|
|
72
|
-
|
|
73
|
-
Methods under the limit are:
|
|
74
|
-
- Easy to review in a single screen
|
|
75
|
-
- Simple to understand without scrolling
|
|
76
|
-
- Quick for AI to analyze and suggest improvements
|
|
77
|
-
- More testable in isolation
|
|
78
|
-
- Self-documenting through well-named extracted methods
|
|
79
|
-
|
|
80
|
-
Extracting logical units into well-named methods makes code more readable for both
|
|
81
|
-
AI and humans.
|
|
82
|
-
|
|
83
|
-
## How to Refactor
|
|
84
|
-
|
|
85
|
-
Instead of:
|
|
86
|
-
\`\`\`typescript
|
|
87
|
-
async processOrder(order: Order): Promise<Result> {
|
|
88
|
-
// 50 lines of validation, transformation, saving, notifications...
|
|
89
|
-
}
|
|
90
|
-
\`\`\`
|
|
91
|
-
|
|
92
|
-
Write:
|
|
93
|
-
\`\`\`typescript
|
|
94
|
-
async processOrder(order: Order): Promise<Result> {
|
|
95
|
-
const validated = this.validateOrder(order);
|
|
96
|
-
const transformed = this.applyBusinessRules(validated);
|
|
97
|
-
const saved = await this.saveToDatabase(transformed);
|
|
98
|
-
await this.notifyStakeholders(saved);
|
|
99
|
-
return this.buildResult(saved);
|
|
100
|
-
}
|
|
101
|
-
\`\`\`
|
|
102
|
-
|
|
103
|
-
Now the main method is a "table of contents" - each line tells part of the story!
|
|
104
|
-
|
|
105
|
-
## Patterns for Extraction
|
|
106
|
-
|
|
107
|
-
### Pattern 1: Extract Loop Bodies
|
|
108
|
-
\`\`\`typescript
|
|
109
|
-
// BEFORE
|
|
110
|
-
for (const item of items) {
|
|
111
|
-
// 20 lines of processing
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
// AFTER
|
|
115
|
-
for (const item of items) {
|
|
116
|
-
this.processItem(item);
|
|
117
|
-
}
|
|
118
|
-
\`\`\`
|
|
119
|
-
|
|
120
|
-
### Pattern 2: Extract Conditional Blocks
|
|
121
|
-
\`\`\`typescript
|
|
122
|
-
// BEFORE
|
|
123
|
-
if (isAdmin(user)) {
|
|
124
|
-
// 15 lines of admin logic
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
// AFTER
|
|
128
|
-
if (isAdmin(user)) {
|
|
129
|
-
this.handleAdminUser(user);
|
|
130
|
-
}
|
|
131
|
-
\`\`\`
|
|
132
|
-
|
|
133
|
-
### Pattern 3: Extract Data Transformations
|
|
134
|
-
\`\`\`typescript
|
|
135
|
-
// BEFORE
|
|
136
|
-
const result = {
|
|
137
|
-
// 10+ lines of object construction
|
|
138
|
-
};
|
|
139
|
-
|
|
140
|
-
// AFTER
|
|
141
|
-
const result = this.buildResultObject(data);
|
|
142
|
-
\`\`\`
|
|
143
|
-
|
|
144
|
-
## If Refactoring Is Not Feasible
|
|
145
|
-
|
|
146
|
-
Sometimes methods genuinely need to be longer (complex algorithms, state machines, etc.).
|
|
147
|
-
|
|
148
|
-
**Escape hatch**: Add a webpieces-disable comment with justification:
|
|
149
|
-
|
|
150
|
-
\`\`\`typescript
|
|
151
|
-
// webpieces-disable max-lines-new-methods -- Complex state machine, splitting reduces clarity
|
|
152
|
-
async complexStateMachine(): Promise<void> {
|
|
153
|
-
// ... longer method with justification
|
|
154
|
-
}
|
|
155
|
-
\`\`\`
|
|
156
|
-
|
|
157
|
-
## AI Agent Action Steps
|
|
158
|
-
|
|
159
|
-
1. **READ** the method to understand its logical sections
|
|
160
|
-
2. **IDENTIFY** logical units that can be extracted
|
|
161
|
-
3. **EXTRACT** into well-named private methods
|
|
162
|
-
4. **VERIFY** the main method now reads like a table of contents
|
|
163
|
-
5. **IF NOT FEASIBLE**: Add webpieces-disable max-lines-new-methods comment with clear justification
|
|
164
|
-
|
|
165
|
-
## Remember
|
|
166
|
-
|
|
167
|
-
- Every method you write today will be read many times tomorrow
|
|
168
|
-
- The best code explains itself through structure
|
|
169
|
-
- When in doubt, extract and name it
|
|
170
|
-
`;
|
|
171
|
-
|
|
172
|
-
/**
|
|
173
|
-
* Write the instructions documentation to tmp directory
|
|
174
|
-
*/
|
|
175
|
-
function writeTmpInstructions(workspaceRoot: string): string {
|
|
176
|
-
const tmpDir = path.join(workspaceRoot, TMP_DIR);
|
|
177
|
-
const mdPath = path.join(tmpDir, TMP_MD_FILE);
|
|
178
|
-
|
|
179
|
-
fs.mkdirSync(tmpDir, { recursive: true });
|
|
180
|
-
fs.writeFileSync(mdPath, METHODSIZE_DOC_CONTENT);
|
|
181
|
-
|
|
182
|
-
return mdPath;
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
/**
|
|
186
|
-
* Get changed TypeScript files between base and head (or working tree if head not specified).
|
|
187
|
-
* Uses `git diff base [head]` to match what `nx affected` does.
|
|
188
|
-
* When head is NOT specified, also includes untracked files (matching nx affected behavior).
|
|
189
|
-
*/
|
|
190
|
-
function getChangedTypeScriptFiles(workspaceRoot: string, base: string, head?: string): string[] {
|
|
191
|
-
try {
|
|
192
|
-
// If head is specified, diff base to head; otherwise diff base to working tree
|
|
193
|
-
const diffTarget = head ? `${base} ${head}` : base;
|
|
194
|
-
const output = execSync(`git diff --name-only ${diffTarget} -- '*.ts' '*.tsx'`, {
|
|
195
|
-
cwd: workspaceRoot,
|
|
196
|
-
encoding: 'utf-8',
|
|
197
|
-
});
|
|
198
|
-
const changedFiles = output
|
|
199
|
-
.trim()
|
|
200
|
-
.split('\n')
|
|
201
|
-
.filter((f) => f && !f.includes('.spec.ts') && !f.includes('.test.ts'));
|
|
202
|
-
|
|
203
|
-
// When comparing to working tree (no head specified), also include untracked files
|
|
204
|
-
// This matches what nx affected does: "All modified files not yet committed or tracked will also be added"
|
|
205
|
-
if (!head) {
|
|
206
|
-
try {
|
|
207
|
-
const untrackedOutput = execSync(`git ls-files --others --exclude-standard '*.ts' '*.tsx'`, {
|
|
208
|
-
cwd: workspaceRoot,
|
|
209
|
-
encoding: 'utf-8',
|
|
210
|
-
});
|
|
211
|
-
const untrackedFiles = untrackedOutput
|
|
212
|
-
.trim()
|
|
213
|
-
.split('\n')
|
|
214
|
-
.filter((f) => f && !f.includes('.spec.ts') && !f.includes('.test.ts'));
|
|
215
|
-
// Merge and dedupe
|
|
216
|
-
const allFiles = new Set([...changedFiles, ...untrackedFiles]);
|
|
217
|
-
return Array.from(allFiles);
|
|
218
|
-
} catch {
|
|
219
|
-
// If ls-files fails, just return the changed files
|
|
220
|
-
return changedFiles;
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
return changedFiles;
|
|
225
|
-
} catch {
|
|
226
|
-
return [];
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
/**
|
|
231
|
-
* Get the diff content for a specific file between base and head (or working tree if head not specified).
|
|
232
|
-
* Uses `git diff base [head]` to match what `nx affected` does.
|
|
233
|
-
* For untracked files, returns the entire file content as additions.
|
|
234
|
-
*/
|
|
235
|
-
function getFileDiff(workspaceRoot: string, file: string, base: string, head?: string): string {
|
|
236
|
-
try {
|
|
237
|
-
// If head is specified, diff base to head; otherwise diff base to working tree
|
|
238
|
-
const diffTarget = head ? `${base} ${head}` : base;
|
|
239
|
-
const diff = execSync(`git diff ${diffTarget} -- "${file}"`, {
|
|
240
|
-
cwd: workspaceRoot,
|
|
241
|
-
encoding: 'utf-8',
|
|
242
|
-
});
|
|
243
|
-
|
|
244
|
-
// If diff is empty and we're comparing to working tree, check if it's an untracked file
|
|
245
|
-
if (!diff && !head) {
|
|
246
|
-
const fullPath = path.join(workspaceRoot, file);
|
|
247
|
-
if (fs.existsSync(fullPath)) {
|
|
248
|
-
// Check if file is untracked
|
|
249
|
-
const isUntracked = execSync(`git ls-files --others --exclude-standard "${file}"`, {
|
|
250
|
-
cwd: workspaceRoot,
|
|
251
|
-
encoding: 'utf-8',
|
|
252
|
-
}).trim();
|
|
253
|
-
|
|
254
|
-
if (isUntracked) {
|
|
255
|
-
// For untracked files, treat entire content as additions
|
|
256
|
-
const content = fs.readFileSync(fullPath, 'utf-8');
|
|
257
|
-
const lines = content.split('\n');
|
|
258
|
-
// Create a pseudo-diff where all lines are additions
|
|
259
|
-
return lines.map((line) => `+${line}`).join('\n');
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
return diff;
|
|
265
|
-
} catch {
|
|
266
|
-
return '';
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
/**
|
|
271
|
-
* Parse diff to find newly added method signatures
|
|
272
|
-
*/
|
|
273
|
-
function findNewMethodSignaturesInDiff(diffContent: string): Set<string> {
|
|
274
|
-
const newMethods = new Set<string>();
|
|
275
|
-
const lines = diffContent.split('\n');
|
|
276
|
-
|
|
277
|
-
// Patterns to match method definitions
|
|
278
|
-
const patterns = [
|
|
279
|
-
// [export] [async] function methodName( - most explicit, check first
|
|
280
|
-
/^\+\s*(?:export\s+)?(?:async\s+)?function\s+(\w+)\s*\(/,
|
|
281
|
-
// [export] const/let methodName = [async] (
|
|
282
|
-
/^\+\s*(?:export\s+)?(?:const|let)\s+(\w+)\s*=\s*(?:async\s*)?\(/,
|
|
283
|
-
// [export] const/let methodName = [async] function
|
|
284
|
-
/^\+\s*(?:export\s+)?(?:const|let)\s+(\w+)\s*=\s*(?:async\s+)?function/,
|
|
285
|
-
// class method: [public/private/protected] [static] [async] methodName( - but NOT constructor, if, for, while, etc.
|
|
286
|
-
/^\+\s*(?:(?:public|private|protected)\s+)?(?:static\s+)?(?:async\s+)?(\w+)\s*\(/,
|
|
287
|
-
];
|
|
288
|
-
|
|
289
|
-
for (const line of lines) {
|
|
290
|
-
if (line.startsWith('+') && !line.startsWith('+++')) {
|
|
291
|
-
for (const pattern of patterns) {
|
|
292
|
-
const match = line.match(pattern);
|
|
293
|
-
if (match) {
|
|
294
|
-
// Extract method name - now always in capture group 1
|
|
295
|
-
const methodName = match[1];
|
|
296
|
-
if (methodName && !['if', 'for', 'while', 'switch', 'catch', 'constructor'].includes(methodName)) {
|
|
297
|
-
newMethods.add(methodName);
|
|
298
|
-
}
|
|
299
|
-
break;
|
|
300
|
-
}
|
|
301
|
-
}
|
|
302
|
-
}
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
return newMethods;
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
/**
|
|
309
|
-
* Check if a line contains a webpieces-disable comment that exempts from new method validation.
|
|
310
|
-
* Both max-lines-new-methods AND max-lines-modified are accepted here.
|
|
311
|
-
*/
|
|
312
|
-
function hasDisableComment(lines: string[], lineNumber: number): boolean {
|
|
313
|
-
// Check the line before the method (lineNumber is 1-indexed, array is 0-indexed)
|
|
314
|
-
// We need to check a few lines before in case there's JSDoc or decorators
|
|
315
|
-
const startCheck = Math.max(0, lineNumber - 5);
|
|
316
|
-
for (let i = lineNumber - 2; i >= startCheck; i--) {
|
|
317
|
-
const line = lines[i]?.trim() ?? '';
|
|
318
|
-
// Stop if we hit another function/class/etc
|
|
319
|
-
if (line.startsWith('function ') || line.startsWith('class ') || line.endsWith('}')) {
|
|
320
|
-
break;
|
|
321
|
-
}
|
|
322
|
-
if (line.includes('webpieces-disable')) {
|
|
323
|
-
// Either escape hatch exempts from the lowLimit new method check
|
|
324
|
-
if (line.includes('max-lines-new-methods') || line.includes('max-lines-modified')) {
|
|
325
|
-
return true;
|
|
326
|
-
}
|
|
327
|
-
}
|
|
328
|
-
}
|
|
329
|
-
return false;
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
/**
|
|
333
|
-
* Parse a TypeScript file and find methods with their line counts
|
|
334
|
-
*/
|
|
335
|
-
// webpieces-disable max-lines-new-methods -- AST traversal requires inline visitor function
|
|
336
|
-
function findMethodsInFile(filePath: string, workspaceRoot: string): MethodInfo[] {
|
|
337
|
-
const fullPath = path.join(workspaceRoot, filePath);
|
|
338
|
-
if (!fs.existsSync(fullPath)) return [];
|
|
339
|
-
|
|
340
|
-
const content = fs.readFileSync(fullPath, 'utf-8');
|
|
341
|
-
const fileLines = content.split('\n');
|
|
342
|
-
const sourceFile = ts.createSourceFile(filePath, content, ts.ScriptTarget.Latest, true);
|
|
343
|
-
|
|
344
|
-
const methods: MethodInfo[] = [];
|
|
345
|
-
|
|
346
|
-
function visit(node: ts.Node): void {
|
|
347
|
-
let methodName: string | undefined;
|
|
348
|
-
let startLine: number | undefined;
|
|
349
|
-
let endLine: number | undefined;
|
|
350
|
-
|
|
351
|
-
if (ts.isMethodDeclaration(node) && node.name) {
|
|
352
|
-
methodName = node.name.getText(sourceFile);
|
|
353
|
-
const start = sourceFile.getLineAndCharacterOfPosition(node.getStart());
|
|
354
|
-
const end = sourceFile.getLineAndCharacterOfPosition(node.getEnd());
|
|
355
|
-
startLine = start.line + 1;
|
|
356
|
-
endLine = end.line + 1;
|
|
357
|
-
} else if (ts.isFunctionDeclaration(node) && node.name) {
|
|
358
|
-
methodName = node.name.getText(sourceFile);
|
|
359
|
-
const start = sourceFile.getLineAndCharacterOfPosition(node.getStart());
|
|
360
|
-
const end = sourceFile.getLineAndCharacterOfPosition(node.getEnd());
|
|
361
|
-
startLine = start.line + 1;
|
|
362
|
-
endLine = end.line + 1;
|
|
363
|
-
} else if (ts.isArrowFunction(node)) {
|
|
364
|
-
// Check if it's assigned to a variable
|
|
365
|
-
if (ts.isVariableDeclaration(node.parent) && ts.isIdentifier(node.parent.name)) {
|
|
366
|
-
methodName = node.parent.name.getText(sourceFile);
|
|
367
|
-
const start = sourceFile.getLineAndCharacterOfPosition(node.getStart());
|
|
368
|
-
const end = sourceFile.getLineAndCharacterOfPosition(node.getEnd());
|
|
369
|
-
startLine = start.line + 1;
|
|
370
|
-
endLine = end.line + 1;
|
|
371
|
-
}
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
if (methodName && startLine !== undefined && endLine !== undefined) {
|
|
375
|
-
methods.push({
|
|
376
|
-
name: methodName,
|
|
377
|
-
line: startLine,
|
|
378
|
-
lines: endLine - startLine + 1,
|
|
379
|
-
hasDisableComment: hasDisableComment(fileLines, startLine),
|
|
380
|
-
});
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
ts.forEachChild(node, visit);
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
visit(sourceFile);
|
|
387
|
-
return methods;
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
/**
|
|
391
|
-
* Find new methods that exceed the line limit
|
|
392
|
-
*/
|
|
393
|
-
function findViolations(
|
|
394
|
-
workspaceRoot: string,
|
|
395
|
-
changedFiles: string[],
|
|
396
|
-
base: string,
|
|
397
|
-
limit: number,
|
|
398
|
-
disableAllowed: boolean,
|
|
399
|
-
head?: string
|
|
400
|
-
): MethodViolation[] {
|
|
401
|
-
const violations: MethodViolation[] = [];
|
|
402
|
-
|
|
403
|
-
for (const file of changedFiles) {
|
|
404
|
-
// Get the diff to find which methods are NEW (not just modified)
|
|
405
|
-
const diff = getFileDiff(workspaceRoot, file, base, head);
|
|
406
|
-
const newMethodNames = findNewMethodSignaturesInDiff(diff);
|
|
407
|
-
|
|
408
|
-
if (newMethodNames.size === 0) continue;
|
|
409
|
-
|
|
410
|
-
// Parse the current file to get method line counts
|
|
411
|
-
const methods = findMethodsInFile(file, workspaceRoot);
|
|
412
|
-
|
|
413
|
-
for (const method of methods) {
|
|
414
|
-
if (!newMethodNames.has(method.name)) continue;
|
|
415
|
-
|
|
416
|
-
if (method.lines > limit) {
|
|
417
|
-
if (!disableAllowed) {
|
|
418
|
-
// No escape possible
|
|
419
|
-
violations.push({
|
|
420
|
-
file,
|
|
421
|
-
methodName: method.name,
|
|
422
|
-
line: method.line,
|
|
423
|
-
lines: method.lines,
|
|
424
|
-
isNew: true,
|
|
425
|
-
limit,
|
|
426
|
-
});
|
|
427
|
-
} else if (!method.hasDisableComment) {
|
|
428
|
-
// Escape allowed but not present
|
|
429
|
-
violations.push({
|
|
430
|
-
file,
|
|
431
|
-
methodName: method.name,
|
|
432
|
-
line: method.line,
|
|
433
|
-
lines: method.lines,
|
|
434
|
-
isNew: true,
|
|
435
|
-
limit,
|
|
436
|
-
});
|
|
437
|
-
}
|
|
438
|
-
}
|
|
439
|
-
}
|
|
440
|
-
}
|
|
441
|
-
|
|
442
|
-
return violations;
|
|
443
|
-
}
|
|
444
|
-
|
|
445
|
-
/**
|
|
446
|
-
* Auto-detect the base branch by finding the merge-base with origin/main.
|
|
447
|
-
* This allows the executor to run even when NX_BASE isn't set (e.g., via dependsOn).
|
|
448
|
-
*/
|
|
449
|
-
function detectBase(workspaceRoot: string): string | null {
|
|
450
|
-
try {
|
|
451
|
-
// First, try to get merge-base with origin/main
|
|
452
|
-
const mergeBase = execSync('git merge-base HEAD origin/main', {
|
|
453
|
-
cwd: workspaceRoot,
|
|
454
|
-
encoding: 'utf-8',
|
|
455
|
-
stdio: ['pipe', 'pipe', 'pipe'],
|
|
456
|
-
}).trim();
|
|
457
|
-
|
|
458
|
-
if (mergeBase) {
|
|
459
|
-
return mergeBase;
|
|
460
|
-
}
|
|
461
|
-
} catch {
|
|
462
|
-
// origin/main might not exist, try main
|
|
463
|
-
try {
|
|
464
|
-
const mergeBase = execSync('git merge-base HEAD main', {
|
|
465
|
-
cwd: workspaceRoot,
|
|
466
|
-
encoding: 'utf-8',
|
|
467
|
-
stdio: ['pipe', 'pipe', 'pipe'],
|
|
468
|
-
}).trim();
|
|
469
|
-
|
|
470
|
-
if (mergeBase) {
|
|
471
|
-
return mergeBase;
|
|
472
|
-
}
|
|
473
|
-
} catch {
|
|
474
|
-
// Ignore - will return null
|
|
475
|
-
}
|
|
476
|
-
}
|
|
477
|
-
return null;
|
|
478
|
-
}
|
|
479
|
-
|
|
480
|
-
/**
|
|
481
|
-
* Report violations to the user with helpful instructions
|
|
482
|
-
*/
|
|
483
|
-
function reportViolations(violations: MethodViolation[], limit: number, disableAllowed: boolean): void {
|
|
484
|
-
console.error('');
|
|
485
|
-
console.error('\u274c New methods exceed ' + limit + ' line limit!');
|
|
486
|
-
console.error('');
|
|
487
|
-
console.error('\ud83d\udcda Methods should read like a "table of contents" - each method call');
|
|
488
|
-
console.error(' describes a larger piece of work.');
|
|
489
|
-
console.error('');
|
|
490
|
-
console.error('\u26a0\ufe0f *** READ tmp/webpieces/webpieces.methodsize.md for detailed guidance on how to fix this easily *** \u26a0\ufe0f');
|
|
491
|
-
console.error('');
|
|
492
|
-
|
|
493
|
-
if (disableAllowed) {
|
|
494
|
-
console.error('\u26a0\ufe0f VIOLATIONS (can use escape hatch):');
|
|
495
|
-
} else {
|
|
496
|
-
console.error('\ud83d\udeab VIOLATIONS (cannot be bypassed with disable comment):');
|
|
497
|
-
}
|
|
498
|
-
console.error('');
|
|
499
|
-
for (const v of violations) {
|
|
500
|
-
console.error(` \u274c ${v.file}:${v.line}`);
|
|
501
|
-
console.error(` Method: ${v.methodName} (${v.lines} lines, limit: ${limit})`);
|
|
502
|
-
}
|
|
503
|
-
console.error('');
|
|
504
|
-
if (disableAllowed) {
|
|
505
|
-
console.error(' Use escape: // webpieces-disable max-lines-new-methods -- [your reason]');
|
|
506
|
-
} else {
|
|
507
|
-
console.error(' These methods MUST be refactored - no escape hatch available (disableAllowed=false).');
|
|
508
|
-
}
|
|
509
|
-
console.error('');
|
|
510
|
-
}
|
|
511
|
-
|
|
512
|
-
export default async function runExecutor(
|
|
513
|
-
options: ValidateNewMethodsOptions,
|
|
514
|
-
context: ExecutorContext
|
|
515
|
-
): Promise<ExecutorResult> {
|
|
516
|
-
const workspaceRoot = context.root;
|
|
517
|
-
const limit = options.limit ?? 80;
|
|
518
|
-
const mode: MethodMaxLimitMode = options.mode ?? 'NEW_AND_MODIFIED_METHODS';
|
|
519
|
-
const disableAllowed = options.disableAllowed ?? true;
|
|
520
|
-
|
|
521
|
-
// Skip validation entirely if mode is OFF
|
|
522
|
-
if (mode === 'OFF') {
|
|
523
|
-
console.log('\n\u23ed\ufe0f Skipping new method validation (mode: OFF)');
|
|
524
|
-
console.log('');
|
|
525
|
-
return { success: true };
|
|
526
|
-
}
|
|
527
|
-
|
|
528
|
-
// Check if running in affected mode via NX_BASE, or auto-detect
|
|
529
|
-
// If NX_HEAD is set (via nx affected --head=X), use it; otherwise compare to working tree
|
|
530
|
-
let base = process.env['NX_BASE'];
|
|
531
|
-
const head = process.env['NX_HEAD'];
|
|
532
|
-
|
|
533
|
-
if (!base) {
|
|
534
|
-
// Try to auto-detect base from git merge-base
|
|
535
|
-
base = detectBase(workspaceRoot) ?? undefined;
|
|
536
|
-
|
|
537
|
-
if (!base) {
|
|
538
|
-
console.log('\n\u23ed\ufe0f Skipping new method validation (could not detect base branch)');
|
|
539
|
-
console.log(' To run explicitly: nx affected --target=validate-new-methods --base=origin/main');
|
|
540
|
-
console.log('');
|
|
541
|
-
return { success: true };
|
|
542
|
-
}
|
|
543
|
-
|
|
544
|
-
console.log('\n\ud83d\udccf Validating New Method Sizes (auto-detected base)\n');
|
|
545
|
-
} else {
|
|
546
|
-
console.log('\n\ud83d\udccf Validating New Method Sizes\n');
|
|
547
|
-
}
|
|
548
|
-
|
|
549
|
-
console.log(` Base: ${base}`);
|
|
550
|
-
console.log(` Head: ${head ?? 'working tree (includes uncommitted changes)'}`);
|
|
551
|
-
console.log(` Mode: ${mode}`);
|
|
552
|
-
console.log(` Limit for new methods: ${limit} lines (${disableAllowed ? 'can escape' : 'NO escape possible'})`);
|
|
553
|
-
console.log('');
|
|
554
|
-
|
|
555
|
-
try {
|
|
556
|
-
// Get changed TypeScript files (base to head, or working tree if head not set)
|
|
557
|
-
const changedFiles = getChangedTypeScriptFiles(workspaceRoot, base, head);
|
|
558
|
-
|
|
559
|
-
if (changedFiles.length === 0) {
|
|
560
|
-
console.log('\u2705 No TypeScript files changed');
|
|
561
|
-
return { success: true };
|
|
562
|
-
}
|
|
563
|
-
|
|
564
|
-
console.log(`\ud83d\udcc2 Checking ${changedFiles.length} changed file(s)...`);
|
|
565
|
-
|
|
566
|
-
// Find violations
|
|
567
|
-
const violations = findViolations(workspaceRoot, changedFiles, base, limit, disableAllowed, head);
|
|
568
|
-
|
|
569
|
-
if (violations.length === 0) {
|
|
570
|
-
console.log('\u2705 All new methods are within ' + limit + ' lines');
|
|
571
|
-
return { success: true };
|
|
572
|
-
}
|
|
573
|
-
|
|
574
|
-
// Write instructions file and report violations
|
|
575
|
-
writeTmpInstructions(workspaceRoot);
|
|
576
|
-
reportViolations(violations, limit, disableAllowed);
|
|
577
|
-
|
|
578
|
-
return { success: false };
|
|
579
|
-
} catch (err: unknown) {
|
|
580
|
-
const error = err instanceof Error ? err : new Error(String(err));
|
|
581
|
-
console.error('\u274c New method validation failed:', error.message);
|
|
582
|
-
return { success: false };
|
|
583
|
-
}
|
|
584
|
-
}
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"$schema": "http://json-schema.org/schema",
|
|
3
|
-
"title": "Validate New Methods Executor",
|
|
4
|
-
"description": "Validates that newly added methods don't exceed a maximum line count. Only runs in affected mode (when NX_BASE is set).",
|
|
5
|
-
"type": "object",
|
|
6
|
-
"properties": {
|
|
7
|
-
"limit": {
|
|
8
|
-
"type": "number",
|
|
9
|
-
"description": "Maximum lines allowed for new methods",
|
|
10
|
-
"default": 80
|
|
11
|
-
},
|
|
12
|
-
"mode": {
|
|
13
|
-
"type": "string",
|
|
14
|
-
"enum": ["OFF", "NEW_METHODS", "NEW_AND_MODIFIED_METHODS", "MODIFIED_FILES"],
|
|
15
|
-
"description": "OFF: skip validation. NEW_METHODS: only new methods in diff. NEW_AND_MODIFIED_METHODS: new methods + methods with changes. MODIFIED_FILES: all methods in modified files.",
|
|
16
|
-
"default": "NEW_AND_MODIFIED_METHODS"
|
|
17
|
-
},
|
|
18
|
-
"disableAllowed": {
|
|
19
|
-
"type": "boolean",
|
|
20
|
-
"description": "Whether disable comments work. When false, no escape hatch.",
|
|
21
|
-
"default": true
|
|
22
|
-
}
|
|
23
|
-
},
|
|
24
|
-
"required": []
|
|
25
|
-
}
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Validate No Any Unknown Executor
|
|
3
|
-
*
|
|
4
|
-
* Validates that `any` and `unknown` TypeScript keywords are not used.
|
|
5
|
-
* Uses LINE-BASED detection (not method-based) for git diff filtering.
|
|
6
|
-
*
|
|
7
|
-
* ============================================================================
|
|
8
|
-
* VIOLATIONS (BAD) - These patterns are flagged:
|
|
9
|
-
* ============================================================================
|
|
10
|
-
*
|
|
11
|
-
* - const x: any = ...
|
|
12
|
-
* - function foo(arg: any): any { }
|
|
13
|
-
* - const data = response as any;
|
|
14
|
-
* - type T = any;
|
|
15
|
-
* - const x: unknown = ...
|
|
16
|
-
* - function foo(arg: unknown): unknown { }
|
|
17
|
-
*
|
|
18
|
-
* ============================================================================
|
|
19
|
-
* MODES (LINE-BASED)
|
|
20
|
-
* ============================================================================
|
|
21
|
-
* - OFF: Skip validation entirely
|
|
22
|
-
* - MODIFIED_CODE: Flag any/unknown on changed lines (lines in diff hunks)
|
|
23
|
-
* - MODIFIED_FILES: Flag ALL any/unknown in files that were modified
|
|
24
|
-
*
|
|
25
|
-
* ============================================================================
|
|
26
|
-
* ESCAPE HATCH
|
|
27
|
-
* ============================================================================
|
|
28
|
-
* Add comment above the violation:
|
|
29
|
-
* // webpieces-disable no-any-unknown -- [your justification]
|
|
30
|
-
* const x: any = ...;
|
|
31
|
-
*/
|
|
32
|
-
import type { ExecutorContext } from '@nx/devkit';
|
|
33
|
-
export type NoAnyUnknownMode = 'OFF' | 'MODIFIED_CODE' | 'MODIFIED_FILES';
|
|
34
|
-
export interface ValidateNoAnyUnknownOptions {
|
|
35
|
-
mode?: NoAnyUnknownMode;
|
|
36
|
-
disableAllowed?: boolean;
|
|
37
|
-
ignoreModifiedUntilEpoch?: number;
|
|
38
|
-
}
|
|
39
|
-
export interface ExecutorResult {
|
|
40
|
-
success: boolean;
|
|
41
|
-
}
|
|
42
|
-
export default function runExecutor(options: ValidateNoAnyUnknownOptions, context: ExecutorContext): Promise<ExecutorResult>;
|