@webpieces/dev-config 0.2.74 → 0.2.75

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.
@@ -1,23 +1,83 @@
1
1
  /**
2
2
  * Validate No Inline Types Executor
3
3
  *
4
- * Validates that inline type literals are not used - prefer named types/interfaces/classes.
5
- * Instead of anonymous object types, use named types for clarity and reusability:
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
- * BAD: function foo(arg: { x: number }) { }
8
- * GOOD: function foo(arg: MyConfig) { }
7
+ * ============================================================================
8
+ * VIOLATIONS (BAD) - These patterns are flagged:
9
+ * ============================================================================
9
10
  *
10
- * BAD: type Nullable = { x: number } | null;
11
- * GOOD: type MyData = { x: number }; type Nullable = MyData | null;
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
- * Modes:
14
- * - OFF: Skip validation entirely
15
- * - NEW_METHODS: Only validate inline types in new methods (detected via git diff)
16
- * - MODIFIED_AND_NEW_METHODS: Validate in new methods + methods with changes in their line range
17
- * - MODIFIED_FILES: Validate all inline types in modified files
18
- * - ALL: Validate all inline types in all TypeScript files
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
- * Escape hatch: Add webpieces-disable no-inline-types comment with justification
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
- let current: ts.Node = node;
200
- while (current.parent) {
201
- const parent = current.parent;
202
- if (ts.isParameter(parent)) {
203
- return 'inline parameter type';
204
- }
205
- if (ts.isFunctionDeclaration(parent) || ts.isMethodDeclaration(parent) || ts.isArrowFunction(parent)) {
206
- if (parent.type === current) {
207
- return 'inline return type';
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
- if (ts.isVariableDeclaration(parent)) {
211
- return 'inline variable type';
212
- }
213
- if (ts.isPropertyDeclaration(parent) || ts.isPropertySignature(parent)) {
214
- if (parent.type === current) {
215
- return 'inline property type';
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
- // Check if it's nested inside another type literal
218
- let ancestor: ts.Node | undefined = parent.parent;
219
- while (ancestor) {
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 (ts.isTypeAliasDeclaration(ancestor)) {
224
- return 'nested inline type in type alias';
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
- if (ts.isUnionTypeNode(parent) || ts.isIntersectionTypeNode(parent)) {
230
- return 'inline type in union/intersection';
231
- }
232
- if (ts.isTypeReferenceNode(parent.parent) && ts.isTypeNode(parent)) {
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
- if (ts.isTypeLiteralNode(node)) {
391
- if (!isInAllowedContext(node)) {
392
- const pos = sourceFile.getLineAndCharacterOfPosition(node.getStart());
393
- const line = pos.line + 1;
394
- const column = pos.character + 1;
395
- const context = getViolationContext(node, sourceFile);
396
- const disabled = hasDisableComment(fileLines, line);
397
-
398
- inlineTypes.push({
399
- line,
400
- column,
401
- context,
402
- hasDisableComment: disabled,
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webpieces/dev-config",
3
- "version": "0.2.74",
3
+ "version": "0.2.75",
4
4
  "description": "Development configuration, scripts, and patterns for WebPieces projects",
5
5
  "type": "commonjs",
6
6
  "bin": {