@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.
@@ -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.76",
4
4
  "description": "Development configuration, scripts, and patterns for WebPieces projects",
5
5
  "type": "commonjs",
6
6
  "bin": {
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
- if (!projectFile.endsWith('project.json'))
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 if enabled (runs on ALL projects - KISS)
116
- if (opts.circularDeps.enabled) {
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 - composite target that runs lint, build, and test
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
  ];