@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.
- package/package.json +4 -3
- package/src/cli.d.ts +1 -0
- package/src/cli.js +19 -0
- package/src/cli.js.map +1 -0
- package/src/diff-utils.d.ts +24 -0
- package/src/{diff-utils.ts → diff-utils.js} +30 -38
- package/src/diff-utils.js.map +1 -0
- package/src/from-shared-config.d.ts +28 -0
- package/src/from-shared-config.js +119 -0
- package/src/from-shared-config.js.map +1 -0
- package/src/index.js +33 -0
- package/src/index.js.map +1 -0
- package/src/validate-catch-error-pattern.d.ts +47 -0
- package/src/{validate-catch-error-pattern.ts → validate-catch-error-pattern.js} +74 -195
- package/src/validate-catch-error-pattern.js.map +1 -0
- package/src/validate-code.d.ts +98 -0
- package/src/{validate-code.ts → validate-code.js} +65 -259
- package/src/validate-code.js.map +1 -0
- package/src/validate-dtos.d.ts +41 -0
- package/src/{validate-dtos.ts → validate-dtos.js} +88 -215
- package/src/validate-dtos.js.map +1 -0
- package/src/validate-modified-files.d.ts +24 -0
- package/src/{validate-modified-files.ts → validate-modified-files.js} +46 -115
- package/src/validate-modified-files.js.map +1 -0
- package/src/validate-modified-methods.d.ts +30 -0
- package/src/{validate-modified-methods.ts → validate-modified-methods.js} +94 -196
- package/src/validate-modified-methods.js.map +1 -0
- package/src/validate-new-methods.d.ts +27 -0
- package/src/{validate-new-methods.ts → validate-new-methods.js} +63 -133
- package/src/validate-new-methods.js.map +1 -0
- package/src/validate-no-any-unknown.d.ts +41 -0
- package/src/{validate-no-any-unknown.ts → validate-no-any-unknown.js} +69 -146
- package/src/validate-no-any-unknown.js.map +1 -0
- package/src/validate-no-destructure.d.ts +51 -0
- package/src/{validate-no-destructure.ts → validate-no-destructure.js} +80 -166
- package/src/validate-no-destructure.js.map +1 -0
- package/src/validate-no-direct-api-resolver.d.ts +46 -0
- package/src/{validate-no-direct-api-resolver.ts → validate-no-direct-api-resolver.js} +112 -211
- package/src/validate-no-direct-api-resolver.js.map +1 -0
- package/src/validate-no-implicit-any.d.ts +36 -0
- package/src/{validate-no-implicit-any.ts → validate-no-implicit-any.js} +94 -141
- package/src/validate-no-implicit-any.js.map +1 -0
- package/src/validate-no-inline-types.d.ts +90 -0
- package/src/{validate-no-inline-types.ts → validate-no-inline-types.js} +93 -198
- package/src/validate-no-inline-types.js.map +1 -0
- package/src/validate-no-unmanaged-exceptions.d.ts +43 -0
- package/src/{validate-no-unmanaged-exceptions.ts → validate-no-unmanaged-exceptions.js} +71 -140
- package/src/validate-no-unmanaged-exceptions.js.map +1 -0
- package/src/validate-prisma-converters.d.ts +59 -0
- package/src/{validate-prisma-converters.ts → validate-prisma-converters.js} +120 -307
- package/src/validate-prisma-converters.js.map +1 -0
- package/src/validate-return-types.d.ts +28 -0
- package/src/{validate-return-types.ts → validate-return-types.js} +84 -168
- package/src/validate-return-types.js.map +1 -0
- package/LICENSE +0 -373
- package/jest.config.ts +0 -20
- package/project.json +0 -22
- package/src/cli.ts +0 -17
- package/src/from-shared-config.ts +0 -118
- package/tsconfig.json +0 -22
- package/tsconfig.lib.json +0 -10
- package/tsconfig.spec.json +0 -14
- /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
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
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
|
|
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
|
-
}
|
|
79
|
+
}
|
|
80
|
+
catch (err) {
|
|
98
81
|
//const error = toError(err);
|
|
99
82
|
return changedFiles;
|
|
100
83
|
}
|
|
101
84
|
}
|
|
102
|
-
|
|
103
85
|
return changedFiles;
|
|
104
|
-
}
|
|
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
|
|
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
|
-
}
|
|
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
|
|
149
|
-
const changedLines = new Set
|
|
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
|
-
}
|
|
140
|
+
}
|
|
141
|
+
else if (line.startsWith('-') && !line.startsWith('---')) {
|
|
164
142
|
// Deletions don't increment line number
|
|
165
|
-
}
|
|
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
|
|
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
|
|
169
|
+
function isPromiseAllDestructure(node) {
|
|
194
170
|
const parent = node.parent;
|
|
195
|
-
if (!ts.isVariableDeclaration(parent))
|
|
171
|
+
if (!ts.isVariableDeclaration(parent))
|
|
172
|
+
return false;
|
|
196
173
|
const initializer = parent.initializer;
|
|
197
|
-
if (!initializer)
|
|
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
|
|
195
|
+
function isObjectEntriesForOf(node) {
|
|
221
196
|
// Walk up: ArrayBindingPattern -> VariableDeclaration -> VariableDeclarationList -> ForOfStatement
|
|
222
197
|
const varDecl = node.parent;
|
|
223
|
-
if (!ts.isVariableDeclaration(varDecl))
|
|
224
|
-
|
|
198
|
+
if (!ts.isVariableDeclaration(varDecl))
|
|
199
|
+
return false;
|
|
225
200
|
const varDeclList = varDecl.parent;
|
|
226
|
-
if (!ts.isVariableDeclarationList(varDeclList))
|
|
227
|
-
|
|
201
|
+
if (!ts.isVariableDeclarationList(varDeclList))
|
|
202
|
+
return false;
|
|
228
203
|
const forOfStmt = varDeclList.parent;
|
|
229
|
-
if (!ts.isForOfStatement(forOfStmt))
|
|
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
|
|
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
|
|
231
|
+
function findDestructuringInFile(filePath, workspaceRoot, disableAllowed) {
|
|
267
232
|
const fullPath = path.join(workspaceRoot, filePath);
|
|
268
|
-
if (!fs.existsSync(fullPath))
|
|
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
|
|
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
|
-
}
|
|
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
|
-
}
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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)
|
|
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))
|
|
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
|
|
414
|
-
const violations
|
|
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)
|
|
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
|
|
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
|
-
}
|
|
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
|
-
}
|
|
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
|
|
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
|
-
}
|
|
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
|
|
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
|
-
|
|
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
|
-
}
|
|
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"]}
|