@webpieces/dev-config 0.2.89 → 0.2.91

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.
@@ -0,0 +1,566 @@
1
+ "use strict";
2
+ /**
3
+ * Validate No Direct API in Resolver Executor
4
+ *
5
+ * Validates two Angular anti-patterns using LINE-BASED detection:
6
+ *
7
+ * ============================================================================
8
+ * VIOLATIONS (BAD) - These patterns are flagged:
9
+ * ============================================================================
10
+ *
11
+ * 1. In *.routes.ts files: inject(XxxApi) — resolvers should inject services, not APIs directly
12
+ * 2. In *.component.ts files: this.<field>.snapshot.data — components should subscribe to
13
+ * service BehaviorSubjects, not read route snapshot data
14
+ *
15
+ * ============================================================================
16
+ * CORRECT PATTERNS (GOOD)
17
+ * ============================================================================
18
+ *
19
+ * 1. In resolvers: inject(XxxService) which calls the API internally
20
+ * 2. In components: this.myService.someObservable$ (subscribe to service BehaviorSubjects)
21
+ *
22
+ * ============================================================================
23
+ * MODES (LINE-BASED)
24
+ * ============================================================================
25
+ * - OFF: Skip validation entirely
26
+ * - MODIFIED_CODE: Flag violations on changed lines (lines in diff hunks)
27
+ * - NEW_AND_MODIFIED_METHODS: Flag violations in new/modified method/route scopes
28
+ * - MODIFIED_FILES: Flag ALL violations in files that were modified
29
+ *
30
+ * ============================================================================
31
+ * ESCAPE HATCH
32
+ * ============================================================================
33
+ * Add comment above the violation:
34
+ * // webpieces-disable no-direct-api-resolver -- [your justification]
35
+ * const myApi = inject(MyApi);
36
+ */
37
+ Object.defineProperty(exports, "__esModule", { value: true });
38
+ exports.default = runExecutor;
39
+ const tslib_1 = require("tslib");
40
+ const child_process_1 = require("child_process");
41
+ const fs = tslib_1.__importStar(require("fs"));
42
+ const path = tslib_1.__importStar(require("path"));
43
+ const ts = tslib_1.__importStar(require("typescript"));
44
+ const diff_utils_1 = require("../diff-utils");
45
+ /**
46
+ * Get changed TypeScript files between base and head (or working tree if head not specified).
47
+ */
48
+ // webpieces-disable max-lines-new-methods -- Git command handling with untracked files requires multiple code paths
49
+ function getChangedTypeScriptFiles(workspaceRoot, base, head) {
50
+ try {
51
+ const diffTarget = head ? `${base} ${head}` : base;
52
+ const output = (0, child_process_1.execSync)(`git diff --name-only ${diffTarget} -- '*.ts' '*.tsx'`, {
53
+ cwd: workspaceRoot,
54
+ encoding: 'utf-8',
55
+ });
56
+ const changedFiles = output
57
+ .trim()
58
+ .split('\n')
59
+ .filter((f) => f && !f.includes('.spec.ts') && !f.includes('.test.ts'));
60
+ if (!head) {
61
+ try {
62
+ const untrackedOutput = (0, child_process_1.execSync)(`git ls-files --others --exclude-standard '*.ts' '*.tsx'`, {
63
+ cwd: workspaceRoot,
64
+ encoding: 'utf-8',
65
+ });
66
+ const untrackedFiles = untrackedOutput
67
+ .trim()
68
+ .split('\n')
69
+ .filter((f) => f && !f.includes('.spec.ts') && !f.includes('.test.ts'));
70
+ const allFiles = new Set([...changedFiles, ...untrackedFiles]);
71
+ return Array.from(allFiles);
72
+ }
73
+ catch {
74
+ return changedFiles;
75
+ }
76
+ }
77
+ return changedFiles;
78
+ }
79
+ catch {
80
+ return [];
81
+ }
82
+ }
83
+ /**
84
+ * Check if a line contains a webpieces-disable comment for no-direct-api-resolver.
85
+ */
86
+ function hasDisableComment(lines, lineNumber) {
87
+ const startCheck = Math.max(0, lineNumber - 5);
88
+ for (let i = lineNumber - 2; i >= startCheck; i--) {
89
+ const line = lines[i]?.trim() ?? '';
90
+ if (line.startsWith('function ') || line.startsWith('class ') || line.endsWith('}')) {
91
+ break;
92
+ }
93
+ if (line.includes('webpieces-disable') && line.includes('no-direct-api-resolver')) {
94
+ return true;
95
+ }
96
+ }
97
+ return false;
98
+ }
99
+ /**
100
+ * Auto-detect the base branch by finding the merge-base with origin/main.
101
+ */
102
+ function detectBase(workspaceRoot) {
103
+ try {
104
+ const mergeBase = (0, child_process_1.execSync)('git merge-base HEAD origin/main', {
105
+ cwd: workspaceRoot,
106
+ encoding: 'utf-8',
107
+ stdio: ['pipe', 'pipe', 'pipe'],
108
+ }).trim();
109
+ if (mergeBase) {
110
+ return mergeBase;
111
+ }
112
+ }
113
+ catch {
114
+ try {
115
+ const mergeBase = (0, child_process_1.execSync)('git merge-base HEAD main', {
116
+ cwd: workspaceRoot,
117
+ encoding: 'utf-8',
118
+ stdio: ['pipe', 'pipe', 'pipe'],
119
+ }).trim();
120
+ if (mergeBase) {
121
+ return mergeBase;
122
+ }
123
+ }
124
+ catch {
125
+ // Ignore
126
+ }
127
+ }
128
+ return null;
129
+ }
130
+ /**
131
+ * Find inject(XxxApi) calls in *.routes.ts files.
132
+ * Flags any CallExpression where callee is `inject` and the first argument is an identifier ending with `Api`.
133
+ */
134
+ function findDirectApiInjections(filePath, workspaceRoot, disableAllowed) {
135
+ if (!filePath.endsWith('.routes.ts'))
136
+ return [];
137
+ const fullPath = path.join(workspaceRoot, filePath);
138
+ if (!fs.existsSync(fullPath))
139
+ return [];
140
+ const content = fs.readFileSync(fullPath, 'utf-8');
141
+ const fileLines = content.split('\n');
142
+ const sourceFile = ts.createSourceFile(filePath, content, ts.ScriptTarget.Latest, true);
143
+ const violations = [];
144
+ function visit(node) {
145
+ try {
146
+ if (ts.isCallExpression(node)) {
147
+ const callee = node.expression;
148
+ if (ts.isIdentifier(callee) && callee.text === 'inject') {
149
+ const firstArg = node.arguments[0];
150
+ if (firstArg && ts.isIdentifier(firstArg) && firstArg.text.endsWith('Api')) {
151
+ const startPos = node.getStart(sourceFile);
152
+ if (startPos >= 0) {
153
+ const pos = sourceFile.getLineAndCharacterOfPosition(startPos);
154
+ const line = pos.line + 1;
155
+ const column = pos.character + 1;
156
+ const disabled = hasDisableComment(fileLines, line);
157
+ if (!disableAllowed && disabled) {
158
+ violations.push({ line, column, context: `inject(${firstArg.text}) in route resolver`, hasDisableComment: false });
159
+ }
160
+ else {
161
+ violations.push({ line, column, context: `inject(${firstArg.text}) in route resolver`, hasDisableComment: disabled });
162
+ }
163
+ }
164
+ }
165
+ }
166
+ }
167
+ }
168
+ catch {
169
+ // Skip nodes that cause errors during analysis
170
+ }
171
+ ts.forEachChild(node, visit);
172
+ }
173
+ visit(sourceFile);
174
+ return violations;
175
+ }
176
+ /**
177
+ * Find this.<field>.snapshot.data access patterns in *.component.ts files.
178
+ * Flags PropertyAccessExpression chains: this.<anything>.snapshot.data
179
+ */
180
+ function findSnapshotDataAccess(filePath, workspaceRoot, disableAllowed) {
181
+ if (!filePath.endsWith('.component.ts'))
182
+ return [];
183
+ const fullPath = path.join(workspaceRoot, filePath);
184
+ if (!fs.existsSync(fullPath))
185
+ return [];
186
+ const content = fs.readFileSync(fullPath, 'utf-8');
187
+ const fileLines = content.split('\n');
188
+ const sourceFile = ts.createSourceFile(filePath, content, ts.ScriptTarget.Latest, true);
189
+ const violations = [];
190
+ function visit(node) {
191
+ try {
192
+ // Looking for: this.<field>.snapshot.data
193
+ // AST shape: PropertyAccessExpression(.data) -> PropertyAccessExpression(.snapshot) -> PropertyAccessExpression(.<field>) -> this
194
+ if (ts.isPropertyAccessExpression(node) && node.name.text === 'data') {
195
+ const snapshotAccess = node.expression;
196
+ if (ts.isPropertyAccessExpression(snapshotAccess) && snapshotAccess.name.text === 'snapshot') {
197
+ const fieldAccess = snapshotAccess.expression;
198
+ if (ts.isPropertyAccessExpression(fieldAccess)) {
199
+ const receiver = fieldAccess.expression;
200
+ if (receiver.kind === ts.SyntaxKind.ThisKeyword) {
201
+ const fieldName = fieldAccess.name.text;
202
+ const startPos = node.getStart(sourceFile);
203
+ if (startPos >= 0) {
204
+ const pos = sourceFile.getLineAndCharacterOfPosition(startPos);
205
+ const line = pos.line + 1;
206
+ const column = pos.character + 1;
207
+ const disabled = hasDisableComment(fileLines, line);
208
+ if (!disableAllowed && disabled) {
209
+ violations.push({ line, column, context: `this.${fieldName}.snapshot.data in component`, hasDisableComment: false });
210
+ }
211
+ else {
212
+ violations.push({ line, column, context: `this.${fieldName}.snapshot.data in component`, hasDisableComment: disabled });
213
+ }
214
+ }
215
+ }
216
+ }
217
+ }
218
+ }
219
+ }
220
+ catch {
221
+ // Skip nodes that cause errors during analysis
222
+ }
223
+ ts.forEachChild(node, visit);
224
+ }
225
+ visit(sourceFile);
226
+ return violations;
227
+ }
228
+ /**
229
+ * Find all violations in a file (both inject(Api) and snapshot.data patterns).
230
+ */
231
+ function findViolationsInFile(filePath, workspaceRoot, disableAllowed) {
232
+ const apiViolations = findDirectApiInjections(filePath, workspaceRoot, disableAllowed);
233
+ const snapshotViolations = findSnapshotDataAccess(filePath, workspaceRoot, disableAllowed);
234
+ return [...apiViolations, ...snapshotViolations];
235
+ }
236
+ /**
237
+ * MODIFIED_CODE mode: Flag violations on changed lines in diff hunks.
238
+ */
239
+ // webpieces-disable max-lines-new-methods -- File iteration with diff parsing and line filtering
240
+ function findViolationsForModifiedCode(workspaceRoot, changedFiles, base, head, disableAllowed) {
241
+ const violations = [];
242
+ for (const file of changedFiles) {
243
+ const diff = (0, diff_utils_1.getFileDiff)(workspaceRoot, file, base, head);
244
+ const changedLines = (0, diff_utils_1.getChangedLineNumbers)(diff);
245
+ if (changedLines.size === 0)
246
+ continue;
247
+ const allViolations = findViolationsInFile(file, workspaceRoot, disableAllowed);
248
+ for (const v of allViolations) {
249
+ if (disableAllowed && v.hasDisableComment)
250
+ continue;
251
+ // LINE-BASED: Only include if the violation is on a changed line
252
+ if (!changedLines.has(v.line))
253
+ continue;
254
+ violations.push({
255
+ file,
256
+ line: v.line,
257
+ column: v.column,
258
+ context: v.context,
259
+ });
260
+ }
261
+ }
262
+ return violations;
263
+ }
264
+ /**
265
+ * MODIFIED_FILES mode: Flag ALL violations in files that were modified.
266
+ */
267
+ function findViolationsForModifiedFiles(workspaceRoot, changedFiles, disableAllowed) {
268
+ const violations = [];
269
+ for (const file of changedFiles) {
270
+ const allViolations = findViolationsInFile(file, workspaceRoot, disableAllowed);
271
+ for (const v of allViolations) {
272
+ if (disableAllowed && v.hasDisableComment)
273
+ continue;
274
+ violations.push({
275
+ file,
276
+ line: v.line,
277
+ column: v.column,
278
+ context: v.context,
279
+ });
280
+ }
281
+ }
282
+ return violations;
283
+ }
284
+ /**
285
+ * Find route object ranges in *.routes.ts files.
286
+ * A route object is an ObjectLiteralExpression that contains (directly or in descendants)
287
+ * a `resolve` property. Returns the line range of each such top-level route object.
288
+ */
289
+ function findRouteObjectRanges(filePath, workspaceRoot) {
290
+ const fullPath = path.join(workspaceRoot, filePath);
291
+ if (!fs.existsSync(fullPath))
292
+ return [];
293
+ const content = fs.readFileSync(fullPath, 'utf-8');
294
+ const sourceFile = ts.createSourceFile(filePath, content, ts.ScriptTarget.Latest, true);
295
+ const ranges = [];
296
+ function hasResolveProperty(node) {
297
+ if (ts.isPropertyAssignment(node) && ts.isIdentifier(node.name) && node.name.text === 'resolve') {
298
+ return true;
299
+ }
300
+ let found = false;
301
+ ts.forEachChild(node, (child) => {
302
+ if (hasResolveProperty(child)) {
303
+ found = true;
304
+ }
305
+ });
306
+ return found;
307
+ }
308
+ function visitTopLevel(node) {
309
+ if (ts.isObjectLiteralExpression(node) && hasResolveProperty(node)) {
310
+ const start = sourceFile.getLineAndCharacterOfPosition(node.getStart());
311
+ const end = sourceFile.getLineAndCharacterOfPosition(node.getEnd());
312
+ ranges.push({
313
+ name: `route@${start.line + 1}`,
314
+ startLine: start.line + 1,
315
+ endLine: end.line + 1,
316
+ });
317
+ return;
318
+ }
319
+ ts.forEachChild(node, visitTopLevel);
320
+ }
321
+ visitTopLevel(sourceFile);
322
+ return ranges;
323
+ }
324
+ /**
325
+ * Find method/function ranges in *.component.ts files.
326
+ * Returns ranges for class methods, function declarations, and arrow functions in variable declarations.
327
+ */
328
+ function findMethodRanges(filePath, workspaceRoot) {
329
+ const fullPath = path.join(workspaceRoot, filePath);
330
+ if (!fs.existsSync(fullPath))
331
+ return [];
332
+ const content = fs.readFileSync(fullPath, 'utf-8');
333
+ const sourceFile = ts.createSourceFile(filePath, content, ts.ScriptTarget.Latest, true);
334
+ const ranges = [];
335
+ function visit(node) {
336
+ let methodName;
337
+ let startLine;
338
+ let endLine;
339
+ if (ts.isMethodDeclaration(node) && node.name) {
340
+ methodName = node.name.getText(sourceFile);
341
+ const start = sourceFile.getLineAndCharacterOfPosition(node.getStart());
342
+ const end = sourceFile.getLineAndCharacterOfPosition(node.getEnd());
343
+ startLine = start.line + 1;
344
+ endLine = end.line + 1;
345
+ }
346
+ else if (ts.isFunctionDeclaration(node) && node.name) {
347
+ methodName = node.name.getText(sourceFile);
348
+ const start = sourceFile.getLineAndCharacterOfPosition(node.getStart());
349
+ const end = sourceFile.getLineAndCharacterOfPosition(node.getEnd());
350
+ startLine = start.line + 1;
351
+ endLine = end.line + 1;
352
+ }
353
+ else if (ts.isArrowFunction(node)) {
354
+ if (ts.isVariableDeclaration(node.parent) && ts.isIdentifier(node.parent.name)) {
355
+ methodName = node.parent.name.getText(sourceFile);
356
+ const start = sourceFile.getLineAndCharacterOfPosition(node.getStart());
357
+ const end = sourceFile.getLineAndCharacterOfPosition(node.getEnd());
358
+ startLine = start.line + 1;
359
+ endLine = end.line + 1;
360
+ }
361
+ }
362
+ if (methodName && startLine !== undefined && endLine !== undefined) {
363
+ ranges.push({ name: methodName, startLine, endLine });
364
+ }
365
+ ts.forEachChild(node, visit);
366
+ }
367
+ visit(sourceFile);
368
+ return ranges;
369
+ }
370
+ /**
371
+ * NEW_AND_MODIFIED_METHODS mode: Flag violations in new/modified method/route scopes.
372
+ * - For *.routes.ts: If any line in a route object is changed, flag all inject(XxxApi) violations in that route
373
+ * - For *.component.ts: If a method is new/modified, flag all snapshot.data violations in that method
374
+ */
375
+ // webpieces-disable max-lines-new-methods -- Method-scoped validation with route objects and component methods
376
+ function findViolationsForModifiedMethods(workspaceRoot, changedFiles, base, head, disableAllowed) {
377
+ const violations = [];
378
+ for (const file of changedFiles) {
379
+ const diff = (0, diff_utils_1.getFileDiff)(workspaceRoot, file, base, head);
380
+ const changedLines = (0, diff_utils_1.getChangedLineNumbers)(diff);
381
+ const newMethodNames = (0, diff_utils_1.findNewMethodSignaturesInDiff)(diff);
382
+ if (changedLines.size === 0 && newMethodNames.size === 0)
383
+ continue;
384
+ if (file.endsWith('.routes.ts')) {
385
+ const routeRanges = findRouteObjectRanges(file, workspaceRoot);
386
+ const allViolations = findDirectApiInjections(file, workspaceRoot, disableAllowed);
387
+ for (const range of routeRanges) {
388
+ let rangeHasChanges = false;
389
+ for (let line = range.startLine; line <= range.endLine; line++) {
390
+ if (changedLines.has(line)) {
391
+ rangeHasChanges = true;
392
+ break;
393
+ }
394
+ }
395
+ if (!rangeHasChanges)
396
+ continue;
397
+ for (const v of allViolations) {
398
+ if (disableAllowed && v.hasDisableComment)
399
+ continue;
400
+ if (v.line >= range.startLine && v.line <= range.endLine) {
401
+ violations.push({
402
+ file,
403
+ line: v.line,
404
+ column: v.column,
405
+ context: v.context,
406
+ });
407
+ }
408
+ }
409
+ }
410
+ }
411
+ else if (file.endsWith('.component.ts')) {
412
+ const methodRanges = findMethodRanges(file, workspaceRoot);
413
+ const allViolations = findSnapshotDataAccess(file, workspaceRoot, disableAllowed);
414
+ for (const range of methodRanges) {
415
+ const isNewMethod = newMethodNames.has(range.name);
416
+ let rangeHasChanges = false;
417
+ if (!isNewMethod) {
418
+ for (let line = range.startLine; line <= range.endLine; line++) {
419
+ if (changedLines.has(line)) {
420
+ rangeHasChanges = true;
421
+ break;
422
+ }
423
+ }
424
+ }
425
+ if (!isNewMethod && !rangeHasChanges)
426
+ continue;
427
+ for (const v of allViolations) {
428
+ if (disableAllowed && v.hasDisableComment)
429
+ continue;
430
+ if (v.line >= range.startLine && v.line <= range.endLine) {
431
+ violations.push({
432
+ file,
433
+ line: v.line,
434
+ column: v.column,
435
+ context: v.context,
436
+ });
437
+ }
438
+ }
439
+ }
440
+ }
441
+ }
442
+ return violations;
443
+ }
444
+ /**
445
+ * Report violations to console.
446
+ */
447
+ // webpieces-disable max-lines-new-methods -- Console output with examples and escape hatch information
448
+ function reportViolations(violations, mode, disableAllowed) {
449
+ console.error('');
450
+ console.error('\u274c Direct API usage in resolvers or snapshot.data in components found!');
451
+ console.error('');
452
+ console.error('\ud83d\udcda Resolvers should use services, and components should subscribe to service observables:');
453
+ console.error('');
454
+ console.error(' BAD (in *.routes.ts resolver):');
455
+ console.error(' const myApi = inject(MyApi);');
456
+ console.error(' resolve: () => inject(MyApi).fetchData()');
457
+ console.error('');
458
+ console.error(' GOOD (in *.routes.ts resolver):');
459
+ console.error(' const myService = inject(MyService);');
460
+ console.error(' resolve: () => inject(MyService).loadData()');
461
+ console.error('');
462
+ console.error(' BAD (in *.component.ts):');
463
+ console.error(' const data = this.route.snapshot.data;');
464
+ console.error('');
465
+ console.error(' GOOD (in *.component.ts):');
466
+ console.error(' this.myService.data$.subscribe(data => ...)');
467
+ console.error('');
468
+ for (const v of violations) {
469
+ console.error(` \u274c ${v.file}:${v.line}:${v.column}`);
470
+ console.error(` ${v.context}`);
471
+ }
472
+ console.error('');
473
+ if (disableAllowed) {
474
+ console.error(' Escape hatch (use sparingly):');
475
+ console.error(' // webpieces-disable no-direct-api-resolver -- [your reason]');
476
+ }
477
+ else {
478
+ console.error(' Escape hatch: DISABLED (disableAllowed: false)');
479
+ console.error(' Disable comments are ignored. Fix the pattern directly.');
480
+ }
481
+ console.error('');
482
+ console.error(` Current mode: ${mode}`);
483
+ console.error('');
484
+ }
485
+ /**
486
+ * Resolve mode considering ignoreModifiedUntilEpoch override.
487
+ * When active, downgrades to OFF. When expired, logs a warning.
488
+ */
489
+ function resolveMode(normalMode, epoch) {
490
+ if (epoch === undefined || normalMode === 'OFF') {
491
+ return normalMode;
492
+ }
493
+ const nowSeconds = Date.now() / 1000;
494
+ if (nowSeconds < epoch) {
495
+ const expiresDate = new Date(epoch * 1000).toISOString().split('T')[0];
496
+ console.log(`\n\u23ed\ufe0f Skipping no-direct-api-resolver validation (ignoreModifiedUntilEpoch active, expires: ${expiresDate})`);
497
+ console.log('');
498
+ return 'OFF';
499
+ }
500
+ return normalMode;
501
+ }
502
+ /**
503
+ * Filter changed files to only those under enforcePaths (if configured).
504
+ */
505
+ function filterByEnforcePaths(changedFiles, enforcePaths) {
506
+ if (!enforcePaths || enforcePaths.length === 0) {
507
+ return changedFiles;
508
+ }
509
+ return changedFiles.filter((file) => enforcePaths.some((prefix) => file.startsWith(prefix)));
510
+ }
511
+ /**
512
+ * Filter to only relevant Angular files (*.routes.ts and *.component.ts).
513
+ */
514
+ function filterRelevantFiles(changedFiles) {
515
+ return changedFiles.filter((file) => file.endsWith('.routes.ts') || file.endsWith('.component.ts'));
516
+ }
517
+ async function runExecutor(options, context) {
518
+ const workspaceRoot = context.root;
519
+ const mode = resolveMode(options.mode ?? 'OFF', options.ignoreModifiedUntilEpoch);
520
+ const disableAllowed = options.disableAllowed ?? true;
521
+ if (mode === 'OFF') {
522
+ console.log('\n\u23ed\ufe0f Skipping no-direct-api-resolver validation (mode: OFF)');
523
+ console.log('');
524
+ return { success: true };
525
+ }
526
+ console.log('\n\ud83d\udccf Validating No Direct API in Resolver\n');
527
+ console.log(` Mode: ${mode}`);
528
+ let base = process.env['NX_BASE'];
529
+ const head = process.env['NX_HEAD'];
530
+ if (!base) {
531
+ base = detectBase(workspaceRoot) ?? undefined;
532
+ if (!base) {
533
+ console.log('\n\u23ed\ufe0f Skipping no-direct-api-resolver validation (could not detect base branch)');
534
+ console.log('');
535
+ return { success: true };
536
+ }
537
+ }
538
+ console.log(` Base: ${base}`);
539
+ console.log(` Head: ${head ?? 'working tree (includes uncommitted changes)'}`);
540
+ console.log('');
541
+ const allChangedFiles = getChangedTypeScriptFiles(workspaceRoot, base, head);
542
+ const scopedFiles = filterByEnforcePaths(allChangedFiles, options.enforcePaths);
543
+ const changedFiles = filterRelevantFiles(scopedFiles);
544
+ if (changedFiles.length === 0) {
545
+ console.log('\u2705 No relevant Angular files changed (*.routes.ts, *.component.ts)');
546
+ return { success: true };
547
+ }
548
+ console.log(`\ud83d\udcc2 Checking ${changedFiles.length} changed file(s)...`);
549
+ let violations = [];
550
+ if (mode === 'MODIFIED_CODE') {
551
+ violations = findViolationsForModifiedCode(workspaceRoot, changedFiles, base, head, disableAllowed);
552
+ }
553
+ else if (mode === 'NEW_AND_MODIFIED_METHODS') {
554
+ violations = findViolationsForModifiedMethods(workspaceRoot, changedFiles, base, head, disableAllowed);
555
+ }
556
+ else if (mode === 'MODIFIED_FILES') {
557
+ violations = findViolationsForModifiedFiles(workspaceRoot, changedFiles, disableAllowed);
558
+ }
559
+ if (violations.length === 0) {
560
+ console.log('\u2705 No direct API resolver patterns found');
561
+ return { success: true };
562
+ }
563
+ reportViolations(violations, mode, disableAllowed);
564
+ return { success: false };
565
+ }
566
+ //# sourceMappingURL=executor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"executor.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/dev-config/architecture/executors/validate-no-direct-api-resolver/executor.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Validate No Direct API in Resolver Executor\n *\n * Validates two Angular anti-patterns using LINE-BASED detection:\n *\n * ============================================================================\n * VIOLATIONS (BAD) - These patterns are flagged:\n * ============================================================================\n *\n * 1. In *.routes.ts files: inject(XxxApi) — resolvers should inject services, not APIs directly\n * 2. In *.component.ts files: this.<field>.snapshot.data — components should subscribe to\n * service BehaviorSubjects, not read route snapshot data\n *\n * ============================================================================\n * CORRECT PATTERNS (GOOD)\n * ============================================================================\n *\n * 1. In resolvers: inject(XxxService) which calls the API internally\n * 2. In components: this.myService.someObservable$ (subscribe to service BehaviorSubjects)\n *\n * ============================================================================\n * MODES (LINE-BASED)\n * ============================================================================\n * - OFF: Skip validation entirely\n * - MODIFIED_CODE: Flag violations on changed lines (lines in diff hunks)\n * - NEW_AND_MODIFIED_METHODS: Flag violations in new/modified method/route scopes\n * - MODIFIED_FILES: Flag ALL violations in files that were modified\n *\n * ============================================================================\n * ESCAPE HATCH\n * ============================================================================\n * Add comment above the violation:\n * // webpieces-disable no-direct-api-resolver -- [your justification]\n * const myApi = inject(MyApi);\n */\n\nimport type { ExecutorContext } from '@nx/devkit';\nimport { execSync } from 'child_process';\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport * as ts from 'typescript';\nimport { getFileDiff, getChangedLineNumbers, findNewMethodSignaturesInDiff } from '../diff-utils';\n\nexport type NoDirectApiResolverMode = 'OFF' | 'MODIFIED_CODE' | 'NEW_AND_MODIFIED_METHODS' | 'MODIFIED_FILES';\n\nexport interface ValidateNoDirectApiResolverOptions {\n mode?: NoDirectApiResolverMode;\n disableAllowed?: boolean;\n ignoreModifiedUntilEpoch?: number;\n enforcePaths?: string[];\n}\n\nexport interface ExecutorResult {\n success: boolean;\n}\n\ninterface Violation {\n file: string;\n line: number;\n column: number;\n context: string;\n}\n\ninterface ViolationInfo {\n line: number;\n column: number;\n context: string;\n hasDisableComment: boolean;\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 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 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 {\n return changedFiles;\n }\n }\n\n return changedFiles;\n } catch {\n return [];\n }\n}\n\n/**\n * Check if a line contains a webpieces-disable comment for no-direct-api-resolver.\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-direct-api-resolver')) {\n return true;\n }\n }\n return false;\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 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 {\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 {\n // Ignore\n }\n }\n return null;\n}\n\n/**\n * Find inject(XxxApi) calls in *.routes.ts files.\n * Flags any CallExpression where callee is `inject` and the first argument is an identifier ending with `Api`.\n */\nfunction findDirectApiInjections(filePath: string, workspaceRoot: string, disableAllowed: boolean): ViolationInfo[] {\n if (!filePath.endsWith('.routes.ts')) return [];\n\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: ViolationInfo[] = [];\n\n function visit(node: ts.Node): void {\n try {\n if (ts.isCallExpression(node)) {\n const callee = node.expression;\n if (ts.isIdentifier(callee) && callee.text === 'inject') {\n const firstArg = node.arguments[0];\n if (firstArg && ts.isIdentifier(firstArg) && firstArg.text.endsWith('Api')) {\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 violations.push({ line, column, context: `inject(${firstArg.text}) in route resolver`, hasDisableComment: false });\n } else {\n violations.push({ line, column, context: `inject(${firstArg.text}) in route resolver`, hasDisableComment: disabled });\n }\n }\n }\n }\n }\n } catch {\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\n/**\n * Find this.<field>.snapshot.data access patterns in *.component.ts files.\n * Flags PropertyAccessExpression chains: this.<anything>.snapshot.data\n */\nfunction findSnapshotDataAccess(filePath: string, workspaceRoot: string, disableAllowed: boolean): ViolationInfo[] {\n if (!filePath.endsWith('.component.ts')) return [];\n\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: ViolationInfo[] = [];\n\n function visit(node: ts.Node): void {\n try {\n // Looking for: this.<field>.snapshot.data\n // AST shape: PropertyAccessExpression(.data) -> PropertyAccessExpression(.snapshot) -> PropertyAccessExpression(.<field>) -> this\n if (ts.isPropertyAccessExpression(node) && node.name.text === 'data') {\n const snapshotAccess = node.expression;\n if (ts.isPropertyAccessExpression(snapshotAccess) && snapshotAccess.name.text === 'snapshot') {\n const fieldAccess = snapshotAccess.expression;\n if (ts.isPropertyAccessExpression(fieldAccess)) {\n const receiver = fieldAccess.expression;\n if (receiver.kind === ts.SyntaxKind.ThisKeyword) {\n const fieldName = fieldAccess.name.text;\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 violations.push({ line, column, context: `this.${fieldName}.snapshot.data in component`, hasDisableComment: false });\n } else {\n violations.push({ line, column, context: `this.${fieldName}.snapshot.data in component`, hasDisableComment: disabled });\n }\n }\n }\n }\n }\n }\n } catch {\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\n/**\n * Find all violations in a file (both inject(Api) and snapshot.data patterns).\n */\nfunction findViolationsInFile(filePath: string, workspaceRoot: string, disableAllowed: boolean): ViolationInfo[] {\n const apiViolations = findDirectApiInjections(filePath, workspaceRoot, disableAllowed);\n const snapshotViolations = findSnapshotDataAccess(filePath, workspaceRoot, disableAllowed);\n return [...apiViolations, ...snapshotViolations];\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): Violation[] {\n const violations: Violation[] = [];\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 = findViolationsInFile(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): Violation[] {\n const violations: Violation[] = [];\n\n for (const file of changedFiles) {\n const allViolations = findViolationsInFile(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\ninterface RangeInfo {\n name: string;\n startLine: number;\n endLine: number;\n}\n\n/**\n * Find route object ranges in *.routes.ts files.\n * A route object is an ObjectLiteralExpression that contains (directly or in descendants)\n * a `resolve` property. Returns the line range of each such top-level route object.\n */\nfunction findRouteObjectRanges(filePath: string, workspaceRoot: string): RangeInfo[] {\n const fullPath = path.join(workspaceRoot, filePath);\n if (!fs.existsSync(fullPath)) return [];\n\n const content = fs.readFileSync(fullPath, 'utf-8');\n const sourceFile = ts.createSourceFile(filePath, content, ts.ScriptTarget.Latest, true);\n\n const ranges: RangeInfo[] = [];\n\n function hasResolveProperty(node: ts.Node): boolean {\n if (ts.isPropertyAssignment(node) && ts.isIdentifier(node.name) && node.name.text === 'resolve') {\n return true;\n }\n let found = false;\n ts.forEachChild(node, (child) => {\n if (hasResolveProperty(child)) {\n found = true;\n }\n });\n return found;\n }\n\n function visitTopLevel(node: ts.Node): void {\n if (ts.isObjectLiteralExpression(node) && hasResolveProperty(node)) {\n const start = sourceFile.getLineAndCharacterOfPosition(node.getStart());\n const end = sourceFile.getLineAndCharacterOfPosition(node.getEnd());\n ranges.push({\n name: `route@${start.line + 1}`,\n startLine: start.line + 1,\n endLine: end.line + 1,\n });\n return;\n }\n ts.forEachChild(node, visitTopLevel);\n }\n\n visitTopLevel(sourceFile);\n return ranges;\n}\n\n/**\n * Find method/function ranges in *.component.ts files.\n * Returns ranges for class methods, function declarations, and arrow functions in variable declarations.\n */\nfunction findMethodRanges(filePath: string, workspaceRoot: string): RangeInfo[] {\n const fullPath = path.join(workspaceRoot, filePath);\n if (!fs.existsSync(fullPath)) return [];\n\n const content = fs.readFileSync(fullPath, 'utf-8');\n const sourceFile = ts.createSourceFile(filePath, content, ts.ScriptTarget.Latest, true);\n\n const ranges: RangeInfo[] = [];\n\n function visit(node: ts.Node): void {\n let methodName: string | undefined;\n let startLine: number | undefined;\n let endLine: number | undefined;\n\n if (ts.isMethodDeclaration(node) && node.name) {\n methodName = node.name.getText(sourceFile);\n const start = sourceFile.getLineAndCharacterOfPosition(node.getStart());\n const end = sourceFile.getLineAndCharacterOfPosition(node.getEnd());\n startLine = start.line + 1;\n endLine = end.line + 1;\n } else if (ts.isFunctionDeclaration(node) && node.name) {\n methodName = node.name.getText(sourceFile);\n const start = sourceFile.getLineAndCharacterOfPosition(node.getStart());\n const end = sourceFile.getLineAndCharacterOfPosition(node.getEnd());\n startLine = start.line + 1;\n endLine = end.line + 1;\n } else if (ts.isArrowFunction(node)) {\n if (ts.isVariableDeclaration(node.parent) && ts.isIdentifier(node.parent.name)) {\n methodName = node.parent.name.getText(sourceFile);\n const start = sourceFile.getLineAndCharacterOfPosition(node.getStart());\n const end = sourceFile.getLineAndCharacterOfPosition(node.getEnd());\n startLine = start.line + 1;\n endLine = end.line + 1;\n }\n }\n\n if (methodName && startLine !== undefined && endLine !== undefined) {\n ranges.push({ name: methodName, startLine, endLine });\n }\n\n ts.forEachChild(node, visit);\n }\n\n visit(sourceFile);\n return ranges;\n}\n\n/**\n * NEW_AND_MODIFIED_METHODS mode: Flag violations in new/modified method/route scopes.\n * - For *.routes.ts: If any line in a route object is changed, flag all inject(XxxApi) violations in that route\n * - For *.component.ts: If a method is new/modified, flag all snapshot.data violations in that method\n */\n// webpieces-disable max-lines-new-methods -- Method-scoped validation with route objects and component methods\nfunction findViolationsForModifiedMethods(\n workspaceRoot: string,\n changedFiles: string[],\n base: string,\n head: string | undefined,\n disableAllowed: boolean\n): Violation[] {\n const violations: Violation[] = [];\n\n for (const file of changedFiles) {\n const diff = getFileDiff(workspaceRoot, file, base, head);\n const changedLines = getChangedLineNumbers(diff);\n const newMethodNames = findNewMethodSignaturesInDiff(diff);\n\n if (changedLines.size === 0 && newMethodNames.size === 0) continue;\n\n if (file.endsWith('.routes.ts')) {\n const routeRanges = findRouteObjectRanges(file, workspaceRoot);\n const allViolations = findDirectApiInjections(file, workspaceRoot, disableAllowed);\n\n for (const range of routeRanges) {\n let rangeHasChanges = false;\n for (let line = range.startLine; line <= range.endLine; line++) {\n if (changedLines.has(line)) {\n rangeHasChanges = true;\n break;\n }\n }\n if (!rangeHasChanges) continue;\n\n for (const v of allViolations) {\n if (disableAllowed && v.hasDisableComment) continue;\n if (v.line >= range.startLine && v.line <= range.endLine) {\n violations.push({\n file,\n line: v.line,\n column: v.column,\n context: v.context,\n });\n }\n }\n }\n } else if (file.endsWith('.component.ts')) {\n const methodRanges = findMethodRanges(file, workspaceRoot);\n const allViolations = findSnapshotDataAccess(file, workspaceRoot, disableAllowed);\n\n for (const range of methodRanges) {\n const isNewMethod = newMethodNames.has(range.name);\n let rangeHasChanges = false;\n if (!isNewMethod) {\n for (let line = range.startLine; line <= range.endLine; line++) {\n if (changedLines.has(line)) {\n rangeHasChanges = true;\n break;\n }\n }\n }\n if (!isNewMethod && !rangeHasChanges) continue;\n\n for (const v of allViolations) {\n if (disableAllowed && v.hasDisableComment) continue;\n if (v.line >= range.startLine && v.line <= range.endLine) {\n violations.push({\n file,\n line: v.line,\n column: v.column,\n context: v.context,\n });\n }\n }\n }\n }\n }\n\n return violations;\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: Violation[], mode: NoDirectApiResolverMode, disableAllowed: boolean): void {\n console.error('');\n console.error('\\u274c Direct API usage in resolvers or snapshot.data in components found!');\n console.error('');\n console.error('\\ud83d\\udcda Resolvers should use services, and components should subscribe to service observables:');\n console.error('');\n console.error(' BAD (in *.routes.ts resolver):');\n console.error(' const myApi = inject(MyApi);');\n console.error(' resolve: () => inject(MyApi).fetchData()');\n console.error('');\n console.error(' GOOD (in *.routes.ts resolver):');\n console.error(' const myService = inject(MyService);');\n console.error(' resolve: () => inject(MyService).loadData()');\n console.error('');\n console.error(' BAD (in *.component.ts):');\n console.error(' const data = this.route.snapshot.data;');\n console.error('');\n console.error(' GOOD (in *.component.ts):');\n console.error(' this.myService.data$.subscribe(data => ...)');\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 if (disableAllowed) {\n console.error(' Escape hatch (use sparingly):');\n console.error(' // webpieces-disable no-direct-api-resolver -- [your reason]');\n } else {\n console.error(' Escape hatch: DISABLED (disableAllowed: false)');\n console.error(' Disable comments are ignored. Fix the pattern 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 resolveMode(normalMode: NoDirectApiResolverMode, epoch: number | undefined): NoDirectApiResolverMode {\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-direct-api-resolver validation (ignoreModifiedUntilEpoch active, expires: ${expiresDate})`);\n console.log('');\n return 'OFF';\n }\n return normalMode;\n}\n\n/**\n * Filter changed files to only those under enforcePaths (if configured).\n */\nfunction filterByEnforcePaths(changedFiles: string[], enforcePaths: string[] | undefined): string[] {\n if (!enforcePaths || enforcePaths.length === 0) {\n return changedFiles;\n }\n return changedFiles.filter((file) =>\n enforcePaths.some((prefix) => file.startsWith(prefix))\n );\n}\n\n/**\n * Filter to only relevant Angular files (*.routes.ts and *.component.ts).\n */\nfunction filterRelevantFiles(changedFiles: string[]): string[] {\n return changedFiles.filter((file) =>\n file.endsWith('.routes.ts') || file.endsWith('.component.ts')\n );\n}\n\nexport default async function runExecutor(\n options: ValidateNoDirectApiResolverOptions,\n context: ExecutorContext\n): Promise<ExecutorResult> {\n const workspaceRoot = context.root;\n const mode: NoDirectApiResolverMode = resolveMode(options.mode ?? 'OFF', options.ignoreModifiedUntilEpoch);\n const disableAllowed = options.disableAllowed ?? true;\n\n if (mode === 'OFF') {\n console.log('\\n\\u23ed\\ufe0f Skipping no-direct-api-resolver validation (mode: OFF)');\n console.log('');\n return { success: true };\n }\n\n console.log('\\n\\ud83d\\udccf Validating No Direct API in Resolver\\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-direct-api-resolver 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 allChangedFiles = getChangedTypeScriptFiles(workspaceRoot, base, head);\n const scopedFiles = filterByEnforcePaths(allChangedFiles, options.enforcePaths);\n const changedFiles = filterRelevantFiles(scopedFiles);\n\n if (changedFiles.length === 0) {\n console.log('\\u2705 No relevant Angular files changed (*.routes.ts, *.component.ts)');\n return { success: true };\n }\n\n console.log(`\\ud83d\\udcc2 Checking ${changedFiles.length} changed file(s)...`);\n\n let violations: Violation[] = [];\n\n if (mode === 'MODIFIED_CODE') {\n violations = findViolationsForModifiedCode(workspaceRoot, changedFiles, base, head, disableAllowed);\n } else if (mode === 'NEW_AND_MODIFIED_METHODS') {\n violations = findViolationsForModifiedMethods(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 direct API resolver patterns found');\n return { success: true };\n }\n\n reportViolations(violations, mode, disableAllowed);\n\n return { success: false };\n}\n"]}