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,210 @@
|
|
|
1
|
+
import { formatCount, plural } from '../checks/context.js';
|
|
2
|
+
import { formatDuration, formatLocation, gradeLabel, kindLabel, overallScore, remainingCount, SCORE_EXPLANATION, severityLabel, topPriorities } from './summary.js';
|
|
3
|
+
/**
|
|
4
|
+
* Renders the report as one self-contained HTML file.
|
|
5
|
+
*
|
|
6
|
+
* Self-contained is a hard requirement, not a style choice: the report is
|
|
7
|
+
* meant to be mailed to a client, attached to a ticket or opened on a machine
|
|
8
|
+
* without network, and it describes a private codebase. So there is no
|
|
9
|
+
* external font, script, stylesheet or image, and nothing on the page phones
|
|
10
|
+
* home. It follows the system light/dark preference and prints cleanly, since
|
|
11
|
+
* "save as PDF" is how many readers will pass it on.
|
|
12
|
+
*/
|
|
13
|
+
export function renderHtml(report) {
|
|
14
|
+
const { project } = report;
|
|
15
|
+
const score = overallScore(report);
|
|
16
|
+
const priorities = topPriorities(report);
|
|
17
|
+
const meta = [kindLabel(project.kind), project.angularVersion ? `Angular ${project.angularVersion}` : undefined, plural(project.sourceFiles, 'TypeScript file'), plural(project.templateFiles, 'template'), plural(project.linesOfCode, 'line') + ' of code']
|
|
18
|
+
.filter(Boolean)
|
|
19
|
+
.join(' · ');
|
|
20
|
+
return `<!doctype html>
|
|
21
|
+
<html lang="en">
|
|
22
|
+
<head>
|
|
23
|
+
<meta charset="utf-8">
|
|
24
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
25
|
+
<meta name="generator" content="${escape(`${report.tool.name} ${report.tool.version}`)}">
|
|
26
|
+
<title>Angular checkup: ${escape(project.name)}</title>
|
|
27
|
+
<style>${STYLES}</style>
|
|
28
|
+
</head>
|
|
29
|
+
<body>
|
|
30
|
+
<main>
|
|
31
|
+
<header class="masthead">
|
|
32
|
+
<div>
|
|
33
|
+
<p class="eyebrow">Angular checkup</p>
|
|
34
|
+
<h1>${escape(project.name)}</h1>
|
|
35
|
+
<p class="meta">${escape(meta)}</p>
|
|
36
|
+
<p class="meta">Generated ${escape(report.generatedAt.slice(0, 10))}</p>
|
|
37
|
+
</div>
|
|
38
|
+
${score === undefined ? '' : scoreRing(score)}
|
|
39
|
+
</header>
|
|
40
|
+
|
|
41
|
+
<section aria-labelledby="overview">
|
|
42
|
+
<h2 id="overview" class="section-title">Overview</h2>
|
|
43
|
+
<div class="cards">
|
|
44
|
+
${report.checks.map(overviewCard).join('\n ')}
|
|
45
|
+
</div>
|
|
46
|
+
</section>
|
|
47
|
+
|
|
48
|
+
${priorities.length === 0
|
|
49
|
+
? ''
|
|
50
|
+
: `<section aria-labelledby="priorities">
|
|
51
|
+
<h2 id="priorities" class="section-title">Top priorities</h2>
|
|
52
|
+
<ol class="priorities">
|
|
53
|
+
${priorities
|
|
54
|
+
.map(({ check, finding }) => `<li><span class="chip sev-${finding.severity}">${severityLabel(finding.severity)}</span><span><strong>${inline(finding.message)}</strong> <a href="#${check.id}" class="area">${escape(check.title)}</a></span></li>`)
|
|
55
|
+
.join('\n ')}
|
|
56
|
+
</ol>
|
|
57
|
+
</section>`}
|
|
58
|
+
|
|
59
|
+
${report.checks.map(checkSection).join('\n ')}
|
|
60
|
+
|
|
61
|
+
<footer>
|
|
62
|
+
<p>${score === undefined ? '' : `Score: ${escape(SCORE_EXPLANATION)} `}File counts are static and need no build; deprecations come from the installed packages' own <code>@deprecated</code> tags.</p>
|
|
63
|
+
<p>Generated by ${escape(report.tool.name)} ${escape(report.tool.version)} in ${escape(formatDuration(report.durationMs))}.</p>
|
|
64
|
+
</footer>
|
|
65
|
+
</main>
|
|
66
|
+
</body>
|
|
67
|
+
</html>
|
|
68
|
+
`;
|
|
69
|
+
}
|
|
70
|
+
/** The score as a ring: the arc length is the score, the colour its band. */
|
|
71
|
+
function scoreRing(score) {
|
|
72
|
+
const radius = 42;
|
|
73
|
+
const circumference = 2 * Math.PI * radius;
|
|
74
|
+
const band = score >= 80 ? 'good' : score >= 50 ? 'fair' : 'poor';
|
|
75
|
+
return `<figure class="score" aria-label="Score ${score} out of 100">
|
|
76
|
+
<svg viewBox="0 0 100 100" width="112" height="112" aria-hidden="true">
|
|
77
|
+
<circle cx="50" cy="50" r="${radius}" class="ring-track"/>
|
|
78
|
+
<circle cx="50" cy="50" r="${radius}" class="ring-value grade-${band}" stroke-dasharray="${((score / 100) * circumference).toFixed(1)} ${circumference.toFixed(1)}" transform="rotate(-90 50 50)"/>
|
|
79
|
+
</svg>
|
|
80
|
+
<figcaption><span class="score-value">${score}</span><span class="score-unit">/100</span></figcaption>
|
|
81
|
+
</figure>`;
|
|
82
|
+
}
|
|
83
|
+
/** One card of the overview grid, linking to its section. */
|
|
84
|
+
function overviewCard(check) {
|
|
85
|
+
return `<a class="card" href="#${check.id}"><span class="pill grade-${check.grade}">${gradeLabel(check.grade)}</span><strong>${escape(check.title)}</strong><span class="card-summary">${inline(check.summary)}</span></a>`;
|
|
86
|
+
}
|
|
87
|
+
/** The full section of one check: summary, metric grid, findings. */
|
|
88
|
+
function checkSection(check) {
|
|
89
|
+
return `<section class="check" id="${check.id}" aria-labelledby="${check.id}-title">
|
|
90
|
+
<div class="check-head">
|
|
91
|
+
<h2 id="${check.id}-title">${escape(check.title)}</h2>
|
|
92
|
+
<span class="pill grade-${check.grade}">${gradeLabel(check.grade)}</span>
|
|
93
|
+
</div>
|
|
94
|
+
<p class="check-summary">${inline(check.summary)}</p>
|
|
95
|
+
${check.metrics.length === 0
|
|
96
|
+
? ''
|
|
97
|
+
: `<dl class="metrics">
|
|
98
|
+
${check.metrics.map((metric) => `<div class="metric"><dt>${escape(metric.label)}</dt><dd>${escape(String(metric.value))}</dd>${metric.hint ? `<dd class="hint">${escape(metric.hint)}</dd>` : ''}</div>`).join('\n ')}
|
|
99
|
+
</dl>`}
|
|
100
|
+
${check.findings.length === 0 ? '' : `<ul class="findings">\n ${check.findings.map(findingItem).join('\n ')}\n </ul>`}
|
|
101
|
+
</section>`;
|
|
102
|
+
}
|
|
103
|
+
/** One finding with its detail and example files. */
|
|
104
|
+
function findingItem(finding) {
|
|
105
|
+
const more = remainingCount(finding);
|
|
106
|
+
const files = finding.files ?? [];
|
|
107
|
+
return `<li class="finding">
|
|
108
|
+
<div class="finding-head"><span class="chip sev-${finding.severity}">${severityLabel(finding.severity)}</span><strong>${inline(finding.message)}</strong></div>
|
|
109
|
+
${finding.detail ? `<p>${inline(finding.detail)}</p>` : ''}
|
|
110
|
+
${files.length === 0 ? '' : `<ul class="files">${files.map((file) => `<li>${escape(formatLocation(file))}</li>`).join('')}${more > 0 ? `<li class="more">and ${formatCount(more)} more</li>` : ''}</ul>`}
|
|
111
|
+
</li>`;
|
|
112
|
+
}
|
|
113
|
+
/** Escapes text and turns backtick spans into `<code>`, the only markup finding text uses. */
|
|
114
|
+
function inline(text) {
|
|
115
|
+
return escape(text).replace(/`([^`]+)`/g, '<code>$1</code>');
|
|
116
|
+
}
|
|
117
|
+
/** HTML-escapes text for element content and double-quoted attributes. */
|
|
118
|
+
export function escape(text) {
|
|
119
|
+
return text.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"').replace(/'/g, ''');
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* The stylesheet. Colours are tokens on `:root`, redefined for dark mode, so
|
|
123
|
+
* the grade and severity colours stay legible on both backgrounds.
|
|
124
|
+
*/
|
|
125
|
+
const STYLES = `
|
|
126
|
+
:root {
|
|
127
|
+
--bg: #f7f7f5; --surface: #ffffff; --text: #1b1d21; --muted: #5d636e; --border: #e3e3df; --code-bg: #f0f0ec;
|
|
128
|
+
--good: #1f7a4d; --good-bg: #e3f3ea; --fair: #8a5a00; --fair-bg: #fbf0d9; --poor: #b42318; --poor-bg: #fde8e6; --info: #5d636e; --info-bg: #ececea;
|
|
129
|
+
--accent: #3b5bdb; --ring-track: #e7e7e3;
|
|
130
|
+
color-scheme: light;
|
|
131
|
+
}
|
|
132
|
+
@media (prefers-color-scheme: dark) {
|
|
133
|
+
:root {
|
|
134
|
+
--bg: #121316; --surface: #1b1d21; --text: #ececec; --muted: #9aa0aa; --border: #2c2f35; --code-bg: #25282e;
|
|
135
|
+
--good: #5cc98f; --good-bg: #173326; --fair: #e8b54d; --fair-bg: #3a2d10; --poor: #f17a6e; --poor-bg: #3d1a17; --info: #9aa0aa; --info-bg: #2a2c31;
|
|
136
|
+
--accent: #8ea6ff; --ring-track: #2c2f35;
|
|
137
|
+
color-scheme: dark;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
* { box-sizing: border-box; }
|
|
141
|
+
body { margin: 0; background: var(--bg); color: var(--text); font: 15px/1.55 ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; -webkit-font-smoothing: antialiased; }
|
|
142
|
+
main { max-width: 960px; margin: 0 auto; padding: 40px 16px 56px; }
|
|
143
|
+
code, .files { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size: 0.88em; }
|
|
144
|
+
code { background: var(--code-bg); padding: 1px 5px; border-radius: 4px; overflow-wrap: anywhere; }
|
|
145
|
+
a { color: inherit; }
|
|
146
|
+
.masthead { display: flex; justify-content: space-between; align-items: center; gap: 24px; padding-bottom: 28px; border-bottom: 1px solid var(--border); }
|
|
147
|
+
.eyebrow { margin: 0 0 4px; color: var(--accent); font-size: 13px; font-weight: 600; letter-spacing: 0.06em; text-transform: uppercase; }
|
|
148
|
+
h1 { margin: 0 0 8px; font-size: 32px; line-height: 1.15; letter-spacing: -0.01em; overflow-wrap: anywhere; }
|
|
149
|
+
.meta { margin: 0; color: var(--muted); font-size: 14px; }
|
|
150
|
+
.score { margin: 0; position: relative; width: 112px; height: 112px; flex: none; }
|
|
151
|
+
.score svg { display: block; }
|
|
152
|
+
.ring-track { fill: none; stroke: var(--ring-track); stroke-width: 9; }
|
|
153
|
+
.ring-value { fill: none; stroke-width: 9; stroke-linecap: round; }
|
|
154
|
+
.ring-value.grade-good { stroke: var(--good); } .ring-value.grade-fair { stroke: var(--fair); } .ring-value.grade-poor { stroke: var(--poor); }
|
|
155
|
+
.score figcaption { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; }
|
|
156
|
+
.score-value { font-size: 30px; font-weight: 700; font-variant-numeric: tabular-nums; }
|
|
157
|
+
.score-unit { color: var(--muted); font-size: 13px; margin-left: 1px; padding-top: 10px; }
|
|
158
|
+
.section-title { font-size: 13px; font-weight: 600; letter-spacing: 0.06em; text-transform: uppercase; color: var(--muted); margin: 36px 0 12px; }
|
|
159
|
+
.cards { display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 12px; }
|
|
160
|
+
.card { display: flex; flex-direction: column; gap: 6px; padding: 16px; background: var(--surface); border: 1px solid var(--border); border-radius: 10px; text-decoration: none; transition: border-color 0.15s; }
|
|
161
|
+
.card:hover { border-color: var(--accent); }
|
|
162
|
+
.card strong { font-size: 15px; }
|
|
163
|
+
.card-summary { color: var(--muted); font-size: 13.5px; }
|
|
164
|
+
.pill, .chip { display: inline-block; width: fit-content; border-radius: 999px; font-size: 12px; font-weight: 600; padding: 2px 10px; }
|
|
165
|
+
.grade-good.pill { color: var(--good); background: var(--good-bg); } .grade-fair.pill { color: var(--fair); background: var(--fair-bg); }
|
|
166
|
+
.grade-poor.pill { color: var(--poor); background: var(--poor-bg); } .grade-info.pill { color: var(--info); background: var(--info-bg); }
|
|
167
|
+
.chip { flex: none; padding: 1px 8px; font-size: 11.5px; }
|
|
168
|
+
.sev-high { color: var(--poor); background: var(--poor-bg); } .sev-medium { color: var(--fair); background: var(--fair-bg); }
|
|
169
|
+
.sev-low, .sev-info { color: var(--info); background: var(--info-bg); }
|
|
170
|
+
.priorities { margin: 0; padding: 0; list-style: none; display: grid; gap: 8px; counter-reset: priority; }
|
|
171
|
+
.priorities li { display: flex; gap: 10px; align-items: baseline; padding: 12px 16px; background: var(--surface); border: 1px solid var(--border); border-radius: 10px; }
|
|
172
|
+
.priorities .area { color: var(--muted); font-size: 13px; margin-left: 6px; white-space: nowrap; }
|
|
173
|
+
.check { margin-top: 40px; padding-top: 28px; border-top: 1px solid var(--border); }
|
|
174
|
+
.check-head { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
|
|
175
|
+
.check-head h2 { margin: 0; font-size: 22px; letter-spacing: -0.005em; }
|
|
176
|
+
.check-summary { margin: 8px 0 16px; color: var(--muted); }
|
|
177
|
+
.metrics { display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 10px; margin: 0 0 18px; }
|
|
178
|
+
.metric { padding: 12px 14px; background: var(--surface); border: 1px solid var(--border); border-radius: 10px; }
|
|
179
|
+
.metric dt { color: var(--muted); font-size: 12.5px; }
|
|
180
|
+
.metric dd { margin: 2px 0 0; font-size: 20px; font-weight: 650; font-variant-numeric: tabular-nums; overflow-wrap: anywhere; }
|
|
181
|
+
.metric dd.hint { font-size: 12.5px; font-weight: 400; color: var(--muted); }
|
|
182
|
+
.findings { list-style: none; margin: 0; padding: 0; display: grid; grid-template-columns: minmax(0, 1fr); gap: 10px; }
|
|
183
|
+
.finding { min-width: 0; padding: 14px 16px; background: var(--surface); border: 1px solid var(--border); border-radius: 10px; break-inside: avoid; }
|
|
184
|
+
.finding-head { display: flex; gap: 10px; align-items: baseline; }
|
|
185
|
+
.finding p { margin: 6px 0 0; color: var(--muted); font-size: 14px; }
|
|
186
|
+
.files { margin: 8px 0 0; padding: 8px 12px; list-style: none; background: var(--code-bg); border-radius: 6px; color: var(--muted); overflow-x: auto; }
|
|
187
|
+
.files li { white-space: nowrap; line-height: 1.7; }
|
|
188
|
+
.files .more { font-family: inherit; font-style: italic; }
|
|
189
|
+
footer { margin-top: 48px; padding-top: 20px; border-top: 1px solid var(--border); color: var(--muted); font-size: 13px; }
|
|
190
|
+
footer p { margin: 4px 0; }
|
|
191
|
+
@media (max-width: 600px) {
|
|
192
|
+
main { padding-top: 24px; }
|
|
193
|
+
.masthead { flex-direction: column-reverse; align-items: flex-start; }
|
|
194
|
+
h1 { font-size: 26px; }
|
|
195
|
+
.priorities li, .finding-head { flex-direction: column; gap: 6px; }
|
|
196
|
+
.priorities .area { margin-left: 0; }
|
|
197
|
+
}
|
|
198
|
+
@media print {
|
|
199
|
+
/* Printing from a dark-mode browser must still give black on white. */
|
|
200
|
+
:root {
|
|
201
|
+
--bg: #fff; --surface: #fff; --text: #1b1d21; --muted: #5d636e; --border: #e3e3df; --code-bg: #f0f0ec;
|
|
202
|
+
--good: #1f7a4d; --good-bg: #e3f3ea; --fair: #8a5a00; --fair-bg: #fbf0d9; --poor: #b42318; --poor-bg: #fde8e6; --info: #5d636e; --info-bg: #ececea;
|
|
203
|
+
--accent: #3b5bdb; --ring-track: #e7e7e3; color-scheme: light;
|
|
204
|
+
}
|
|
205
|
+
main { padding: 0; max-width: none; }
|
|
206
|
+
.card:hover { border-color: var(--border); }
|
|
207
|
+
.check { break-before: auto; }
|
|
208
|
+
}
|
|
209
|
+
`;
|
|
210
|
+
//# sourceMappingURL=html.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"html.js","sourceRoot":"","sources":["../../src/report/html.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { Report } from '../types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Renders the report as GitHub-flavoured Markdown, for a pull request, a
|
|
4
|
+
* wiki page or an issue. Finding text already uses Markdown backticks, so it
|
|
5
|
+
* goes in as written; only table cells are escaped, where a `|` would split
|
|
6
|
+
* the row.
|
|
7
|
+
*/
|
|
8
|
+
export declare function renderMarkdown(report: Report): string;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { formatCount, plural } from '../checks/context.js';
|
|
2
|
+
import { formatDuration, formatLocation, gradeLabel, kindLabel, overallScore, remainingCount, SCORE_EXPLANATION, severityLabel, topPriorities } from './summary.js';
|
|
3
|
+
/**
|
|
4
|
+
* Renders the report as GitHub-flavoured Markdown, for a pull request, a
|
|
5
|
+
* wiki page or an issue. Finding text already uses Markdown backticks, so it
|
|
6
|
+
* goes in as written; only table cells are escaped, where a `|` would split
|
|
7
|
+
* the row.
|
|
8
|
+
*/
|
|
9
|
+
export function renderMarkdown(report) {
|
|
10
|
+
const { project } = report;
|
|
11
|
+
const lines = [];
|
|
12
|
+
const score = overallScore(report);
|
|
13
|
+
lines.push(`# Angular checkup: ${project.name}`, '');
|
|
14
|
+
lines.push(`${kindLabel(project.kind)}${project.angularVersion ? ` · Angular ${project.angularVersion}` : ''} · ${plural(project.sourceFiles, 'TypeScript file')} · ${plural(project.templateFiles, 'template')} · ${plural(project.linesOfCode, 'line')} of application code`, '');
|
|
15
|
+
if (score !== undefined)
|
|
16
|
+
lines.push(`**Score: ${score}/100.** ${SCORE_EXPLANATION}`, '');
|
|
17
|
+
lines.push('| Area | Grade | Summary |', '| --- | --- | --- |');
|
|
18
|
+
for (const check of report.checks)
|
|
19
|
+
lines.push(`| ${cell(check.title)} | ${gradeLabel(check.grade)} | ${cell(check.summary)} |`);
|
|
20
|
+
lines.push('');
|
|
21
|
+
const priorities = topPriorities(report);
|
|
22
|
+
if (priorities.length > 0) {
|
|
23
|
+
lines.push('## Top priorities', '');
|
|
24
|
+
priorities.forEach(({ check, finding }, index) => lines.push(`${index + 1}. **${severityLabel(finding.severity)}:** ${finding.message} _(${check.title})_`));
|
|
25
|
+
lines.push('');
|
|
26
|
+
}
|
|
27
|
+
for (const check of report.checks) {
|
|
28
|
+
lines.push(`## ${check.title}: ${gradeLabel(check.grade)}`, '', check.summary, '');
|
|
29
|
+
if (check.metrics.length > 0) {
|
|
30
|
+
lines.push('| Metric | Value | |', '| --- | --- | --- |');
|
|
31
|
+
for (const metric of check.metrics)
|
|
32
|
+
lines.push(`| ${cell(metric.label)} | ${cell(String(metric.value))} | ${cell(metric.hint ?? '')} |`);
|
|
33
|
+
lines.push('');
|
|
34
|
+
}
|
|
35
|
+
for (const finding of check.findings) {
|
|
36
|
+
lines.push(`- **${severityLabel(finding.severity)}:** ${finding.message}`);
|
|
37
|
+
if (finding.detail)
|
|
38
|
+
lines.push(` ${finding.detail}`);
|
|
39
|
+
for (const file of finding.files ?? [])
|
|
40
|
+
lines.push(` - \`${formatLocation(file)}\``);
|
|
41
|
+
const more = remainingCount(finding);
|
|
42
|
+
if (more > 0)
|
|
43
|
+
lines.push(` - and ${formatCount(more)} more`);
|
|
44
|
+
}
|
|
45
|
+
if (check.findings.length > 0)
|
|
46
|
+
lines.push('');
|
|
47
|
+
}
|
|
48
|
+
lines.push('---', '', `Generated ${report.generatedAt.slice(0, 10)} by ${report.tool.name} ${report.tool.version} in ${formatDuration(report.durationMs)}.`, '');
|
|
49
|
+
return lines.join('\n');
|
|
50
|
+
}
|
|
51
|
+
/** Escapes a table cell: a `|` would end it and a newline would end the row. */
|
|
52
|
+
function cell(text) {
|
|
53
|
+
return text.replace(/\|/g, '\\|').replace(/\n/g, ' ');
|
|
54
|
+
}
|
|
55
|
+
//# sourceMappingURL=markdown.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"markdown.js","sourceRoot":"","sources":["../../src/report/markdown.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { CheckResult, Finding, Grade, Report, Severity } from '../types.js';
|
|
2
|
+
/** A finding together with the check it belongs to, for the cross-check priority list. */
|
|
3
|
+
export interface Priority {
|
|
4
|
+
check: CheckResult;
|
|
5
|
+
finding: Finding;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* The overall score, 0 to 100: the average of the graded areas at 100 for
|
|
9
|
+
* good, 60 for fair and 20 for poor. Areas graded `info` are left out, so a
|
|
10
|
+
* repository is not marked down for what the tool could not measure.
|
|
11
|
+
* Undefined when nothing was graded at all.
|
|
12
|
+
*
|
|
13
|
+
* A deliberately simple formula. Every report prints it next to the score,
|
|
14
|
+
* because a number whose derivation is hidden invites arguing with it instead
|
|
15
|
+
* of with the findings.
|
|
16
|
+
*/
|
|
17
|
+
export declare function overallScore(report: Report): number | undefined;
|
|
18
|
+
/** The sentence every format prints under the score. */
|
|
19
|
+
export declare const SCORE_EXPLANATION = "Average of the graded areas: good counts 100, fair 60, poor 20. Areas marked info are not counted.";
|
|
20
|
+
/**
|
|
21
|
+
* The findings to act on first, across all checks: every `high` finding,
|
|
22
|
+
* then `medium` ones if fewer than `limit` are high, in report order within a
|
|
23
|
+
* severity. Low and info findings never make the list.
|
|
24
|
+
*/
|
|
25
|
+
export declare function topPriorities(report: Report, limit?: number): Priority[];
|
|
26
|
+
/** Display label of a grade. */
|
|
27
|
+
export declare function gradeLabel(grade: Grade): string;
|
|
28
|
+
/** Display label of a severity. */
|
|
29
|
+
export declare function severityLabel(severity: Severity): string;
|
|
30
|
+
/** `path:line`, or just `path` when the location has no line. */
|
|
31
|
+
export declare function formatLocation(ref: {
|
|
32
|
+
path: string;
|
|
33
|
+
line?: number;
|
|
34
|
+
}): string;
|
|
35
|
+
/** "1.5 s" or "820 ms". */
|
|
36
|
+
export declare function formatDuration(milliseconds: number): string;
|
|
37
|
+
/** The workspace kind as a reader would name it. */
|
|
38
|
+
export declare function kindLabel(kind: Report['project']['kind']): string;
|
|
39
|
+
/**
|
|
40
|
+
* How many more files a finding covers than it lists. 0 when it lists them
|
|
41
|
+
* all, and 0 when it lists none: then `count` is a number of occurrences in
|
|
42
|
+
* the message, not of files, and "and 12 more" under an empty list would
|
|
43
|
+
* read as a rendering bug.
|
|
44
|
+
*/
|
|
45
|
+
export declare function remainingCount(finding: Finding): number;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/** Points per grade for the overall score. `info` is absent: it never counts. */
|
|
2
|
+
const GRADE_POINTS = { good: 100, fair: 60, poor: 20 };
|
|
3
|
+
/** Severity order, most urgent first. */
|
|
4
|
+
const SEVERITY_RANK = { high: 0, medium: 1, low: 2, info: 3 };
|
|
5
|
+
/**
|
|
6
|
+
* The overall score, 0 to 100: the average of the graded areas at 100 for
|
|
7
|
+
* good, 60 for fair and 20 for poor. Areas graded `info` are left out, so a
|
|
8
|
+
* repository is not marked down for what the tool could not measure.
|
|
9
|
+
* Undefined when nothing was graded at all.
|
|
10
|
+
*
|
|
11
|
+
* A deliberately simple formula. Every report prints it next to the score,
|
|
12
|
+
* because a number whose derivation is hidden invites arguing with it instead
|
|
13
|
+
* of with the findings.
|
|
14
|
+
*/
|
|
15
|
+
export function overallScore(report) {
|
|
16
|
+
const graded = report.checks.filter((check) => check.grade !== 'info');
|
|
17
|
+
if (graded.length === 0)
|
|
18
|
+
return undefined;
|
|
19
|
+
return Math.round(graded.reduce((sum, check) => sum + GRADE_POINTS[check.grade], 0) / graded.length);
|
|
20
|
+
}
|
|
21
|
+
/** The sentence every format prints under the score. */
|
|
22
|
+
export const SCORE_EXPLANATION = 'Average of the graded areas: good counts 100, fair 60, poor 20. Areas marked info are not counted.';
|
|
23
|
+
/**
|
|
24
|
+
* The findings to act on first, across all checks: every `high` finding,
|
|
25
|
+
* then `medium` ones if fewer than `limit` are high, in report order within a
|
|
26
|
+
* severity. Low and info findings never make the list.
|
|
27
|
+
*/
|
|
28
|
+
export function topPriorities(report, limit = 5) {
|
|
29
|
+
return report.checks
|
|
30
|
+
.flatMap((check) => check.findings.map((finding) => ({ check, finding })))
|
|
31
|
+
.filter(({ finding }) => finding.severity === 'high' || finding.severity === 'medium')
|
|
32
|
+
.sort((left, right) => SEVERITY_RANK[left.finding.severity] - SEVERITY_RANK[right.finding.severity])
|
|
33
|
+
.slice(0, limit);
|
|
34
|
+
}
|
|
35
|
+
/** Display label of a grade. */
|
|
36
|
+
export function gradeLabel(grade) {
|
|
37
|
+
return { good: 'Good', fair: 'Fair', poor: 'Poor', info: 'Info' }[grade];
|
|
38
|
+
}
|
|
39
|
+
/** Display label of a severity. */
|
|
40
|
+
export function severityLabel(severity) {
|
|
41
|
+
return { high: 'High', medium: 'Medium', low: 'Low', info: 'Note' }[severity];
|
|
42
|
+
}
|
|
43
|
+
/** `path:line`, or just `path` when the location has no line. */
|
|
44
|
+
export function formatLocation(ref) {
|
|
45
|
+
return ref.line === undefined ? ref.path : `${ref.path}:${ref.line}`;
|
|
46
|
+
}
|
|
47
|
+
/** "1.5 s" or "820 ms". */
|
|
48
|
+
export function formatDuration(milliseconds) {
|
|
49
|
+
return milliseconds >= 1000 ? `${(milliseconds / 1000).toFixed(1)} s` : `${milliseconds} ms`;
|
|
50
|
+
}
|
|
51
|
+
/** The workspace kind as a reader would name it. */
|
|
52
|
+
export function kindLabel(kind) {
|
|
53
|
+
return { 'angular-cli': 'Angular CLI', nx: 'Nx', unknown: 'unknown workspace' }[kind];
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* How many more files a finding covers than it lists. 0 when it lists them
|
|
57
|
+
* all, and 0 when it lists none: then `count` is a number of occurrences in
|
|
58
|
+
* the message, not of files, and "and 12 more" under an empty list would
|
|
59
|
+
* read as a rendering bug.
|
|
60
|
+
*/
|
|
61
|
+
export function remainingCount(finding) {
|
|
62
|
+
const listed = finding.files?.length ?? 0;
|
|
63
|
+
return listed === 0 ? 0 : Math.max(0, (finding.count ?? 0) - listed);
|
|
64
|
+
}
|
|
65
|
+
//# sourceMappingURL=summary.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"summary.js","sourceRoot":"","sources":["../../src/report/summary.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { Report } from '../types.js';
|
|
2
|
+
/** Options for the terminal renderer. */
|
|
3
|
+
export interface TerminalOptions {
|
|
4
|
+
/** ANSI colours. The CLI turns them off for a non-TTY, `NO_COLOR` or `--no-color`. */
|
|
5
|
+
color: boolean;
|
|
6
|
+
/** Every finding of every check instead of the summary and the top priorities. */
|
|
7
|
+
verbose: boolean;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Renders the report for a terminal.
|
|
11
|
+
*
|
|
12
|
+
* By default it shows one line per check and the top priorities, which fits a
|
|
13
|
+
* screen and is what someone running the tool for the first time wants;
|
|
14
|
+
* `verbose` adds every finding with its example files. The file reports
|
|
15
|
+
* (`--report x.html`) always carry everything, so the terminal never has to.
|
|
16
|
+
*/
|
|
17
|
+
export declare function renderTerminal(report: Report, options: TerminalOptions): string;
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { formatCount, plural } from '../checks/context.js';
|
|
2
|
+
import { formatDuration, formatLocation, gradeLabel, kindLabel, overallScore, remainingCount, SCORE_EXPLANATION, severityLabel, topPriorities } from './summary.js';
|
|
3
|
+
/** A tiny ANSI palette; every function is the identity when colour is off. */
|
|
4
|
+
function palette(enabled) {
|
|
5
|
+
const wrap = (open, close) => (text) => (enabled ? `\u001b[${open}m${text}\u001b[${close}m` : text);
|
|
6
|
+
return { bold: wrap(1, 22), dim: wrap(2, 22), red: wrap(31, 39), green: wrap(32, 39), yellow: wrap(33, 39), cyan: wrap(36, 39), gray: wrap(90, 39) };
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Renders the report for a terminal.
|
|
10
|
+
*
|
|
11
|
+
* By default it shows one line per check and the top priorities, which fits a
|
|
12
|
+
* screen and is what someone running the tool for the first time wants;
|
|
13
|
+
* `verbose` adds every finding with its example files. The file reports
|
|
14
|
+
* (`--report x.html`) always carry everything, so the terminal never has to.
|
|
15
|
+
*/
|
|
16
|
+
export function renderTerminal(report, options) {
|
|
17
|
+
const c = palette(options.color);
|
|
18
|
+
/** Backtick spans become cyan text without the backticks; without colour the backticks stay as the only marker. */
|
|
19
|
+
const code = (text) => (options.color ? text.replace(/`([^`]+)`/g, (_, inner) => c.cyan(inner)) : text);
|
|
20
|
+
const gradeBadge = (grade) => {
|
|
21
|
+
const label = ` ${gradeLabel(grade).toUpperCase().padEnd(4)} `;
|
|
22
|
+
return { good: c.green, fair: c.yellow, poor: c.red, info: c.gray }[grade](options.color ? c.bold(label) : `[${gradeLabel(grade).toUpperCase()}]`.padEnd(7));
|
|
23
|
+
};
|
|
24
|
+
const severityTag = (severity) => ({ high: c.red, medium: c.yellow, low: c.gray, info: c.gray })[severity](severityLabel(severity).toLowerCase().padEnd(6));
|
|
25
|
+
const lines = [];
|
|
26
|
+
const { project } = report;
|
|
27
|
+
lines.push(`${c.bold(report.tool.name)} ${c.dim(report.tool.version)} ${c.bold(project.name)} ${c.dim(`· ${kindLabel(project.kind)}${project.angularVersion ? ` · Angular ${project.angularVersion}` : ''}`)}`);
|
|
28
|
+
lines.push(c.dim(`${plural(project.sourceFiles, 'TypeScript file')}, ${plural(project.templateFiles, 'template')}, ${plural(project.linesOfCode, 'line')} of application code, analysed in ${formatDuration(report.durationMs)}`));
|
|
29
|
+
lines.push('');
|
|
30
|
+
const titleWidth = Math.max(...report.checks.map((check) => check.title.length));
|
|
31
|
+
for (const check of report.checks) {
|
|
32
|
+
lines.push(`${gradeBadge(check.grade)} ${c.bold(check.title.padEnd(titleWidth))} ${code(check.summary)}`);
|
|
33
|
+
if (!options.verbose)
|
|
34
|
+
continue;
|
|
35
|
+
for (const finding of check.findings) {
|
|
36
|
+
lines.push(` ${severityTag(finding.severity)} ${code(finding.message)}`);
|
|
37
|
+
if (finding.detail)
|
|
38
|
+
lines.push(` ${c.dim(code(finding.detail))}`);
|
|
39
|
+
for (const file of finding.files ?? [])
|
|
40
|
+
lines.push(` ${c.gray(formatLocation(file))}`);
|
|
41
|
+
const more = remainingCount(finding);
|
|
42
|
+
if (more > 0)
|
|
43
|
+
lines.push(` ${c.gray(`and ${formatCount(more)} more`)}`);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
const score = overallScore(report);
|
|
47
|
+
if (score !== undefined) {
|
|
48
|
+
lines.push('');
|
|
49
|
+
const color = score >= 80 ? c.green : score >= 50 ? c.yellow : c.red;
|
|
50
|
+
lines.push(`${c.bold('Score')} ${color(c.bold(`${score}/100`))} ${c.dim(SCORE_EXPLANATION)}`);
|
|
51
|
+
}
|
|
52
|
+
const priorities = topPriorities(report);
|
|
53
|
+
if (!options.verbose && priorities.length > 0) {
|
|
54
|
+
lines.push('');
|
|
55
|
+
lines.push(c.bold('Top priorities'));
|
|
56
|
+
priorities.forEach(({ check, finding }, index) => {
|
|
57
|
+
lines.push(` ${index + 1}. ${severityTag(finding.severity)} ${code(finding.message)} ${c.dim(`(${check.title})`)}`);
|
|
58
|
+
const [first] = finding.files ?? [];
|
|
59
|
+
const total = finding.count ?? finding.files?.length ?? 0;
|
|
60
|
+
if (first)
|
|
61
|
+
lines.push(` ${c.gray(`${formatLocation(first)}${total > 1 ? ` and ${formatCount(total - 1)} more` : ''}`)}`);
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
if (!options.verbose) {
|
|
65
|
+
lines.push('');
|
|
66
|
+
lines.push(c.dim('Every finding with its files: --verbose, or write a report with --report checkup.html (also .md, .json).'));
|
|
67
|
+
}
|
|
68
|
+
return lines.join('\n');
|
|
69
|
+
}
|
|
70
|
+
//# sourceMappingURL=terminal.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"terminal.js","sourceRoot":"","sources":["../../src/report/terminal.ts"],"names":[],"mappings":"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"}
|
package/dist/scan.d.ts
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/** One name brought in by an import or re-export. */
|
|
2
|
+
export interface ImportBinding {
|
|
3
|
+
/** The name the module exports. `default` for a default import, `*` for a namespace import. */
|
|
4
|
+
imported: string;
|
|
5
|
+
/** The name it is bound to in this file. */
|
|
6
|
+
local: string;
|
|
7
|
+
typeOnly: boolean;
|
|
8
|
+
}
|
|
9
|
+
/** One `import`, `export … from` or dynamic `import()` in a source file. */
|
|
10
|
+
export interface ImportFact {
|
|
11
|
+
specifier: string;
|
|
12
|
+
line: number;
|
|
13
|
+
/**
|
|
14
|
+
* True for `import type`, and for a named import whose every binding is
|
|
15
|
+
* `type`. Such an import is erased at compile time, so it cannot take part in
|
|
16
|
+
* a runtime cycle and the dependency graph ignores it.
|
|
17
|
+
*/
|
|
18
|
+
typeOnly: boolean;
|
|
19
|
+
/** `import()` loads lazily and does not form an eager cycle either. */
|
|
20
|
+
dynamic: boolean;
|
|
21
|
+
/** Empty for a side-effect import (`import 'zone.js'`) and for `export * from`. */
|
|
22
|
+
bindings: ImportBinding[];
|
|
23
|
+
}
|
|
24
|
+
/** One `@Component` class. */
|
|
25
|
+
export interface ComponentFact {
|
|
26
|
+
name: string;
|
|
27
|
+
line: number;
|
|
28
|
+
/**
|
|
29
|
+
* The literal value of `standalone` in the decorator, undefined when absent.
|
|
30
|
+
* What an absent flag means depends on the Angular version (standalone is the
|
|
31
|
+
* default from v19), which is why the scanner records only what is written
|
|
32
|
+
* and the components check interprets it.
|
|
33
|
+
*/
|
|
34
|
+
standalone?: boolean;
|
|
35
|
+
/**
|
|
36
|
+
* The strategy named in `changeDetection`, undefined when absent. Like
|
|
37
|
+
* `standalone`, the default changed (OnPush from v22), so an absent value is
|
|
38
|
+
* interpreted by the check that knows the version.
|
|
39
|
+
*/
|
|
40
|
+
changeDetection?: string;
|
|
41
|
+
}
|
|
42
|
+
/** Control-flow and escape-hatch counts for one template, inline or external. */
|
|
43
|
+
export interface TemplateStats {
|
|
44
|
+
ngIf: number;
|
|
45
|
+
ngFor: number;
|
|
46
|
+
ngSwitch: number;
|
|
47
|
+
ifBlocks: number;
|
|
48
|
+
forBlocks: number;
|
|
49
|
+
switchBlocks: number;
|
|
50
|
+
deferBlocks: number;
|
|
51
|
+
/** `$any(...)`, the template's way of switching type checking off for one expression. */
|
|
52
|
+
anyCasts: number;
|
|
53
|
+
}
|
|
54
|
+
/** A template and where its first legacy structural directive sits, for report examples. */
|
|
55
|
+
export interface TemplateFact {
|
|
56
|
+
path: string;
|
|
57
|
+
/** True for a `template:` string inside a component file. */
|
|
58
|
+
inline: boolean;
|
|
59
|
+
stats: TemplateStats;
|
|
60
|
+
/** 1-based line of the first `*ngIf`/`*ngFor`/`[ngSwitch]`, undefined when there is none. */
|
|
61
|
+
firstLegacyLine?: number;
|
|
62
|
+
}
|
|
63
|
+
/** Everything the checks need from one TypeScript file, gathered in a single AST pass. */
|
|
64
|
+
export interface SourceFileFacts {
|
|
65
|
+
/** Report path: relative to the root, `/`-separated. */
|
|
66
|
+
path: string;
|
|
67
|
+
absolutePath: string;
|
|
68
|
+
isSpec: boolean;
|
|
69
|
+
/** Non-blank lines. */
|
|
70
|
+
linesOfCode: number;
|
|
71
|
+
imports: ImportFact[];
|
|
72
|
+
components: ComponentFact[];
|
|
73
|
+
ngModules: number;
|
|
74
|
+
injectables: number;
|
|
75
|
+
/** `@Input()` decorators on properties and setters. */
|
|
76
|
+
decoratorInputs: number;
|
|
77
|
+
/** `@Output()` decorators. */
|
|
78
|
+
decoratorOutputs: number;
|
|
79
|
+
/**
|
|
80
|
+
* Calls of imported functions, keyed `module#name` (`@angular/core#signal`)
|
|
81
|
+
* or `module#name.member` for calls such as `input.required()`. Values are the
|
|
82
|
+
* 1-based lines of each call. Resolving through the imports is what keeps a
|
|
83
|
+
* local helper that happens to be called `signal` out of the signal count.
|
|
84
|
+
*/
|
|
85
|
+
calls: Map<string, number[]>;
|
|
86
|
+
/** `new X()` of an imported class, keyed like `calls`. */
|
|
87
|
+
constructions: Map<string, number[]>;
|
|
88
|
+
/** `X.member` on an imported binding, keyed `module#X.member`. Used for deprecated enum members. */
|
|
89
|
+
memberAccesses: Map<string, number[]>;
|
|
90
|
+
/**
|
|
91
|
+
* Calls of test-framework globals that are never imported: `spyOn` and
|
|
92
|
+
* `jasmine.*` for Jasmine. Each one is a line to rewrite when moving to Vitest
|
|
93
|
+
* or Jest, which is what the testing check reports them for.
|
|
94
|
+
*/
|
|
95
|
+
jasmineCalls: number;
|
|
96
|
+
/** Lines of every `any` type annotation or `as any`. */
|
|
97
|
+
anyLines: number[];
|
|
98
|
+
/** Lines of every `x!` non-null assertion. */
|
|
99
|
+
nonNullLines: number[];
|
|
100
|
+
/** `x!: T` property declarations, which skip definite-assignment checking. */
|
|
101
|
+
definiteAssignments: number;
|
|
102
|
+
/** `@ts-ignore`, `@ts-expect-error` and `@ts-nocheck` comments. */
|
|
103
|
+
typeSuppressions: number;
|
|
104
|
+
/** `templateUrl`/`template` is resolved by the template scan; inline templates are collected here. */
|
|
105
|
+
inlineTemplates: TemplateFact[];
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Parses one file and extracts its facts. Parsing is syntax-only (no type
|
|
109
|
+
* checker, no program), which is what lets a 3 000-file repository finish in
|
|
110
|
+
* seconds and lets the tool run without the project's dependencies installed.
|
|
111
|
+
* The cost is that identifiers are matched by their import, never by type.
|
|
112
|
+
*/
|
|
113
|
+
export declare function scanSourceFile(root: string, absolutePath: string): SourceFileFacts;
|
|
114
|
+
/** Counts control flow in one template's text. */
|
|
115
|
+
export declare function scanTemplate(text: string): TemplateStats;
|
|
116
|
+
/** Character offset of the first legacy structural directive in a template, or undefined. */
|
|
117
|
+
export declare function firstLegacyOffset(text: string): number | undefined;
|
|
118
|
+
/** Scans an external template file. */
|
|
119
|
+
export declare function scanTemplateFile(root: string, absolutePath: string): TemplateFact;
|