@webpieces/code-rules 0.0.1 → 0.2.114

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 (63) hide show
  1. package/package.json +4 -3
  2. package/src/cli.d.ts +1 -0
  3. package/src/cli.js +19 -0
  4. package/src/cli.js.map +1 -0
  5. package/src/diff-utils.d.ts +24 -0
  6. package/src/{diff-utils.ts → diff-utils.js} +30 -38
  7. package/src/diff-utils.js.map +1 -0
  8. package/src/from-shared-config.d.ts +28 -0
  9. package/src/from-shared-config.js +119 -0
  10. package/src/from-shared-config.js.map +1 -0
  11. package/src/index.js +33 -0
  12. package/src/index.js.map +1 -0
  13. package/src/validate-catch-error-pattern.d.ts +47 -0
  14. package/src/{validate-catch-error-pattern.ts → validate-catch-error-pattern.js} +74 -195
  15. package/src/validate-catch-error-pattern.js.map +1 -0
  16. package/src/validate-code.d.ts +98 -0
  17. package/src/{validate-code.ts → validate-code.js} +65 -259
  18. package/src/validate-code.js.map +1 -0
  19. package/src/validate-dtos.d.ts +41 -0
  20. package/src/{validate-dtos.ts → validate-dtos.js} +88 -215
  21. package/src/validate-dtos.js.map +1 -0
  22. package/src/validate-modified-files.d.ts +24 -0
  23. package/src/{validate-modified-files.ts → validate-modified-files.js} +46 -115
  24. package/src/validate-modified-files.js.map +1 -0
  25. package/src/validate-modified-methods.d.ts +30 -0
  26. package/src/{validate-modified-methods.ts → validate-modified-methods.js} +94 -196
  27. package/src/validate-modified-methods.js.map +1 -0
  28. package/src/validate-new-methods.d.ts +27 -0
  29. package/src/{validate-new-methods.ts → validate-new-methods.js} +63 -133
  30. package/src/validate-new-methods.js.map +1 -0
  31. package/src/validate-no-any-unknown.d.ts +41 -0
  32. package/src/{validate-no-any-unknown.ts → validate-no-any-unknown.js} +69 -146
  33. package/src/validate-no-any-unknown.js.map +1 -0
  34. package/src/validate-no-destructure.d.ts +51 -0
  35. package/src/{validate-no-destructure.ts → validate-no-destructure.js} +80 -166
  36. package/src/validate-no-destructure.js.map +1 -0
  37. package/src/validate-no-direct-api-resolver.d.ts +46 -0
  38. package/src/{validate-no-direct-api-resolver.ts → validate-no-direct-api-resolver.js} +112 -211
  39. package/src/validate-no-direct-api-resolver.js.map +1 -0
  40. package/src/validate-no-implicit-any.d.ts +36 -0
  41. package/src/{validate-no-implicit-any.ts → validate-no-implicit-any.js} +94 -141
  42. package/src/validate-no-implicit-any.js.map +1 -0
  43. package/src/validate-no-inline-types.d.ts +90 -0
  44. package/src/{validate-no-inline-types.ts → validate-no-inline-types.js} +93 -198
  45. package/src/validate-no-inline-types.js.map +1 -0
  46. package/src/validate-no-unmanaged-exceptions.d.ts +43 -0
  47. package/src/{validate-no-unmanaged-exceptions.ts → validate-no-unmanaged-exceptions.js} +71 -140
  48. package/src/validate-no-unmanaged-exceptions.js.map +1 -0
  49. package/src/validate-prisma-converters.d.ts +59 -0
  50. package/src/{validate-prisma-converters.ts → validate-prisma-converters.js} +120 -307
  51. package/src/validate-prisma-converters.js.map +1 -0
  52. package/src/validate-return-types.d.ts +28 -0
  53. package/src/{validate-return-types.ts → validate-return-types.js} +84 -168
  54. package/src/validate-return-types.js.map +1 -0
  55. package/LICENSE +0 -373
  56. package/jest.config.ts +0 -20
  57. package/project.json +0 -22
  58. package/src/cli.ts +0 -17
  59. package/src/from-shared-config.ts +0 -118
  60. package/tsconfig.json +0 -22
  61. package/tsconfig.lib.json +0 -10
  62. package/tsconfig.spec.json +0 -14
  63. /package/src/{index.ts → index.d.ts} +0 -0
@@ -1,3 +1,4 @@
1
+ "use strict";
1
2
  /**
2
3
  * Validate No Destructure Executor
3
4
  *
@@ -39,40 +40,22 @@
39
40
  * // webpieces-disable no-destructure -- [your justification]
40
41
  * const { x, y } = obj;
41
42
  */
42
-
43
- import { execSync } from 'child_process';
44
- import * as fs from 'fs';
45
- import * as path from 'path';
46
- import * as ts from 'typescript';
47
-
48
- export type NoDestructureMode = 'OFF' | 'MODIFIED_CODE' | 'MODIFIED_FILES';
49
-
50
- export interface ValidateNoDestructureOptions {
51
- mode?: NoDestructureMode;
52
- disableAllowed?: boolean;
53
- ignoreModifiedUntilEpoch?: number;
54
- }
55
-
56
- export interface ExecutorResult {
57
- success: boolean;
58
- }
59
-
60
- interface DestructureViolation {
61
- file: string;
62
- line: number;
63
- column: number;
64
- context: string;
65
- }
66
-
43
+ Object.defineProperty(exports, "__esModule", { value: true });
44
+ exports.default = runValidator;
45
+ const tslib_1 = require("tslib");
46
+ const child_process_1 = require("child_process");
47
+ const fs = tslib_1.__importStar(require("fs"));
48
+ const path = tslib_1.__importStar(require("path"));
49
+ const ts = tslib_1.__importStar(require("typescript"));
67
50
  /**
68
51
  * Get changed TypeScript files between base and head (or working tree if head not specified).
69
52
  */
70
53
  // webpieces-disable max-lines-new-methods -- Git command handling with untracked files requires multiple code paths
