ng-checkup 0.0.0-stage → 0.1.0
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/LICENSE +21 -0
- package/README.md +107 -2
- package/dist/checks/change-detection.d.ts +22 -0
- package/dist/checks/change-detection.js +99 -0
- package/dist/checks/change-detection.js.map +1 -0
- package/dist/checks/circular-deps.d.ts +13 -0
- package/dist/checks/circular-deps.js +43 -0
- package/dist/checks/circular-deps.js.map +1 -0
- package/dist/checks/components.d.ts +19 -0
- package/dist/checks/components.js +97 -0
- package/dist/checks/components.js.map +1 -0
- package/dist/checks/context.d.ts +51 -0
- package/dist/checks/context.js +53 -0
- package/dist/checks/context.js.map +1 -0
- package/dist/checks/deprecated-apis.d.ts +14 -0
- package/dist/checks/deprecated-apis.js +141 -0
- package/dist/checks/deprecated-apis.js.map +1 -0
- package/dist/checks/state.d.ts +13 -0
- package/dist/checks/state.js +72 -0
- package/dist/checks/state.js.map +1 -0
- package/dist/checks/testing.d.ts +13 -0
- package/dist/checks/testing.js +98 -0
- package/dist/checks/testing.js.map +1 -0
- package/dist/checks/type-safety.d.ts +14 -0
- package/dist/checks/type-safety.js +105 -0
- package/dist/checks/type-safety.js.map +1 -0
- package/dist/checks/version.d.ts +12 -0
- package/dist/checks/version.js +107 -0
- package/dist/checks/version.js.map +1 -0
- package/dist/cli.d.ts +28 -0
- package/dist/cli.js +166 -0
- package/dist/cli.js.map +1 -0
- package/dist/deprecations.d.ts +30 -0
- package/dist/deprecations.js +279 -0
- package/dist/deprecations.js.map +1 -0
- package/dist/files.d.ts +29 -0
- package/dist/files.js +92 -0
- package/dist/files.js.map +1 -0
- package/dist/graph.d.ts +44 -0
- package/dist/graph.js +151 -0
- package/dist/graph.js.map +1 -0
- package/dist/index.d.ts +39 -0
- package/dist/index.js +126 -0
- package/dist/index.js.map +1 -0
- package/dist/report/html.d.ts +14 -0
- package/dist/report/html.js +210 -0
- package/dist/report/html.js.map +1 -0
- package/dist/report/markdown.d.ts +8 -0
- package/dist/report/markdown.js +55 -0
- package/dist/report/markdown.js.map +1 -0
- package/dist/report/summary.d.ts +45 -0
- package/dist/report/summary.js +65 -0
- package/dist/report/summary.js.map +1 -0
- package/dist/report/terminal.d.ts +17 -0
- package/dist/report/terminal.js +70 -0
- package/dist/report/terminal.js.map +1 -0
- package/dist/scan.d.ts +119 -0
- package/dist/scan.js +270 -0
- package/dist/scan.js.map +1 -0
- package/dist/types.d.ts +95 -0
- package/dist/types.js +9 -0
- package/dist/types.js.map +1 -0
- package/dist/workspace.d.ts +70 -0
- package/dist/workspace.js +155 -0
- package/dist/workspace.js.map +1 -0
- package/package.json +60 -4
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { isSpecFile } from '../files.js';
|
|
2
|
+
import { formatCount, plural } from './context.js';
|
|
3
|
+
/**
|
|
4
|
+
* Deprecated Angular and NgRx APIs the code uses, as the installed packages
|
|
5
|
+
* mark them, plus the template-side use of the deprecated structural
|
|
6
|
+
* directives (`*ngIf`, `*ngFor`, `[ngSwitch]`), which no import reveals because
|
|
7
|
+
* templates reach them through `CommonModule`.
|
|
8
|
+
*
|
|
9
|
+
* A symbol deprecated two or more majors before the installed one is `high`:
|
|
10
|
+
* Angular removes deprecated APIs no earlier than two majors after
|
|
11
|
+
* deprecating them, so from then on any `ng update` may be the one that breaks
|
|
12
|
+
* the build.
|
|
13
|
+
*/
|
|
14
|
+
export function deprecatedApisCheck(context) {
|
|
15
|
+
const { deprecations, workspace } = context;
|
|
16
|
+
const legacyTemplates = context.templates.filter((template) => template.firstLegacyLine !== undefined);
|
|
17
|
+
const legacyUses = legacyTemplates.reduce((sum, template) => sum + template.stats.ngIf + template.stats.ngFor + template.stats.ngSwitch, 0);
|
|
18
|
+
if (deprecations.size === 0) {
|
|
19
|
+
return {
|
|
20
|
+
id: 'deprecated-apis',
|
|
21
|
+
title: 'Deprecated APIs',
|
|
22
|
+
grade: 'info',
|
|
23
|
+
summary: 'Dependencies are not installed, so deprecations could not be read from the Angular packages.',
|
|
24
|
+
metrics: [{ label: 'Legacy template directives', value: formatCount(legacyUses) }],
|
|
25
|
+
findings: [
|
|
26
|
+
{
|
|
27
|
+
severity: 'info',
|
|
28
|
+
message: 'Run `npm install` (or your package manager) and re-run for this check',
|
|
29
|
+
detail: 'Deprecations are read from the `@deprecated` tags of the installed `@angular/*` and `@ngrx/*` type definitions, so they always match the version the project actually uses.',
|
|
30
|
+
},
|
|
31
|
+
],
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
const usages = new Map();
|
|
35
|
+
const add = (module, name, info, ref) => {
|
|
36
|
+
const key = `${module}#${name}`;
|
|
37
|
+
const usage = usages.get(key) ?? { module, name, info, refs: [] };
|
|
38
|
+
if (!usage.refs.some((existing) => existing.path === ref.path))
|
|
39
|
+
usage.refs.push(ref);
|
|
40
|
+
usages.set(key, usage);
|
|
41
|
+
};
|
|
42
|
+
for (const file of context.sources) {
|
|
43
|
+
for (const fact of file.imports) {
|
|
44
|
+
const entry = deprecations.get(fact.specifier);
|
|
45
|
+
if (!entry)
|
|
46
|
+
continue;
|
|
47
|
+
for (const binding of fact.bindings) {
|
|
48
|
+
const info = entry.symbols.get(binding.imported);
|
|
49
|
+
if (info)
|
|
50
|
+
add(fact.specifier, binding.imported, info, { path: file.path, line: fact.line });
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
for (const [key, lines] of file.memberAccesses) {
|
|
54
|
+
const [module, member] = splitKey(key);
|
|
55
|
+
const info = deprecations.get(module)?.members.get(member);
|
|
56
|
+
if (info)
|
|
57
|
+
add(module, member, info, { path: file.path, line: lines[0] });
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
const current = workspace.angular.major;
|
|
61
|
+
const isOverdue = (info) => current !== undefined && info.since !== undefined && current - Number.parseInt(info.since, 10) >= 2;
|
|
62
|
+
const sorted = [...usages.values()];
|
|
63
|
+
const groups = groupByDeprecation(sorted).sort((left, right) => Number(isOverdue(right.info)) - Number(isOverdue(left.info)) || right.refs.length - left.refs.length);
|
|
64
|
+
const findings = groups.map((group) => ({
|
|
65
|
+
severity: isOverdue(group.info) ? 'high' : 'medium',
|
|
66
|
+
message: `${group.names.map((name) => `\`${name}\``).join(', ')} from ${group.module}, used in ${plural(group.refs.length, 'file')}${testShare(group.refs)}`,
|
|
67
|
+
detail: describe(group.info),
|
|
68
|
+
files: group.refs.slice(0, context.maxExamples),
|
|
69
|
+
count: group.refs.length,
|
|
70
|
+
}));
|
|
71
|
+
const ngIf = deprecations.get('@angular/common')?.symbols.get('NgIf');
|
|
72
|
+
if (ngIf && legacyUses > 0) {
|
|
73
|
+
findings.push({
|
|
74
|
+
severity: isOverdue(ngIf) ? 'high' : 'medium',
|
|
75
|
+
message: `${plural(legacyUses, 'use')} of \`*ngIf\`, \`*ngFor\` and \`[ngSwitch]\` in ${plural(legacyTemplates.length, 'template')}`,
|
|
76
|
+
detail: `${describe(ngIf)} \`ng generate @angular/core:control-flow\` migrates most templates to \`@if\`, \`@for\` and \`@switch\` automatically; what it leaves behind is usually an \`ngFor\` with a custom \`trackBy\` or a template reference it cannot follow.`,
|
|
77
|
+
files: legacyTemplates.slice(0, context.maxExamples).map((template) => ({ path: template.path, line: template.firstLegacyLine })),
|
|
78
|
+
count: legacyTemplates.length,
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
const affectedFiles = new Set(sorted.flatMap((usage) => usage.refs.map((ref) => ref.path)));
|
|
82
|
+
const grade = findings.length === 0 ? 'good' : findings.some((finding) => finding.severity === 'high') ? 'poor' : 'fair';
|
|
83
|
+
return {
|
|
84
|
+
id: 'deprecated-apis',
|
|
85
|
+
title: 'Deprecated APIs',
|
|
86
|
+
grade,
|
|
87
|
+
summary: findings.length === 0
|
|
88
|
+
? current !== undefined && current < context.latestAngular.major
|
|
89
|
+
? `Nothing deprecated as of Angular ${current} is in use; APIs deprecated in later versions surface at each upgrade step.`
|
|
90
|
+
: 'No deprecated Angular or NgRx API in use.'
|
|
91
|
+
: `${plural(usages.size, 'deprecated symbol')} in ${plural(affectedFiles.size, 'file')}${ngIf && legacyUses > 0 ? ', plus deprecated control flow in templates' : ''}.`,
|
|
92
|
+
metrics: [
|
|
93
|
+
{ label: 'Deprecated symbols', value: usages.size },
|
|
94
|
+
{ label: 'Files affected', value: formatCount(affectedFiles.size) },
|
|
95
|
+
{ label: 'Legacy template directives', value: formatCount(legacyUses), hint: ngIf ? 'deprecated' : 'not deprecated in this version' },
|
|
96
|
+
],
|
|
97
|
+
findings,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Merges usages that share a module, a version and a message. A package
|
|
102
|
+
* deprecated as a whole tags every export with the same sentence
|
|
103
|
+
* (`@angular/animations`: `trigger`, `state`, `style`, `animate`, …), and one
|
|
104
|
+
* finding per export would fill the top priorities with a single decision.
|
|
105
|
+
*/
|
|
106
|
+
function groupByDeprecation(usages) {
|
|
107
|
+
const groups = new Map();
|
|
108
|
+
for (const usage of usages) {
|
|
109
|
+
const key = `${usage.module}\u0000${usage.info.since ?? ''}\u0000${usage.info.message}`;
|
|
110
|
+
const group = groups.get(key) ?? { module: usage.module, names: [], info: usage.info, refs: [] };
|
|
111
|
+
group.names.push(usage.name);
|
|
112
|
+
for (const ref of usage.refs)
|
|
113
|
+
if (!group.refs.some((existing) => existing.path === ref.path))
|
|
114
|
+
group.refs.push(ref);
|
|
115
|
+
groups.set(key, group);
|
|
116
|
+
}
|
|
117
|
+
return [...groups.values()];
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* " (all in tests)" or " (12 in tests)" when some of the files are specs, since
|
|
121
|
+
* a deprecated testing module is a smaller job than one in shipped code.
|
|
122
|
+
* Empty when none are.
|
|
123
|
+
*/
|
|
124
|
+
function testShare(refs) {
|
|
125
|
+
const inTests = refs.filter((ref) => isSpecFile(ref.path)).length;
|
|
126
|
+
if (inTests === 0)
|
|
127
|
+
return '';
|
|
128
|
+
return inTests === refs.length ? ' (all in tests)' : ` (${formatCount(inTests)} in tests)`;
|
|
129
|
+
}
|
|
130
|
+
/** `module#Owner.member` split at the `#`. Module names never contain one. */
|
|
131
|
+
function splitKey(key) {
|
|
132
|
+
const separator = key.indexOf('#');
|
|
133
|
+
return [key.slice(0, separator), key.slice(separator + 1)];
|
|
134
|
+
}
|
|
135
|
+
/** "Deprecated since 20.0: Use the `@if` block instead." in the form the reports show. */
|
|
136
|
+
function describe(info) {
|
|
137
|
+
const since = info.since ? `Deprecated since ${info.since}` : 'Deprecated';
|
|
138
|
+
const message = info.message.replace(/\.?$/, '.');
|
|
139
|
+
return info.message ? `${since}: ${message}` : `${since}.`;
|
|
140
|
+
}
|
|
141
|
+
//# sourceMappingURL=deprecated-apis.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"deprecated-apis.js","sourceRoot":"","sources":["../../src/checks/deprecated-apis.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { CheckResult } from '../types.js';
|
|
2
|
+
import { type AnalysisContext } from './context.js';
|
|
3
|
+
/**
|
|
4
|
+
* How application state is held: classic NgRx, the NgRx signal store,
|
|
5
|
+
* Angular signals, or services wrapping a `BehaviorSubject`.
|
|
6
|
+
*
|
|
7
|
+
* Never graded `poor`. Classic NgRx is supported and not deprecated, and a
|
|
8
|
+
* `BehaviorSubject` service is a legitimate pattern, so neither is a defect;
|
|
9
|
+
* the check reports how far the codebase has moved towards signals, which is
|
|
10
|
+
* the direction zoneless change detection rewards, and how big the remaining
|
|
11
|
+
* move would be.
|
|
12
|
+
*/
|
|
13
|
+
export declare function stateCheck(context: AnalysisContext): CheckResult;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { applicationFiles, countCalls, filesImporting, formatCount, plural, verb } from './context.js';
|
|
2
|
+
/** The classic, action-based NgRx packages. */
|
|
3
|
+
const CLASSIC_NGRX = ['@ngrx/store', '@ngrx/effects', '@ngrx/entity', '@ngrx/router-store', '@ngrx/component-store'];
|
|
4
|
+
/**
|
|
5
|
+
* How application state is held: classic NgRx, the NgRx signal store,
|
|
6
|
+
* Angular signals, or services wrapping a `BehaviorSubject`.
|
|
7
|
+
*
|
|
8
|
+
* Never graded `poor`. Classic NgRx is supported and not deprecated, and a
|
|
9
|
+
* `BehaviorSubject` service is a legitimate pattern, so neither is a defect;
|
|
10
|
+
* the check reports how far the codebase has moved towards signals, which is
|
|
11
|
+
* the direction zoneless change detection rewards, and how big the remaining
|
|
12
|
+
* move would be.
|
|
13
|
+
*/
|
|
14
|
+
export function stateCheck(context) {
|
|
15
|
+
const application = applicationFiles(context);
|
|
16
|
+
const classicFiles = filesImporting(application, CLASSIC_NGRX);
|
|
17
|
+
const signalStoreFiles = filesImporting(application, ['@ngrx/signals']);
|
|
18
|
+
const signals = countCalls(application, '@angular/core#signal', '@angular/core#computed', '@angular/core#linkedSignal');
|
|
19
|
+
const toSignal = countCalls(application, '@angular/core/rxjs-interop#toSignal');
|
|
20
|
+
const subjectServices = application.filter((file) => file.injectables > 0 && (file.constructions.get('rxjs#BehaviorSubject')?.length ?? 0) > 0);
|
|
21
|
+
const reducers = countCalls(application, '@ngrx/store#createReducer');
|
|
22
|
+
const effects = countCalls(application, '@ngrx/effects#createEffect');
|
|
23
|
+
const selectors = countCalls(application, '@ngrx/store#createSelector', '@ngrx/store#createFeatureSelector');
|
|
24
|
+
const actions = countCalls(application, '@ngrx/store#createAction', '@ngrx/store#createActionGroup');
|
|
25
|
+
const removedEffectDecorators = countCalls(application, '@ngrx/effects#Effect');
|
|
26
|
+
const findings = [];
|
|
27
|
+
if (removedEffectDecorators > 0) {
|
|
28
|
+
findings.push({
|
|
29
|
+
severity: 'high',
|
|
30
|
+
message: `${plural(removedEffectDecorators, '`@Effect()` decorator')} from NgRx`,
|
|
31
|
+
detail: 'The `@Effect()` decorator was removed in NgRx 15. These have to become `createEffect()` before NgRx can be upgraded past 14.',
|
|
32
|
+
count: removedEffectDecorators,
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
if (classicFiles.length > 0) {
|
|
36
|
+
findings.push({
|
|
37
|
+
severity: 'low',
|
|
38
|
+
message: `Classic NgRx in ${plural(classicFiles.length, 'file')}: ${plural(reducers, 'reducer')}, ${plural(effects, 'effect')}, ${plural(selectors, 'selector')}, ${plural(actions, 'action creator')}`,
|
|
39
|
+
detail: 'Classic NgRx is supported, so this is a choice rather than a defect. Moving a feature to `@ngrx/signals` removes the action, reducer and effect boilerplate and fits zoneless change detection without the async pipe; the cost is a rewrite per feature, and the action log that the devtools show goes with it.',
|
|
40
|
+
files: classicFiles.slice(0, context.maxExamples),
|
|
41
|
+
count: classicFiles.length,
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
if (subjectServices.length > 0) {
|
|
45
|
+
findings.push({
|
|
46
|
+
severity: 'low',
|
|
47
|
+
message: `${plural(subjectServices.length, 'service')} ${verb(subjectServices.length, 'holds', 'hold')} state in a \`BehaviorSubject\``,
|
|
48
|
+
detail: 'Each is a candidate for a `signal()`-based service: templates read it without the async pipe, `computed()` replaces `combineLatest` chains, and zoneless change detection tracks it automatically. `toObservable()` keeps existing RxJS consumers working during the move.',
|
|
49
|
+
files: subjectServices.slice(0, context.maxExamples).map((file) => ({ path: file.path, line: file.constructions.get('rxjs#BehaviorSubject')?.[0] })),
|
|
50
|
+
count: subjectServices.length,
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
const usesSignals = signals + signalStoreFiles.length > 0;
|
|
54
|
+
const nothingToGrade = classicFiles.length === 0 && signalStoreFiles.length === 0 && signals === 0 && subjectServices.length === 0;
|
|
55
|
+
const grade = nothingToGrade ? 'info' : removedEffectDecorators > 0 ? 'poor' : usesSignals && classicFiles.length === 0 ? 'good' : 'fair';
|
|
56
|
+
return {
|
|
57
|
+
id: 'state',
|
|
58
|
+
title: 'State management',
|
|
59
|
+
grade,
|
|
60
|
+
summary: nothingToGrade
|
|
61
|
+
? 'No state-management library or signal usage found.'
|
|
62
|
+
: `${classicFiles.length > 0 ? `Classic NgRx in ${plural(classicFiles.length, 'file')}` : 'No classic NgRx'}, ${formatCount(signals)} signal()/computed() calls, ${plural(signalStoreFiles.length, 'signal-store file')}.`,
|
|
63
|
+
metrics: [
|
|
64
|
+
{ label: 'Classic NgRx files', value: formatCount(classicFiles.length) },
|
|
65
|
+
{ label: 'Signal store files', value: formatCount(signalStoreFiles.length) },
|
|
66
|
+
{ label: 'signal() / computed()', value: formatCount(signals), hint: `${formatCount(toSignal)} toSignal()` },
|
|
67
|
+
{ label: 'BehaviorSubject services', value: formatCount(subjectServices.length) },
|
|
68
|
+
],
|
|
69
|
+
findings,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
//# sourceMappingURL=state.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"state.js","sourceRoot":"","sources":["../../src/checks/state.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { CheckResult } from '../types.js';
|
|
2
|
+
import { type AnalysisContext } from './context.js';
|
|
3
|
+
/**
|
|
4
|
+
* The unit-test runner, how much of the code has a spec next to it, and the
|
|
5
|
+
* end-to-end setup.
|
|
6
|
+
*
|
|
7
|
+
* "Has a spec next to it" is a proxy for coverage, not coverage: it counts a
|
|
8
|
+
* component or service as tested when `x.spec.ts` sits beside `x.ts`. It needs
|
|
9
|
+
* no test run and no coverage report, which is what lets this tool run in a
|
|
10
|
+
* minute on a repository it has never seen; real coverage is lower or higher
|
|
11
|
+
* depending on what those specs assert.
|
|
12
|
+
*/
|
|
13
|
+
export declare function testingCheck(context: AnalysisContext): CheckResult;
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { applicationFiles, formatCount, percent, plural, specFiles } from './context.js';
|
|
4
|
+
/**
|
|
5
|
+
* The unit-test runner, how much of the code has a spec next to it, and the
|
|
6
|
+
* end-to-end setup.
|
|
7
|
+
*
|
|
8
|
+
* "Has a spec next to it" is a proxy for coverage, not coverage: it counts a
|
|
9
|
+
* component or service as tested when `x.spec.ts` sits beside `x.ts`. It needs
|
|
10
|
+
* no test run and no coverage report, which is what lets this tool run in a
|
|
11
|
+
* minute on a repository it has never seen; real coverage is lower or higher
|
|
12
|
+
* depending on what those specs assert.
|
|
13
|
+
*/
|
|
14
|
+
export function testingCheck(context) {
|
|
15
|
+
const { workspace } = context;
|
|
16
|
+
const runners = detectRunners(context);
|
|
17
|
+
const specs = specFiles(context);
|
|
18
|
+
const testable = applicationFiles(context).filter((file) => file.components.length > 0 || file.injectables > 0);
|
|
19
|
+
const specPaths = new Set(specs.map((file) => file.path));
|
|
20
|
+
const untested = testable.filter((file) => !specPaths.has(file.path.replace(/\.ts$/, '.spec.ts')));
|
|
21
|
+
const coverage = percent(testable.length - untested.length, testable.length);
|
|
22
|
+
const jasmineCalls = specs.reduce((sum, file) => sum + file.jasmineCalls, 0);
|
|
23
|
+
const protractor = 'protractor' in workspace.dependencies || workspace.targets.some((target) => target.builder.endsWith(':protractor'));
|
|
24
|
+
const e2e = protractor ? 'Protractor' : '@playwright/test' in workspace.dependencies ? 'Playwright' : 'cypress' in workspace.dependencies ? 'Cypress' : undefined;
|
|
25
|
+
const findings = [];
|
|
26
|
+
if (runners.has('Karma')) {
|
|
27
|
+
findings.push({
|
|
28
|
+
severity: 'high',
|
|
29
|
+
message: 'Unit tests run on Karma, which is deprecated',
|
|
30
|
+
detail: `Karma was deprecated in 2023 and receives no further development. Vitest is the Angular CLI's default runner since v21 (\`@angular/build:unit-test\`). ${specs.length === 0
|
|
31
|
+
? 'With no specs written yet, moving costs only the configuration, and the first specs can be written for Vitest directly.'
|
|
32
|
+
: `Moving means replacing ${plural(jasmineCalls, 'Jasmine-specific call')} (\`spyOn\`, \`jasmine.*\`) with their Vitest equivalents across ${plural(specs.length, 'spec file')}.`}`,
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
if (specs.length === 0 && testable.length > 0) {
|
|
36
|
+
findings.push({ severity: 'high', message: 'No unit tests found', detail: `${plural(testable.length, 'component and service file')} without a single spec. Every refactoring and upgrade step is verified by hand.` });
|
|
37
|
+
}
|
|
38
|
+
else if (testable.length > 0 && coverage < 60) {
|
|
39
|
+
findings.push({
|
|
40
|
+
severity: coverage < 30 ? 'medium' : 'low',
|
|
41
|
+
message: `${100 - coverage}% of components and services have no spec file next to them`,
|
|
42
|
+
detail: 'Counted by file name (`x.spec.ts` beside `x.ts`), so a spec kept elsewhere is missed and a spec that asserts nothing still counts. It is a lower bound on where an upgrade would be verified by hand.',
|
|
43
|
+
files: untested.slice(0, context.maxExamples).map((file) => ({ path: file.path })),
|
|
44
|
+
count: untested.length,
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
if (protractor) {
|
|
48
|
+
findings.push({ severity: 'high', message: 'End-to-end tests use Protractor, which is end of life', detail: 'Protractor reached end of life in 2023 and gets no fixes, including for new browser versions. Playwright and Cypress are the usual replacements.' });
|
|
49
|
+
}
|
|
50
|
+
else if (!e2e && testable.length > 0) {
|
|
51
|
+
findings.push({ severity: 'info', message: 'No end-to-end test setup found', detail: 'Neither Playwright nor Cypress is a dependency. A handful of smoke tests over the main flows is the cheapest safety net for an upgrade.' });
|
|
52
|
+
}
|
|
53
|
+
const grade = testable.length === 0 ? 'info' : runners.has('Karma') || protractor || specs.length === 0 ? 'poor' : coverage < 30 ? 'fair' : 'good';
|
|
54
|
+
return {
|
|
55
|
+
id: 'testing',
|
|
56
|
+
title: 'Testing',
|
|
57
|
+
grade,
|
|
58
|
+
summary: `${runners.size > 0 ? [...runners].join(' + ') : 'No unit-test runner detected'}, ${plural(specs.length, 'spec file')}, ${coverage}% of components and services with a spec.`,
|
|
59
|
+
metrics: [
|
|
60
|
+
{ label: 'Unit-test runner', value: runners.size > 0 ? [...runners].join(' + ') : 'none' },
|
|
61
|
+
{ label: 'Spec files', value: formatCount(specs.length) },
|
|
62
|
+
{ label: 'With a spec', value: `${coverage}%`, hint: `${formatCount(testable.length - untested.length)} of ${plural(testable.length, 'component/service file')}` },
|
|
63
|
+
{ label: 'End-to-end', value: e2e ?? 'none' },
|
|
64
|
+
],
|
|
65
|
+
findings,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Runners in use, from the test targets' builders first and the dependencies
|
|
70
|
+
* second. Both are needed: an Nx or CLI workspace names the runner in its
|
|
71
|
+
* builder, while a hand-rolled setup only shows it as a dependency and a config
|
|
72
|
+
* file. A repository can report two during a migration.
|
|
73
|
+
*/
|
|
74
|
+
function detectRunners(context) {
|
|
75
|
+
const { workspace } = context;
|
|
76
|
+
const runners = new Set();
|
|
77
|
+
for (const target of workspace.targets) {
|
|
78
|
+
const builder = target.builder;
|
|
79
|
+
if (builder.endsWith(':karma'))
|
|
80
|
+
runners.add('Karma');
|
|
81
|
+
else if (builder.endsWith(':jest') || builder === '@angular-builders/jest:run')
|
|
82
|
+
runners.add('Jest');
|
|
83
|
+
else if (builder.endsWith(':vitest') || builder === '@nx/vite:test' || builder === '@nx/vitest:test')
|
|
84
|
+
runners.add('Vitest');
|
|
85
|
+
else if (builder === '@angular/build:unit-test')
|
|
86
|
+
runners.add(target.options['runner'] === 'karma' ? 'Karma' : 'Vitest');
|
|
87
|
+
}
|
|
88
|
+
if (runners.size === 0) {
|
|
89
|
+
if ('karma' in workspace.dependencies || existsSync(join(workspace.root, 'karma.conf.js')))
|
|
90
|
+
runners.add('Karma');
|
|
91
|
+
if ('jest' in workspace.dependencies)
|
|
92
|
+
runners.add('Jest');
|
|
93
|
+
if ('vitest' in workspace.dependencies)
|
|
94
|
+
runners.add('Vitest');
|
|
95
|
+
}
|
|
96
|
+
return runners;
|
|
97
|
+
}
|
|
98
|
+
//# sourceMappingURL=testing.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"testing.js","sourceRoot":"","sources":["../../src/checks/testing.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { CheckResult } from '../types.js';
|
|
2
|
+
import { type AnalysisContext } from './context.js';
|
|
3
|
+
/**
|
|
4
|
+
* How much of the code the compiler actually checks: the strictness flags of
|
|
5
|
+
* the application tsconfig, and the escape hatches the code uses to get
|
|
6
|
+
* around them.
|
|
7
|
+
*
|
|
8
|
+
* `strictNullChecks` and `strictTemplates` carry the grade. They are the two
|
|
9
|
+
* flags whose absence lets the most common runtime errors through (an
|
|
10
|
+
* `undefined` dereference, a mistyped template binding), and both are the
|
|
11
|
+
* ones large codebases most often have switched off because turning them on
|
|
12
|
+
* is a campaign rather than an edit.
|
|
13
|
+
*/
|
|
14
|
+
export declare function typeSafetyCheck(context: AnalysisContext): CheckResult;
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { applicationFiles, formatCount, plural } from './context.js';
|
|
2
|
+
/**
|
|
3
|
+
* Explicit `any` per 1 000 lines of application code above which the count is
|
|
4
|
+
* a `medium` finding rather than a `low` one. Two per thousand lines is roughly
|
|
5
|
+
* one per large component: past that, `any` is a habit rather than an exception.
|
|
6
|
+
*/
|
|
7
|
+
const ANY_DENSITY_THRESHOLD = 2;
|
|
8
|
+
/**
|
|
9
|
+
* How much of the code the compiler actually checks: the strictness flags of
|
|
10
|
+
* the application tsconfig, and the escape hatches the code uses to get
|
|
11
|
+
* around them.
|
|
12
|
+
*
|
|
13
|
+
* `strictNullChecks` and `strictTemplates` carry the grade. They are the two
|
|
14
|
+
* flags whose absence lets the most common runtime errors through (an
|
|
15
|
+
* `undefined` dereference, a mistyped template binding), and both are the
|
|
16
|
+
* ones large codebases most often have switched off because turning them on
|
|
17
|
+
* is a campaign rather than an edit.
|
|
18
|
+
*/
|
|
19
|
+
export function typeSafetyCheck(context) {
|
|
20
|
+
const options = context.appCompilerOptions;
|
|
21
|
+
const angular = context.angularCompilerOptions;
|
|
22
|
+
const hasConfig = context.workspace.appTsconfig !== undefined;
|
|
23
|
+
const strictNullChecks = options.strictNullChecks ?? options.strict ?? false;
|
|
24
|
+
const noImplicitAny = options.noImplicitAny ?? options.strict ?? false;
|
|
25
|
+
const strictTemplates = angular['strictTemplates'] === true;
|
|
26
|
+
const application = applicationFiles(context);
|
|
27
|
+
const linesOfCode = application.reduce((sum, file) => sum + file.linesOfCode, 0);
|
|
28
|
+
const anyCount = application.reduce((sum, file) => sum + file.anyLines.length, 0);
|
|
29
|
+
const anyDensity = linesOfCode === 0 ? 0 : (anyCount / linesOfCode) * 1000;
|
|
30
|
+
const nonNull = application.reduce((sum, file) => sum + file.nonNullLines.length, 0);
|
|
31
|
+
const suppressions = context.sources.reduce((sum, file) => sum + file.typeSuppressions, 0);
|
|
32
|
+
const templateAnyCasts = context.templates.reduce((sum, template) => sum + template.stats.anyCasts, 0);
|
|
33
|
+
const untypedForms = application.filter((file) => file.imports.some((fact) => fact.specifier === '@angular/forms' && fact.bindings.some((binding) => binding.imported.startsWith('Untyped'))));
|
|
34
|
+
const findings = [];
|
|
35
|
+
if (hasConfig && !strictNullChecks) {
|
|
36
|
+
findings.push({
|
|
37
|
+
severity: 'high',
|
|
38
|
+
message: '`strictNullChecks` is off',
|
|
39
|
+
detail: 'Every value that may be `null` or `undefined` is typed as if it never is, so the most common runtime error in an Angular app is invisible to the compiler. In a large codebase switching it on is staged: a separate tsconfig that checks one folder at a time, with a count that may only go down.',
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
if (hasConfig && !strictTemplates) {
|
|
43
|
+
findings.push({
|
|
44
|
+
severity: 'high',
|
|
45
|
+
message: '`strictTemplates` is off',
|
|
46
|
+
detail: 'Template bindings are only loosely type-checked: a wrong input type, a misspelled output payload or a possibly-undefined value passed to a component compiles and fails at runtime. Turning it on typically surfaces a batch of real bugs alongside the type noise.',
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
if (hasConfig && !noImplicitAny) {
|
|
50
|
+
findings.push({ severity: 'medium', message: '`noImplicitAny` is off', detail: 'An unannotated parameter silently becomes `any`, and no lint rule that counts the word `any` can see it.' });
|
|
51
|
+
}
|
|
52
|
+
if (anyCount > 0) {
|
|
53
|
+
const examples = application.filter((file) => file.anyLines.length > 0).sort((left, right) => right.anyLines.length - left.anyLines.length);
|
|
54
|
+
findings.push({
|
|
55
|
+
severity: anyDensity > ANY_DENSITY_THRESHOLD ? 'medium' : 'low',
|
|
56
|
+
message: `${formatCount(anyCount)} explicit \`any\` in application code (${anyDensity.toFixed(1)} per 1 000 lines)`,
|
|
57
|
+
detail: 'Files with the most are listed first. Each `any` switches type checking off for everything that flows through it.',
|
|
58
|
+
files: examples.slice(0, context.maxExamples).map((file) => ({ path: file.path, line: file.anyLines[0] })),
|
|
59
|
+
count: examples.length,
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
if (nonNull > 0) {
|
|
63
|
+
findings.push({ severity: 'low', message: `${plural(nonNull, 'non-null assertion')} (\`x!\`) in application code`, detail: 'Each tells the compiler a value cannot be `null` without proving it; under `strictNullChecks` they are where the remaining null errors hide.' });
|
|
64
|
+
}
|
|
65
|
+
if (suppressions > 0) {
|
|
66
|
+
findings.push({ severity: 'low', message: `${plural(suppressions, '`@ts-ignore` / `@ts-expect-error` comment')}`, detail: 'Each switches off checking for the next line, including errors nobody has seen yet.' });
|
|
67
|
+
}
|
|
68
|
+
if (templateAnyCasts > 0) {
|
|
69
|
+
findings.push({ severity: 'low', message: `${plural(templateAnyCasts, '`$any()` cast')} in templates`, detail: 'The template equivalent of `as any`; usually a sign the component exposes a type the template cannot narrow.' });
|
|
70
|
+
}
|
|
71
|
+
if (untypedForms.length > 0) {
|
|
72
|
+
findings.push({
|
|
73
|
+
severity: 'low',
|
|
74
|
+
message: `${plural(untypedForms.length, 'file')} ${untypedForms.length === 1 ? 'imports' : 'import'} \`UntypedForm*\` classes`,
|
|
75
|
+
detail: 'Typed reactive forms exist since Angular 14. The `Untyped*` aliases were the escape hatch `ng update` inserted so the upgrade would compile; each is a form whose values are still `any`.',
|
|
76
|
+
files: untypedForms.slice(0, context.maxExamples).map((file) => ({ path: file.path })),
|
|
77
|
+
count: untypedForms.length,
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
const grade = !hasConfig
|
|
81
|
+
? 'info'
|
|
82
|
+
: !strictNullChecks && !strictTemplates
|
|
83
|
+
? 'poor'
|
|
84
|
+
: !strictNullChecks || !strictTemplates || !noImplicitAny || anyDensity > ANY_DENSITY_THRESHOLD
|
|
85
|
+
? 'fair'
|
|
86
|
+
: 'good';
|
|
87
|
+
const on = (flag) => (flag ? 'on' : 'off');
|
|
88
|
+
return {
|
|
89
|
+
id: 'type-safety',
|
|
90
|
+
title: 'Type safety',
|
|
91
|
+
grade,
|
|
92
|
+
summary: hasConfig
|
|
93
|
+
? `strictNullChecks ${on(strictNullChecks)}, strictTemplates ${on(strictTemplates)}, ${formatCount(anyCount)} explicit any.`
|
|
94
|
+
: 'No application tsconfig found.',
|
|
95
|
+
metrics: [
|
|
96
|
+
{ label: 'strictNullChecks', value: on(strictNullChecks) },
|
|
97
|
+
{ label: 'strictTemplates', value: on(strictTemplates) },
|
|
98
|
+
{ label: 'noImplicitAny', value: on(noImplicitAny) },
|
|
99
|
+
{ label: 'Explicit any', value: formatCount(anyCount), hint: `${anyDensity.toFixed(1)} per 1 000 lines` },
|
|
100
|
+
{ label: 'Non-null assertions', value: formatCount(nonNull) },
|
|
101
|
+
],
|
|
102
|
+
findings,
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
//# sourceMappingURL=type-safety.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"type-safety.js","sourceRoot":"","sources":["../../src/checks/type-safety.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { CheckResult } from '../types.js';
|
|
2
|
+
import { type AnalysisContext } from './context.js';
|
|
3
|
+
/**
|
|
4
|
+
* Where the codebase stands against the newest Angular release, and whether
|
|
5
|
+
* its toolchain is current.
|
|
6
|
+
*
|
|
7
|
+
* Graded on the support window: Angular releases a major roughly every six
|
|
8
|
+
* months and supports each for 18 (6 active, 12 LTS), so the newest three
|
|
9
|
+
* majors are supported and anything older gets no security fixes. `ng update`
|
|
10
|
+
* moves one major at a time, so the gap is also the number of upgrade steps.
|
|
11
|
+
*/
|
|
12
|
+
export declare function versionCheck(context: AnalysisContext): CheckResult;
|