@webpieces/code-rules 0.0.1 → 0.2.113
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 Inline Types Executor
|
|
3
4
|
*
|
|
@@ -78,40 +79,22 @@
|
|
|
78
79
|
* - Third-party library APIs that expect inline types
|
|
79
80
|
* - Legacy code being incrementally migrated
|
|
80
81
|
*/
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
export interface ValidateNoInlineTypesOptions {
|
|
90
|
-
mode?: NoInlineTypesMode;
|
|
91
|
-
disableAllowed?: boolean;
|
|
92
|
-
ignoreModifiedUntilEpoch?: number;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
export interface ExecutorResult {
|
|
96
|
-
success: boolean;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
interface InlineTypeViolation {
|
|
100
|
-
file: string;
|
|
101
|
-
line: number;
|
|
102
|
-
column: number;
|
|
103
|
-
context: string;
|
|
104
|
-
}
|
|
105
|
-
|
|
82
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
83
|
+
exports.default = runValidator;
|
|
84
|
+
const tslib_1 = require("tslib");
|
|
85
|
+
const child_process_1 = require("child_process");
|
|
86
|
+
const fs = tslib_1.__importStar(require("fs"));
|
|
87
|
+
const path = tslib_1.__importStar(require("path"));
|
|
88
|
+
const ts = tslib_1.__importStar(require("typescript"));
|
|
106
89
|
/**
|
|
107
90
|
* Get changed TypeScript files between base and head (or working tree if head not specified).
|
|
108
91
|
*/
|
|
109
92
|
// webpieces-disable max-lines-new-methods -- Git command handling with untracked files requires multiple code paths
|
|
110
|
-
function getChangedTypeScriptFiles(workspaceRoot
|
|
93
|
+
function getChangedTypeScriptFiles(workspaceRoot, base, head) {
|
|
111
94
|
// eslint-disable-next-line @webpieces/no-unmanaged-exceptions
|
|
112
95
|
try {
|
|
113
96
|
const diffTarget = head ? `${base} ${head}` : base;
|
|
114
|
-
const output = execSync(`git diff --name-only ${diffTarget} -- '*.ts' '*.tsx'`, {
|
|
97
|
+
const output = (0, child_process_1.execSync)(`git diff --name-only ${diffTarget} -- '*.ts' '*.tsx'`, {
|
|
115
98
|
cwd: workspaceRoot,
|
|
116
99
|
encoding: 'utf-8',
|
|
117
100
|
});
|
|
@@ -119,11 +102,10 @@ function getChangedTypeScriptFiles(workspaceRoot: string, base: string, head?: s
|
|
|
119
102
|
.trim()
|
|
120
103
|
.split('\n')
|
|
121
104
|
.filter((f) => f && !f.includes('.spec.ts') && !f.includes('.test.ts'));
|
|
122
|
-
|
|
123
105
|
if (!head) {
|
|
124
106
|
// eslint-disable-next-line @webpieces/no-unmanaged-exceptions
|
|
125
107
|
try {
|
|
126
|
-
const untrackedOutput = execSync(`git ls-files --others --exclude-standard '*.ts' '*.tsx'`, {
|
|
108
|
+
const untrackedOutput = (0, child_process_1.execSync)(`git ls-files --others --exclude-standard '*.ts' '*.tsx'`, {
|
|
127
109
|
cwd: workspaceRoot,
|
|
128
110
|
encoding: 'utf-8',
|
|
129
111
|
});
|
|
@@ -133,39 +115,37 @@ function getChangedTypeScriptFiles(workspaceRoot: string, base: string, head?: s
|
|
|
133
115
|
.filter((f) => f && !f.includes('.spec.ts') && !f.includes('.test.ts'));
|
|
134
116
|
const allFiles = new Set([...changedFiles, ...untrackedFiles]);
|
|
135
117
|
return Array.from(allFiles);
|
|
136
|
-
}
|
|
118
|
+
}
|
|
119
|
+
catch (err) {
|
|
137
120
|
//const error = toError(err);
|
|
138
121
|
return changedFiles;
|
|
139
122
|
}
|
|
140
123
|
}
|
|
141
|
-
|
|
142
124
|
return changedFiles;
|
|
143
|
-
}
|
|
125
|
+
}
|
|
126
|
+
catch (err) {
|
|
144
127
|
//const error = toError(err);
|
|
145
128
|
return [];
|
|
146
129
|
}
|
|
147
130
|
}
|
|
148
|
-
|
|
149
131
|
/**
|
|
150
132
|
* Get the diff content for a specific file.
|
|
151
133
|
*/
|
|
152
|
-
function getFileDiff(workspaceRoot
|
|
134
|
+
function getFileDiff(workspaceRoot, file, base, head) {
|
|
153
135
|
// eslint-disable-next-line @webpieces/no-unmanaged-exceptions
|
|
154
136
|
try {
|
|
155
137
|
const diffTarget = head ? `${base} ${head}` : base;
|
|
156
|
-
const diff = execSync(`git diff ${diffTarget} -- "${file}"`, {
|
|
138
|
+
const diff = (0, child_process_1.execSync)(`git diff ${diffTarget} -- "${file}"`, {
|
|
157
139
|
cwd: workspaceRoot,
|
|
158
140
|
encoding: 'utf-8',
|
|
159
141
|
});
|
|
160
|
-
|
|
161
142
|
if (!diff && !head) {
|
|
162
143
|
const fullPath = path.join(workspaceRoot, file);
|
|
163
144
|
if (fs.existsSync(fullPath)) {
|
|
164
|
-
const isUntracked = execSync(`git ls-files --others --exclude-standard "${file}"`, {
|
|
145
|
+
const isUntracked = (0, child_process_1.execSync)(`git ls-files --others --exclude-standard "${file}"`, {
|
|
165
146
|
cwd: workspaceRoot,
|
|
166
147
|
encoding: 'utf-8',
|
|
167
148
|
}).trim();
|
|
168
|
-
|
|
169
149
|
if (isUntracked) {
|
|
170
150
|
const content = fs.readFileSync(fullPath, 'utf-8');
|
|
171
151
|
const lines = content.split('\n');
|
|
@@ -173,46 +153,43 @@ function getFileDiff(workspaceRoot: string, file: string, base: string, head?: s
|
|
|
173
153
|
}
|
|
174
154
|
}
|
|
175
155
|
}
|
|
176
|
-
|
|
177
156
|
return diff;
|
|
178
|
-
}
|
|
157
|
+
}
|
|
158
|
+
catch (err) {
|
|
179
159
|
//const error = toError(err);
|
|
180
160
|
return '';
|
|
181
161
|
}
|
|
182
162
|
}
|
|
183
|
-
|
|
184
163
|
/**
|
|
185
164
|
* Parse diff to extract changed line numbers (both additions and modifications).
|
|
186
165
|
*/
|
|
187
|
-
function getChangedLineNumbers(diffContent
|
|
188
|
-
const changedLines = new Set
|
|
166
|
+
function getChangedLineNumbers(diffContent) {
|
|
167
|
+
const changedLines = new Set();
|
|
189
168
|
const lines = diffContent.split('\n');
|
|
190
169
|
let currentLine = 0;
|
|
191
|
-
|
|
192
170
|
for (const line of lines) {
|
|
193
171
|
const hunkMatch = line.match(/^@@ -\d+(?:,\d+)? \+(\d+)(?:,\d+)? @@/);
|
|
194
172
|
if (hunkMatch) {
|
|
195
173
|
currentLine = parseInt(hunkMatch[1], 10);
|
|
196
174
|
continue;
|
|
197
175
|
}
|
|
198
|
-
|
|
199
176
|
if (line.startsWith('+') && !line.startsWith('+++')) {
|
|
200
177
|
changedLines.add(currentLine);
|
|
201
178
|
currentLine++;
|
|
202
|
-
}
|
|
179
|
+
}
|
|
180
|
+
else if (line.startsWith('-') && !line.startsWith('---')) {
|
|
203
181
|
// Deletions don't increment line number
|
|
204
|
-
}
|
|
182
|
+
}
|
|
183
|
+
else {
|
|
205
184
|
currentLine++;
|
|
206
185
|
}
|
|
207
186
|
}
|
|
208
|
-
|
|
209
187
|
return changedLines;
|
|
210
188
|
}
|
|
211
|
-
|
|
212
189
|
/**
|
|
213
190
|
* Check if a line contains a webpieces-disable comment for no-inline-types.
|
|
214
191
|
*/
|
|
215
|
-
function hasDisableComment(lines
|
|
192
|
+
function hasDisableComment(lines, lineNumber) {
|
|
216
193
|
const startCheck = Math.max(0, lineNumber - 5);
|
|
217
194
|
for (let i = lineNumber - 2; i >= startCheck; i--) {
|
|
218
195
|
const line = lines[i]?.trim() ?? '';
|
|
@@ -225,7 +202,6 @@ function hasDisableComment(lines: string[], lineNumber: number): boolean {
|
|
|
225
202
|
}
|
|
226
203
|
return false;
|
|
227
204
|
}
|
|
228
|
-
|
|
229
205
|
/**
|
|
230
206
|
* Check if a TypeLiteral or TupleType node is in an allowed context.
|
|
231
207
|
* Only allowed if the DIRECT parent is a TypeAliasDeclaration.
|
|
@@ -244,17 +220,16 @@ function hasDisableComment(lines: string[], lineNumber: number): boolean {
|
|
|
244
220
|
* // webpieces-disable no-inline-types -- Prisma API requires inline type argument
|
|
245
221
|
* type T = Prisma.GetPayload<{ include: {...} }>;
|
|
246
222
|
*/
|
|
247
|
-
function isInAllowedContext(node
|
|
223
|
+
function isInAllowedContext(node) {
|
|
248
224
|
const parent = node.parent;
|
|
249
|
-
if (!parent)
|
|
250
|
-
|
|
225
|
+
if (!parent)
|
|
226
|
+
return false;
|
|
251
227
|
// Only allowed if it's the DIRECT body of a type alias
|
|
252
228
|
if (ts.isTypeAliasDeclaration(parent)) {
|
|
253
229
|
return true;
|
|
254
230
|
}
|
|
255
231
|
return false;
|
|
256
232
|
}
|
|
257
|
-
|
|
258
233
|
/**
|
|
259
234
|
* Get a description of the context where the inline type or tuple appears.
|
|
260
235
|
*
|
|
@@ -264,13 +239,12 @@ function isInAllowedContext(node: ts.TypeLiteralNode | ts.TupleTypeNode): boolea
|
|
|
264
239
|
* - "inline type in generic argument"
|
|
265
240
|
*/
|
|
266
241
|
// webpieces-disable max-lines-new-methods -- Context detection requires checking many AST node types
|
|
267
|
-
function getViolationContext(node
|
|
242
|
+
function getViolationContext(node, sourceFile) {
|
|
268
243
|
// eslint-disable-next-line @webpieces/no-unmanaged-exceptions
|
|
269
244
|
try {
|
|
270
245
|
const isTuple = ts.isTupleTypeNode(node);
|
|
271
246
|
const prefix = isTuple ? 'tuple' : 'inline';
|
|
272
|
-
|
|
273
|
-
let current: ts.Node = node;
|
|
247
|
+
let current = node;
|
|
274
248
|
while (current.parent) {
|
|
275
249
|
const parent = current.parent;
|
|
276
250
|
if (ts.isParameter(parent)) {
|
|
@@ -289,7 +263,7 @@ function getViolationContext(node: ts.TypeLiteralNode | ts.TupleTypeNode, source
|
|
|
289
263
|
return `${prefix} property type`;
|
|
290
264
|
}
|
|
291
265
|
// Check if it's nested inside another type literal
|
|
292
|
-
let ancestor
|
|
266
|
+
let ancestor = parent.parent;
|
|
293
267
|
while (ancestor) {
|
|
294
268
|
if (ts.isTypeLiteralNode(ancestor)) {
|
|
295
269
|
return `nested ${prefix} type`;
|
|
@@ -320,51 +294,44 @@ function getViolationContext(node: ts.TypeLiteralNode | ts.TupleTypeNode, source
|
|
|
320
294
|
current = parent;
|
|
321
295
|
}
|
|
322
296
|
return isTuple ? 'tuple type' : 'inline type literal';
|
|
323
|
-
}
|
|
297
|
+
}
|
|
298
|
+
catch (err) {
|
|
324
299
|
//const error = toError(err);
|
|
325
300
|
// Defensive: return generic context if AST traversal fails
|
|
326
301
|
return ts.isTupleTypeNode(node) ? 'tuple type' : 'inline type literal';
|
|
327
302
|
}
|
|
328
303
|
}
|
|
329
|
-
|
|
330
|
-
interface MethodInfo {
|
|
331
|
-
name: string;
|
|
332
|
-
startLine: number;
|
|
333
|
-
endLine: number;
|
|
334
|
-
}
|
|
335
|
-
|
|
336
304
|
/**
|
|
337
305
|
* Find all methods/functions in a file with their line ranges.
|
|
338
306
|
*/
|
|
339
307
|
// webpieces-disable max-lines-new-methods -- AST traversal requires inline visitor function
|
|
340
|
-
function findMethodsInFile(filePath
|
|
308
|
+
function findMethodsInFile(filePath, workspaceRoot) {
|
|
341
309
|
const fullPath = path.join(workspaceRoot, filePath);
|
|
342
|
-
if (!fs.existsSync(fullPath))
|
|
343
|
-
|
|
310
|
+
if (!fs.existsSync(fullPath))
|
|
311
|
+
return [];
|
|
344
312
|
const content = fs.readFileSync(fullPath, 'utf-8');
|
|
345
313
|
const sourceFile = ts.createSourceFile(filePath, content, ts.ScriptTarget.Latest, true);
|
|
346
|
-
|
|
347
|
-
const methods: MethodInfo[] = [];
|
|
348
|
-
|
|
314
|
+
const methods = [];
|
|
349
315
|
// webpieces-disable max-lines-new-methods -- AST visitor pattern requires handling multiple node types
|
|
350
|
-
function visit(node
|
|
351
|
-
let methodName
|
|
352
|
-
let startLine
|
|
353
|
-
let endLine
|
|
354
|
-
|
|
316
|
+
function visit(node) {
|
|
317
|
+
let methodName;
|
|
318
|
+
let startLine;
|
|
319
|
+
let endLine;
|
|
355
320
|
if (ts.isMethodDeclaration(node) && node.name) {
|
|
356
321
|
methodName = node.name.getText(sourceFile);
|
|
357
322
|
const start = sourceFile.getLineAndCharacterOfPosition(node.getStart());
|
|
358
323
|
const end = sourceFile.getLineAndCharacterOfPosition(node.getEnd());
|
|
359
324
|
startLine = start.line + 1;
|
|
360
325
|
endLine = end.line + 1;
|
|
361
|
-
}
|
|
326
|
+
}
|
|
327
|
+
else if (ts.isFunctionDeclaration(node) && node.name) {
|
|
362
328
|
methodName = node.name.getText(sourceFile);
|
|
363
329
|
const start = sourceFile.getLineAndCharacterOfPosition(node.getStart());
|
|
364
330
|
const end = sourceFile.getLineAndCharacterOfPosition(node.getEnd());
|
|
365
331
|
startLine = start.line + 1;
|
|
366
332
|
endLine = end.line + 1;
|
|
367
|
-
}
|
|
333
|
+
}
|
|
334
|
+
else if (ts.isArrowFunction(node)) {
|
|
368
335
|
if (ts.isVariableDeclaration(node.parent) && ts.isIdentifier(node.parent.name)) {
|
|
369
336
|
methodName = node.parent.name.getText(sourceFile);
|
|
370
337
|
const start = sourceFile.getLineAndCharacterOfPosition(node.getStart());
|
|
@@ -373,27 +340,18 @@ function findMethodsInFile(filePath: string, workspaceRoot: string): MethodInfo[
|
|
|
373
340
|
endLine = end.line + 1;
|
|
374
341
|
}
|
|
375
342
|
}
|
|
376
|
-
|
|
377
343
|
if (methodName && startLine !== undefined && endLine !== undefined) {
|
|
378
344
|
methods.push({ name: methodName, startLine, endLine });
|
|
379
345
|
}
|
|
380
|
-
|
|
381
346
|
ts.forEachChild(node, visit);
|
|
382
347
|
}
|
|
383
|
-
|
|
384
348
|
visit(sourceFile);
|
|
385
349
|
return methods;
|
|
386
350
|
}
|
|
387
|
-
|
|
388
351
|
/**
|
|
389
352
|
* Check if a line is within any method's range and if that method has changes.
|
|
390
353
|
*/
|
|
391
|
-
function isLineInChangedMethod(
|
|
392
|
-
line: number,
|
|
393
|
-
methods: MethodInfo[],
|
|
394
|
-
changedLines: Set<number>,
|
|
395
|
-
newMethodNames: Set<string>
|
|
396
|
-
): boolean {
|
|
354
|
+
function isLineInChangedMethod(line, methods, changedLines, newMethodNames) {
|
|
397
355
|
for (const method of methods) {
|
|
398
356
|
if (line >= method.startLine && line <= method.endLine) {
|
|
399
357
|
// Check if this method is new or has changes
|
|
@@ -410,11 +368,10 @@ function isLineInChangedMethod(
|
|
|
410
368
|
}
|
|
411
369
|
return false;
|
|
412
370
|
}
|
|
413
|
-
|
|
414
371
|
/**
|
|
415
372
|
* Check if a line is within a new method.
|
|
416
373
|
*/
|
|
417
|
-
function isLineInNewMethod(line
|
|
374
|
+
function isLineInNewMethod(line, methods, newMethodNames) {
|
|
418
375
|
for (const method of methods) {
|
|
419
376
|
if (line >= method.startLine && line <= method.endLine && newMethodNames.has(method.name)) {
|
|
420
377
|
return true;
|
|
@@ -422,21 +379,18 @@ function isLineInNewMethod(line: number, methods: MethodInfo[], newMethodNames:
|
|
|
422
379
|
}
|
|
423
380
|
return false;
|
|
424
381
|
}
|
|
425
|
-
|
|
426
382
|
/**
|
|
427
383
|
* Parse diff to find newly added method signatures.
|
|
428
384
|
*/
|
|
429
|
-
function findNewMethodSignaturesInDiff(diffContent
|
|
430
|
-
const newMethods = new Set
|
|
385
|
+
function findNewMethodSignaturesInDiff(diffContent) {
|
|
386
|
+
const newMethods = new Set();
|
|
431
387
|
const lines = diffContent.split('\n');
|
|
432
|
-
|
|
433
388
|
const patterns = [
|
|
434
389
|
/^\+\s*(?:export\s+)?(?:async\s+)?function\s+(\w+)\s*\(/,
|
|
435
390
|
/^\+\s*(?:export\s+)?(?:const|let)\s+(\w+)\s*=\s*(?:async\s*)?\(/,
|
|
436
391
|
/^\+\s*(?:export\s+)?(?:const|let)\s+(\w+)\s*=\s*(?:async\s+)?function/,
|
|
437
392
|
/^\+\s*(?:(?:public|private|protected)\s+)?(?:static\s+)?(?:async\s+)?(\w+)\s*\(/,
|
|
438
393
|
];
|
|
439
|
-
|
|
440
394
|
for (const line of lines) {
|
|
441
395
|
if (line.startsWith('+') && !line.startsWith('+++')) {
|
|
442
396
|
for (const pattern of patterns) {
|
|
@@ -451,17 +405,8 @@ function findNewMethodSignaturesInDiff(diffContent: string): Set<string> {
|
|
|
451
405
|
}
|
|
452
406
|
}
|
|
453
407
|
}
|
|
454
|
-
|
|
455
408
|
return newMethods;
|
|
456
409
|
}
|
|
457
|
-
|
|
458
|
-
interface InlineTypeInfo {
|
|
459
|
-
line: number;
|
|
460
|
-
column: number;
|
|
461
|
-
context: string;
|
|
462
|
-
hasDisableComment: boolean;
|
|
463
|
-
}
|
|
464
|
-
|
|
465
410
|
/**
|
|
466
411
|
* Find all inline type literals AND tuple types in a file.
|
|
467
412
|
*
|
|
@@ -472,17 +417,15 @@ interface InlineTypeInfo {
|
|
|
472
417
|
* Both are flagged unless they are the DIRECT body of a type alias.
|
|
473
418
|
*/
|
|
474
419
|
// webpieces-disable max-lines-new-methods -- AST traversal with visitor pattern
|
|
475
|
-
function findInlineTypesInFile(filePath
|
|
420
|
+
function findInlineTypesInFile(filePath, workspaceRoot) {
|
|
476
421
|
const fullPath = path.join(workspaceRoot, filePath);
|
|
477
|
-
if (!fs.existsSync(fullPath))
|
|
478
|
-
|
|
422
|
+
if (!fs.existsSync(fullPath))
|
|
423
|
+
return [];
|
|
479
424
|
const content = fs.readFileSync(fullPath, 'utf-8');
|
|
480
425
|
const fileLines = content.split('\n');
|
|
481
426
|
const sourceFile = ts.createSourceFile(filePath, content, ts.ScriptTarget.Latest, true);
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
function visit(node: ts.Node): void {
|
|
427
|
+
const inlineTypes = [];
|
|
428
|
+
function visit(node) {
|
|
486
429
|
// eslint-disable-next-line @webpieces/no-unmanaged-exceptions
|
|
487
430
|
try {
|
|
488
431
|
// Check for inline type literals: { x: number }
|
|
@@ -495,7 +438,6 @@ function findInlineTypesInFile(filePath: string, workspaceRoot: string): InlineT
|
|
|
495
438
|
const column = pos.character + 1;
|
|
496
439
|
const context = getViolationContext(node, sourceFile);
|
|
497
440
|
const disabled = hasDisableComment(fileLines, line);
|
|
498
|
-
|
|
499
441
|
inlineTypes.push({
|
|
500
442
|
line,
|
|
501
443
|
column,
|
|
@@ -505,7 +447,6 @@ function findInlineTypesInFile(filePath: string, workspaceRoot: string): InlineT
|
|
|
505
447
|
}
|
|
506
448
|
}
|
|
507
449
|
}
|
|
508
|
-
|
|
509
450
|
// Check for tuple types: [A, B]
|
|
510
451
|
if (ts.isTupleTypeNode(node)) {
|
|
511
452
|
if (!isInAllowedContext(node)) {
|
|
@@ -516,7 +457,6 @@ function findInlineTypesInFile(filePath: string, workspaceRoot: string): InlineT
|
|
|
516
457
|
const column = pos.character + 1;
|
|
517
458
|
const context = getViolationContext(node, sourceFile);
|
|
518
459
|
const disabled = hasDisableComment(fileLines, line);
|
|
519
|
-
|
|
520
460
|
inlineTypes.push({
|
|
521
461
|
line,
|
|
522
462
|
column,
|
|
@@ -526,44 +466,34 @@ function findInlineTypesInFile(filePath: string, workspaceRoot: string): InlineT
|
|
|
526
466
|
}
|
|
527
467
|
}
|
|
528
468
|
}
|
|
529
|
-
}
|
|
469
|
+
}
|
|
470
|
+
catch (err) {
|
|
530
471
|
//const error = toError(err);
|
|
531
472
|
// Skip nodes that cause errors during analysis
|
|
532
473
|
}
|
|
533
|
-
|
|
534
474
|
ts.forEachChild(node, visit);
|
|
535
475
|
}
|
|
536
|
-
|
|
537
476
|
visit(sourceFile);
|
|
538
477
|
return inlineTypes;
|
|
539
478
|
}
|
|
540
|
-
|
|
541
479
|
/**
|
|
542
480
|
* Find violations in new methods only (NEW_METHODS mode).
|
|
543
481
|
*/
|
|
544
482
|
// webpieces-disable max-lines-new-methods -- File iteration with diff parsing and method matching
|
|
545
|
-
function findViolationsForNewMethods(
|
|
546
|
-
|
|
547
|
-
changedFiles: string[],
|
|
548
|
-
base: string,
|
|
549
|
-
head: string | undefined,
|
|
550
|
-
disableAllowed: boolean
|
|
551
|
-
): InlineTypeViolation[] {
|
|
552
|
-
const violations: InlineTypeViolation[] = [];
|
|
553
|
-
|
|
483
|
+
function findViolationsForNewMethods(workspaceRoot, changedFiles, base, head, disableAllowed) {
|
|
484
|
+
const violations = [];
|
|
554
485
|
for (const file of changedFiles) {
|
|
555
486
|
const diff = getFileDiff(workspaceRoot, file, base, head);
|
|
556
487
|
const newMethodNames = findNewMethodSignaturesInDiff(diff);
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
488
|
+
if (newMethodNames.size === 0)
|
|
489
|
+
continue;
|
|
560
490
|
const methods = findMethodsInFile(file, workspaceRoot);
|
|
561
491
|
const inlineTypes = findInlineTypesInFile(file, workspaceRoot);
|
|
562
|
-
|
|
563
492
|
for (const inlineType of inlineTypes) {
|
|
564
|
-
if (disableAllowed && inlineType.hasDisableComment)
|
|
565
|
-
|
|
566
|
-
|
|
493
|
+
if (disableAllowed && inlineType.hasDisableComment)
|
|
494
|
+
continue;
|
|
495
|
+
if (!isLineInNewMethod(inlineType.line, methods, newMethodNames))
|
|
496
|
+
continue;
|
|
567
497
|
violations.push({
|
|
568
498
|
file,
|
|
569
499
|
line: inlineType.line,
|
|
@@ -572,35 +502,25 @@ function findViolationsForNewMethods(
|
|
|
572
502
|
});
|
|
573
503
|
}
|
|
574
504
|
}
|
|
575
|
-
|
|
576
505
|
return violations;
|
|
577
506
|
}
|
|
578
|
-
|
|
579
507
|
/**
|
|
580
508
|
* Find violations in new and modified methods (NEW_AND_MODIFIED_METHODS mode).
|
|
581
509
|
*/
|
|
582
510
|
// webpieces-disable max-lines-new-methods -- Combines new method detection with change detection
|
|
583
|
-
function findViolationsForModifiedAndNewMethods(
|
|
584
|
-
|
|
585
|
-
changedFiles: string[],
|
|
586
|
-
base: string,
|
|
587
|
-
head: string | undefined,
|
|
588
|
-
disableAllowed: boolean
|
|
589
|
-
): InlineTypeViolation[] {
|
|
590
|
-
const violations: InlineTypeViolation[] = [];
|
|
591
|
-
|
|
511
|
+
function findViolationsForModifiedAndNewMethods(workspaceRoot, changedFiles, base, head, disableAllowed) {
|
|
512
|
+
const violations = [];
|
|
592
513
|
for (const file of changedFiles) {
|
|
593
514
|
const diff = getFileDiff(workspaceRoot, file, base, head);
|
|
594
515
|
const newMethodNames = findNewMethodSignaturesInDiff(diff);
|
|
595
516
|
const changedLines = getChangedLineNumbers(diff);
|
|
596
|
-
|
|
597
517
|
const methods = findMethodsInFile(file, workspaceRoot);
|
|
598
518
|
const inlineTypes = findInlineTypesInFile(file, workspaceRoot);
|
|
599
|
-
|
|
600
519
|
for (const inlineType of inlineTypes) {
|
|
601
|
-
if (disableAllowed && inlineType.hasDisableComment)
|
|
602
|
-
|
|
603
|
-
|
|
520
|
+
if (disableAllowed && inlineType.hasDisableComment)
|
|
521
|
+
continue;
|
|
522
|
+
if (!isLineInChangedMethod(inlineType.line, methods, changedLines, newMethodNames))
|
|
523
|
+
continue;
|
|
604
524
|
violations.push({
|
|
605
525
|
file,
|
|
606
526
|
line: inlineType.line,
|
|
@@ -609,22 +529,18 @@ function findViolationsForModifiedAndNewMethods(
|
|
|
609
529
|
});
|
|
610
530
|
}
|
|
611
531
|
}
|
|
612
|
-
|
|
613
532
|
return violations;
|
|
614
533
|
}
|
|
615
|
-
|
|
616
534
|
/**
|
|
617
535
|
* Find all violations in modified files (MODIFIED_FILES mode).
|
|
618
536
|
*/
|
|
619
|
-
function findViolationsForModifiedFiles(workspaceRoot
|
|
620
|
-
const violations
|
|
621
|
-
|
|
537
|
+
function findViolationsForModifiedFiles(workspaceRoot, changedFiles, disableAllowed) {
|
|
538
|
+
const violations = [];
|
|
622
539
|
for (const file of changedFiles) {
|
|
623
540
|
const inlineTypes = findInlineTypesInFile(file, workspaceRoot);
|
|
624
|
-
|
|
625
541
|
for (const inlineType of inlineTypes) {
|
|
626
|
-
if (disableAllowed && inlineType.hasDisableComment)
|
|
627
|
-
|
|
542
|
+
if (disableAllowed && inlineType.hasDisableComment)
|
|
543
|
+
continue;
|
|
628
544
|
violations.push({
|
|
629
545
|
file,
|
|
630
546
|
line: inlineType.line,
|
|
@@ -633,50 +549,47 @@ function findViolationsForModifiedFiles(workspaceRoot: string, changedFiles: str
|
|
|
633
549
|
});
|
|
634
550
|
}
|
|
635
551
|
}
|
|
636
|
-
|
|
637
552
|
return violations;
|
|
638
553
|
}
|
|
639
|
-
|
|
640
554
|
/**
|
|
641
555
|
* Auto-detect the base branch by finding the merge-base with origin/main.
|
|
642
556
|
*/
|
|
643
|
-
function detectBase(workspaceRoot
|
|
557
|
+
function detectBase(workspaceRoot) {
|
|
644
558
|
// eslint-disable-next-line @webpieces/no-unmanaged-exceptions
|
|
645
559
|
try {
|
|
646
|
-
const mergeBase = execSync('git merge-base HEAD origin/main', {
|
|
560
|
+
const mergeBase = (0, child_process_1.execSync)('git merge-base HEAD origin/main', {
|
|
647
561
|
cwd: workspaceRoot,
|
|
648
562
|
encoding: 'utf-8',
|
|
649
563
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
650
564
|
}).trim();
|
|
651
|
-
|
|
652
565
|
if (mergeBase) {
|
|
653
566
|
return mergeBase;
|
|
654
567
|
}
|
|
655
|
-
}
|
|
568
|
+
}
|
|
569
|
+
catch (err) {
|
|
656
570
|
//const error = toError(err);
|
|
657
571
|
// eslint-disable-next-line @webpieces/no-unmanaged-exceptions
|
|
658
572
|
try {
|
|
659
|
-
const mergeBase = execSync('git merge-base HEAD main', {
|
|
573
|
+
const mergeBase = (0, child_process_1.execSync)('git merge-base HEAD main', {
|
|
660
574
|
cwd: workspaceRoot,
|
|
661
575
|
encoding: 'utf-8',
|
|
662
576
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
663
577
|
}).trim();
|
|
664
|
-
|
|
665
578
|
if (mergeBase) {
|
|
666
579
|
return mergeBase;
|
|
667
580
|
}
|
|
668
|
-
}
|
|
581
|
+
}
|
|
582
|
+
catch (err2) {
|
|
669
583
|
//const error2 = toError(err2);
|
|
670
584
|
// Ignore
|
|
671
585
|
}
|
|
672
586
|
}
|
|
673
587
|
return null;
|
|
674
588
|
}
|
|
675
|
-
|
|
676
589
|
/**
|
|
677
590
|
* Report violations to console.
|
|
678
591
|
*/
|
|
679
|
-
function reportViolations(violations
|
|
592
|
+
function reportViolations(violations, mode) {
|
|
680
593
|
console.error('');
|
|
681
594
|
console.error('❌ Inline type literals found! Use named types instead.');
|
|
682
595
|
console.error('');
|
|
@@ -690,13 +603,11 @@ function reportViolations(violations: InlineTypeViolation[], mode: NoInlineTypes
|
|
|
690
603
|
console.error(' GOOD: type MyData = { x: number };');
|
|
691
604
|
console.error(' type Nullable = MyData | null;');
|
|
692
605
|
console.error('');
|
|
693
|
-
|
|
694
606
|
for (const v of violations) {
|
|
695
607
|
console.error(` ❌ ${v.file}:${v.line}:${v.column}`);
|
|
696
608
|
console.error(` ${v.context}`);
|
|
697
609
|
}
|
|
698
610
|
console.error('');
|
|
699
|
-
|
|
700
611
|
console.error(' To fix: Extract inline types to named type aliases or interfaces');
|
|
701
612
|
console.error('');
|
|
702
613
|
console.error(' Escape hatch (use sparingly):');
|
|
@@ -705,12 +616,11 @@ function reportViolations(violations: InlineTypeViolation[], mode: NoInlineTypes
|
|
|
705
616
|
console.error(` Current mode: ${mode}`);
|
|
706
617
|
console.error('');
|
|
707
618
|
}
|
|
708
|
-
|
|
709
619
|
/**
|
|
710
620
|
* Resolve mode considering ignoreModifiedUntilEpoch override.
|
|
711
621
|
* When active, downgrades to OFF. When expired, logs a warning.
|
|
712
622
|
*/
|
|
713
|
-
function resolveMode(normalMode
|
|
623
|
+
function resolveMode(normalMode, epoch) {
|
|
714
624
|
if (epoch === undefined || normalMode === 'OFF') {
|
|
715
625
|
return normalMode;
|
|
716
626
|
}
|
|
@@ -723,65 +633,50 @@ function resolveMode(normalMode: NoInlineTypesMode, epoch: number | undefined):
|
|
|
723
633
|
}
|
|
724
634
|
return normalMode;
|
|
725
635
|
}
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
options: ValidateNoInlineTypesOptions,
|
|
729
|
-
workspaceRoot: string
|
|
730
|
-
): Promise<ExecutorResult> {
|
|
731
|
-
const mode: NoInlineTypesMode = resolveMode(options.mode ?? 'OFF', options.ignoreModifiedUntilEpoch);
|
|
636
|
+
async function runValidator(options, workspaceRoot) {
|
|
637
|
+
const mode = resolveMode(options.mode ?? 'OFF', options.ignoreModifiedUntilEpoch);
|
|
732
638
|
const disableAllowed = options.disableAllowed ?? true;
|
|
733
|
-
|
|
734
639
|
if (mode === 'OFF') {
|
|
735
640
|
console.log('\n⏭️ Skipping no-inline-types validation (mode: OFF)');
|
|
736
641
|
console.log('');
|
|
737
642
|
return { success: true };
|
|
738
643
|
}
|
|
739
|
-
|
|
740
644
|
console.log('\n📏 Validating No Inline Types\n');
|
|
741
645
|
console.log(` Mode: ${mode}`);
|
|
742
|
-
|
|
743
646
|
let base = process.env['NX_BASE'];
|
|
744
647
|
const head = process.env['NX_HEAD'];
|
|
745
|
-
|
|
746
648
|
if (!base) {
|
|
747
649
|
base = detectBase(workspaceRoot) ?? undefined;
|
|
748
|
-
|
|
749
650
|
if (!base) {
|
|
750
651
|
console.log('\n⏭️ Skipping no-inline-types validation (could not detect base branch)');
|
|
751
652
|
console.log('');
|
|
752
653
|
return { success: true };
|
|
753
654
|
}
|
|
754
655
|
}
|
|
755
|
-
|
|
756
656
|
console.log(` Base: ${base}`);
|
|
757
657
|
console.log(` Head: ${head ?? 'working tree (includes uncommitted changes)'}`);
|
|
758
658
|
console.log('');
|
|
759
|
-
|
|
760
659
|
const changedFiles = getChangedTypeScriptFiles(workspaceRoot, base, head);
|
|
761
|
-
|
|
762
660
|
if (changedFiles.length === 0) {
|
|
763
661
|
console.log('✅ No TypeScript files changed');
|
|
764
662
|
return { success: true };
|
|
765
663
|
}
|
|
766
|
-
|
|
767
664
|
console.log(`📂 Checking ${changedFiles.length} changed file(s)...`);
|
|
768
|
-
|
|
769
|
-
let violations: InlineTypeViolation[] = [];
|
|
770
|
-
|
|
665
|
+
let violations = [];
|
|
771
666
|
if (mode === 'NEW_METHODS') {
|
|
772
667
|
violations = findViolationsForNewMethods(workspaceRoot, changedFiles, base, head, disableAllowed);
|
|
773
|
-
}
|
|
668
|
+
}
|
|
669
|
+
else if (mode === 'NEW_AND_MODIFIED_METHODS') {
|
|
774
670
|
violations = findViolationsForModifiedAndNewMethods(workspaceRoot, changedFiles, base, head, disableAllowed);
|
|
775
|
-
}
|
|
671
|
+
}
|
|
672
|
+
else if (mode === 'MODIFIED_FILES') {
|
|
776
673
|
violations = findViolationsForModifiedFiles(workspaceRoot, changedFiles, disableAllowed);
|
|
777
674
|
}
|
|
778
|
-
|
|
779
675
|
if (violations.length === 0) {
|
|
780
676
|
console.log('✅ No inline type literals found');
|
|
781
677
|
return { success: true };
|
|
782
678
|
}
|
|
783
|
-
|
|
784
679
|
reportViolations(violations, mode);
|
|
785
|
-
|
|
786
680
|
return { success: false };
|
|
787
681
|
}
|
|
682
|
+
//# sourceMappingURL=validate-no-inline-types.js.map
|