@rsdk/depdoc.cli 6.0.0-next.42 → 6.0.0-next.43
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/DEPDOC_MODEL.md +22 -11
- package/__tests__/compatibility.test.ts +74 -15
- package/__tests__/config-validation.test.ts +16 -1
- package/__tests__/engine.test.ts +77 -0
- package/__tests__/fixtures/imports/config/aliases.json +8 -0
- package/__tests__/fixtures/imports/src/aliases.ts +4 -0
- package/__tests__/fixtures/imports/src/virtual.ts +3 -0
- package/__tests__/fixtures/imports/tsconfig.build.json +3 -0
- package/__tests__/fixtures/imports/tsconfig.json +8 -0
- package/__tests__/imports.test.ts +31 -0
- package/dist/collectors/tsconfig-aliases.d.ts +1 -0
- package/dist/collectors/tsconfig-aliases.js +40 -0
- package/dist/collectors/tsconfig-aliases.js.map +1 -0
- package/dist/collectors/workspaces.d.ts +1 -1
- package/dist/collectors/workspaces.js +12 -5
- package/dist/collectors/workspaces.js.map +1 -1
- package/dist/lib/imports.d.ts +6 -3
- package/dist/lib/imports.js +24 -8
- package/dist/lib/imports.js.map +1 -1
- package/dist/model/config-validation.d.ts +1 -0
- package/dist/model/config-validation.js +15 -1
- package/dist/model/config-validation.js.map +1 -1
- package/dist/model/diagnostics.d.ts +1 -0
- package/dist/model/diagnostics.js +67 -0
- package/dist/model/diagnostics.js.map +1 -1
- package/dist/model/engine.js +1 -0
- package/dist/model/engine.js.map +1 -1
- package/dist/model/types.d.ts +2 -1
- package/dist/model/types.js.map +1 -1
- package/dist/runner.js +1 -1
- package/dist/runner.js.map +1 -1
- package/package.json +2 -2
- package/src/collectors/tsconfig-aliases.ts +45 -0
- package/src/collectors/workspaces.ts +17 -4
- package/src/lib/imports.ts +57 -6
- package/src/model/config-validation.ts +17 -1
- package/src/model/diagnostics.ts +105 -1
- package/src/model/engine.ts +7 -1
- package/src/model/types.ts +2 -0
- package/src/runner.ts +5 -1
package/src/model/diagnostics.ts
CHANGED
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
getExpectedSection,
|
|
13
13
|
} from './expected';
|
|
14
14
|
import { isExternal } from './placement';
|
|
15
|
-
import { getEffectiveRule, isWorkspaceRole } from './rules';
|
|
15
|
+
import { getEffectiveRule, isWorkspaceRole, matchesPatterns } from './rules';
|
|
16
16
|
import type {
|
|
17
17
|
DependencyRule,
|
|
18
18
|
DependencyViolation,
|
|
@@ -42,6 +42,57 @@ function hasCollectedPackageUsage(workspace: WorkspaceFacts): boolean {
|
|
|
42
42
|
return workspace.sourceUsage.size > 0 || workspace.dtsImports.size > 0;
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
+
function getRuleMatches(rule: DependencyRule): string[] {
|
|
46
|
+
return Array.isArray(rule.match) ? rule.match : [rule.match];
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function getConcreteRuleMatches(rule: DependencyRule): string[] {
|
|
50
|
+
return getRuleMatches(rule).filter((match) => !match.includes('*'));
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function getWorkspacePatternLabel(rule: DependencyRule): string {
|
|
54
|
+
if (!rule.workspace) return '<all>';
|
|
55
|
+
|
|
56
|
+
return Array.isArray(rule.workspace)
|
|
57
|
+
? rule.workspace.join(', ')
|
|
58
|
+
: rule.workspace;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function getRuleTargetWorkspaces(
|
|
62
|
+
rule: DependencyRule,
|
|
63
|
+
workspaces: WorkspaceFacts[],
|
|
64
|
+
): WorkspaceFacts[] {
|
|
65
|
+
if (!rule.workspace) return workspaces;
|
|
66
|
+
|
|
67
|
+
return workspaces.filter(
|
|
68
|
+
(workspace) =>
|
|
69
|
+
matchesPatterns(workspace.name, rule.workspace!) ||
|
|
70
|
+
matchesPatterns(workspace.location, rule.workspace!),
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function expectedHasDependency(
|
|
75
|
+
expected: ExpectedWorkspace | undefined,
|
|
76
|
+
depIdent: string,
|
|
77
|
+
): boolean {
|
|
78
|
+
if (!expected) return false;
|
|
79
|
+
|
|
80
|
+
return SECTIONS.some((section) => expected.sections[section].has(depIdent));
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function workspaceHasDependencySignal(
|
|
84
|
+
workspace: WorkspaceFacts,
|
|
85
|
+
expected: ExpectedWorkspace | undefined,
|
|
86
|
+
depIdent: string,
|
|
87
|
+
): boolean {
|
|
88
|
+
return (
|
|
89
|
+
workspace.sourceUsage.has(depIdent) ||
|
|
90
|
+
workspace.dtsImports.has(depIdent) ||
|
|
91
|
+
collectActualDependencyNames(workspace).has(depIdent) ||
|
|
92
|
+
expectedHasDependency(expected, depIdent)
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
|
|
45
96
|
export function compareManifest(
|
|
46
97
|
expected: ExpectedWorkspace,
|
|
47
98
|
violations: DependencyViolation[],
|
|
@@ -330,6 +381,59 @@ export function validateBasicShape(
|
|
|
330
381
|
}
|
|
331
382
|
}
|
|
332
383
|
|
|
384
|
+
export function collectStaleRuleViolations(
|
|
385
|
+
workspaces: WorkspaceFacts[],
|
|
386
|
+
expectedByLocation: Map<string, ExpectedWorkspace>,
|
|
387
|
+
rules: DependencyRule[],
|
|
388
|
+
): DependencyViolation[] {
|
|
389
|
+
const violations: DependencyViolation[] = [];
|
|
390
|
+
|
|
391
|
+
rules.forEach((rule) => {
|
|
392
|
+
const concreteMatches = getConcreteRuleMatches(rule);
|
|
393
|
+
if (concreteMatches.length === 0) return;
|
|
394
|
+
|
|
395
|
+
const targetWorkspaces = getRuleTargetWorkspaces(rule, workspaces);
|
|
396
|
+
const workspaceLabel = getWorkspacePatternLabel(rule);
|
|
397
|
+
|
|
398
|
+
for (const depIdent of concreteMatches) {
|
|
399
|
+
if (targetWorkspaces.length === 0) {
|
|
400
|
+
violations.push({
|
|
401
|
+
code: 'stale-rule',
|
|
402
|
+
workspace: '<root>',
|
|
403
|
+
workspaceLocation: '.',
|
|
404
|
+
dependency: depIdent,
|
|
405
|
+
message:
|
|
406
|
+
`${depIdent} has a workspace-scoped depdoc.yml rule, but no workspace matches ${workspaceLabel}`,
|
|
407
|
+
});
|
|
408
|
+
continue;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
const isUsed = targetWorkspaces.some((workspace) =>
|
|
412
|
+
workspaceHasDependencySignal(
|
|
413
|
+
workspace,
|
|
414
|
+
expectedByLocation.get(workspace.location),
|
|
415
|
+
depIdent,
|
|
416
|
+
),
|
|
417
|
+
);
|
|
418
|
+
if (isUsed) continue;
|
|
419
|
+
|
|
420
|
+
const workspace = rule.workspace ? targetWorkspaces[0]! : workspaces[0]!;
|
|
421
|
+
|
|
422
|
+
violations.push({
|
|
423
|
+
code: 'stale-rule',
|
|
424
|
+
workspace: rule.workspace ? getWorkspaceLabel(workspace) : '<root>',
|
|
425
|
+
workspaceLocation: rule.workspace ? workspace.location : '.',
|
|
426
|
+
dependency: depIdent,
|
|
427
|
+
message: rule.workspace
|
|
428
|
+
? `${depIdent} has a depdoc.yml rule scoped to ${workspaceLabel}, but no matching usage or declaration was found`
|
|
429
|
+
: `${depIdent} has a depdoc.yml rule, but no matching usage or declaration was found`,
|
|
430
|
+
});
|
|
431
|
+
}
|
|
432
|
+
});
|
|
433
|
+
|
|
434
|
+
return violations;
|
|
435
|
+
}
|
|
436
|
+
|
|
333
437
|
export function dedupeViolations(
|
|
334
438
|
violations: DependencyViolation[],
|
|
335
439
|
): DependencyViolation[] {
|
package/src/model/engine.ts
CHANGED
|
@@ -5,7 +5,12 @@
|
|
|
5
5
|
* diagnostics. It must not read files, execute Yarn, or inspect `node_modules`;
|
|
6
6
|
* those concerns belong to collectors and the runner.
|
|
7
7
|
*/
|
|
8
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
collectStaleRuleViolations,
|
|
10
|
+
compareManifest,
|
|
11
|
+
dedupeViolations,
|
|
12
|
+
validateBasicShape,
|
|
13
|
+
} from './diagnostics';
|
|
9
14
|
import { emptySections } from './expected';
|
|
10
15
|
import { propagatePeers } from './peer-propagation';
|
|
11
16
|
import {
|
|
@@ -111,6 +116,7 @@ export function deriveDependencyModel(
|
|
|
111
116
|
violations.push(
|
|
112
117
|
...collectUnconstrainedVersionViolations(workspaces, expected, rules),
|
|
113
118
|
);
|
|
119
|
+
violations.push(...collectStaleRuleViolations(workspaces, expected, rules));
|
|
114
120
|
|
|
115
121
|
return {
|
|
116
122
|
violations: dedupeViolations(violations),
|
package/src/model/types.ts
CHANGED
|
@@ -25,6 +25,7 @@ export interface DependencyRule {
|
|
|
25
25
|
|
|
26
26
|
export interface DependencyModelConfig {
|
|
27
27
|
version?: number;
|
|
28
|
+
ignoredImports?: string[];
|
|
28
29
|
rules?: DependencyRule[];
|
|
29
30
|
doctor?: DoctorConfig;
|
|
30
31
|
}
|
|
@@ -55,6 +56,7 @@ export interface DependencyViolation {
|
|
|
55
56
|
| 'root-only'
|
|
56
57
|
| 'root-only-usage'
|
|
57
58
|
| 'unconstrained-version'
|
|
59
|
+
| 'stale-rule'
|
|
58
60
|
| 'mirror'
|
|
59
61
|
| 'stale'
|
|
60
62
|
| 'dist-missing';
|
package/src/runner.ts
CHANGED
|
@@ -33,7 +33,11 @@ export function runDependencyModel(
|
|
|
33
33
|
const config = loadConfig(rootDir, options.depdocYamlPath);
|
|
34
34
|
const rules = config.rules ?? [];
|
|
35
35
|
const withDts = options.withDts === true;
|
|
36
|
-
const contexts = loadWorkspaces(
|
|
36
|
+
const contexts = loadWorkspaces(
|
|
37
|
+
rootDir,
|
|
38
|
+
withDts,
|
|
39
|
+
config.ignoredImports ?? [],
|
|
40
|
+
);
|
|
37
41
|
const packageExtensions = loadPackageExtensions(rootDir);
|
|
38
42
|
const workspaceNames = new Set(
|
|
39
43
|
contexts.filter((ws) => !ws.isRoot).map((ws) => ws.name),
|