71
- function getChangedTypeScriptFiles(workspaceRoot: string, base: string, head?: string): string[] {
54
+ function getChangedTypeScriptFiles(workspaceRoot, base, head) {
72
55
  // eslint-disable-next-line @webpieces/no-unmanaged-exceptions
73
56
  try {
74
57
  const diffTarget = head ? `${base} ${head}` : base;
75
- const output = execSync(`git diff --name-only ${diffTarget} -- '*.ts' '*.tsx'`, {
58
+ const output = (0, child_process_1.execSync)(`git diff --name-only ${diffTarget} -- '*.ts' '*.tsx'`, {
76
59
  cwd: workspaceRoot,
77
60
  encoding: 'utf-8',
78
61
  });
@@ -80,11 +63,10 @@ function getChangedTypeScriptFiles(workspaceRoot: string, base: string, head?: s
80
63
  .trim()
81
64
  .split('\n')
82
65
  .filter((f) => f && !f.includes('.spec.ts') && !f.includes('.test.ts'));
83
-
84
66
  if (!head) {
85
67
  // eslint-disable-next-line @webpieces/no-unmanaged-exceptions
86
68
  try {
87
- const untrackedOutput = execSync(`git ls-files --others --exclude-standard '*.ts' '*.tsx'`, {
69
+ const untrackedOutput = (0, child_process_1.execSync)(`git ls-files --others --exclude-standard '*.ts' '*.tsx'`, {
88
70
  cwd: workspaceRoot,
89
71
  encoding: 'utf-8',
90
72
  });
@@ -94,39 +76,37 @@ function getChangedTypeScriptFiles(workspaceRoot: string, base: string, head?: s
94
76
  .filter((f) => f && !f.includes('.spec.ts') && !f.includes('.test.ts'));
95
77
  const allFiles = new Set([...changedFiles, ...untrackedFiles]);
96
78
  return Array.from(allFiles);
97
- } catch (err: unknown) {
79
+ }
80
+ catch (err) {
98
81
  //const error = toError(err);
99
82
  return changedFiles;
100
83
  }
101
84
  }
102
-
103
85
  return changedFiles;
104
- } catch (err: unknown) {
86
+ }
87
+ catch (err) {
105
88
  //const error = toError(err);
106
89
  return [];
107
90
  }
108
91
  }
109
-
110
92
  /**
111
93
  * Get the diff content for a specific file.
112
94
  */
113
- function getFileDiff(workspaceRoot: string, file: string, base: string, head?: string): string {
95
+ function getFileDiff(workspaceRoot, file, base, head) {
114
96
  // eslint-disable-next-line @webpieces/no-unmanaged-exceptions
115
97
  try {
116
98
  const diffTarget = head ? `${base} ${head}` : base;
117
- const diff = execSync(`git diff ${diffTarget} -- "${file}"`, {
99
+ const diff = (0, child_process_1.execSync)(`git diff ${diffTarget} -- "${file}"`, {
118
100
  cwd: workspaceRoot,
119
101
  encoding: 'utf-8',
120
102
  });
121
-
122
103
  if (!diff && !head) {
123
104
  const fullPath = path.join(workspaceRoot, file);
124
105
  if (fs.existsSync(fullPath)) {
125
- const isUntracked = execSync(`git ls-files --others --exclude-standard "${file}"`, {
106
+ const isUntracked = (0, child_process_1.execSync)(`git ls-files --others --exclude-standard "${file}"`, {
126
107
  cwd: workspaceRoot,
127
108
  encoding: 'utf-8',
128
109
  }).trim();
129
-
130
110
  if (isUntracked) {
131
111
  const content = fs.readFileSync(fullPath, 'utf-8');
132
112
  const lines = content.split('\n');
@@ -134,46 +114,43 @@ function getFileDiff(workspaceRoot: string, file: string, base: string, head?: s
134
114
  }
135
115
  }
136
116
  }
137
-
138
117
  return diff;
139
- } catch (err: unknown) {
118
+ }
119
+ catch (err) {
140
120
  //const error = toError(err);
141
121
  return '';
142
122
  }
143
123
  }
144
-
145
124
  /**
146
125
  * Parse diff to extract changed line numbers (additions only - lines starting with +).
147
126
  */
148
- function getChangedLineNumbers(diffContent: string): Set<number> {
149
- const changedLines = new Set<number>();
127
+ function getChangedLineNumbers(diffContent) {
128
+ const changedLines = new Set();
150
129
  const lines = diffContent.split('\n');
151
130
  let currentLine = 0;
152
-
153
131
  for (const line of lines) {
154
132
  const hunkMatch = line.match(/^@@ -\d+(?:,\d+)? \+(\d+)(?:,\d+)? @@/);
155
133
  if (hunkMatch) {
156
134
  currentLine = parseInt(hunkMatch[1], 10);
157
135
  continue;
158
136
  }
159
-
160
137
  if (line.startsWith('+') && !line.startsWith('+++')) {
161
138
  changedLines.add(currentLine);
162
139
  currentLine++;
163
- } else if (line.startsWith('-') && !line.startsWith('---')) {
140
+ }
141
+ else if (line.startsWith('-') && !line.startsWith('---')) {
164
142
  // Deletions don't increment line number
165
- } else {
143
+ }
144
+ else {
166
145
  currentLine++;
167
146
  }
168
147
  }
169
-
170
148
  return changedLines;
171
149
  }
172
-
173
150
  /**
174
151
  * Check if a line contains a webpieces-disable comment for no-destructure.
175
152
  */
176
- function hasDisableComment(lines: string[], lineNumber: number): boolean {
153
+ function hasDisableComment(lines, lineNumber) {
177
154
  const startCheck = Math.max(0, lineNumber - 5);
178
155
  for (let i = lineNumber - 2; i >= startCheck; i--) {
179
156
  const line = lines[i]?.trim() ?? '';
@@ -186,16 +163,16 @@ function hasDisableComment(lines: string[], lineNumber: number): boolean {
186
163
  }
187
164
  return false;
188
165
  }
189
-
190
166
  /**
191
167
  * Check if an ArrayBindingPattern's initializer is `await Promise.all(...)`.
192
168
  */
193
- function isPromiseAllDestructure(node: ts.ArrayBindingPattern): boolean {
169
+ function isPromiseAllDestructure(node) {
194
170
  const parent = node.parent;
195
- if (!ts.isVariableDeclaration(parent)) return false;
171
+ if (!ts.isVariableDeclaration(parent))
172
+ return false;
196
173
  const initializer = parent.initializer;
197
- if (!initializer) return false;
198
-
174
+ if (!initializer)
175
+ return false;
199
176
  // Handle: const [a, b] = await Promise.all([...])
200
177
  if (ts.isAwaitExpression(initializer)) {
201
178
  const awaitedExpr = initializer.expression;
@@ -210,24 +187,22 @@ function isPromiseAllDestructure(node: ts.ArrayBindingPattern): boolean {
210
187
  }
211
188
  }
212
189
  }
213
-
214
190
  return false;
215
191
  }
216
-
217
192
  /**
218
193
  * Check if an ArrayBindingPattern in a for-of loop iterates over Object.entries(...).
219
194
  */
220
- function isObjectEntriesForOf(node: ts.ArrayBindingPattern): boolean {
195
+ function isObjectEntriesForOf(node) {
221
196
  // Walk up: ArrayBindingPattern -> VariableDeclaration -> VariableDeclarationList -> ForOfStatement
222
197
  const varDecl = node.parent;
223
- if (!ts.isVariableDeclaration(varDecl)) return false;
224
-
198
+ if (!ts.isVariableDeclaration(varDecl))
199
+ return false;
225
200
  const varDeclList = varDecl.parent;
226
- if (!ts.isVariableDeclarationList(varDeclList)) return false;
227
-
201
+ if (!ts.isVariableDeclarationList(varDeclList))
202
+ return false;
228
203
  const forOfStmt = varDeclList.parent;
229
- if (!ts.isForOfStatement(forOfStmt)) return false;
230
-
204
+ if (!ts.isForOfStatement(forOfStmt))
205
+ return false;
231
206
  // Check iterable expression ends with .entries()
232
207
  const iterable = forOfStmt.expression;
233
208
  if (ts.isCallExpression(iterable)) {
@@ -236,14 +211,12 @@ function isObjectEntriesForOf(node: ts.ArrayBindingPattern): boolean {
236
211
  return true;
237
212
  }
238
213
  }
239
-
240
214
  return false;
241
215
  }
242
-
243
216
  /**
244
217
  * Check if an ObjectBindingPattern contains a rest element (...rest).
245
218
  */
246
- function hasRestElement(node: ts.ObjectBindingPattern): boolean {
219
+ function hasRestElement(node) {
247
220
  for (const element of node.elements) {
248
221
  if (element.dotDotDotToken) {
249
222
  return true;
@@ -251,30 +224,20 @@ function hasRestElement(node: ts.ObjectBindingPattern): boolean {
251
224
  }
252
225
  return false;
253
226
  }
254
-
255
- interface DestructureInfo {
256
- line: number;
257
- column: number;
258
- context: string;
259
- hasDisableComment: boolean;
260
- }
261
-
262
227
  /**
263
228
  * Find all destructuring patterns in a file using AST.
264
229
  */
265
230
  // webpieces-disable max-lines-new-methods -- AST traversal with multiple destructuring pattern checks and exception detection
266
- function findDestructuringInFile(filePath: string, workspaceRoot: string, disableAllowed: boolean): DestructureInfo[] {
231
+ function findDestructuringInFile(filePath, workspaceRoot, disableAllowed) {
267
232
  const fullPath = path.join(workspaceRoot, filePath);
268
- if (!fs.existsSync(fullPath)) return [];
269
-
233
+ if (!fs.existsSync(fullPath))
234
+ return [];
270
235
  const content = fs.readFileSync(fullPath, 'utf-8');
271
236
  const fileLines = content.split('\n');
272
237
  const sourceFile = ts.createSourceFile(filePath, content, ts.ScriptTarget.Latest, true);
273
-
274
- const violations: DestructureInfo[] = [];
275
-
238
+ const violations = [];
276
239
  // webpieces-disable max-lines-new-methods -- AST visitor needs to handle object/array binding patterns in declarations, for-of, and parameters
277
- function visit(node: ts.Node): void {
240
+ function visit(node) {
278
241
  // eslint-disable-next-line @webpieces/no-unmanaged-exceptions
279
242
  try {
280
243
  // Check ObjectBindingPattern
@@ -284,11 +247,9 @@ function findDestructuringInFile(filePath: string, workspaceRoot: string, disabl
284
247
  ts.forEachChild(node, visit);
285
248
  return;
286
249
  }
287
-
288
250
  const context = getDestructureContext(node);
289
251
  recordViolation(node, context, fileLines, sourceFile, violations, disableAllowed);
290
252
  }
291
-
292
253
  // Check ArrayBindingPattern
293
254
  if (ts.isArrayBindingPattern(node)) {
294
255
  // Exception: Promise.all destructure
@@ -296,56 +257,44 @@ function findDestructuringInFile(filePath: string, workspaceRoot: string, disabl
296
257
  ts.forEachChild(node, visit);
297
258
  return;
298
259
  }
299
-
300
260
  // Exception: Object.entries in for-of
301
261
  if (isObjectEntriesForOf(node)) {
302
262
  ts.forEachChild(node, visit);
303
263
  return;
304
264
  }
305
-
306
265
  const context = getDestructureContext(node);
307
266
  recordViolation(node, context, fileLines, sourceFile, violations, disableAllowed);
308
267
  }
309
- } catch (err: unknown) {
268
+ }
269
+ catch (err) {
310
270
  //const error = toError(err);
311
271
  // Skip nodes that cause errors during analysis
312
272
  }
313
-
314
273
  ts.forEachChild(node, visit);
315
274
  }
316
-
317
275
  visit(sourceFile);
318
276
  return violations;
319
277
  }
320
-
321
- function recordViolation(
322
- node: ts.Node,
323
- context: string,
324
- fileLines: string[],
325
- sourceFile: ts.SourceFile,
326
- violations: DestructureInfo[],
327
- disableAllowed: boolean,
328
- ): void {
278
+ function recordViolation(node, context, fileLines, sourceFile, violations, disableAllowed) {
329
279
  const startPos = node.getStart(sourceFile);
330
280
  if (startPos >= 0) {
331
281
  const pos = sourceFile.getLineAndCharacterOfPosition(startPos);
332
282
  const line = pos.line + 1;
333
283
  const column = pos.character + 1;
334
284
  const disabled = hasDisableComment(fileLines, line);
335
-
336
285
  if (!disableAllowed && disabled) {
337
286
  // When disableAllowed is false, ignore disable comments — still a violation
338
287
  violations.push({ line, column, context, hasDisableComment: false });
339
- } else {
288
+ }
289
+ else {
340
290
  violations.push({ line, column, context, hasDisableComment: disabled });
341
291
  }
342
292
  }
343
293
  }
344
-
345
294
  /**
346
295
  * Get a description of where the destructuring pattern appears.
347
296
  */
348
- function getDestructureContext(node: ts.Node): string {
297
+ function getDestructureContext(node) {
349
298
  const parent = node.parent;
350
299
  if (ts.isParameter(parent)) {
351
300
  return 'function parameter destructuring';
@@ -368,33 +317,24 @@ function getDestructureContext(node: ts.Node): string {
368
317
  ? 'object destructuring'
369
318
  : 'array destructuring';
370
319
  }
371
-
372
320
  /**
373
321
  * MODIFIED_CODE mode: Flag violations on changed lines in diff hunks.
374
322
  */
375
323
  // webpieces-disable max-lines-new-methods -- File iteration with diff parsing and line filtering
376
- function findViolationsForModifiedCode(
377
- workspaceRoot: string,
378
- changedFiles: string[],
379
- base: string,
380
- head: string | undefined,
381
- disableAllowed: boolean
382
- ): DestructureViolation[] {
383
- const violations: DestructureViolation[] = [];
384
-
324
+ function findViolationsForModifiedCode(workspaceRoot, changedFiles, base, head, disableAllowed) {
325
+ const violations = [];
385
326
  for (const file of changedFiles) {
386
327
  const diff = getFileDiff(workspaceRoot, file, base, head);
387
328
  const changedLines = getChangedLineNumbers(diff);
388
-
389
- if (changedLines.size === 0) continue;
390
-
329
+ if (changedLines.size === 0)
330
+ continue;
391
331
  const allViolations = findDestructuringInFile(file, workspaceRoot, disableAllowed);
392
-
393
332
  for (const v of allViolations) {
394
- if (disableAllowed && v.hasDisableComment) continue;
333
+ if (disableAllowed && v.hasDisableComment)
334
+ continue;
395
335
  // LINE-BASED: Only include if the violation is on a changed line
396
- if (!changedLines.has(v.line)) continue;
397
-
336
+ if (!changedLines.has(v.line))
337
+ continue;
398
338
  violations.push({
399
339
  file,
400
340
  line: v.line,
@@ -403,22 +343,18 @@ function findViolationsForModifiedCode(
403
343
  });
404
344
  }
405
345
  }
406
-
407
346
  return violations;
408
347
  }
409
-
410
348
  /**
411
349
  * MODIFIED_FILES mode: Flag ALL violations in files that were modified.
412
350
  */
413
- function findViolationsForModifiedFiles(workspaceRoot: string, changedFiles: string[], disableAllowed: boolean): DestructureViolation[] {
414
- const violations: DestructureViolation[] = [];
415
-
351
+ function findViolationsForModifiedFiles(workspaceRoot, changedFiles, disableAllowed) {
352
+ const violations = [];
416
353
  for (const file of changedFiles) {
417
354
  const allViolations = findDestructuringInFile(file, workspaceRoot, disableAllowed);
418
-
419
355
  for (const v of allViolations) {
420
- if (disableAllowed && v.hasDisableComment) continue;
421
-
356
+ if (disableAllowed && v.hasDisableComment)
357
+ continue;
422
358
  violations.push({
423
359
  file,
424
360
  line: v.line,
@@ -427,51 +363,48 @@ function findViolationsForModifiedFiles(workspaceRoot: string, changedFiles: str
427
363
  });
428
364
  }
429
365
  }
430
-
431
366
  return violations;
432
367
  }
433
-
434
368
  /**
435
369
  * Auto-detect the base branch by finding the merge-base with origin/main.
436
370
  */
437
- function detectBase(workspaceRoot: string): string | null {
371
+ function detectBase(workspaceRoot) {
438
372
  // eslint-disable-next-line @webpieces/no-unmanaged-exceptions
439
373
  try {
440
- const mergeBase = execSync('git merge-base HEAD origin/main', {
374
+ const mergeBase = (0, child_process_1.execSync)('git merge-base HEAD origin/main', {
441
375
  cwd: workspaceRoot,
442
376
  encoding: 'utf-8',
443
377
  stdio: ['pipe', 'pipe', 'pipe'],
444
378
  }).trim();
445
-
446
379
  if (mergeBase) {
447
380
  return mergeBase;
448
381
  }
449
- } catch (err: unknown) {
382
+ }
383
+ catch (err) {
450
384
  //const error = toError(err);
451
385
  // eslint-disable-next-line @webpieces/no-unmanaged-exceptions
452
386
  try {
453
- const mergeBase = execSync('git merge-base HEAD main', {
387
+ const mergeBase = (0, child_process_1.execSync)('git merge-base HEAD main', {
454
388
  cwd: workspaceRoot,
455
389
  encoding: 'utf-8',
456
390
  stdio: ['pipe', 'pipe', 'pipe'],
457
391
  }).trim();
458
-
459
392
  if (mergeBase) {
460
393
  return mergeBase;
461
394
  }
462
- } catch (err2: unknown) {
395
+ }
396
+ catch (err2) {
463
397
  //const error2 = toError(err2);
464
398
  // Ignore
465
399
  }
466
400
  }
467
401
  return null;
468
402
  }
469
-
470
403
  /**
471
404
  * Report violations to console.
472
405
  */
473
406
  // webpieces-disable max-lines-new-methods -- Console output with examples and escape hatch information
474
- function reportViolations(violations: DestructureViolation[], mode: NoDestructureMode, disableAllowed: boolean): void {
407
+ function reportViolations(violations, mode, disableAllowed) {
475
408
  console.error('');
476
409
  console.error('\u274c Destructuring patterns found! Use explicit property access instead.');
477
410
  console.error('');
@@ -484,23 +417,21 @@ function reportViolations(violations: DestructureViolation[], mode: NoDestructur
484
417
  console.error(' BAD: function process({ x, y }: Point) { }');
485
418
  console.error(' GOOD: function process(point: Point) { point.x; point.y; }');
486
419
  console.error('');
487
-
488
420
  for (const v of violations) {
489
421
  console.error(` \u274c ${v.file}:${v.line}:${v.column}`);
490
422
  console.error(` ${v.context}`);
491
423
  }
492
424
  console.error('');
493
-
494
425
  console.error(' Allowed exceptions:');
495
426
  console.error(' - const [a, b] = await Promise.all([...])');
496
427
  console.error(' - for (const [key, value] of Object.entries(obj))');
497
428
  console.error(' - const { extracted, ...rest } = obj (rest operator separation)');
498
429
  console.error('');
499
-
500
430
  if (disableAllowed) {
501
431
  console.error(' Escape hatch (use sparingly):');
502
432
  console.error(' // webpieces-disable no-destructure -- [your reason]');
503
- } else {
433
+ }
434
+ else {
504
435
  console.error(' Escape hatch: DISABLED (disableAllowed: false)');
505
436
  console.error(' Disable comments are ignored. Fix the destructuring directly.');
506
437
  }
@@ -508,12 +439,11 @@ function reportViolations(violations: DestructureViolation[], mode: NoDestructur
508
439
  console.error(` Current mode: ${mode}`);
509
440
  console.error('');
510
441
  }
511
-
512
442
  /**
513
443
  * Resolve mode considering ignoreModifiedUntilEpoch override.
514
444
  * When active, downgrades to OFF. When expired, logs a warning.
515
445
  */
516
- function resolveNoDestructureMode(normalMode: NoDestructureMode, epoch: number | undefined): NoDestructureMode {
446
+ function resolveNoDestructureMode(normalMode, epoch) {
517
447
  if (epoch === undefined || normalMode === 'OFF') {
518
448
  return normalMode;
519
449
  }
@@ -526,63 +456,47 @@ function resolveNoDestructureMode(normalMode: NoDestructureMode, epoch: number |
526
456
  }
527
457
  return normalMode;
528
458
  }
529
-
530
- export default async function runValidator(
531
- options: ValidateNoDestructureOptions,
532
- workspaceRoot: string
533
- ): Promise<ExecutorResult> {
534
- const mode: NoDestructureMode = resolveNoDestructureMode(options.mode ?? 'OFF', options.ignoreModifiedUntilEpoch);
459
+ async function runValidator(options, workspaceRoot) {
460
+ const mode = resolveNoDestructureMode(options.mode ?? 'OFF', options.ignoreModifiedUntilEpoch);
535
461
  const disableAllowed = options.disableAllowed ?? true;
536
-
537
462
  if (mode === 'OFF') {
538
463
  console.log('\n\u23ed\ufe0f Skipping no-destructure validation (mode: OFF)');
539
464
  console.log('');
540
465
  return { success: true };
541
466
  }
542
-
543
467
  console.log('\n\ud83d\udccf Validating No Destructuring\n');
544
468
  console.log(` Mode: ${mode}`);
545
-
546
469
  let base = process.env['NX_BASE'];
547
470
  const head = process.env['NX_HEAD'];
548
-
549
471
  if (!base) {
550
472
  base = detectBase(workspaceRoot) ?? undefined;
551
-
552
473
  if (!base) {
553
474
  console.log('\n\u23ed\ufe0f Skipping no-destructure validation (could not detect base branch)');
554
475
  console.log('');
555
476
  return { success: true };
556
477
  }
557
478
  }
558
-
559
479
  console.log(` Base: ${base}`);
560
480
  console.log(` Head: ${head ?? 'working tree (includes uncommitted changes)'}`);
561
481
  console.log('');
562
-
563
482
  const changedFiles = getChangedTypeScriptFiles(workspaceRoot, base, head);
564
-
565
483
  if (changedFiles.length === 0) {
566
484
  console.log('\u2705 No TypeScript files changed');
567
485
  return { success: true };
568
486
  }
569
-
570
487
  console.log(`\ud83d\udcc2 Checking ${changedFiles.length} changed file(s)...`);
571
-
572
- let violations: DestructureViolation[] = [];
573
-
488
+ let violations = [];
574
489
  if (mode === 'MODIFIED_CODE') {
575
490
  violations = findViolationsForModifiedCode(workspaceRoot, changedFiles, base, head, disableAllowed);
576
- } else if (mode === 'MODIFIED_FILES') {
491
+ }
492
+ else if (mode === 'MODIFIED_FILES') {
577
493
  violations = findViolationsForModifiedFiles(workspaceRoot, changedFiles, disableAllowed);
578
494
  }
579
-
580
495
  if (violations.length === 0) {
581
496
  console.log('\u2705 No destructuring patterns found');
582
497
  return { success: true };
583
498
  }
584
-
585
499
  reportViolations(violations, mode, disableAllowed);
586
-
587
500
  return { success: false };
588
501
  }
502
+ //# sourceMappingURL=validate-no-destructure.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validate-no-destructure.js","sourceRoot":"","sources":["../../../../../packages/tooling/code-rules/src/validate-no-destructure.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Validate No Destructure Executor\n *\n * Validates that destructuring patterns are not used in TypeScript code.\n * Uses LINE-BASED detection (not method-based) for git diff filtering.\n *\n * ============================================================================\n * VIOLATIONS (BAD) - These patterns are flagged:\n * ============================================================================\n *\n * - const { x, y } = obj — object destructuring in variable declarations\n * - const [a, b] = fn() — array destructuring (except Promise.all)\n * - for (const { email } of items) — object destructuring in for-of loops\n * - for (const [a, b] of items) — array destructuring in for-of (except Object.entries)\n * - const { page = 0 } = opts — destructuring with defaults\n * - const { done: streamDone } = obj — destructuring with renaming\n * - function foo({ x, y }: Type) — function parameter destructuring\n *\n * ============================================================================\n * ALLOWED (skip — NOT violations)\n * ============================================================================\n *\n * - const [a, b] = await Promise.all([...]) — Promise.all array destructuring\n * - for (const [key, value] of Object.entries(obj)) — Object.entries in for-of\n * - const { extracted, ...rest } = obj — rest operator separation\n * - Lines with // webpieces-disable no-destructure -- [reason] (only when disableAllowed: true)\n *\n * ============================================================================\n * MODES (LINE-BASED)\n * ============================================================================\n * - OFF: Skip validation entirely\n * - MODIFIED_CODE: Flag destructuring on changed lines (lines in diff hunks)\n * - MODIFIED_FILES: Flag ALL destructuring in files that were modified\n *\n * ============================================================================\n * ESCAPE HATCH\n * ============================================================================\n * Add comment above the violation:\n * // webpieces-disable no-destructure -- [your justification]\n * const { x, y } = obj;\n */\n\nimport { execSync } from 'child_process';\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport * as ts from 'typescript';\n\nexport type NoDestructureMode = 'OFF' | 'MODIFIED_CODE' | 'MODIFIED_FILES';\n\nexport interface ValidateNoDestructureOptions {\n mode?: NoDestructureMode;\n disableAllowed?: boolean;\n ignoreModifiedUntilEpoch?: number;\n}\n\nexport interface ExecutorResult {\n success: boolean;\n}\n\ninterface DestructureViolation {\n file: string;\n line: number;\n column: number;\n context: string;\n}\n\n/**\n * Get changed TypeScript files between base and head (or working tree if head not specified).\n */\n// webpieces-disable max-lines-new-methods -- Git command handling with untracked files requires multiple code paths\nfunction getChangedTypeScriptFiles(workspaceRoot: string, base: string, head?: string): string[] {\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n const diffTarget = head ? `${base} ${head}` : base;\n const output = execSync(`git diff --name-only ${diffTarget} -- '*.ts' '*.tsx'`, {\n cwd: workspaceRoot,\n encoding: 'utf-8',\n });\n const changedFiles = output\n .trim()\n .split('\\n')\n .filter((f) => f && !f.includes('.spec.ts') && !f.includes('.test.ts'));\n\n if (!head) {\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n const untrackedOutput = execSync(`git ls-files --others --exclude-standard '*.ts' '*.tsx'`, {\n cwd: workspaceRoot,\n encoding: 'utf-8',\n });\n const untrackedFiles = untrackedOutput\n .trim()\n .split('\\n')\n .filter((f) => f && !f.includes('.spec.ts') && !f.includes('.test.ts'));\n const allFiles = new Set([...changedFiles, ...untrackedFiles]);\n return Array.from(allFiles);\n } catch (err: unknown) {\n //const error = toError(err);\n return changedFiles;\n }\n }\n\n return changedFiles;\n } catch (err: unknown) {\n //const error = toError(err);\n return [];\n }\n}\n\n/**\n * Get the diff content for a specific file.\n */\nfunction getFileDiff(workspaceRoot: string, file: string, base: string, head?: string): string {\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n const diffTarget = head ? `${base} ${head}` : base;\n const diff = execSync(`git diff ${diffTarget} -- \"${file}\"`, {\n cwd: workspaceRoot,\n encoding: 'utf-8',\n });\n\n if (!diff && !head) {\n const fullPath = path.join(workspaceRoot, file);\n if (fs.existsSync(fullPath)) {\n const isUntracked = execSync(`git ls-files --others --exclude-standard \"${file}\"`, {\n cwd: workspaceRoot,\n encoding: 'utf-8',\n }).trim();\n\n if (isUntracked) {\n const content = fs.readFileSync(fullPath, 'utf-8');\n const lines = content.split('\\n');\n return lines.map((line) => `+${line}`).join('\\n');\n }\n }\n }\n\n return diff;\n } catch (err: unknown) {\n //const error = toError(err);\n return '';\n }\n}\n\n/**\n * Parse diff to extract changed line numbers (additions only - lines starting with +).\n */\nfunction getChangedLineNumbers(diffContent: string): Set<number> {\n const changedLines = new Set<number>();\n const lines = diffContent.split('\\n');\n let currentLine = 0;\n\n for (const line of lines) {\n const hunkMatch = line.match(/^@@ -\\d+(?:,\\d+)? \\+(\\d+)(?:,\\d+)? @@/);\n if (hunkMatch) {\n currentLine = parseInt(hunkMatch[1], 10);\n continue;\n }\n\n if (line.startsWith('+') && !line.startsWith('+++')) {\n changedLines.add(currentLine);\n currentLine++;\n } else if (line.startsWith('-') && !line.startsWith('---')) {\n // Deletions don't increment line number\n } else {\n currentLine++;\n }\n }\n\n return changedLines;\n}\n\n/**\n * Check if a line contains a webpieces-disable comment for no-destructure.\n */\nfunction hasDisableComment(lines: string[], lineNumber: number): boolean {\n const startCheck = Math.max(0, lineNumber - 5);\n for (let i = lineNumber - 2; i >= startCheck; i--) {\n const line = lines[i]?.trim() ?? '';\n if (line.startsWith('function ') || line.startsWith('class ') || line.endsWith('}')) {\n break;\n }\n if (line.includes('webpieces-disable') && line.includes('no-destructure')) {\n return true;\n }\n }\n return false;\n}\n\n/**\n * Check if an ArrayBindingPattern's initializer is `await Promise.all(...)`.\n */\nfunction isPromiseAllDestructure(node: ts.ArrayBindingPattern): boolean {\n const parent = node.parent;\n if (!ts.isVariableDeclaration(parent)) return false;\n const initializer = parent.initializer;\n if (!initializer) return false;\n\n // Handle: const [a, b] = await Promise.all([...])\n if (ts.isAwaitExpression(initializer)) {\n const awaitedExpr = initializer.expression;\n if (ts.isCallExpression(awaitedExpr)) {\n const callExpr = awaitedExpr.expression;\n // Promise.all(...)\n if (ts.isPropertyAccessExpression(callExpr) && callExpr.name.text === 'all') {\n const obj = callExpr.expression;\n if (ts.isIdentifier(obj) && obj.text === 'Promise') {\n return true;\n }\n }\n }\n }\n\n return false;\n}\n\n/**\n * Check if an ArrayBindingPattern in a for-of loop iterates over Object.entries(...).\n */\nfunction isObjectEntriesForOf(node: ts.ArrayBindingPattern): boolean {\n // Walk up: ArrayBindingPattern -> VariableDeclaration -> VariableDeclarationList -> ForOfStatement\n const varDecl = node.parent;\n if (!ts.isVariableDeclaration(varDecl)) return false;\n\n const varDeclList = varDecl.parent;\n if (!ts.isVariableDeclarationList(varDeclList)) return false;\n\n const forOfStmt = varDeclList.parent;\n if (!ts.isForOfStatement(forOfStmt)) return false;\n\n // Check iterable expression ends with .entries()\n const iterable = forOfStmt.expression;\n if (ts.isCallExpression(iterable)) {\n const callExpr = iterable.expression;\n if (ts.isPropertyAccessExpression(callExpr) && callExpr.name.text === 'entries') {\n return true;\n }\n }\n\n return false;\n}\n\n/**\n * Check if an ObjectBindingPattern contains a rest element (...rest).\n */\nfunction hasRestElement(node: ts.ObjectBindingPattern): boolean {\n for (const element of node.elements) {\n if (element.dotDotDotToken) {\n return true;\n }\n }\n return false;\n}\n\ninterface DestructureInfo {\n line: number;\n column: number;\n context: string;\n hasDisableComment: boolean;\n}\n\n/**\n * Find all destructuring patterns in a file using AST.\n */\n// webpieces-disable max-lines-new-methods -- AST traversal with multiple destructuring pattern checks and exception detection\nfunction findDestructuringInFile(filePath: string, workspaceRoot: string, disableAllowed: boolean): DestructureInfo[] {\n const fullPath = path.join(workspaceRoot, filePath);\n if (!fs.existsSync(fullPath)) return [];\n\n const content = fs.readFileSync(fullPath, 'utf-8');\n const fileLines = content.split('\\n');\n const sourceFile = ts.createSourceFile(filePath, content, ts.ScriptTarget.Latest, true);\n\n const violations: DestructureInfo[] = [];\n\n // webpieces-disable max-lines-new-methods -- AST visitor needs to handle object/array binding patterns in declarations, for-of, and parameters\n function visit(node: ts.Node): void {\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n // Check ObjectBindingPattern\n if (ts.isObjectBindingPattern(node)) {\n // Exception: rest operator separation\n if (hasRestElement(node)) {\n ts.forEachChild(node, visit);\n return;\n }\n\n const context = getDestructureContext(node);\n recordViolation(node, context, fileLines, sourceFile, violations, disableAllowed);\n }\n\n // Check ArrayBindingPattern\n if (ts.isArrayBindingPattern(node)) {\n // Exception: Promise.all destructure\n if (isPromiseAllDestructure(node)) {\n ts.forEachChild(node, visit);\n return;\n }\n\n // Exception: Object.entries in for-of\n if (isObjectEntriesForOf(node)) {\n ts.forEachChild(node, visit);\n return;\n }\n\n const context = getDestructureContext(node);\n recordViolation(node, context, fileLines, sourceFile, violations, disableAllowed);\n }\n } catch (err: unknown) {\n //const error = toError(err);\n // Skip nodes that cause errors during analysis\n }\n\n ts.forEachChild(node, visit);\n }\n\n visit(sourceFile);\n return violations;\n}\n\nfunction recordViolation(\n node: ts.Node,\n context: string,\n fileLines: string[],\n sourceFile: ts.SourceFile,\n violations: DestructureInfo[],\n disableAllowed: boolean,\n): void {\n const startPos = node.getStart(sourceFile);\n if (startPos >= 0) {\n const pos = sourceFile.getLineAndCharacterOfPosition(startPos);\n const line = pos.line + 1;\n const column = pos.character + 1;\n const disabled = hasDisableComment(fileLines, line);\n\n if (!disableAllowed && disabled) {\n // When disableAllowed is false, ignore disable comments — still a violation\n violations.push({ line, column, context, hasDisableComment: false });\n } else {\n violations.push({ line, column, context, hasDisableComment: disabled });\n }\n }\n}\n\n/**\n * Get a description of where the destructuring pattern appears.\n */\nfunction getDestructureContext(node: ts.Node): string {\n const parent = node.parent;\n if (ts.isParameter(parent)) {\n return 'function parameter destructuring';\n }\n if (ts.isVariableDeclaration(parent)) {\n const grandparent = parent.parent;\n if (grandparent && ts.isVariableDeclarationList(grandparent)) {\n const forOfParent = grandparent.parent;\n if (forOfParent && ts.isForOfStatement(forOfParent)) {\n return ts.isObjectBindingPattern(node)\n ? 'object destructuring in for-of loop'\n : 'array destructuring in for-of loop';\n }\n }\n return ts.isObjectBindingPattern(node)\n ? 'object destructuring in variable declaration'\n : 'array destructuring in variable declaration';\n }\n return ts.isObjectBindingPattern(node)\n ? 'object destructuring'\n : 'array destructuring';\n}\n\n/**\n * MODIFIED_CODE mode: Flag violations on changed lines in diff hunks.\n */\n// webpieces-disable max-lines-new-methods -- File iteration with diff parsing and line filtering\nfunction findViolationsForModifiedCode(\n workspaceRoot: string,\n changedFiles: string[],\n base: string,\n head: string | undefined,\n disableAllowed: boolean\n): DestructureViolation[] {\n const violations: DestructureViolation[] = [];\n\n for (const file of changedFiles) {\n const diff = getFileDiff(workspaceRoot, file, base, head);\n const changedLines = getChangedLineNumbers(diff);\n\n if (changedLines.size === 0) continue;\n\n const allViolations = findDestructuringInFile(file, workspaceRoot, disableAllowed);\n\n for (const v of allViolations) {\n if (disableAllowed && v.hasDisableComment) continue;\n // LINE-BASED: Only include if the violation is on a changed line\n if (!changedLines.has(v.line)) continue;\n\n violations.push({\n file,\n line: v.line,\n column: v.column,\n context: v.context,\n });\n }\n }\n\n return violations;\n}\n\n/**\n * MODIFIED_FILES mode: Flag ALL violations in files that were modified.\n */\nfunction findViolationsForModifiedFiles(workspaceRoot: string, changedFiles: string[], disableAllowed: boolean): DestructureViolation[] {\n const violations: DestructureViolation[] = [];\n\n for (const file of changedFiles) {\n const allViolations = findDestructuringInFile(file, workspaceRoot, disableAllowed);\n\n for (const v of allViolations) {\n if (disableAllowed && v.hasDisableComment) continue;\n\n violations.push({\n file,\n line: v.line,\n column: v.column,\n context: v.context,\n });\n }\n }\n\n return violations;\n}\n\n/**\n * Auto-detect the base branch by finding the merge-base with origin/main.\n */\nfunction detectBase(workspaceRoot: string): string | null {\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n const mergeBase = execSync('git merge-base HEAD origin/main', {\n cwd: workspaceRoot,\n encoding: 'utf-8',\n stdio: ['pipe', 'pipe', 'pipe'],\n }).trim();\n\n if (mergeBase) {\n return mergeBase;\n }\n } catch (err: unknown) {\n //const error = toError(err);\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n const mergeBase = execSync('git merge-base HEAD main', {\n cwd: workspaceRoot,\n encoding: 'utf-8',\n stdio: ['pipe', 'pipe', 'pipe'],\n }).trim();\n\n if (mergeBase) {\n return mergeBase;\n }\n } catch (err2: unknown) {\n //const error2 = toError(err2);\n // Ignore\n }\n }\n return null;\n}\n\n/**\n * Report violations to console.\n */\n// webpieces-disable max-lines-new-methods -- Console output with examples and escape hatch information\nfunction reportViolations(violations: DestructureViolation[], mode: NoDestructureMode, disableAllowed: boolean): void {\n console.error('');\n console.error('\\u274c Destructuring patterns found! Use explicit property access instead.');\n console.error('');\n console.error('\\ud83d\\udcda Avoiding destructuring improves code traceability:');\n console.error('');\n console.error(' BAD: const { name, age } = user;');\n console.error(' GOOD: const name = user.name;');\n console.error(' const age = user.age;');\n console.error('');\n console.error(' BAD: function process({ x, y }: Point) { }');\n console.error(' GOOD: function process(point: Point) { point.x; point.y; }');\n console.error('');\n\n for (const v of violations) {\n console.error(` \\u274c ${v.file}:${v.line}:${v.column}`);\n console.error(` ${v.context}`);\n }\n console.error('');\n\n console.error(' Allowed exceptions:');\n console.error(' - const [a, b] = await Promise.all([...])');\n console.error(' - for (const [key, value] of Object.entries(obj))');\n console.error(' - const { extracted, ...rest } = obj (rest operator separation)');\n console.error('');\n\n if (disableAllowed) {\n console.error(' Escape hatch (use sparingly):');\n console.error(' // webpieces-disable no-destructure -- [your reason]');\n } else {\n console.error(' Escape hatch: DISABLED (disableAllowed: false)');\n console.error(' Disable comments are ignored. Fix the destructuring directly.');\n }\n console.error('');\n console.error(` Current mode: ${mode}`);\n console.error('');\n}\n\n/**\n * Resolve mode considering ignoreModifiedUntilEpoch override.\n * When active, downgrades to OFF. When expired, logs a warning.\n */\nfunction resolveNoDestructureMode(normalMode: NoDestructureMode, epoch: number | undefined): NoDestructureMode {\n if (epoch === undefined || normalMode === 'OFF') {\n return normalMode;\n }\n const nowSeconds = Date.now() / 1000;\n if (nowSeconds < epoch) {\n const expiresDate = new Date(epoch * 1000).toISOString().split('T')[0];\n console.log(`\\n\\u23ed\\ufe0f Skipping no-destructure validation (ignoreModifiedUntilEpoch active, expires: ${expiresDate})`);\n console.log('');\n return 'OFF';\n }\n return normalMode;\n}\n\nexport default async function runValidator(\n options: ValidateNoDestructureOptions,\n workspaceRoot: string\n): Promise<ExecutorResult> {\n const mode: NoDestructureMode = resolveNoDestructureMode(options.mode ?? 'OFF', options.ignoreModifiedUntilEpoch);\n const disableAllowed = options.disableAllowed ?? true;\n\n if (mode === 'OFF') {\n console.log('\\n\\u23ed\\ufe0f Skipping no-destructure validation (mode: OFF)');\n console.log('');\n return { success: true };\n }\n\n console.log('\\n\\ud83d\\udccf Validating No Destructuring\\n');\n console.log(` Mode: ${mode}`);\n\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 no-destructure validation (could not detect base branch)');\n console.log('');\n return { success: true };\n }\n }\n\n console.log(` Base: ${base}`);\n console.log(` Head: ${head ?? 'working tree (includes uncommitted changes)'}`);\n console.log('');\n\n const changedFiles = getChangedTypeScriptFiles(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 let violations: DestructureViolation[] = [];\n\n if (mode === 'MODIFIED_CODE') {\n violations = findViolationsForModifiedCode(workspaceRoot, changedFiles, base, head, disableAllowed);\n } else if (mode === 'MODIFIED_FILES') {\n violations = findViolationsForModifiedFiles(workspaceRoot, changedFiles, disableAllowed);\n }\n\n if (violations.length === 0) {\n console.log('\\u2705 No destructuring patterns found');\n return { success: true };\n }\n\n reportViolations(violations, mode, disableAllowed);\n\n return { success: false };\n}\n"]}