patram 0.2.0 → 0.3.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/lib/build-graph-identity.js +20 -19
- package/lib/build-graph.js +369 -16
- package/lib/build-graph.types.ts +5 -2
- package/lib/check-directive-metadata.js +516 -0
- package/lib/check-directive-value.js +282 -0
- package/lib/check-graph.js +24 -5
- package/lib/cli-help-metadata.js +44 -16
- package/lib/derived-summary.js +10 -8
- package/lib/directive-diagnostics.js +38 -0
- package/lib/directive-type-rules.js +133 -0
- package/lib/discover-fields.js +427 -0
- package/lib/discover-fields.types.ts +52 -0
- package/lib/format-node-header.js +9 -7
- package/lib/format-output-metadata.js +15 -23
- package/lib/layout-stored-queries.js +6 -60
- package/lib/load-patram-config.js +433 -96
- package/lib/load-patram-config.types.ts +98 -3
- package/lib/load-project-graph.js +4 -1
- package/lib/output-view.types.ts +14 -6
- package/lib/parse-cli-arguments.types.ts +1 -1
- package/lib/parse-where-clause.js +117 -51
- package/lib/parse-where-clause.types.ts +4 -2
- package/lib/patram-cli.js +36 -4
- package/lib/patram-config.js +31 -31
- package/lib/patram-config.types.ts +10 -4
- package/lib/query-graph.js +241 -22
- package/lib/query-inspection.js +285 -10
- package/lib/render-field-discovery.js +148 -0
- package/lib/render-json-output.js +21 -22
- package/lib/render-output-view.js +240 -19
- package/lib/render-plain-output.js +1 -1
- package/lib/render-rich-output.js +1 -1
- package/lib/resolve-patram-graph-config.js +15 -9
- package/lib/show-document.js +51 -7
- package/package.json +5 -5
package/lib/query-inspection.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
/** @import * as $k$$l$parse$j$where$j$clause$k$types$k$ts from './parse-where-clause.types.ts'; */
|
|
1
2
|
/* eslint-disable max-lines */
|
|
2
3
|
/**
|
|
3
4
|
* @import { PatramDiagnostic, PatramRepoConfig } from './load-patram-config.types.ts';
|
|
@@ -19,6 +20,10 @@ import { parseWhereClause } from './parse-where-clause.js';
|
|
|
19
20
|
* @typedef {{ kind: 'ad_hoc' } | { kind: 'stored_query', name: string }} QuerySource
|
|
20
21
|
*/
|
|
21
22
|
|
|
23
|
+
/**
|
|
24
|
+
* @typedef {import('./parse-where-clause.types.ts').ParsedFieldTerm | import('./parse-where-clause.types.ts').ParsedFieldSetTerm} FieldDiagnosticTerm
|
|
25
|
+
*/
|
|
26
|
+
|
|
22
27
|
/**
|
|
23
28
|
* @typedef {{ query_source: QuerySource, where_clause: string }} ResolvedWhereClause
|
|
24
29
|
*/
|
|
@@ -67,7 +72,7 @@ export function inspectQuery(
|
|
|
67
72
|
};
|
|
68
73
|
}
|
|
69
74
|
|
|
70
|
-
const diagnostics =
|
|
75
|
+
const diagnostics = getQuerySemanticDiagnostics(
|
|
71
76
|
repo_config,
|
|
72
77
|
resolved_where_clause.query_source,
|
|
73
78
|
parse_result.clauses,
|
|
@@ -143,12 +148,10 @@ export function renderQueryInspection(query_inspection, output_mode) {
|
|
|
143
148
|
* @param {ParsedClause[]} clauses
|
|
144
149
|
* @returns {PatramDiagnostic[]}
|
|
145
150
|
*/
|
|
146
|
-
function
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
const known_relation_names = new Set(Object.keys(repo_config.relations));
|
|
151
|
+
function collectSemanticDiagnostics(repo_config, query_source, clauses) {
|
|
152
|
+
const known_relation_names = new Set(
|
|
153
|
+
Object.keys(repo_config.relations ?? {}),
|
|
154
|
+
);
|
|
152
155
|
/** @type {PatramDiagnostic[]} */
|
|
153
156
|
const diagnostics = [];
|
|
154
157
|
|
|
@@ -156,22 +159,41 @@ function collectRelationDiagnostics(repo_config, query_source, clauses) {
|
|
|
156
159
|
clauses,
|
|
157
160
|
diagnostics,
|
|
158
161
|
known_relation_names,
|
|
162
|
+
repo_config.fields ?? {},
|
|
159
163
|
formatQueryDiagnosticPath(query_source),
|
|
160
164
|
);
|
|
161
165
|
|
|
162
166
|
return diagnostics;
|
|
163
167
|
}
|
|
164
168
|
|
|
169
|
+
/**
|
|
170
|
+
* Collect schema-aware diagnostics for one parsed where clause.
|
|
171
|
+
*
|
|
172
|
+
* @param {PatramRepoConfig} repo_config
|
|
173
|
+
* @param {QuerySource} query_source
|
|
174
|
+
* @param {ParsedClause[]} clauses
|
|
175
|
+
* @returns {PatramDiagnostic[]}
|
|
176
|
+
*/
|
|
177
|
+
export function getQuerySemanticDiagnostics(
|
|
178
|
+
repo_config,
|
|
179
|
+
query_source,
|
|
180
|
+
clauses,
|
|
181
|
+
) {
|
|
182
|
+
return collectSemanticDiagnostics(repo_config, query_source, clauses);
|
|
183
|
+
}
|
|
184
|
+
|
|
165
185
|
/**
|
|
166
186
|
* @param {ParsedClause[]} clauses
|
|
167
187
|
* @param {PatramDiagnostic[]} diagnostics
|
|
168
188
|
* @param {Set<string>} known_relation_names
|
|
189
|
+
* @param {Record<string, import('./load-patram-config.types.ts').MetadataFieldConfig>} known_field_definitions
|
|
169
190
|
* @param {string} diagnostic_path
|
|
170
191
|
*/
|
|
171
192
|
function collectClauseDiagnostics(
|
|
172
193
|
clauses,
|
|
173
194
|
diagnostics,
|
|
174
195
|
known_relation_names,
|
|
196
|
+
known_field_definitions,
|
|
175
197
|
diagnostic_path,
|
|
176
198
|
) {
|
|
177
199
|
for (const clause of clauses) {
|
|
@@ -179,6 +201,7 @@ function collectClauseDiagnostics(
|
|
|
179
201
|
clause.term,
|
|
180
202
|
diagnostics,
|
|
181
203
|
known_relation_names,
|
|
204
|
+
known_field_definitions,
|
|
182
205
|
diagnostic_path,
|
|
183
206
|
);
|
|
184
207
|
}
|
|
@@ -188,12 +211,14 @@ function collectClauseDiagnostics(
|
|
|
188
211
|
* @param {ParsedTerm} term
|
|
189
212
|
* @param {PatramDiagnostic[]} diagnostics
|
|
190
213
|
* @param {Set<string>} known_relation_names
|
|
214
|
+
* @param {Record<string, import('./load-patram-config.types.ts').MetadataFieldConfig>} known_field_definitions
|
|
191
215
|
* @param {string} diagnostic_path
|
|
192
216
|
*/
|
|
193
217
|
function collectTermDiagnostics(
|
|
194
218
|
term,
|
|
195
219
|
diagnostics,
|
|
196
220
|
known_relation_names,
|
|
221
|
+
known_field_definitions,
|
|
197
222
|
diagnostic_path,
|
|
198
223
|
) {
|
|
199
224
|
if (term.kind === 'aggregate') {
|
|
@@ -207,6 +232,7 @@ function collectTermDiagnostics(
|
|
|
207
232
|
term.clauses,
|
|
208
233
|
diagnostics,
|
|
209
234
|
known_relation_names,
|
|
235
|
+
known_field_definitions,
|
|
210
236
|
diagnostic_path,
|
|
211
237
|
);
|
|
212
238
|
|
|
@@ -233,7 +259,256 @@ function collectTermDiagnostics(
|
|
|
233
259
|
diagnostic_path,
|
|
234
260
|
'relation clause',
|
|
235
261
|
);
|
|
262
|
+
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
collectFieldDiagnostics(
|
|
267
|
+
/** @type {FieldDiagnosticTerm} */ (term),
|
|
268
|
+
diagnostics,
|
|
269
|
+
known_field_definitions,
|
|
270
|
+
diagnostic_path,
|
|
271
|
+
);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* @param {FieldDiagnosticTerm} term
|
|
276
|
+
* @param {PatramDiagnostic[]} diagnostics
|
|
277
|
+
* @param {Record<string, import('./load-patram-config.types.ts').MetadataFieldConfig>} known_field_definitions
|
|
278
|
+
* @param {string} diagnostic_path
|
|
279
|
+
*/
|
|
280
|
+
function collectFieldDiagnostics(
|
|
281
|
+
term,
|
|
282
|
+
diagnostics,
|
|
283
|
+
known_field_definitions,
|
|
284
|
+
diagnostic_path,
|
|
285
|
+
) {
|
|
286
|
+
const field_diagnostics_collector = getFieldDiagnosticsCollector(
|
|
287
|
+
term.field_name,
|
|
288
|
+
);
|
|
289
|
+
|
|
290
|
+
if (field_diagnostics_collector) {
|
|
291
|
+
field_diagnostics_collector(term, diagnostics, diagnostic_path);
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
if (term.field_name.startsWith('$')) {
|
|
296
|
+
diagnostics.push(
|
|
297
|
+
createQueryDiagnostic(
|
|
298
|
+
diagnostic_path,
|
|
299
|
+
term.column,
|
|
300
|
+
`Reserved field "${term.field_name}" is not available.`,
|
|
301
|
+
'query.reserved_field',
|
|
302
|
+
),
|
|
303
|
+
);
|
|
304
|
+
|
|
305
|
+
return;
|
|
236
306
|
}
|
|
307
|
+
|
|
308
|
+
const field_definition = known_field_definitions[term.field_name];
|
|
309
|
+
|
|
310
|
+
if (!field_definition) {
|
|
311
|
+
diagnostics.push(
|
|
312
|
+
createQueryDiagnostic(
|
|
313
|
+
diagnostic_path,
|
|
314
|
+
term.column,
|
|
315
|
+
`Unknown field "${term.field_name}".`,
|
|
316
|
+
'query.unknown_field',
|
|
317
|
+
),
|
|
318
|
+
);
|
|
319
|
+
|
|
320
|
+
return;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
if (term.kind === 'field_set') {
|
|
324
|
+
if (term.operator === 'in' || term.operator === 'not in') {
|
|
325
|
+
return;
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
if (term.kind === 'field') {
|
|
330
|
+
const operator_diagnostic = getMetadataFieldOperatorDiagnostic(
|
|
331
|
+
term,
|
|
332
|
+
field_definition,
|
|
333
|
+
diagnostic_path,
|
|
334
|
+
);
|
|
335
|
+
|
|
336
|
+
if (operator_diagnostic) {
|
|
337
|
+
diagnostics.push(operator_diagnostic);
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
/**
|
|
343
|
+
* @param {string} field_name
|
|
344
|
+
* @returns {((term: FieldDiagnosticTerm, diagnostics: PatramDiagnostic[], diagnostic_path: string) => void) | null}
|
|
345
|
+
*/
|
|
346
|
+
function getFieldDiagnosticsCollector(field_name) {
|
|
347
|
+
if (field_name === 'title') {
|
|
348
|
+
return collectTitleFieldDiagnostics;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
if (
|
|
352
|
+
field_name === '$id' ||
|
|
353
|
+
field_name === '$class' ||
|
|
354
|
+
field_name === '$path'
|
|
355
|
+
) {
|
|
356
|
+
return collectStructuralFieldDiagnostics;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
return null;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
/**
|
|
363
|
+
* @param {FieldDiagnosticTerm} term
|
|
364
|
+
* @param {PatramDiagnostic[]} diagnostics
|
|
365
|
+
* @param {string} diagnostic_path
|
|
366
|
+
*/
|
|
367
|
+
function collectTitleFieldDiagnostics(term, diagnostics, diagnostic_path) {
|
|
368
|
+
const operator = term.kind === 'field' ? term.operator : term.operator;
|
|
369
|
+
const allowed = new Set(['=', '!=', 'in', 'not in', '~']);
|
|
370
|
+
|
|
371
|
+
if (allowed.has(operator)) {
|
|
372
|
+
return;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
diagnostics.push(
|
|
376
|
+
createQueryDiagnostic(
|
|
377
|
+
diagnostic_path,
|
|
378
|
+
term.column,
|
|
379
|
+
`Field "title" does not support the "${operator}" operator.`,
|
|
380
|
+
'query.invalid_operator',
|
|
381
|
+
),
|
|
382
|
+
);
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
/**
|
|
386
|
+
* @param {FieldDiagnosticTerm} term
|
|
387
|
+
* @param {PatramDiagnostic[]} diagnostics
|
|
388
|
+
* @param {string} diagnostic_path
|
|
389
|
+
*/
|
|
390
|
+
function collectStructuralFieldDiagnostics(term, diagnostics, diagnostic_path) {
|
|
391
|
+
const operator = term.kind === 'field' ? term.operator : term.operator;
|
|
392
|
+
const allowed_by_field = new Map([
|
|
393
|
+
['$class', new Set(['=', '!=', 'in', 'not in'])],
|
|
394
|
+
['$id', new Set(['=', '!=', 'in', 'not in', '^='])],
|
|
395
|
+
['$path', new Set(['=', '!=', 'in', 'not in', '^='])],
|
|
396
|
+
]);
|
|
397
|
+
const allowed = allowed_by_field.get(term.field_name);
|
|
398
|
+
|
|
399
|
+
if (!allowed || allowed.has(operator)) {
|
|
400
|
+
return;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
diagnostics.push(
|
|
404
|
+
createQueryDiagnostic(
|
|
405
|
+
diagnostic_path,
|
|
406
|
+
term.column,
|
|
407
|
+
`Field "${term.field_name}" does not support the "${operator}" operator.`,
|
|
408
|
+
'query.invalid_operator',
|
|
409
|
+
),
|
|
410
|
+
);
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
/**
|
|
414
|
+
* @param {import('./parse-where-clause.types.ts').ParsedFieldTerm} term
|
|
415
|
+
* @param {import('./load-patram-config.types.ts').MetadataFieldConfig} field_definition
|
|
416
|
+
* @param {string} diagnostic_path
|
|
417
|
+
* @returns {PatramDiagnostic | null}
|
|
418
|
+
*/
|
|
419
|
+
function getMetadataFieldOperatorDiagnostic(
|
|
420
|
+
term,
|
|
421
|
+
field_definition,
|
|
422
|
+
diagnostic_path,
|
|
423
|
+
) {
|
|
424
|
+
if (supportsMetadataFieldOperator(term.operator, field_definition)) {
|
|
425
|
+
return null;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
return createQueryDiagnostic(
|
|
429
|
+
diagnostic_path,
|
|
430
|
+
term.column,
|
|
431
|
+
getUnsupportedOperatorMessage(term.field_name, term.operator),
|
|
432
|
+
'query.invalid_operator',
|
|
433
|
+
);
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
/**
|
|
437
|
+
* @param {string} operator
|
|
438
|
+
* @param {import('./load-patram-config.types.ts').MetadataFieldConfig} field_definition
|
|
439
|
+
* @returns {boolean}
|
|
440
|
+
*/
|
|
441
|
+
function supportsMetadataFieldOperator(operator, field_definition) {
|
|
442
|
+
if (operator === '=' || operator === '!=') {
|
|
443
|
+
return true;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
if (operator === '^=') {
|
|
447
|
+
return supportsPrefixOperator(field_definition);
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
if (operator === '~') {
|
|
451
|
+
return supportsContainsOperator(field_definition);
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
return supportsOrderedComparison(operator, field_definition.type);
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
/**
|
|
458
|
+
* @param {import('./load-patram-config.types.ts').MetadataFieldConfig} field_definition
|
|
459
|
+
* @returns {boolean}
|
|
460
|
+
*/
|
|
461
|
+
function supportsPrefixOperator(field_definition) {
|
|
462
|
+
if (field_definition.type === 'path') {
|
|
463
|
+
return true;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
return (
|
|
467
|
+
field_definition.type === 'string' &&
|
|
468
|
+
field_definition.query?.prefix === true
|
|
469
|
+
);
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
/**
|
|
473
|
+
* @param {import('./load-patram-config.types.ts').MetadataFieldConfig} field_definition
|
|
474
|
+
* @returns {boolean}
|
|
475
|
+
*/
|
|
476
|
+
function supportsContainsOperator(field_definition) {
|
|
477
|
+
return (
|
|
478
|
+
field_definition.type === 'string' &&
|
|
479
|
+
field_definition.query?.contains === true
|
|
480
|
+
);
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
/**
|
|
484
|
+
* @param {string} operator
|
|
485
|
+
* @param {string} field_type
|
|
486
|
+
* @returns {boolean}
|
|
487
|
+
*/
|
|
488
|
+
function supportsOrderedComparison(operator, field_type) {
|
|
489
|
+
if (
|
|
490
|
+
operator !== '<' &&
|
|
491
|
+
operator !== '<=' &&
|
|
492
|
+
operator !== '>' &&
|
|
493
|
+
operator !== '>='
|
|
494
|
+
) {
|
|
495
|
+
return false;
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
return (
|
|
499
|
+
field_type === 'integer' ||
|
|
500
|
+
field_type === 'date' ||
|
|
501
|
+
field_type === 'date_time'
|
|
502
|
+
);
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
/**
|
|
506
|
+
* @param {string} field_name
|
|
507
|
+
* @param {string} operator
|
|
508
|
+
* @returns {string}
|
|
509
|
+
*/
|
|
510
|
+
function getUnsupportedOperatorMessage(field_name, operator) {
|
|
511
|
+
return `Field "${field_name}" does not support the "${operator}" operator.`;
|
|
237
512
|
}
|
|
238
513
|
|
|
239
514
|
/**
|
|
@@ -290,7 +565,6 @@ function collectTraversalDiagnostic(
|
|
|
290
565
|
|
|
291
566
|
/**
|
|
292
567
|
* @param {QueryInspectionSuccess} query_inspection
|
|
293
|
-
* @returns {object}
|
|
294
568
|
*/
|
|
295
569
|
function formatJsonQueryInspection(query_inspection) {
|
|
296
570
|
if (query_inspection.inspection_mode === 'lint') {
|
|
@@ -489,11 +763,12 @@ function formatQueryDiagnosticPath(query_source) {
|
|
|
489
763
|
* @param {string} diagnostic_path
|
|
490
764
|
* @param {number} column
|
|
491
765
|
* @param {string} message
|
|
766
|
+
* @param {PatramDiagnostic['code']} [code]
|
|
492
767
|
* @returns {PatramDiagnostic}
|
|
493
768
|
*/
|
|
494
|
-
function createQueryDiagnostic(diagnostic_path, column, message) {
|
|
769
|
+
function createQueryDiagnostic(diagnostic_path, column, message, code) {
|
|
495
770
|
return {
|
|
496
|
-
code: 'query.unknown_relation',
|
|
771
|
+
code: code ?? 'query.unknown_relation',
|
|
497
772
|
column,
|
|
498
773
|
level: 'error',
|
|
499
774
|
line: 1,
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @import { ResolvedOutputMode } from './output-view.types.ts';
|
|
3
|
+
* @import { FieldDiscoveryResult, FieldDiscoverySuggestion } from './discover-fields.types.ts';
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { Ansis } from 'ansis';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Render field discovery output.
|
|
10
|
+
*
|
|
11
|
+
* @param {FieldDiscoveryResult} discovery_result
|
|
12
|
+
* @param {ResolvedOutputMode} output_mode
|
|
13
|
+
* @returns {string}
|
|
14
|
+
*/
|
|
15
|
+
export function renderFieldDiscovery(discovery_result, output_mode) {
|
|
16
|
+
if (output_mode.renderer_name === 'json') {
|
|
17
|
+
return `${JSON.stringify(formatJsonFieldDiscovery(discovery_result), null, 2)}\n`;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return renderTextFieldDiscovery(discovery_result, output_mode);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* @param {FieldDiscoveryResult} discovery_result
|
|
25
|
+
* @returns {{ fields: Array<Record<string, unknown>>, summary: FieldDiscoveryResult['summary'] }}
|
|
26
|
+
*/
|
|
27
|
+
function formatJsonFieldDiscovery(discovery_result) {
|
|
28
|
+
return {
|
|
29
|
+
fields: discovery_result.fields.map(formatJsonFieldSuggestion),
|
|
30
|
+
summary: discovery_result.summary,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* @param {FieldDiscoverySuggestion} field_suggestion
|
|
36
|
+
* @returns {Record<string, unknown>}
|
|
37
|
+
*/
|
|
38
|
+
function formatJsonFieldSuggestion(field_suggestion) {
|
|
39
|
+
return {
|
|
40
|
+
confidence: field_suggestion.confidence,
|
|
41
|
+
conflicting_evidence: field_suggestion.conflicting_evidence,
|
|
42
|
+
evidence_references: field_suggestion.evidence_references,
|
|
43
|
+
likely_class_usage: field_suggestion.likely_class_usage,
|
|
44
|
+
likely_multiplicity: field_suggestion.likely_multiplicity,
|
|
45
|
+
likely_type: field_suggestion.likely_type,
|
|
46
|
+
name: field_suggestion.name,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* @param {FieldDiscoveryResult} discovery_result
|
|
52
|
+
* @param {ResolvedOutputMode} output_mode
|
|
53
|
+
* @returns {string}
|
|
54
|
+
*/
|
|
55
|
+
function renderTextFieldDiscovery(discovery_result, output_mode) {
|
|
56
|
+
const ansi = new Ansis(
|
|
57
|
+
output_mode.renderer_name === 'rich' && output_mode.color_enabled ? 3 : 0,
|
|
58
|
+
);
|
|
59
|
+
/** @type {string[]} */
|
|
60
|
+
const output_lines = [];
|
|
61
|
+
|
|
62
|
+
output_lines.push(
|
|
63
|
+
output_mode.renderer_name === 'rich'
|
|
64
|
+
? ansi.green('Field discovery')
|
|
65
|
+
: 'Field discovery',
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
output_lines.push(
|
|
69
|
+
output_mode.renderer_name === 'rich'
|
|
70
|
+
? ansi.gray(
|
|
71
|
+
`Found ${discovery_result.summary.count} suggested fields from ${discovery_result.summary.source_file_count} source files.`,
|
|
72
|
+
)
|
|
73
|
+
: `Found ${discovery_result.summary.count} suggested fields from ${discovery_result.summary.source_file_count} source files.`,
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
for (const field_suggestion of discovery_result.fields) {
|
|
77
|
+
output_lines.push(
|
|
78
|
+
'',
|
|
79
|
+
...formatTextFieldSuggestion(field_suggestion, {
|
|
80
|
+
header: (value) =>
|
|
81
|
+
output_mode.renderer_name === 'rich' ? ansi.green(value) : value,
|
|
82
|
+
label: (value) =>
|
|
83
|
+
output_mode.renderer_name === 'rich' ? ansi.gray(value) : value,
|
|
84
|
+
}),
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (discovery_result.fields.length === 0) {
|
|
89
|
+
output_lines.push('', 'No field candidates discovered.');
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return `${output_lines.join('\n')}\n`;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* @param {FieldDiscoverySuggestion} field_suggestion
|
|
97
|
+
* @param {{ header: (value: string) => string, label: (value: string) => string }} render_options
|
|
98
|
+
* @returns {string[]}
|
|
99
|
+
*/
|
|
100
|
+
function formatTextFieldSuggestion(field_suggestion, render_options) {
|
|
101
|
+
/** @type {string[]} */
|
|
102
|
+
const lines = [render_options.header(field_suggestion.name)];
|
|
103
|
+
|
|
104
|
+
lines.push(
|
|
105
|
+
`${render_options.label(' likely type:')} ${field_suggestion.likely_type.name}`,
|
|
106
|
+
`${render_options.label(' likely multiplicity:')} ${field_suggestion.likely_multiplicity.name}`,
|
|
107
|
+
`${render_options.label(' likely class usage:')} ${field_suggestion.likely_class_usage.classes.join(', ')}`,
|
|
108
|
+
`${render_options.label(' confidence:')} ${formatConfidence(field_suggestion.confidence)}`,
|
|
109
|
+
);
|
|
110
|
+
|
|
111
|
+
if (field_suggestion.evidence_references.length > 0) {
|
|
112
|
+
lines.push(render_options.label(' evidence:'));
|
|
113
|
+
lines.push(
|
|
114
|
+
...field_suggestion.evidence_references.map(
|
|
115
|
+
(evidence_reference) =>
|
|
116
|
+
`${render_options.label(' ')}${formatEvidenceReference(evidence_reference)}`,
|
|
117
|
+
),
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (field_suggestion.conflicting_evidence.length > 0) {
|
|
122
|
+
lines.push(render_options.label(' conflicting evidence:'));
|
|
123
|
+
lines.push(
|
|
124
|
+
...field_suggestion.conflicting_evidence.map(
|
|
125
|
+
(evidence_reference) =>
|
|
126
|
+
`${render_options.label(' ')}${formatEvidenceReference(evidence_reference)}`,
|
|
127
|
+
),
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return lines;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* @param {import('./discover-fields.types.ts').FieldDiscoveryEvidenceReference} evidence_reference
|
|
136
|
+
* @returns {string}
|
|
137
|
+
*/
|
|
138
|
+
function formatEvidenceReference(evidence_reference) {
|
|
139
|
+
return `${evidence_reference.path}:${evidence_reference.line}:${evidence_reference.column} ${JSON.stringify(evidence_reference.value)}`;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* @param {number} confidence
|
|
144
|
+
* @returns {string}
|
|
145
|
+
*/
|
|
146
|
+
function formatConfidence(confidence) {
|
|
147
|
+
return confidence.toFixed(2);
|
|
148
|
+
}
|
|
@@ -55,19 +55,19 @@ export function renderJsonOutput(output_view) {
|
|
|
55
55
|
|
|
56
56
|
/**
|
|
57
57
|
* @param {OutputNodeItem} output_item
|
|
58
|
-
* @returns {{ derived?: Record<string, boolean | number | string | null>, derived_summary?: string,
|
|
58
|
+
* @returns {{ '$class': string, '$id': string, '$path'?: string, derived?: Record<string, boolean | number | string | null>, derived_summary?: string, fields: Record<string, string | string[]>, title: string }}
|
|
59
59
|
*/
|
|
60
60
|
function formatJsonQueryItem(output_item) {
|
|
61
|
-
/** @type {{ derived?: Record<string, boolean | number | string | null>, derived_summary?: string,
|
|
61
|
+
/** @type {{ '$class': string, '$id': string, '$path'?: string, derived?: Record<string, boolean | number | string | null>, derived_summary?: string, fields: Record<string, string | string[]>, title: string }} */
|
|
62
62
|
const query_item = {
|
|
63
|
-
|
|
64
|
-
|
|
63
|
+
$class: output_item.node_kind,
|
|
64
|
+
$id: output_item.id,
|
|
65
|
+
fields: output_item.fields,
|
|
65
66
|
title: output_item.title,
|
|
66
|
-
path: output_item.path,
|
|
67
67
|
};
|
|
68
68
|
|
|
69
|
-
if (output_item.
|
|
70
|
-
query_item
|
|
69
|
+
if (output_item.path) {
|
|
70
|
+
query_item.$path = output_item.path;
|
|
71
71
|
}
|
|
72
72
|
|
|
73
73
|
if (output_item.derived_summary) {
|
|
@@ -96,25 +96,23 @@ function formatJsonStoredQuery(output_item) {
|
|
|
96
96
|
|
|
97
97
|
/**
|
|
98
98
|
* @param {OutputResolvedLinkItem} output_item
|
|
99
|
-
* @returns {{ label: string, reference: number, target: { derived?: Record<string, boolean | number | string | null>, derived_summary?: string,
|
|
99
|
+
* @returns {{ label: string, reference: number, target: { '$class': string, '$id': string, '$path'?: string, derived?: Record<string, boolean | number | string | null>, derived_summary?: string, fields: Record<string, string | string[]>, title: string } }}
|
|
100
100
|
*/
|
|
101
101
|
function formatJsonResolvedLink(output_item) {
|
|
102
|
-
/** @type {{ label: string, reference: number, target: { derived?: Record<string, boolean | number | string | null>, derived_summary?: string,
|
|
102
|
+
/** @type {{ label: string, reference: number, target: { '$class': string, '$id': string, '$path'?: string, derived?: Record<string, boolean | number | string | null>, derived_summary?: string, fields: Record<string, string | string[]>, title: string } }} */
|
|
103
103
|
const resolved_link = {
|
|
104
104
|
reference: output_item.reference,
|
|
105
105
|
label: output_item.label,
|
|
106
106
|
target: {
|
|
107
|
+
$class: output_item.target.kind,
|
|
108
|
+
$id: output_item.target.id,
|
|
109
|
+
fields: output_item.target.fields,
|
|
107
110
|
title: output_item.target.title,
|
|
108
|
-
path: output_item.target.path,
|
|
109
111
|
},
|
|
110
112
|
};
|
|
111
113
|
|
|
112
|
-
if (output_item.target.
|
|
113
|
-
resolved_link.target
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
if (output_item.target.status) {
|
|
117
|
-
resolved_link.target.status = output_item.target.status;
|
|
114
|
+
if (output_item.target.path) {
|
|
115
|
+
resolved_link.target.$path = output_item.target.path;
|
|
118
116
|
}
|
|
119
117
|
|
|
120
118
|
if (output_item.target.derived_summary) {
|
|
@@ -133,18 +131,19 @@ function formatJsonResolvedLink(output_item) {
|
|
|
133
131
|
|
|
134
132
|
/**
|
|
135
133
|
* @param {OutputNodeItem} output_item
|
|
136
|
-
* @returns {{ derived?: Record<string, boolean | number | string | null>, derived_summary?: string,
|
|
134
|
+
* @returns {{ '$class': string, '$id': string, '$path'?: string, derived?: Record<string, boolean | number | string | null>, derived_summary?: string, fields: Record<string, string | string[]>, title: string }}
|
|
137
135
|
*/
|
|
138
136
|
function formatJsonShowDocument(output_item) {
|
|
139
|
-
/** @type {{ derived?: Record<string, boolean | number | string | null>, derived_summary?: string,
|
|
137
|
+
/** @type {{ '$class': string, '$id': string, '$path'?: string, derived?: Record<string, boolean | number | string | null>, derived_summary?: string, fields: Record<string, string | string[]>, title: string }} */
|
|
140
138
|
const document_summary = {
|
|
141
|
-
|
|
142
|
-
|
|
139
|
+
$class: output_item.node_kind,
|
|
140
|
+
$id: output_item.id,
|
|
141
|
+
fields: output_item.fields,
|
|
143
142
|
title: output_item.title,
|
|
144
143
|
};
|
|
145
144
|
|
|
146
|
-
if (output_item.
|
|
147
|
-
document_summary
|
|
145
|
+
if (output_item.path) {
|
|
146
|
+
document_summary.$path = output_item.path;
|
|
148
147
|
}
|
|
149
148
|
|
|
150
149
|
if (output_item.derived_summary) {
|