@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.
Files changed (181) hide show
  1. package/config/eslint/base.mjs +1 -1
  2. package/executors.json +6 -91
  3. package/package.json +6 -19
  4. package/{executors → src/executors}/help/executor.d.ts +4 -2
  5. package/src/executors/help/executor.js.map +1 -0
  6. package/src/executors/validate-eslint-sync/executor.js.map +1 -0
  7. package/{executors → src/executors}/validate-versions-locked/executor.js +2 -1
  8. package/src/executors/validate-versions-locked/executor.js.map +1 -0
  9. package/src/index.d.ts +1 -1
  10. package/src/index.js +1 -1
  11. package/src/index.js.map +1 -1
  12. package/src/plugin.d.ts +86 -0
  13. package/{plugin.js → src/plugin.js} +31 -15
  14. package/src/plugin.js.map +1 -0
  15. package/src/toError.d.ts +5 -0
  16. package/src/toError.js +37 -0
  17. package/src/toError.js.map +1 -0
  18. package/templates/eslint.webpieces.config.mjs +1 -1
  19. package/architecture/executors/diff-utils.d.ts +0 -24
  20. package/architecture/executors/diff-utils.js +0 -119
  21. package/architecture/executors/diff-utils.js.map +0 -1
  22. package/architecture/executors/diff-utils.ts +0 -127
  23. package/architecture/executors/generate/executor.d.ts +0 -16
  24. package/architecture/executors/generate/executor.js +0 -44
  25. package/architecture/executors/generate/executor.js.map +0 -1
  26. package/architecture/executors/generate/executor.ts +0 -59
  27. package/architecture/executors/generate/schema.json +0 -14
  28. package/architecture/executors/validate-architecture-unchanged/executor.d.ts +0 -17
  29. package/architecture/executors/validate-architecture-unchanged/executor.js +0 -229
  30. package/architecture/executors/validate-architecture-unchanged/executor.js.map +0 -1
  31. package/architecture/executors/validate-architecture-unchanged/executor.ts +0 -251
  32. package/architecture/executors/validate-architecture-unchanged/schema.json +0 -14
  33. package/architecture/executors/validate-code/executor.d.ts +0 -78
  34. package/architecture/executors/validate-code/executor.js +0 -243
  35. package/architecture/executors/validate-code/executor.js.map +0 -1
  36. package/architecture/executors/validate-code/executor.ts +0 -406
  37. package/architecture/executors/validate-code/schema.json +0 -227
  38. package/architecture/executors/validate-dtos/executor.d.ts +0 -42
  39. package/architecture/executors/validate-dtos/executor.js +0 -561
  40. package/architecture/executors/validate-dtos/executor.js.map +0 -1
  41. package/architecture/executors/validate-dtos/executor.ts +0 -689
  42. package/architecture/executors/validate-dtos/schema.json +0 -33
  43. package/architecture/executors/validate-modified-files/executor.d.ts +0 -25
  44. package/architecture/executors/validate-modified-files/executor.js +0 -501
  45. package/architecture/executors/validate-modified-files/executor.js.map +0 -1
  46. package/architecture/executors/validate-modified-files/executor.ts +0 -571
  47. package/architecture/executors/validate-modified-files/schema.json +0 -25
  48. package/architecture/executors/validate-modified-methods/executor.d.ts +0 -31
  49. package/architecture/executors/validate-modified-methods/executor.js +0 -694
  50. package/architecture/executors/validate-modified-methods/executor.js.map +0 -1
  51. package/architecture/executors/validate-modified-methods/executor.ts +0 -797
  52. package/architecture/executors/validate-modified-methods/schema.json +0 -25
  53. package/architecture/executors/validate-new-methods/executor.d.ts +0 -28
  54. package/architecture/executors/validate-new-methods/executor.js +0 -513
  55. package/architecture/executors/validate-new-methods/executor.js.map +0 -1
  56. package/architecture/executors/validate-new-methods/executor.ts +0 -584
  57. package/architecture/executors/validate-new-methods/schema.json +0 -25
  58. package/architecture/executors/validate-no-any-unknown/executor.d.ts +0 -42
  59. package/architecture/executors/validate-no-any-unknown/executor.js +0 -462
  60. package/architecture/executors/validate-no-any-unknown/executor.js.map +0 -1
  61. package/architecture/executors/validate-no-any-unknown/executor.ts +0 -540
  62. package/architecture/executors/validate-no-any-unknown/schema.json +0 -24
  63. package/architecture/executors/validate-no-architecture-cycles/executor.d.ts +0 -16
  64. package/architecture/executors/validate-no-architecture-cycles/executor.js +0 -48
  65. package/architecture/executors/validate-no-architecture-cycles/executor.js.map +0 -1
  66. package/architecture/executors/validate-no-architecture-cycles/executor.ts +0 -60
  67. package/architecture/executors/validate-no-architecture-cycles/schema.json +0 -8
  68. package/architecture/executors/validate-no-destructure/executor.d.ts +0 -52
  69. package/architecture/executors/validate-no-destructure/executor.js +0 -491
  70. package/architecture/executors/validate-no-destructure/executor.js.map +0 -1
  71. package/architecture/executors/validate-no-destructure/executor.ts +0 -578
  72. package/architecture/executors/validate-no-destructure/schema.json +0 -24
  73. package/architecture/executors/validate-no-direct-api-resolver/executor.d.ts +0 -47
  74. package/architecture/executors/validate-no-direct-api-resolver/executor.js +0 -566
  75. package/architecture/executors/validate-no-direct-api-resolver/executor.js.map +0 -1
  76. package/architecture/executors/validate-no-direct-api-resolver/executor.ts +0 -666
  77. package/architecture/executors/validate-no-direct-api-resolver/schema.json +0 -29
  78. package/architecture/executors/validate-no-inline-types/executor.d.ts +0 -91
  79. package/architecture/executors/validate-no-inline-types/executor.js +0 -669
  80. package/architecture/executors/validate-no-inline-types/executor.js.map +0 -1
  81. package/architecture/executors/validate-no-inline-types/executor.ts +0 -775
  82. package/architecture/executors/validate-no-inline-types/schema.json +0 -24
  83. package/architecture/executors/validate-no-skiplevel-deps/executor.d.ts +0 -19
  84. package/architecture/executors/validate-no-skiplevel-deps/executor.js +0 -227
  85. package/architecture/executors/validate-no-skiplevel-deps/executor.js.map +0 -1
  86. package/architecture/executors/validate-no-skiplevel-deps/executor.ts +0 -267
  87. package/architecture/executors/validate-no-skiplevel-deps/schema.json +0 -8
  88. package/architecture/executors/validate-packagejson/executor.d.ts +0 -16
  89. package/architecture/executors/validate-packagejson/executor.js +0 -57
  90. package/architecture/executors/validate-packagejson/executor.js.map +0 -1
  91. package/architecture/executors/validate-packagejson/executor.ts +0 -74
  92. package/architecture/executors/validate-packagejson/schema.json +0 -8
  93. package/architecture/executors/validate-prisma-converters/executor.d.ts +0 -60
  94. package/architecture/executors/validate-prisma-converters/executor.js +0 -634
  95. package/architecture/executors/validate-prisma-converters/executor.js.map +0 -1
  96. package/architecture/executors/validate-prisma-converters/executor.ts +0 -822
  97. package/architecture/executors/validate-prisma-converters/schema.json +0 -38
  98. package/architecture/executors/validate-return-types/executor.d.ts +0 -29
  99. package/architecture/executors/validate-return-types/executor.js +0 -439
  100. package/architecture/executors/validate-return-types/executor.js.map +0 -1
  101. package/architecture/executors/validate-return-types/executor.ts +0 -524
  102. package/architecture/executors/validate-return-types/schema.json +0 -24
  103. package/architecture/executors/visualize/executor.d.ts +0 -17
  104. package/architecture/executors/visualize/executor.js +0 -49
  105. package/architecture/executors/visualize/executor.js.map +0 -1
  106. package/architecture/executors/visualize/executor.ts +0 -63
  107. package/architecture/executors/visualize/schema.json +0 -14
  108. package/architecture/index.d.ts +0 -19
  109. package/architecture/index.js +0 -23
  110. package/architecture/index.js.map +0 -1
  111. package/architecture/index.ts +0 -20
  112. package/architecture/lib/graph-comparator.d.ts +0 -39
  113. package/architecture/lib/graph-comparator.js +0 -100
  114. package/architecture/lib/graph-comparator.js.map +0 -1
  115. package/architecture/lib/graph-comparator.ts +0 -141
  116. package/architecture/lib/graph-generator.d.ts +0 -19
  117. package/architecture/lib/graph-generator.js +0 -84
  118. package/architecture/lib/graph-generator.js.map +0 -1
  119. package/architecture/lib/graph-generator.ts +0 -97
  120. package/architecture/lib/graph-loader.d.ts +0 -31
  121. package/architecture/lib/graph-loader.js +0 -98
  122. package/architecture/lib/graph-loader.js.map +0 -1
  123. package/architecture/lib/graph-loader.ts +0 -116
  124. package/architecture/lib/graph-sorter.d.ts +0 -37
  125. package/architecture/lib/graph-sorter.js +0 -110
  126. package/architecture/lib/graph-sorter.js.map +0 -1
  127. package/architecture/lib/graph-sorter.ts +0 -137
  128. package/architecture/lib/graph-visualizer.d.ts +0 -29
  129. package/architecture/lib/graph-visualizer.js +0 -217
  130. package/architecture/lib/graph-visualizer.js.map +0 -1
  131. package/architecture/lib/graph-visualizer.ts +0 -231
  132. package/architecture/lib/package-validator.d.ts +0 -38
  133. package/architecture/lib/package-validator.js +0 -126
  134. package/architecture/lib/package-validator.js.map +0 -1
  135. package/architecture/lib/package-validator.ts +0 -170
  136. package/eslint-plugin/__tests__/catch-error-pattern.test.ts +0 -374
  137. package/eslint-plugin/__tests__/max-file-lines.test.ts +0 -207
  138. package/eslint-plugin/__tests__/max-method-lines.test.ts +0 -258
  139. package/eslint-plugin/__tests__/no-unmanaged-exceptions.test.ts +0 -359
  140. package/eslint-plugin/index.d.ts +0 -23
  141. package/eslint-plugin/index.js +0 -30
  142. package/eslint-plugin/index.js.map +0 -1
  143. package/eslint-plugin/index.ts +0 -29
  144. package/eslint-plugin/rules/catch-error-pattern.d.ts +0 -11
  145. package/eslint-plugin/rules/catch-error-pattern.js +0 -143
  146. package/eslint-plugin/rules/catch-error-pattern.js.map +0 -1
  147. package/eslint-plugin/rules/catch-error-pattern.ts +0 -246
  148. package/eslint-plugin/rules/enforce-architecture.d.ts +0 -15
  149. package/eslint-plugin/rules/enforce-architecture.js +0 -476
  150. package/eslint-plugin/rules/enforce-architecture.js.map +0 -1
  151. package/eslint-plugin/rules/enforce-architecture.ts +0 -543
  152. package/eslint-plugin/rules/max-file-lines.d.ts +0 -12
  153. package/eslint-plugin/rules/max-file-lines.js +0 -257
  154. package/eslint-plugin/rules/max-file-lines.js.map +0 -1
  155. package/eslint-plugin/rules/max-file-lines.ts +0 -272
  156. package/eslint-plugin/rules/max-method-lines.d.ts +0 -12
  157. package/eslint-plugin/rules/max-method-lines.js +0 -240
  158. package/eslint-plugin/rules/max-method-lines.js.map +0 -1
  159. package/eslint-plugin/rules/max-method-lines.ts +0 -287
  160. package/eslint-plugin/rules/no-unmanaged-exceptions.d.ts +0 -22
  161. package/eslint-plugin/rules/no-unmanaged-exceptions.js +0 -160
  162. package/eslint-plugin/rules/no-unmanaged-exceptions.js.map +0 -1
  163. package/eslint-plugin/rules/no-unmanaged-exceptions.ts +0 -179
  164. package/executors/help/executor.js.map +0 -1
  165. package/executors/help/executor.ts +0 -61
  166. package/executors/validate-eslint-sync/executor.js.map +0 -1
  167. package/executors/validate-eslint-sync/executor.ts +0 -87
  168. package/executors/validate-versions-locked/executor.js.map +0 -1
  169. package/executors/validate-versions-locked/executor.ts +0 -368
  170. package/plugin/README.md +0 -243
  171. package/plugin/index.d.ts +0 -4
  172. package/plugin/index.js +0 -8
  173. package/plugin/index.js.map +0 -1
  174. package/plugin/index.ts +0 -4
  175. /package/{executors → src/executors}/help/executor.js +0 -0
  176. /package/{executors → src/executors}/help/schema.json +0 -0
  177. /package/{executors → src/executors}/validate-eslint-sync/executor.d.ts +0 -0
  178. /package/{executors → src/executors}/validate-eslint-sync/executor.js +0 -0
  179. /package/{executors → src/executors}/validate-eslint-sync/schema.json +0 -0
  180. /package/{executors → src/executors}/validate-versions-locked/executor.d.ts +0 -0
  181. /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>;