patram 0.0.2 → 0.2.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/bin/patram.js +25 -147
- package/lib/build-graph-identity.js +270 -0
- package/lib/build-graph.js +156 -77
- package/lib/check-graph.js +23 -7
- package/lib/claim-helpers.js +55 -0
- package/lib/cli-help-metadata.js +552 -0
- package/lib/command-output.js +83 -0
- package/lib/derived-summary.js +278 -0
- package/lib/format-derived-summary-row.js +9 -0
- package/lib/format-node-header.js +19 -0
- package/lib/format-output-item-block.js +22 -0
- package/lib/format-output-metadata.js +62 -0
- package/lib/layout-stored-queries.js +361 -0
- package/lib/list-queries.js +18 -0
- package/lib/list-source-files.js +50 -15
- package/lib/load-patram-config.js +505 -18
- package/lib/load-patram-config.types.ts +40 -0
- package/lib/load-project-graph.js +124 -0
- package/lib/output-view.types.ts +88 -0
- package/lib/parse-claims.js +38 -158
- package/lib/parse-claims.types.ts +7 -0
- package/lib/parse-cli-arguments-helpers.js +446 -0
- package/lib/parse-cli-arguments.js +266 -0
- package/lib/parse-cli-arguments.types.ts +69 -0
- package/lib/parse-cli-color-options.js +44 -0
- package/lib/parse-cli-query-pagination.js +49 -0
- package/lib/parse-jsdoc-blocks.js +184 -0
- package/lib/parse-jsdoc-claims.js +280 -0
- package/lib/parse-jsdoc-prose.js +111 -0
- package/lib/parse-markdown-claims.js +242 -0
- package/lib/parse-markdown-directives.js +136 -0
- package/lib/parse-where-clause.js +707 -0
- package/lib/parse-where-clause.types.ts +70 -0
- package/lib/patram-cli.js +464 -0
- package/lib/patram-config.js +3 -1
- package/lib/patram-config.types.ts +2 -1
- package/lib/patram.js +6 -0
- package/lib/query-graph.js +368 -0
- package/lib/query-inspection.js +523 -0
- package/lib/render-check-output.js +315 -0
- package/lib/render-cli-help.js +419 -0
- package/lib/render-json-output.js +161 -0
- package/lib/render-output-view.js +222 -0
- package/lib/render-plain-output.js +182 -0
- package/lib/render-rich-output.js +240 -0
- package/lib/render-rich-source.js +1333 -0
- package/lib/resolve-check-target.js +190 -0
- package/lib/resolve-output-mode.js +60 -0
- package/lib/resolve-patram-graph-config.js +88 -0
- package/lib/resolve-where-clause.js +66 -0
- package/lib/show-document.js +311 -0
- package/lib/source-file-defaults.js +28 -0
- package/lib/tagged-fenced-block-error.js +17 -0
- package/lib/tagged-fenced-block-markdown.js +111 -0
- package/lib/tagged-fenced-block-metadata.js +97 -0
- package/lib/tagged-fenced-block-parser.js +292 -0
- package/lib/tagged-fenced-blocks.js +100 -0
- package/lib/tagged-fenced-blocks.types.ts +38 -0
- package/lib/write-paged-output.js +87 -0
- package/package.json +28 -12
- package/bin/patram.test.js +0 -184
- package/lib/build-graph.test.js +0 -141
- package/lib/check-graph.test.js +0 -103
- package/lib/list-source-files.test.js +0 -101
- package/lib/load-patram-config.test.js +0 -211
- package/lib/parse-claims.test.js +0 -113
- package/lib/patram-config.test.js +0 -147
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @import { PatramDiagnostic } from './load-patram-config.types.ts';
|
|
3
|
+
* @import { ResolvedOutputMode } from './output-view.types.ts';
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { Ansis } from 'ansis';
|
|
7
|
+
import stringWidth from 'string-width';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Render check diagnostics for one output mode.
|
|
11
|
+
*
|
|
12
|
+
* @param {PatramDiagnostic[]} diagnostics
|
|
13
|
+
* @param {ResolvedOutputMode} output_mode
|
|
14
|
+
* @returns {string}
|
|
15
|
+
*/
|
|
16
|
+
export function renderCheckDiagnostics(diagnostics, output_mode) {
|
|
17
|
+
if (output_mode.renderer_name === 'json') {
|
|
18
|
+
return `${JSON.stringify(
|
|
19
|
+
{
|
|
20
|
+
diagnostics: diagnostics.map(formatJsonDiagnostic),
|
|
21
|
+
},
|
|
22
|
+
null,
|
|
23
|
+
2,
|
|
24
|
+
)}\n`;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (output_mode.renderer_name === 'plain') {
|
|
28
|
+
return renderPlainCheckDiagnostics(diagnostics);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return renderRichCheckDiagnostics(diagnostics, createAnsi(output_mode));
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Render the success summary for one check run.
|
|
36
|
+
*
|
|
37
|
+
* @param {number} source_file_count
|
|
38
|
+
* @param {ResolvedOutputMode} output_mode
|
|
39
|
+
* @returns {string}
|
|
40
|
+
*/
|
|
41
|
+
export function renderCheckSuccess(source_file_count, output_mode) {
|
|
42
|
+
if (output_mode.renderer_name === 'json') {
|
|
43
|
+
return `${JSON.stringify({ diagnostics: [] }, null, 2)}\n`;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (output_mode.renderer_name === 'plain') {
|
|
47
|
+
return formatCheckSuccess(source_file_count);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const ansi = createAnsi(output_mode);
|
|
51
|
+
return `${ansi.green('Check passed.')}\n${ansi.gray(`Scanned ${source_file_count} ${pluralize('file', source_file_count)}. Found 0 errors.`)}\n`;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* @param {PatramDiagnostic[]} diagnostics
|
|
56
|
+
* @returns {string}
|
|
57
|
+
*/
|
|
58
|
+
function renderPlainCheckDiagnostics(diagnostics) {
|
|
59
|
+
const grouped_diagnostics = groupDiagnosticsByPath(diagnostics);
|
|
60
|
+
const diagnostic_prefix_width = measureMaxDiagnosticPrefixWidth(diagnostics);
|
|
61
|
+
return `${grouped_diagnostics
|
|
62
|
+
.map((diagnostic_group) =>
|
|
63
|
+
formatPlainDiagnosticGroup(diagnostic_group, diagnostic_prefix_width),
|
|
64
|
+
)
|
|
65
|
+
.join('\n\n')}\n\n${formatPlainSummary(diagnostics)}\n`;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* @param {PatramDiagnostic[]} diagnostics
|
|
70
|
+
* @param {Ansis} ansi
|
|
71
|
+
* @returns {string}
|
|
72
|
+
*/
|
|
73
|
+
function renderRichCheckDiagnostics(diagnostics, ansi) {
|
|
74
|
+
const grouped_diagnostics = groupDiagnosticsByPath(diagnostics);
|
|
75
|
+
const diagnostic_prefix_width = measureMaxDiagnosticPrefixWidth(diagnostics);
|
|
76
|
+
return `${grouped_diagnostics
|
|
77
|
+
.map((diagnostic_group) =>
|
|
78
|
+
formatRichDiagnosticGroup(
|
|
79
|
+
diagnostic_group,
|
|
80
|
+
diagnostic_prefix_width,
|
|
81
|
+
ansi,
|
|
82
|
+
),
|
|
83
|
+
)
|
|
84
|
+
.join('\n\n')}\n\n${formatRichSummary(diagnostics, ansi)}\n`;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* @param {PatramDiagnostic[]} diagnostics
|
|
89
|
+
* @returns {Array<{ diagnostics: PatramDiagnostic[], path: string }>}
|
|
90
|
+
*/
|
|
91
|
+
function groupDiagnosticsByPath(diagnostics) {
|
|
92
|
+
/** @type {Array<{ diagnostics: PatramDiagnostic[], path: string }>} */
|
|
93
|
+
const grouped_diagnostics = [];
|
|
94
|
+
/** @type {Map<string, { diagnostics: PatramDiagnostic[], path: string }>} */
|
|
95
|
+
const grouped_diagnostics_by_path = new Map();
|
|
96
|
+
|
|
97
|
+
for (const diagnostic of diagnostics) {
|
|
98
|
+
let diagnostic_group = grouped_diagnostics_by_path.get(diagnostic.path);
|
|
99
|
+
|
|
100
|
+
if (!diagnostic_group) {
|
|
101
|
+
diagnostic_group = {
|
|
102
|
+
diagnostics: [],
|
|
103
|
+
path: diagnostic.path,
|
|
104
|
+
};
|
|
105
|
+
grouped_diagnostics_by_path.set(diagnostic.path, diagnostic_group);
|
|
106
|
+
grouped_diagnostics.push(diagnostic_group);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
diagnostic_group.diagnostics.push(diagnostic);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return grouped_diagnostics;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* @param {{ diagnostics: PatramDiagnostic[], path: string }} diagnostic_group
|
|
117
|
+
* @param {number} diagnostic_prefix_width
|
|
118
|
+
* @returns {string}
|
|
119
|
+
*/
|
|
120
|
+
function formatPlainDiagnosticGroup(diagnostic_group, diagnostic_prefix_width) {
|
|
121
|
+
return `${formatDiagnosticGroupHeader(diagnostic_group.path)}\n${diagnostic_group.diagnostics
|
|
122
|
+
.map((diagnostic) =>
|
|
123
|
+
formatPlainDiagnosticRow(diagnostic, diagnostic_prefix_width),
|
|
124
|
+
)
|
|
125
|
+
.join('\n')}`;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* @param {PatramDiagnostic} diagnostic
|
|
130
|
+
* @param {number} diagnostic_prefix_width
|
|
131
|
+
* @returns {string}
|
|
132
|
+
*/
|
|
133
|
+
function formatPlainDiagnosticRow(diagnostic, diagnostic_prefix_width) {
|
|
134
|
+
const diagnostic_prefix = formatDiagnosticPrefix(diagnostic);
|
|
135
|
+
return `${diagnostic_prefix}${createDiagnosticCodePadding(diagnostic_prefix, diagnostic_prefix_width)} ${diagnostic.code}`;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* @param {PatramDiagnostic[]} diagnostics
|
|
140
|
+
* @returns {string}
|
|
141
|
+
*/
|
|
142
|
+
function formatPlainSummary(diagnostics) {
|
|
143
|
+
const error_count = countDiagnosticsByLevel(diagnostics, 'error');
|
|
144
|
+
const warning_count = countDiagnosticsByLevel(diagnostics, 'warning');
|
|
145
|
+
const problem_count = diagnostics.length;
|
|
146
|
+
|
|
147
|
+
return `\u2716 ${problem_count} ${pluralize('problem', problem_count)} (${error_count} ${pluralize('error', error_count)}, ${warning_count} ${pluralize('warning', warning_count)})`;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* @param {{ diagnostics: PatramDiagnostic[], path: string }} diagnostic_group
|
|
152
|
+
* @param {number} diagnostic_prefix_width
|
|
153
|
+
* @param {Ansis} ansi
|
|
154
|
+
* @returns {string}
|
|
155
|
+
*/
|
|
156
|
+
function formatRichDiagnosticGroup(
|
|
157
|
+
diagnostic_group,
|
|
158
|
+
diagnostic_prefix_width,
|
|
159
|
+
ansi,
|
|
160
|
+
) {
|
|
161
|
+
return `${ansi.green(formatDiagnosticGroupHeader(diagnostic_group.path))}\n${diagnostic_group.diagnostics
|
|
162
|
+
.map((diagnostic) =>
|
|
163
|
+
formatRichDiagnosticRow(diagnostic, diagnostic_prefix_width, ansi),
|
|
164
|
+
)
|
|
165
|
+
.join('\n')}`;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* @param {PatramDiagnostic} diagnostic
|
|
170
|
+
* @param {number} diagnostic_prefix_width
|
|
171
|
+
* @param {Ansis} ansi
|
|
172
|
+
* @returns {string}
|
|
173
|
+
*/
|
|
174
|
+
function formatRichDiagnosticRow(diagnostic, diagnostic_prefix_width, ansi) {
|
|
175
|
+
const location = `${diagnostic.line}:${diagnostic.column}`;
|
|
176
|
+
const diagnostic_prefix = formatDiagnosticPrefix(diagnostic);
|
|
177
|
+
return ` ${ansi.gray(location)} ${formatRichDiagnosticLevel(diagnostic.level, ansi)} ${diagnostic.message}${createDiagnosticCodePadding(diagnostic_prefix, diagnostic_prefix_width)} ${ansi.gray(diagnostic.code)}`;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* @param {PatramDiagnostic[]} diagnostics
|
|
182
|
+
* @param {Ansis} ansi
|
|
183
|
+
* @returns {string}
|
|
184
|
+
*/
|
|
185
|
+
function formatRichSummary(diagnostics, ansi) {
|
|
186
|
+
return ansi.red(formatPlainSummary(diagnostics));
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* @param {PatramDiagnostic} diagnostic
|
|
191
|
+
* @returns {{ code: string, column: number, level: 'error', line: number, message: string, path: string }}
|
|
192
|
+
*/
|
|
193
|
+
function formatJsonDiagnostic(diagnostic) {
|
|
194
|
+
return {
|
|
195
|
+
path: diagnostic.path,
|
|
196
|
+
line: diagnostic.line,
|
|
197
|
+
column: diagnostic.column,
|
|
198
|
+
level: diagnostic.level,
|
|
199
|
+
code: diagnostic.code,
|
|
200
|
+
message: diagnostic.message,
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* @param {number} source_file_count
|
|
206
|
+
* @returns {string}
|
|
207
|
+
*/
|
|
208
|
+
function formatCheckSuccess(source_file_count) {
|
|
209
|
+
return `Check passed.\nScanned ${source_file_count} ${pluralize('file', source_file_count)}. Found 0 errors.\n`;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* @param {PatramDiagnostic} diagnostic
|
|
214
|
+
*/
|
|
215
|
+
function formatDiagnosticPrefix(diagnostic) {
|
|
216
|
+
return ` ${diagnostic.line}:${diagnostic.column} ${diagnostic.level} ${diagnostic.message}`;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* @param {PatramDiagnostic[]} diagnostics
|
|
221
|
+
*/
|
|
222
|
+
function measureMaxDiagnosticPrefixWidth(diagnostics) {
|
|
223
|
+
let max_width = 0;
|
|
224
|
+
|
|
225
|
+
for (const diagnostic of diagnostics) {
|
|
226
|
+
const diagnostic_prefix_width = stringWidth(
|
|
227
|
+
formatDiagnosticPrefix(diagnostic),
|
|
228
|
+
);
|
|
229
|
+
|
|
230
|
+
if (diagnostic_prefix_width > max_width) {
|
|
231
|
+
max_width = diagnostic_prefix_width;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
return max_width;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* @param {string} diagnostic_prefix
|
|
240
|
+
* @param {number} diagnostic_prefix_width
|
|
241
|
+
*/
|
|
242
|
+
function createDiagnosticCodePadding(
|
|
243
|
+
diagnostic_prefix,
|
|
244
|
+
diagnostic_prefix_width,
|
|
245
|
+
) {
|
|
246
|
+
return ' '.repeat(
|
|
247
|
+
Math.max(0, diagnostic_prefix_width - stringWidth(diagnostic_prefix)),
|
|
248
|
+
);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* @param {PatramDiagnostic[]} diagnostics
|
|
253
|
+
* @param {'error' | 'warning'} level
|
|
254
|
+
*/
|
|
255
|
+
function countDiagnosticsByLevel(diagnostics, level) {
|
|
256
|
+
let count = 0;
|
|
257
|
+
|
|
258
|
+
for (const diagnostic of diagnostics) {
|
|
259
|
+
if (diagnostic.level === level) {
|
|
260
|
+
count += 1;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
return count;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* @param {'error' | 'warning'} level
|
|
269
|
+
* @param {Ansis} ansi
|
|
270
|
+
* @returns {string}
|
|
271
|
+
*/
|
|
272
|
+
function formatRichDiagnosticLevel(level, ansi) {
|
|
273
|
+
if (level === 'warning') {
|
|
274
|
+
return ansi.yellow(level);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
return ansi.red(level);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* @param {string} noun
|
|
282
|
+
* @param {number} count
|
|
283
|
+
*/
|
|
284
|
+
function pluralize(noun, count) {
|
|
285
|
+
if (count === 1) {
|
|
286
|
+
return noun;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
return `${noun}s`;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* @param {ResolvedOutputMode} output_mode
|
|
294
|
+
*/
|
|
295
|
+
function createAnsi(output_mode) {
|
|
296
|
+
return new Ansis(output_mode.color_enabled ? 3 : 0);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* @param {string} path
|
|
301
|
+
*/
|
|
302
|
+
function formatDiagnosticGroupHeader(path) {
|
|
303
|
+
return `${resolveDiagnosticPathType(path)} ${path}`;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* @param {string} path
|
|
308
|
+
*/
|
|
309
|
+
function resolveDiagnosticPathType(path) {
|
|
310
|
+
if (path.endsWith('.json') || path.startsWith('<')) {
|
|
311
|
+
return 'file';
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
return 'document';
|
|
315
|
+
}
|