@webpieces/dev-config 0.2.74 → 0.2.76
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/architecture/executors/validate-code/executor.d.ts +2 -0
- package/architecture/executors/validate-code/executor.js +6 -1
- package/architecture/executors/validate-code/executor.js.map +1 -1
- package/architecture/executors/validate-code/executor.ts +8 -1
- package/architecture/executors/validate-code/schema.json +6 -0
- package/architecture/executors/validate-no-any-unknown/executor.d.ts +41 -0
- package/architecture/executors/validate-no-any-unknown/executor.js +442 -0
- package/architecture/executors/validate-no-any-unknown/executor.js.map +1 -0
- package/architecture/executors/validate-no-any-unknown/executor.ts +513 -0
- package/architecture/executors/validate-no-any-unknown/schema.json +15 -0
- package/architecture/executors/validate-no-inline-types/executor.d.ts +73 -13
- package/architecture/executors/validate-no-inline-types/executor.js +195 -61
- package/architecture/executors/validate-no-inline-types/executor.js.map +1 -1
- package/architecture/executors/validate-no-inline-types/executor.ts +200 -64
- package/executors.json +5 -0
- package/package.json +1 -1
- package/plugin.js +24 -7
|
@@ -1,23 +1,83 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Validate No Inline Types Executor
|
|
3
3
|
*
|
|
4
|
-
* Validates that inline type literals are not used
|
|
5
|
-
*
|
|
4
|
+
* Validates that inline type literals AND tuple types are not used in type positions.
|
|
5
|
+
* Prefer named types/interfaces/classes for clarity and reusability.
|
|
6
6
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
7
|
+
* ============================================================================
|
|
8
|
+
* VIOLATIONS (BAD) - These patterns are flagged:
|
|
9
|
+
* ============================================================================
|
|
9
10
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
11
|
+
* 1. INLINE TYPE LITERALS { }
|
|
12
|
+
* -------------------------
|
|
13
|
+
* - Inline parameter type: function foo(arg: { x: number }) { }
|
|
14
|
+
* - Inline return type: function foo(): { x: number } { }
|
|
15
|
+
* - Inline variable type: const config: { timeout: number } = { timeout: 5 };
|
|
16
|
+
* - Inline property type: class C { data: { id: number }; }
|
|
17
|
+
* - Inline in union: type T = { x: number } | null;
|
|
18
|
+
* - Inline in intersection: type T = { x: number } & { y: number };
|
|
19
|
+
* - Inline in generic: Promise<{ data: string }>
|
|
20
|
+
* - Inline in array: function foo(): { id: string }[] { }
|
|
21
|
+
* - Nested inline in alias: type T = { data: { nested: number } }; // inner { } flagged
|
|
22
|
+
* - Inline in tuple: type T = [{ x: number }, string];
|
|
12
23
|
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
24
|
+
* 2. TUPLE TYPES [ ]
|
|
25
|
+
* ----------------
|
|
26
|
+
* - Tuple return type: function foo(): [Items[], number] { }
|
|
27
|
+
* - Tuple parameter type: function foo(arg: [string, number]) { }
|
|
28
|
+
* - Tuple variable type: const result: [Data[], number] = getData();
|
|
29
|
+
* - Tuple in generic: Promise<[Items[], number]>
|
|
30
|
+
* - Tuple in union: type T = [A, B] | null;
|
|
31
|
+
* - Nested tuple: type T = { data: [A, B] };
|
|
19
32
|
*
|
|
20
|
-
*
|
|
33
|
+
* ============================================================================
|
|
34
|
+
* ALLOWED (GOOD) - These patterns pass validation:
|
|
35
|
+
* ============================================================================
|
|
36
|
+
*
|
|
37
|
+
* 1. TYPE ALIAS DEFINITIONS (direct body only)
|
|
38
|
+
* -----------------------------------------
|
|
39
|
+
* - Type alias with literal: type MyConfig = { timeout: number };
|
|
40
|
+
* - Type alias with tuple: type MyResult = [Items[], number];
|
|
41
|
+
* - Interface definition: interface MyData { id: number }
|
|
42
|
+
* - Class definition: class UserData { id: number; name: string; }
|
|
43
|
+
*
|
|
44
|
+
* 2. USING NAMED TYPES
|
|
45
|
+
* ------------------
|
|
46
|
+
* - Named param type: function foo(arg: MyConfig) { }
|
|
47
|
+
* - Named return type: function foo(): MyConfig { }
|
|
48
|
+
* - Named with null: function foo(): MyConfig | null { }
|
|
49
|
+
* - Named with undefined: function foo(): MyConfig | undefined { }
|
|
50
|
+
* - Union of named types: type Either = TypeA | TypeB;
|
|
51
|
+
* - Named in generic: Promise<MyResult>
|
|
52
|
+
* - Named tuple alias: function foo(): MyTupleResult { }
|
|
53
|
+
*
|
|
54
|
+
* 3. PRIMITIVES AND BUILT-INS
|
|
55
|
+
* -------------------------
|
|
56
|
+
* - Primitive types: function foo(): string { }
|
|
57
|
+
* - Primitive arrays: function foo(): string[] { }
|
|
58
|
+
* - Built-in generics: function foo(): Promise<string> { }
|
|
59
|
+
* - Void return: function foo(): void { }
|
|
60
|
+
*
|
|
61
|
+
* ============================================================================
|
|
62
|
+
* MODES
|
|
63
|
+
* ============================================================================
|
|
64
|
+
* - OFF: Skip validation entirely
|
|
65
|
+
* - NEW_METHODS: Only validate in new methods (detected via git diff)
|
|
66
|
+
* - MODIFIED_AND_NEW_METHODS: Validate in new methods + methods with changes
|
|
67
|
+
* - MODIFIED_FILES: Validate all violations in modified files
|
|
68
|
+
* - ALL: Validate all violations in all TypeScript files
|
|
69
|
+
*
|
|
70
|
+
* ============================================================================
|
|
71
|
+
* ESCAPE HATCH
|
|
72
|
+
* ============================================================================
|
|
73
|
+
* Add comment above the violation:
|
|
74
|
+
* // webpieces-disable no-inline-types -- [your justification]
|
|
75
|
+
* function foo(arg: { x: number }) { }
|
|
76
|
+
*
|
|
77
|
+
* Use sparingly! Common valid reasons:
|
|
78
|
+
* - Prisma payload types that require inline generics
|
|
79
|
+
* - Third-party library APIs that expect inline types
|
|
80
|
+
* - Legacy code being incrementally migrated
|
|
21
81
|
*/
|
|
22
82
|
|
|
23
83
|
import type { ExecutorContext } from '@nx/devkit';
|
|
@@ -179,11 +239,27 @@ function hasDisableComment(lines: string[], lineNumber: number): boolean {
|
|
|
179
239
|
}
|
|
180
240
|
|
|
181
241
|
/**
|
|
182
|
-
* Check if a TypeLiteral node is in an allowed context.
|
|
242
|
+
* Check if a TypeLiteral or TupleType node is in an allowed context.
|
|
183
243
|
* Only allowed if the DIRECT parent is a TypeAliasDeclaration.
|
|
244
|
+
*
|
|
245
|
+
* ALLOWED:
|
|
246
|
+
* type MyConfig = { x: number }; // TypeLiteral direct child of TypeAliasDeclaration
|
|
247
|
+
* type MyTuple = [A, B]; // TupleType direct child of TypeAliasDeclaration
|
|
248
|
+
*
|
|
249
|
+
* NOT ALLOWED (flagged):
|
|
250
|
+
* type T = { x: number } | null; // Parent is UnionType, not TypeAliasDeclaration
|
|
251
|
+
* type T = { data: { nested: number } }; // Inner TypeLiteral's parent is PropertySignature
|
|
252
|
+
* function foo(): [A, B] { } // TupleType's parent is FunctionDeclaration
|
|
253
|
+
* type T = Prisma.GetPayload<{ include: {...} }>; // TypeLiteral in generic argument
|
|
254
|
+
*
|
|
255
|
+
* NOTE: Prisma types require inline type literals in generic arguments. Use the escape hatch:
|
|
256
|
+
* // webpieces-disable no-inline-types -- Prisma API requires inline type argument
|
|
257
|
+
* type T = Prisma.GetPayload<{ include: {...} }>;
|
|
184
258
|
*/
|
|
185
|
-
function isInAllowedContext(node: ts.TypeLiteralNode): boolean {
|
|
259
|
+
function isInAllowedContext(node: ts.TypeLiteralNode | ts.TupleTypeNode): boolean {
|
|
186
260
|
const parent = node.parent;
|
|
261
|
+
if (!parent) return false;
|
|
262
|
+
|
|
187
263
|
// Only allowed if it's the DIRECT body of a type alias
|
|
188
264
|
if (ts.isTypeAliasDeclaration(parent)) {
|
|
189
265
|
return true;
|
|
@@ -192,49 +268,73 @@ function isInAllowedContext(node: ts.TypeLiteralNode): boolean {
|
|
|
192
268
|
}
|
|
193
269
|
|
|
194
270
|
/**
|
|
195
|
-
* Get a description of the context where the inline type appears.
|
|
271
|
+
* Get a description of the context where the inline type or tuple appears.
|
|
272
|
+
*
|
|
273
|
+
* Returns human-readable context like:
|
|
274
|
+
* - "inline parameter type"
|
|
275
|
+
* - "tuple return type"
|
|
276
|
+
* - "inline type in generic argument"
|
|
196
277
|
*/
|
|
197
278
|
// webpieces-disable max-lines-new-methods -- Context detection requires checking many AST node types
|
|
198
|
-
function getViolationContext(node: ts.TypeLiteralNode, sourceFile: ts.SourceFile): string {
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
const
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
if (parent
|
|
207
|
-
return
|
|
279
|
+
function getViolationContext(node: ts.TypeLiteralNode | ts.TupleTypeNode, sourceFile: ts.SourceFile): string {
|
|
280
|
+
try {
|
|
281
|
+
const isTuple = ts.isTupleTypeNode(node);
|
|
282
|
+
const prefix = isTuple ? 'tuple' : 'inline';
|
|
283
|
+
|
|
284
|
+
let current: ts.Node = node;
|
|
285
|
+
while (current.parent) {
|
|
286
|
+
const parent = current.parent;
|
|
287
|
+
if (ts.isParameter(parent)) {
|
|
288
|
+
return `${prefix} parameter type`;
|
|
208
289
|
}
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
if (parent
|
|
215
|
-
return
|
|
290
|
+
if (ts.isFunctionDeclaration(parent) || ts.isMethodDeclaration(parent) || ts.isArrowFunction(parent)) {
|
|
291
|
+
if (parent.type === current) {
|
|
292
|
+
return `${prefix} return type`;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
if (ts.isVariableDeclaration(parent)) {
|
|
296
|
+
return `${prefix} variable type`;
|
|
216
297
|
}
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
if (ts.isTypeLiteralNode(ancestor)) {
|
|
221
|
-
return 'nested inline type';
|
|
298
|
+
if (ts.isPropertyDeclaration(parent) || ts.isPropertySignature(parent)) {
|
|
299
|
+
if (parent.type === current) {
|
|
300
|
+
return `${prefix} property type`;
|
|
222
301
|
}
|
|
223
|
-
if
|
|
224
|
-
|
|
302
|
+
// Check if it's nested inside another type literal
|
|
303
|
+
let ancestor: ts.Node | undefined = parent.parent;
|
|
304
|
+
while (ancestor) {
|
|
305
|
+
if (ts.isTypeLiteralNode(ancestor)) {
|
|
306
|
+
return `nested ${prefix} type`;
|
|
307
|
+
}
|
|
308
|
+
if (ts.isTypeAliasDeclaration(ancestor)) {
|
|
309
|
+
return `nested ${prefix} type in type alias`;
|
|
310
|
+
}
|
|
311
|
+
ancestor = ancestor.parent;
|
|
225
312
|
}
|
|
226
|
-
ancestor = ancestor.parent;
|
|
227
313
|
}
|
|
314
|
+
if (ts.isUnionTypeNode(parent) || ts.isIntersectionTypeNode(parent)) {
|
|
315
|
+
return `${prefix} type in union/intersection`;
|
|
316
|
+
}
|
|
317
|
+
// Safely check parent.parent before accessing it
|
|
318
|
+
if (parent.parent && ts.isTypeReferenceNode(parent.parent) && ts.isTypeNode(parent)) {
|
|
319
|
+
return `${prefix} type in generic argument`;
|
|
320
|
+
}
|
|
321
|
+
// Direct parent is TypeReferenceNode (e.g., Prisma.GetPayload<{...}>)
|
|
322
|
+
if (ts.isTypeReferenceNode(parent)) {
|
|
323
|
+
return `${prefix} type in generic argument`;
|
|
324
|
+
}
|
|
325
|
+
if (ts.isArrayTypeNode(parent)) {
|
|
326
|
+
return `${prefix} type in array`;
|
|
327
|
+
}
|
|
328
|
+
if (ts.isTupleTypeNode(parent) && !isTuple) {
|
|
329
|
+
return `inline type in tuple`;
|
|
330
|
+
}
|
|
331
|
+
current = parent;
|
|
228
332
|
}
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
return 'inline type in generic argument';
|
|
234
|
-
}
|
|
235
|
-
current = parent;
|
|
333
|
+
return isTuple ? 'tuple type' : 'inline type literal';
|
|
334
|
+
} catch (error) {
|
|
335
|
+
// Defensive: return generic context if AST traversal fails
|
|
336
|
+
return ts.isTupleTypeNode(node) ? 'tuple type' : 'inline type literal';
|
|
236
337
|
}
|
|
237
|
-
return 'inline type literal';
|
|
238
338
|
}
|
|
239
339
|
|
|
240
340
|
interface MethodInfo {
|
|
@@ -373,7 +473,13 @@ interface InlineTypeInfo {
|
|
|
373
473
|
}
|
|
374
474
|
|
|
375
475
|
/**
|
|
376
|
-
* Find all inline type literals in a file.
|
|
476
|
+
* Find all inline type literals AND tuple types in a file.
|
|
477
|
+
*
|
|
478
|
+
* Detects:
|
|
479
|
+
* - TypeLiteral nodes: { x: number }
|
|
480
|
+
* - TupleType nodes: [A, B]
|
|
481
|
+
*
|
|
482
|
+
* Both are flagged unless they are the DIRECT body of a type alias.
|
|
377
483
|
*/
|
|
378
484
|
// webpieces-disable max-lines-new-methods -- AST traversal with visitor pattern
|
|
379
485
|
function findInlineTypesInFile(filePath: string, workspaceRoot: string): InlineTypeInfo[] {
|
|
@@ -387,22 +493,52 @@ function findInlineTypesInFile(filePath: string, workspaceRoot: string): InlineT
|
|
|
387
493
|
const inlineTypes: InlineTypeInfo[] = [];
|
|
388
494
|
|
|
389
495
|
function visit(node: ts.Node): void {
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
496
|
+
try {
|
|
497
|
+
// Check for inline type literals: { x: number }
|
|
498
|
+
if (ts.isTypeLiteralNode(node)) {
|
|
499
|
+
if (!isInAllowedContext(node)) {
|
|
500
|
+
const startPos = node.getStart(sourceFile);
|
|
501
|
+
if (startPos >= 0) {
|
|
502
|
+
const pos = sourceFile.getLineAndCharacterOfPosition(startPos);
|
|
503
|
+
const line = pos.line + 1;
|
|
504
|
+
const column = pos.character + 1;
|
|
505
|
+
const context = getViolationContext(node, sourceFile);
|
|
506
|
+
const disabled = hasDisableComment(fileLines, line);
|
|
507
|
+
|
|
508
|
+
inlineTypes.push({
|
|
509
|
+
line,
|
|
510
|
+
column,
|
|
511
|
+
context,
|
|
512
|
+
hasDisableComment: disabled,
|
|
513
|
+
});
|
|
514
|
+
}
|
|
515
|
+
}
|
|
404
516
|
}
|
|
517
|
+
|
|
518
|
+
// Check for tuple types: [A, B]
|
|
519
|
+
if (ts.isTupleTypeNode(node)) {
|
|
520
|
+
if (!isInAllowedContext(node)) {
|
|
521
|
+
const startPos = node.getStart(sourceFile);
|
|
522
|
+
if (startPos >= 0) {
|
|
523
|
+
const pos = sourceFile.getLineAndCharacterOfPosition(startPos);
|
|
524
|
+
const line = pos.line + 1;
|
|
525
|
+
const column = pos.character + 1;
|
|
526
|
+
const context = getViolationContext(node, sourceFile);
|
|
527
|
+
const disabled = hasDisableComment(fileLines, line);
|
|
528
|
+
|
|
529
|
+
inlineTypes.push({
|
|
530
|
+
line,
|
|
531
|
+
column,
|
|
532
|
+
context,
|
|
533
|
+
hasDisableComment: disabled,
|
|
534
|
+
});
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
} catch (error) {
|
|
539
|
+
// Skip nodes that cause errors during analysis
|
|
405
540
|
}
|
|
541
|
+
|
|
406
542
|
ts.forEachChild(node, visit);
|
|
407
543
|
}
|
|
408
544
|
|
package/executors.json
CHANGED
|
@@ -74,6 +74,11 @@
|
|
|
74
74
|
"implementation": "./architecture/executors/validate-no-inline-types/executor",
|
|
75
75
|
"schema": "./architecture/executors/validate-no-inline-types/schema.json",
|
|
76
76
|
"description": "Validate no inline type literals are used - prefer named types"
|
|
77
|
+
},
|
|
78
|
+
"validate-no-any-unknown": {
|
|
79
|
+
"implementation": "./architecture/executors/validate-no-any-unknown/executor",
|
|
80
|
+
"schema": "./architecture/executors/validate-no-any-unknown/schema.json",
|
|
81
|
+
"description": "Validate no any/unknown keywords are used - use specific types instead"
|
|
77
82
|
}
|
|
78
83
|
}
|
|
79
84
|
}
|
package/package.json
CHANGED
package/plugin.js
CHANGED
|
@@ -105,21 +105,38 @@ function addArchitectureProject(results, projectFiles, opts, context) {
|
|
|
105
105
|
}
|
|
106
106
|
}
|
|
107
107
|
function addPerProjectTargets(results, projectFiles, opts, context) {
|
|
108
|
+
// Track processed project roots to avoid duplicates when both files exist
|
|
109
|
+
const processedRoots = new Set();
|
|
108
110
|
for (const projectFile of projectFiles) {
|
|
109
|
-
|
|
111
|
+
const isProjectJson = projectFile.endsWith('project.json');
|
|
112
|
+
const isPackageJson = projectFile.endsWith('package.json');
|
|
113
|
+
if (!isProjectJson && !isPackageJson)
|
|
110
114
|
continue;
|
|
111
115
|
const projectRoot = (0, path_1.dirname)(projectFile);
|
|
116
|
+
// Skip root (workspace manifest, not a project)
|
|
112
117
|
if (projectRoot === '.')
|
|
113
118
|
continue;
|
|
119
|
+
// Skip if we've already processed this project root
|
|
120
|
+
if (processedRoots.has(projectRoot))
|
|
121
|
+
continue;
|
|
122
|
+
// For package.json, skip if project.json also exists in same directory
|
|
123
|
+
// (prefer project.json - it will be processed separately)
|
|
124
|
+
if (isPackageJson) {
|
|
125
|
+
const projectJsonPath = (0, path_1.join)(context.workspaceRoot, projectRoot, 'project.json');
|
|
126
|
+
if ((0, fs_1.existsSync)(projectJsonPath))
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
processedRoots.add(projectRoot);
|
|
114
130
|
const targets = {};
|
|
115
|
-
// Add circular-deps target
|
|
116
|
-
|
|
131
|
+
// Add circular-deps target ONLY for project.json projects
|
|
132
|
+
// (package.json-only projects may not have TypeScript source)
|
|
133
|
+
if (isProjectJson && opts.circularDeps.enabled) {
|
|
117
134
|
if (!isExcluded(projectRoot, opts.circularDeps.excludePatterns)) {
|
|
118
135
|
const targetName = opts.circularDeps.targetName;
|
|
119
136
|
targets[targetName] = createCircularDepsTarget(projectRoot, targetName);
|
|
120
137
|
}
|
|
121
138
|
}
|
|
122
|
-
// Add ci target
|
|
139
|
+
// Add ci target to ALL projects (both project.json and package.json)
|
|
123
140
|
targets['ci'] = createCiTarget();
|
|
124
141
|
if (Object.keys(targets).length === 0)
|
|
125
142
|
continue;
|
|
@@ -135,11 +152,11 @@ function addPerProjectTargets(results, projectFiles, opts, context) {
|
|
|
135
152
|
}
|
|
136
153
|
/**
|
|
137
154
|
* Nx V2 Inference Plugin
|
|
138
|
-
* Matches project.json files to create targets
|
|
155
|
+
* Matches project.json and package.json files to create targets
|
|
139
156
|
*/
|
|
140
157
|
exports.createNodesV2 = [
|
|
141
|
-
// Pattern to match project.json files
|
|
142
|
-
'**/project.json',
|
|
158
|
+
// Pattern to match project.json and package.json files
|
|
159
|
+
'**/{project,package}.json',
|
|
143
160
|
// Inference function
|
|
144
161
|
createNodesFunction,
|
|
145
162
|
];
|