@webpieces/code-rules 0.0.1 → 0.2.113
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +4 -3
- package/src/cli.d.ts +1 -0
- package/src/cli.js +19 -0
- package/src/cli.js.map +1 -0
- package/src/diff-utils.d.ts +24 -0
- package/src/{diff-utils.ts → diff-utils.js} +30 -38
- package/src/diff-utils.js.map +1 -0
- package/src/from-shared-config.d.ts +28 -0
- package/src/from-shared-config.js +119 -0
- package/src/from-shared-config.js.map +1 -0
- package/src/index.js +33 -0
- package/src/index.js.map +1 -0
- package/src/validate-catch-error-pattern.d.ts +47 -0
- package/src/{validate-catch-error-pattern.ts → validate-catch-error-pattern.js} +74 -195
- package/src/validate-catch-error-pattern.js.map +1 -0
- package/src/validate-code.d.ts +98 -0
- package/src/{validate-code.ts → validate-code.js} +65 -259
- package/src/validate-code.js.map +1 -0
- package/src/validate-dtos.d.ts +41 -0
- package/src/{validate-dtos.ts → validate-dtos.js} +88 -215
- package/src/validate-dtos.js.map +1 -0
- package/src/validate-modified-files.d.ts +24 -0
- package/src/{validate-modified-files.ts → validate-modified-files.js} +46 -115
- package/src/validate-modified-files.js.map +1 -0
- package/src/validate-modified-methods.d.ts +30 -0
- package/src/{validate-modified-methods.ts → validate-modified-methods.js} +94 -196
- package/src/validate-modified-methods.js.map +1 -0
- package/src/validate-new-methods.d.ts +27 -0
- package/src/{validate-new-methods.ts → validate-new-methods.js} +63 -133
- package/src/validate-new-methods.js.map +1 -0
- package/src/validate-no-any-unknown.d.ts +41 -0
- package/src/{validate-no-any-unknown.ts → validate-no-any-unknown.js} +69 -146
- package/src/validate-no-any-unknown.js.map +1 -0
- package/src/validate-no-destructure.d.ts +51 -0
- package/src/{validate-no-destructure.ts → validate-no-destructure.js} +80 -166
- package/src/validate-no-destructure.js.map +1 -0
- package/src/validate-no-direct-api-resolver.d.ts +46 -0
- package/src/{validate-no-direct-api-resolver.ts → validate-no-direct-api-resolver.js} +112 -211
- package/src/validate-no-direct-api-resolver.js.map +1 -0
- package/src/validate-no-implicit-any.d.ts +36 -0
- package/src/{validate-no-implicit-any.ts → validate-no-implicit-any.js} +94 -141
- package/src/validate-no-implicit-any.js.map +1 -0
- package/src/validate-no-inline-types.d.ts +90 -0
- package/src/{validate-no-inline-types.ts → validate-no-inline-types.js} +93 -198
- package/src/validate-no-inline-types.js.map +1 -0
- package/src/validate-no-unmanaged-exceptions.d.ts +43 -0
- package/src/{validate-no-unmanaged-exceptions.ts → validate-no-unmanaged-exceptions.js} +71 -140
- package/src/validate-no-unmanaged-exceptions.js.map +1 -0
- package/src/validate-prisma-converters.d.ts +59 -0
- package/src/{validate-prisma-converters.ts → validate-prisma-converters.js} +120 -307
- package/src/validate-prisma-converters.js.map +1 -0
- package/src/validate-return-types.d.ts +28 -0
- package/src/{validate-return-types.ts → validate-return-types.js} +84 -168
- package/src/validate-return-types.js.map +1 -0
- package/LICENSE +0 -373
- package/jest.config.ts +0 -20
- package/project.json +0 -22
- package/src/cli.ts +0 -17
- package/src/from-shared-config.ts +0 -118
- package/tsconfig.json +0 -22
- package/tsconfig.lib.json +0 -10
- package/tsconfig.spec.json +0 -14
- /package/src/{index.ts → index.d.ts} +0 -0
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
"use strict";
|
|
1
2
|
/**
|
|
2
3
|
* Validate No Direct API in Resolver Executor
|
|
3
4
|
*
|
|
@@ -33,49 +34,23 @@
|
|
|
33
34
|
* // webpieces-disable no-direct-api-resolver -- [your justification]
|
|
34
35
|
* const myApi = inject(MyApi);
|
|
35
36
|
*/
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
export interface ValidateNoDirectApiResolverOptions {
|
|
46
|
-
mode?: NoDirectApiResolverMode;
|
|
47
|
-
disableAllowed?: boolean;
|
|
48
|
-
ignoreModifiedUntilEpoch?: number;
|
|
49
|
-
enforcePaths?: string[];
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
export interface ExecutorResult {
|
|
53
|
-
success: boolean;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
interface Violation {
|
|
57
|
-
file: string;
|
|
58
|
-
line: number;
|
|
59
|
-
column: number;
|
|
60
|
-
context: string;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
interface ViolationInfo {
|
|
64
|
-
line: number;
|
|
65
|
-
column: number;
|
|
66
|
-
context: string;
|
|
67
|
-
hasDisableComment: boolean;
|
|
68
|
-
}
|
|
69
|
-
|
|
37
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
38
|
+
exports.default = runValidator;
|
|
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");
|
|
70
45
|
/**
|
|
71
46
|
* Get changed TypeScript files between base and head (or working tree if head not specified).
|
|
72
47
|
*/
|
|
73
48
|
// webpieces-disable max-lines-new-methods -- Git command handling with untracked files requires multiple code paths
|
|
74
|
-
function getChangedTypeScriptFiles(workspaceRoot
|
|
49
|
+
function getChangedTypeScriptFiles(workspaceRoot, base, head) {
|
|
75
50
|
// eslint-disable-next-line @webpieces/no-unmanaged-exceptions
|
|
76
51
|
try {
|
|
77
52
|
const diffTarget = head ? `${base} ${head}` : base;
|
|
78
|
-
const output = execSync(`git diff --name-only ${diffTarget} -- '*.ts' '*.tsx'`, {
|
|
53
|
+
const output = (0, child_process_1.execSync)(`git diff --name-only ${diffTarget} -- '*.ts' '*.tsx'`, {
|
|
79
54
|
cwd: workspaceRoot,
|
|
80
55
|
encoding: 'utf-8',
|
|
81
56
|
});
|
|
@@ -83,11 +58,10 @@ function getChangedTypeScriptFiles(workspaceRoot: string, base: string, head?: s
|
|
|
83
58
|
.trim()
|
|
84
59
|
.split('\n')
|
|
85
60
|
.filter((f) => f && !f.includes('.spec.ts') && !f.includes('.test.ts'));
|
|
86
|
-
|
|
87
61
|
if (!head) {
|
|
88
62
|
// eslint-disable-next-line @webpieces/no-unmanaged-exceptions
|
|
89
63
|
try {
|
|
90
|
-
const untrackedOutput = execSync(`git ls-files --others --exclude-standard '*.ts' '*.tsx'`, {
|
|
64
|
+
const untrackedOutput = (0, child_process_1.execSync)(`git ls-files --others --exclude-standard '*.ts' '*.tsx'`, {
|
|
91
65
|
cwd: workspaceRoot,
|
|
92
66
|
encoding: 'utf-8',
|
|
93
67
|
});
|
|
@@ -97,23 +71,23 @@ function getChangedTypeScriptFiles(workspaceRoot: string, base: string, head?: s
|
|
|
97
71
|
.filter((f) => f && !f.includes('.spec.ts') && !f.includes('.test.ts'));
|
|
98
72
|
const allFiles = new Set([...changedFiles, ...untrackedFiles]);
|
|
99
73
|
return Array.from(allFiles);
|
|
100
|
-
}
|
|
74
|
+
}
|
|
75
|
+
catch (err) {
|
|
101
76
|
//const error = toError(err);
|
|
102
77
|
return changedFiles;
|
|
103
78
|
}
|
|
104
79
|
}
|
|
105
|
-
|
|
106
80
|
return changedFiles;
|
|
107
|
-
}
|
|
81
|
+
}
|
|
82
|
+
catch (err) {
|
|
108
83
|
//const error = toError(err);
|
|
109
84
|
return [];
|
|
110
85
|
}
|
|
111
86
|
}
|
|
112
|
-
|
|
113
87
|
/**
|
|
114
88
|
* Check if a line contains a webpieces-disable comment for no-direct-api-resolver.
|
|
115
89
|
*/
|
|
116
|
-
function hasDisableComment(lines
|
|
90
|
+
function hasDisableComment(lines, lineNumber) {
|
|
117
91
|
const startCheck = Math.max(0, lineNumber - 5);
|
|
118
92
|
for (let i = lineNumber - 2; i >= startCheck; i--) {
|
|
119
93
|
const line = lines[i]?.trim() ?? '';
|
|
@@ -126,60 +100,56 @@ function hasDisableComment(lines: string[], lineNumber: number): boolean {
|
|
|
126
100
|
}
|
|
127
101
|
return false;
|
|
128
102
|
}
|
|
129
|
-
|
|
130
103
|
/**
|
|
131
104
|
* Auto-detect the base branch by finding the merge-base with origin/main.
|
|
132
105
|
*/
|
|
133
|
-
function detectBase(workspaceRoot
|
|
106
|
+
function detectBase(workspaceRoot) {
|
|
134
107
|
// eslint-disable-next-line @webpieces/no-unmanaged-exceptions
|
|
135
108
|
try {
|
|
136
|
-
const mergeBase = execSync('git merge-base HEAD origin/main', {
|
|
109
|
+
const mergeBase = (0, child_process_1.execSync)('git merge-base HEAD origin/main', {
|
|
137
110
|
cwd: workspaceRoot,
|
|
138
111
|
encoding: 'utf-8',
|
|
139
112
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
140
113
|
}).trim();
|
|
141
|
-
|
|
142
114
|
if (mergeBase) {
|
|
143
115
|
return mergeBase;
|
|
144
116
|
}
|
|
145
|
-
}
|
|
117
|
+
}
|
|
118
|
+
catch (err) {
|
|
146
119
|
//const error = toError(err);
|
|
147
120
|
// eslint-disable-next-line @webpieces/no-unmanaged-exceptions
|
|
148
121
|
try {
|
|
149
|
-
const mergeBase = execSync('git merge-base HEAD main', {
|
|
122
|
+
const mergeBase = (0, child_process_1.execSync)('git merge-base HEAD main', {
|
|
150
123
|
cwd: workspaceRoot,
|
|
151
124
|
encoding: 'utf-8',
|
|
152
125
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
153
126
|
}).trim();
|
|
154
|
-
|
|
155
127
|
if (mergeBase) {
|
|
156
128
|
return mergeBase;
|
|
157
129
|
}
|
|
158
|
-
}
|
|
130
|
+
}
|
|
131
|
+
catch (err2) {
|
|
159
132
|
//const error2 = toError(err2);
|
|
160
133
|
// Ignore
|
|
161
134
|
}
|
|
162
135
|
}
|
|
163
136
|
return null;
|
|
164
137
|
}
|
|
165
|
-
|
|
166
138
|
/**
|
|
167
139
|
* Find inject(XxxApi) calls in *.routes.ts files.
|
|
168
140
|
* Flags any CallExpression where callee is `inject` and the first argument is an identifier ending with `Api`.
|
|
169
141
|
*/
|
|
170
|
-
function findDirectApiInjections(filePath
|
|
171
|
-
if (!filePath.endsWith('.routes.ts'))
|
|
172
|
-
|
|
142
|
+
function findDirectApiInjections(filePath, workspaceRoot, disableAllowed) {
|
|
143
|
+
if (!filePath.endsWith('.routes.ts'))
|
|
144
|
+
return [];
|
|
173
145
|
const fullPath = path.join(workspaceRoot, filePath);
|
|
174
|
-
if (!fs.existsSync(fullPath))
|
|
175
|
-
|
|
146
|
+
if (!fs.existsSync(fullPath))
|
|
147
|
+
return [];
|
|
176
148
|
const content = fs.readFileSync(fullPath, 'utf-8');
|
|
177
149
|
const fileLines = content.split('\n');
|
|
178
150
|
const sourceFile = ts.createSourceFile(filePath, content, ts.ScriptTarget.Latest, true);
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
function visit(node: ts.Node): void {
|
|
151
|
+
const violations = [];
|
|
152
|
+
function visit(node) {
|
|
183
153
|
// eslint-disable-next-line @webpieces/no-unmanaged-exceptions
|
|
184
154
|
try {
|
|
185
155
|
if (ts.isCallExpression(node)) {
|
|
@@ -193,45 +163,41 @@ function findDirectApiInjections(filePath: string, workspaceRoot: string, disabl
|
|
|
193
163
|
const line = pos.line + 1;
|
|
194
164
|
const column = pos.character + 1;
|
|
195
165
|
const disabled = hasDisableComment(fileLines, line);
|
|
196
|
-
|
|
197
166
|
if (!disableAllowed && disabled) {
|
|
198
167
|
violations.push({ line, column, context: `inject(${firstArg.text}) in route resolver`, hasDisableComment: false });
|
|
199
|
-
}
|
|
168
|
+
}
|
|
169
|
+
else {
|
|
200
170
|
violations.push({ line, column, context: `inject(${firstArg.text}) in route resolver`, hasDisableComment: disabled });
|
|
201
171
|
}
|
|
202
172
|
}
|
|
203
173
|
}
|
|
204
174
|
}
|
|
205
175
|
}
|
|
206
|
-
}
|
|
176
|
+
}
|
|
177
|
+
catch (err) {
|
|
207
178
|
//const error = toError(err);
|
|
208
179
|
// Skip nodes that cause errors during analysis
|
|
209
180
|
}
|
|
210
|
-
|
|
211
181
|
ts.forEachChild(node, visit);
|
|
212
182
|
}
|
|
213
|
-
|
|
214
183
|
visit(sourceFile);
|
|
215
184
|
return violations;
|
|
216
185
|
}
|
|
217
|
-
|
|
218
186
|
/**
|
|
219
187
|
* Find this.<field>.snapshot.data access patterns in *.component.ts files.
|
|
220
188
|
* Flags PropertyAccessExpression chains: this.<anything>.snapshot.data
|
|
221
189
|
*/
|
|
222
|
-
function findSnapshotDataAccess(filePath
|
|
223
|
-
if (!filePath.endsWith('.component.ts'))
|
|
224
|
-
|
|
190
|
+
function findSnapshotDataAccess(filePath, workspaceRoot, disableAllowed) {
|
|
191
|
+
if (!filePath.endsWith('.component.ts'))
|
|
192
|
+
return [];
|
|
225
193
|
const fullPath = path.join(workspaceRoot, filePath);
|
|
226
|
-
if (!fs.existsSync(fullPath))
|
|
227
|
-
|
|
194
|
+
if (!fs.existsSync(fullPath))
|
|
195
|
+
return [];
|
|
228
196
|
const content = fs.readFileSync(fullPath, 'utf-8');
|
|
229
197
|
const fileLines = content.split('\n');
|
|
230
198
|
const sourceFile = ts.createSourceFile(filePath, content, ts.ScriptTarget.Latest, true);
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
function visit(node: ts.Node): void {
|
|
199
|
+
const violations = [];
|
|
200
|
+
function visit(node) {
|
|
235
201
|
// eslint-disable-next-line @webpieces/no-unmanaged-exceptions
|
|
236
202
|
try {
|
|
237
203
|
// Looking for: this.<field>.snapshot.data
|
|
@@ -250,10 +216,10 @@ function findSnapshotDataAccess(filePath: string, workspaceRoot: string, disable
|
|
|
250
216
|
const line = pos.line + 1;
|
|
251
217
|
const column = pos.character + 1;
|
|
252
218
|
const disabled = hasDisableComment(fileLines, line);
|
|
253
|
-
|
|
254
219
|
if (!disableAllowed && disabled) {
|
|
255
220
|
violations.push({ line, column, context: `this.${fieldName}.snapshot.data in component`, hasDisableComment: false });
|
|
256
|
-
}
|
|
221
|
+
}
|
|
222
|
+
else {
|
|
257
223
|
violations.push({ line, column, context: `this.${fieldName}.snapshot.data in component`, hasDisableComment: disabled });
|
|
258
224
|
}
|
|
259
225
|
}
|
|
@@ -261,53 +227,42 @@ function findSnapshotDataAccess(filePath: string, workspaceRoot: string, disable
|
|
|
261
227
|
}
|
|
262
228
|
}
|
|
263
229
|
}
|
|
264
|
-
}
|
|
230
|
+
}
|
|
231
|
+
catch (err) {
|
|
265
232
|
//const error = toError(err);
|
|
266
233
|
// Skip nodes that cause errors during analysis
|
|
267
234
|
}
|
|
268
|
-
|
|
269
235
|
ts.forEachChild(node, visit);
|
|
270
236
|
}
|
|
271
|
-
|
|
272
237
|
visit(sourceFile);
|
|
273
238
|
return violations;
|
|
274
239
|
}
|
|
275
|
-
|
|
276
240
|
/**
|
|
277
241
|
* Find all violations in a file (both inject(Api) and snapshot.data patterns).
|
|
278
242
|
*/
|
|
279
|
-
function findViolationsInFile(filePath
|
|
243
|
+
function findViolationsInFile(filePath, workspaceRoot, disableAllowed) {
|
|
280
244
|
const apiViolations = findDirectApiInjections(filePath, workspaceRoot, disableAllowed);
|
|
281
245
|
const snapshotViolations = findSnapshotDataAccess(filePath, workspaceRoot, disableAllowed);
|
|
282
246
|
return [...apiViolations, ...snapshotViolations];
|
|
283
247
|
}
|
|
284
|
-
|
|
285
248
|
/**
|
|
286
249
|
* MODIFIED_CODE mode: Flag violations on changed lines in diff hunks.
|
|
287
250
|
*/
|
|
288
251
|
// webpieces-disable max-lines-new-methods -- File iteration with diff parsing and line filtering
|
|
289
|
-
function findViolationsForModifiedCode(
|
|
290
|
-
|
|
291
|
-
changedFiles: string[],
|
|
292
|
-
base: string,
|
|
293
|
-
head: string | undefined,
|
|
294
|
-
disableAllowed: boolean
|
|
295
|
-
): Violation[] {
|
|
296
|
-
const violations: Violation[] = [];
|
|
297
|
-
|
|
252
|
+
function findViolationsForModifiedCode(workspaceRoot, changedFiles, base, head, disableAllowed) {
|
|
253
|
+
const violations = [];
|
|
298
254
|
for (const file of changedFiles) {
|
|
299
|
-
const diff = getFileDiff(workspaceRoot, file, base, head);
|
|
300
|
-
const changedLines = getChangedLineNumbers(diff);
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
255
|
+
const diff = (0, diff_utils_1.getFileDiff)(workspaceRoot, file, base, head);
|
|
256
|
+
const changedLines = (0, diff_utils_1.getChangedLineNumbers)(diff);
|
|
257
|
+
if (changedLines.size === 0)
|
|
258
|
+
continue;
|
|
304
259
|
const allViolations = findViolationsInFile(file, workspaceRoot, disableAllowed);
|
|
305
|
-
|
|
306
260
|
for (const v of allViolations) {
|
|
307
|
-
if (disableAllowed && v.hasDisableComment)
|
|
261
|
+
if (disableAllowed && v.hasDisableComment)
|
|
262
|
+
continue;
|
|
308
263
|
// LINE-BASED: Only include if the violation is on a changed line
|
|
309
|
-
if (!changedLines.has(v.line))
|
|
310
|
-
|
|
264
|
+
if (!changedLines.has(v.line))
|
|
265
|
+
continue;
|
|
311
266
|
violations.push({
|
|
312
267
|
file,
|
|
313
268
|
line: v.line,
|
|
@@ -316,22 +271,18 @@ function findViolationsForModifiedCode(
|
|
|
316
271
|
});
|
|
317
272
|
}
|
|
318
273
|
}
|
|
319
|
-
|
|
320
274
|
return violations;
|
|
321
275
|
}
|
|
322
|
-
|
|
323
276
|
/**
|
|
324
277
|
* MODIFIED_FILES mode: Flag ALL violations in files that were modified.
|
|
325
278
|
*/
|
|
326
|
-
function findViolationsForModifiedFiles(workspaceRoot
|
|
327
|
-
const violations
|
|
328
|
-
|
|
279
|
+
function findViolationsForModifiedFiles(workspaceRoot, changedFiles, disableAllowed) {
|
|
280
|
+
const violations = [];
|
|
329
281
|
for (const file of changedFiles) {
|
|
330
282
|
const allViolations = findViolationsInFile(file, workspaceRoot, disableAllowed);
|
|
331
|
-
|
|
332
283
|
for (const v of allViolations) {
|
|
333
|
-
if (disableAllowed && v.hasDisableComment)
|
|
334
|
-
|
|
284
|
+
if (disableAllowed && v.hasDisableComment)
|
|
285
|
+
continue;
|
|
335
286
|
violations.push({
|
|
336
287
|
file,
|
|
337
288
|
line: v.line,
|
|
@@ -340,31 +291,21 @@ function findViolationsForModifiedFiles(workspaceRoot: string, changedFiles: str
|
|
|
340
291
|
});
|
|
341
292
|
}
|
|
342
293
|
}
|
|
343
|
-
|
|
344
294
|
return violations;
|
|
345
295
|
}
|
|
346
|
-
|
|
347
|
-
interface RangeInfo {
|
|
348
|
-
name: string;
|
|
349
|
-
startLine: number;
|
|
350
|
-
endLine: number;
|
|
351
|
-
}
|
|
352
|
-
|
|
353
296
|
/**
|
|
354
297
|
* Find route object ranges in *.routes.ts files.
|
|
355
298
|
* A route object is an ObjectLiteralExpression that contains (directly or in descendants)
|
|
356
299
|
* a `resolve` property. Returns the line range of each such top-level route object.
|
|
357
300
|
*/
|
|
358
|
-
function findRouteObjectRanges(filePath
|
|
301
|
+
function findRouteObjectRanges(filePath, workspaceRoot) {
|
|
359
302
|
const fullPath = path.join(workspaceRoot, filePath);
|
|
360
|
-
if (!fs.existsSync(fullPath))
|
|
361
|
-
|
|
303
|
+
if (!fs.existsSync(fullPath))
|
|
304
|
+
return [];
|
|
362
305
|
const content = fs.readFileSync(fullPath, 'utf-8');
|
|
363
306
|
const sourceFile = ts.createSourceFile(filePath, content, ts.ScriptTarget.Latest, true);
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
function hasResolveProperty(node: ts.Node): boolean {
|
|
307
|
+
const ranges = [];
|
|
308
|
+
function hasResolveProperty(node) {
|
|
368
309
|
if (ts.isPropertyAssignment(node) && ts.isIdentifier(node.name) && node.name.text === 'resolve') {
|
|
369
310
|
return true;
|
|
370
311
|
}
|
|
@@ -376,8 +317,7 @@ function findRouteObjectRanges(filePath: string, workspaceRoot: string): RangeIn
|
|
|
376
317
|
});
|
|
377
318
|
return found;
|
|
378
319
|
}
|
|
379
|
-
|
|
380
|
-
function visitTopLevel(node: ts.Node): void {
|
|
320
|
+
function visitTopLevel(node) {
|
|
381
321
|
if (ts.isObjectLiteralExpression(node) && hasResolveProperty(node)) {
|
|
382
322
|
const start = sourceFile.getLineAndCharacterOfPosition(node.getStart());
|
|
383
323
|
const end = sourceFile.getLineAndCharacterOfPosition(node.getEnd());
|
|
@@ -390,42 +330,39 @@ function findRouteObjectRanges(filePath: string, workspaceRoot: string): RangeIn
|
|
|
390
330
|
}
|
|
391
331
|
ts.forEachChild(node, visitTopLevel);
|
|
392
332
|
}
|
|
393
|
-
|
|
394
333
|
visitTopLevel(sourceFile);
|
|
395
334
|
return ranges;
|
|
396
335
|
}
|
|
397
|
-
|
|
398
336
|
/**
|
|
399
337
|
* Find method/function ranges in *.component.ts files.
|
|
400
338
|
* Returns ranges for class methods, function declarations, and arrow functions in variable declarations.
|
|
401
339
|
*/
|
|
402
|
-
function findMethodRanges(filePath
|
|
340
|
+
function findMethodRanges(filePath, workspaceRoot) {
|
|
403
341
|
const fullPath = path.join(workspaceRoot, filePath);
|
|
404
|
-
if (!fs.existsSync(fullPath))
|
|
405
|
-
|
|
342
|
+
if (!fs.existsSync(fullPath))
|
|
343
|
+
return [];
|
|
406
344
|
const content = fs.readFileSync(fullPath, 'utf-8');
|
|
407
345
|
const sourceFile = ts.createSourceFile(filePath, content, ts.ScriptTarget.Latest, true);
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
let
|
|
413
|
-
let startLine: number | undefined;
|
|
414
|
-
let endLine: number | undefined;
|
|
415
|
-
|
|
346
|
+
const ranges = [];
|
|
347
|
+
function visit(node) {
|
|
348
|
+
let methodName;
|
|
349
|
+
let startLine;
|
|
350
|
+
let endLine;
|
|
416
351
|
if (ts.isMethodDeclaration(node) && node.name) {
|
|
417
352
|
methodName = node.name.getText(sourceFile);
|
|
418
353
|
const start = sourceFile.getLineAndCharacterOfPosition(node.getStart());
|
|
419
354
|
const end = sourceFile.getLineAndCharacterOfPosition(node.getEnd());
|
|
420
355
|
startLine = start.line + 1;
|
|
421
356
|
endLine = end.line + 1;
|
|
422
|
-
}
|
|
357
|
+
}
|
|
358
|
+
else if (ts.isFunctionDeclaration(node) && node.name) {
|
|
423
359
|
methodName = node.name.getText(sourceFile);
|
|
424
360
|
const start = sourceFile.getLineAndCharacterOfPosition(node.getStart());
|
|
425
361
|
const end = sourceFile.getLineAndCharacterOfPosition(node.getEnd());
|
|
426
362
|
startLine = start.line + 1;
|
|
427
363
|
endLine = end.line + 1;
|
|
428
|
-
}
|
|
364
|
+
}
|
|
365
|
+
else if (ts.isArrowFunction(node)) {
|
|
429
366
|
if (ts.isVariableDeclaration(node.parent) && ts.isIdentifier(node.parent.name)) {
|
|
430
367
|
methodName = node.parent.name.getText(sourceFile);
|
|
431
368
|
const start = sourceFile.getLineAndCharacterOfPosition(node.getStart());
|
|
@@ -434,44 +371,31 @@ function findMethodRanges(filePath: string, workspaceRoot: string): RangeInfo[]
|
|
|
434
371
|
endLine = end.line + 1;
|
|
435
372
|
}
|
|
436
373
|
}
|
|
437
|
-
|
|
438
374
|
if (methodName && startLine !== undefined && endLine !== undefined) {
|
|
439
375
|
ranges.push({ name: methodName, startLine, endLine });
|
|
440
376
|
}
|
|
441
|
-
|
|
442
377
|
ts.forEachChild(node, visit);
|
|
443
378
|
}
|
|
444
|
-
|
|
445
379
|
visit(sourceFile);
|
|
446
380
|
return ranges;
|
|
447
381
|
}
|
|
448
|
-
|
|
449
382
|
/**
|
|
450
383
|
* NEW_AND_MODIFIED_METHODS mode: Flag violations in new/modified method/route scopes.
|
|
451
384
|
* - For *.routes.ts: If any line in a route object is changed, flag all inject(XxxApi) violations in that route
|
|
452
385
|
* - For *.component.ts: If a method is new/modified, flag all snapshot.data violations in that method
|
|
453
386
|
*/
|
|
454
387
|
// webpieces-disable max-lines-new-methods -- Method-scoped validation with route objects and component methods
|
|
455
|
-
function findViolationsForModifiedMethods(
|
|
456
|
-
|
|
457
|
-
changedFiles: string[],
|
|
458
|
-
base: string,
|
|
459
|
-
head: string | undefined,
|
|
460
|
-
disableAllowed: boolean
|
|
461
|
-
): Violation[] {
|
|
462
|
-
const violations: Violation[] = [];
|
|
463
|
-
|
|
388
|
+
function findViolationsForModifiedMethods(workspaceRoot, changedFiles, base, head, disableAllowed) {
|
|
389
|
+
const violations = [];
|
|
464
390
|
for (const file of changedFiles) {
|
|
465
|
-
const diff = getFileDiff(workspaceRoot, file, base, head);
|
|
466
|
-
const changedLines = getChangedLineNumbers(diff);
|
|
467
|
-
const newMethodNames = findNewMethodSignaturesInDiff(diff);
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
391
|
+
const diff = (0, diff_utils_1.getFileDiff)(workspaceRoot, file, base, head);
|
|
392
|
+
const changedLines = (0, diff_utils_1.getChangedLineNumbers)(diff);
|
|
393
|
+
const newMethodNames = (0, diff_utils_1.findNewMethodSignaturesInDiff)(diff);
|
|
394
|
+
if (changedLines.size === 0 && newMethodNames.size === 0)
|
|
395
|
+
continue;
|
|
471
396
|
if (file.endsWith('.routes.ts')) {
|
|
472
397
|
const routeRanges = findRouteObjectRanges(file, workspaceRoot);
|
|
473
398
|
const allViolations = findDirectApiInjections(file, workspaceRoot, disableAllowed);
|
|
474
|
-
|
|
475
399
|
for (const range of routeRanges) {
|
|
476
400
|
let rangeHasChanges = false;
|
|
477
401
|
for (let line = range.startLine; line <= range.endLine; line++) {
|
|
@@ -480,10 +404,11 @@ function findViolationsForModifiedMethods(
|
|
|
480
404
|
break;
|
|
481
405
|
}
|
|
482
406
|
}
|
|
483
|
-
if (!rangeHasChanges)
|
|
484
|
-
|
|
407
|
+
if (!rangeHasChanges)
|
|
408
|
+
continue;
|
|
485
409
|
for (const v of allViolations) {
|
|
486
|
-
if (disableAllowed && v.hasDisableComment)
|
|
410
|
+
if (disableAllowed && v.hasDisableComment)
|
|
411
|
+
continue;
|
|
487
412
|
if (v.line >= range.startLine && v.line <= range.endLine) {
|
|
488
413
|
violations.push({
|
|
489
414
|
file,
|
|
@@ -494,10 +419,10 @@ function findViolationsForModifiedMethods(
|
|
|
494
419
|
}
|
|
495
420
|
}
|
|
496
421
|
}
|
|
497
|
-
}
|
|
422
|
+
}
|
|
423
|
+
else if (file.endsWith('.component.ts')) {
|
|
498
424
|
const methodRanges = findMethodRanges(file, workspaceRoot);
|
|
499
425
|
const allViolations = findSnapshotDataAccess(file, workspaceRoot, disableAllowed);
|
|
500
|
-
|
|
501
426
|
for (const range of methodRanges) {
|
|
502
427
|
const isNewMethod = newMethodNames.has(range.name);
|
|
503
428
|
let rangeHasChanges = false;
|
|
@@ -509,10 +434,11 @@ function findViolationsForModifiedMethods(
|
|
|
509
434
|
}
|
|
510
435
|
}
|
|
511
436
|
}
|
|
512
|
-
if (!isNewMethod && !rangeHasChanges)
|
|
513
|
-
|
|
437
|
+
if (!isNewMethod && !rangeHasChanges)
|
|
438
|
+
continue;
|
|
514
439
|
for (const v of allViolations) {
|
|
515
|
-
if (disableAllowed && v.hasDisableComment)
|
|
440
|
+
if (disableAllowed && v.hasDisableComment)
|
|
441
|
+
continue;
|
|
516
442
|
if (v.line >= range.startLine && v.line <= range.endLine) {
|
|
517
443
|
violations.push({
|
|
518
444
|
file,
|
|
@@ -525,15 +451,13 @@ function findViolationsForModifiedMethods(
|
|
|
525
451
|
}
|
|
526
452
|
}
|
|
527
453
|
}
|
|
528
|
-
|
|
529
454
|
return violations;
|
|
530
455
|
}
|
|
531
|
-
|
|
532
456
|
/**
|
|
533
457
|
* Report violations to console.
|
|
534
458
|
*/
|
|
535
459
|
// webpieces-disable max-lines-new-methods -- Console output with examples and escape hatch information
|
|
536
|
-
function reportViolations(violations
|
|
460
|
+
function reportViolations(violations, mode, disableAllowed) {
|
|
537
461
|
console.error('');
|
|
538
462
|
console.error('\u274c Direct API usage in resolvers or snapshot.data in components found!');
|
|
539
463
|
console.error('');
|
|
@@ -553,17 +477,16 @@ function reportViolations(violations: Violation[], mode: NoDirectApiResolverMode
|
|
|
553
477
|
console.error(' GOOD (in *.component.ts):');
|
|
554
478
|
console.error(' this.myService.data$.subscribe(data => ...)');
|
|
555
479
|
console.error('');
|
|
556
|
-
|
|
557
480
|
for (const v of violations) {
|
|
558
481
|
console.error(` \u274c ${v.file}:${v.line}:${v.column}`);
|
|
559
482
|
console.error(` ${v.context}`);
|
|
560
483
|
}
|
|
561
484
|
console.error('');
|
|
562
|
-
|
|
563
485
|
if (disableAllowed) {
|
|
564
486
|
console.error(' Escape hatch (use sparingly):');
|
|
565
487
|
console.error(' // webpieces-disable no-direct-api-resolver -- [your reason]');
|
|
566
|
-
}
|
|
488
|
+
}
|
|
489
|
+
else {
|
|
567
490
|
console.error(' Escape hatch: DISABLED (disableAllowed: false)');
|
|
568
491
|
console.error(' Disable comments are ignored. Fix the pattern directly.');
|
|
569
492
|
}
|
|
@@ -571,12 +494,11 @@ function reportViolations(violations: Violation[], mode: NoDirectApiResolverMode
|
|
|
571
494
|
console.error(` Current mode: ${mode}`);
|
|
572
495
|
console.error('');
|
|
573
496
|
}
|
|
574
|
-
|
|
575
497
|
/**
|
|
576
498
|
* Resolve mode considering ignoreModifiedUntilEpoch override.
|
|
577
499
|
* When active, downgrades to OFF. When expired, logs a warning.
|
|
578
500
|
*/
|
|
579
|
-
function resolveMode(normalMode
|
|
501
|
+
function resolveMode(normalMode, epoch) {
|
|
580
502
|
if (epoch === undefined || normalMode === 'OFF') {
|
|
581
503
|
return normalMode;
|
|
582
504
|
}
|
|
@@ -589,88 +511,67 @@ function resolveMode(normalMode: NoDirectApiResolverMode, epoch: number | undefi
|
|
|
589
511
|
}
|
|
590
512
|
return normalMode;
|
|
591
513
|
}
|
|
592
|
-
|
|
593
514
|
/**
|
|
594
515
|
* Filter changed files to only those under enforcePaths (if configured).
|
|
595
516
|
*/
|
|
596
|
-
function filterByEnforcePaths(changedFiles
|
|
517
|
+
function filterByEnforcePaths(changedFiles, enforcePaths) {
|
|
597
518
|
if (!enforcePaths || enforcePaths.length === 0) {
|
|
598
519
|
return changedFiles;
|
|
599
520
|
}
|
|
600
|
-
return changedFiles.filter((file) =>
|
|
601
|
-
enforcePaths.some((prefix) => file.startsWith(prefix))
|
|
602
|
-
);
|
|
521
|
+
return changedFiles.filter((file) => enforcePaths.some((prefix) => file.startsWith(prefix)));
|
|
603
522
|
}
|
|
604
|
-
|
|
605
523
|
/**
|
|
606
524
|
* Filter to only relevant Angular files (*.routes.ts and *.component.ts).
|
|
607
525
|
*/
|
|
608
|
-
function filterRelevantFiles(changedFiles
|
|
609
|
-
return changedFiles.filter((file) =>
|
|
610
|
-
file.endsWith('.routes.ts') || file.endsWith('.component.ts')
|
|
611
|
-
);
|
|
526
|
+
function filterRelevantFiles(changedFiles) {
|
|
527
|
+
return changedFiles.filter((file) => file.endsWith('.routes.ts') || file.endsWith('.component.ts'));
|
|
612
528
|
}
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
options: ValidateNoDirectApiResolverOptions,
|
|
616
|
-
workspaceRoot: string
|
|
617
|
-
): Promise<ExecutorResult> {
|
|
618
|
-
const mode: NoDirectApiResolverMode = resolveMode(options.mode ?? 'OFF', options.ignoreModifiedUntilEpoch);
|
|
529
|
+
async function runValidator(options, workspaceRoot) {
|
|
530
|
+
const mode = resolveMode(options.mode ?? 'OFF', options.ignoreModifiedUntilEpoch);
|
|
619
531
|
const disableAllowed = options.disableAllowed ?? true;
|
|
620
|
-
|
|
621
532
|
if (mode === 'OFF') {
|
|
622
533
|
console.log('\n\u23ed\ufe0f Skipping no-direct-api-resolver validation (mode: OFF)');
|
|
623
534
|
console.log('');
|
|
624
535
|
return { success: true };
|
|
625
536
|
}
|
|
626
|
-
|
|
627
537
|
console.log('\n\ud83d\udccf Validating No Direct API in Resolver\n');
|
|
628
538
|
console.log(` Mode: ${mode}`);
|
|
629
|
-
|
|
630
539
|
let base = process.env['NX_BASE'];
|
|
631
540
|
const head = process.env['NX_HEAD'];
|
|
632
|
-
|
|
633
541
|
if (!base) {
|
|
634
542
|
base = detectBase(workspaceRoot) ?? undefined;
|
|
635
|
-
|
|
636
543
|
if (!base) {
|
|
637
544
|
console.log('\n\u23ed\ufe0f Skipping no-direct-api-resolver validation (could not detect base branch)');
|
|
638
545
|
console.log('');
|
|
639
546
|
return { success: true };
|
|
640
547
|
}
|
|
641
548
|
}
|
|
642
|
-
|
|
643
549
|
console.log(` Base: ${base}`);
|
|
644
550
|
console.log(` Head: ${head ?? 'working tree (includes uncommitted changes)'}`);
|
|
645
551
|
console.log('');
|
|
646
|
-
|
|
647
552
|
const allChangedFiles = getChangedTypeScriptFiles(workspaceRoot, base, head);
|
|
648
553
|
const scopedFiles = filterByEnforcePaths(allChangedFiles, options.enforcePaths);
|
|
649
554
|
const changedFiles = filterRelevantFiles(scopedFiles);
|
|
650
|
-
|
|
651
555
|
if (changedFiles.length === 0) {
|
|
652
556
|
console.log('\u2705 No relevant Angular files changed (*.routes.ts, *.component.ts)');
|
|
653
557
|
return { success: true };
|
|
654
558
|
}
|
|
655
|
-
|
|
656
559
|
console.log(`\ud83d\udcc2 Checking ${changedFiles.length} changed file(s)...`);
|
|
657
|
-
|
|
658
|
-
let violations: Violation[] = [];
|
|
659
|
-
|
|
560
|
+
let violations = [];
|
|
660
561
|
if (mode === 'MODIFIED_CODE') {
|
|
661
562
|
violations = findViolationsForModifiedCode(workspaceRoot, changedFiles, base, head, disableAllowed);
|
|
662
|
-
}
|
|
563
|
+
}
|
|
564
|
+
else if (mode === 'NEW_AND_MODIFIED_METHODS') {
|
|
663
565
|
violations = findViolationsForModifiedMethods(workspaceRoot, changedFiles, base, head, disableAllowed);
|
|
664
|
-
}
|
|
566
|
+
}
|
|
567
|
+
else if (mode === 'MODIFIED_FILES') {
|
|
665
568
|
violations = findViolationsForModifiedFiles(workspaceRoot, changedFiles, disableAllowed);
|
|
666
569
|
}
|
|
667
|
-
|
|
668
570
|
if (violations.length === 0) {
|
|
669
571
|
console.log('\u2705 No direct API resolver patterns found');
|
|
670
572
|
return { success: true };
|
|
671
573
|
}
|
|
672
|
-
|
|
673
574
|
reportViolations(violations, mode, disableAllowed);
|
|
674
|
-
|
|
675
575
|
return { success: false };
|
|
676
576
|
}
|
|
577
|
+
//# sourceMappingURL=validate-no-direct-api-resolver.js.map
|