patram 0.10.0 → 0.12.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 +4 -4
- package/lib/cli/arguments.types.d.ts +1 -0
- package/lib/cli/commands/check.js +27 -15
- package/lib/cli/commands/fields.js +0 -4
- package/lib/cli/commands/queries.js +179 -1
- package/lib/cli/commands/query.js +1 -8
- package/lib/cli/commands/refs.js +3 -10
- package/lib/cli/commands/show.js +1 -8
- package/lib/cli/help-metadata.js +106 -111
- package/lib/cli/main.js +10 -10
- package/lib/cli/parse-arguments-helpers.js +416 -66
- package/lib/cli/parse-arguments.js +4 -4
- package/lib/cli/render-help.js +10 -4
- package/lib/config/defaults.js +33 -25
- package/lib/config/load-patram-config.d.ts +19 -33
- package/lib/config/load-patram-config.js +18 -121
- package/lib/config/load-patram-config.types.d.ts +3 -40
- package/lib/config/manage-stored-queries-helpers.d.ts +69 -0
- package/lib/config/manage-stored-queries-helpers.js +320 -0
- package/lib/config/manage-stored-queries-jsonc.d.ts +31 -0
- package/lib/config/manage-stored-queries-jsonc.js +95 -0
- package/lib/config/manage-stored-queries.d.ts +77 -0
- package/lib/config/manage-stored-queries.js +300 -0
- package/lib/config/patram-config.d.ts +34 -34
- package/lib/config/patram-config.js +3 -3
- package/lib/config/patram-config.types.d.ts +5 -11
- package/lib/config/resolve-patram-graph-config.d.ts +5 -1
- package/lib/config/resolve-patram-graph-config.js +3 -119
- package/lib/config/schema.d.ts +158 -269
- package/lib/config/schema.js +72 -210
- package/lib/config/validate-patram-config-value.d.ts +13 -0
- package/lib/config/validate-patram-config-value.js +94 -0
- package/lib/config/validation.d.ts +2 -12
- package/lib/config/validation.js +125 -483
- package/lib/find-close-match.d.ts +4 -1
- package/lib/graph/build-graph-identity.d.ts +1 -32
- package/lib/graph/build-graph-identity.js +5 -269
- package/lib/graph/build-graph.d.ts +13 -4
- package/lib/graph/build-graph.js +347 -488
- package/lib/graph/build-graph.types.d.ts +8 -9
- package/lib/graph/check-directive-metadata-helpers.d.ts +30 -0
- package/lib/graph/check-directive-metadata-helpers.js +126 -0
- package/lib/graph/check-directive-metadata.d.ts +8 -9
- package/lib/graph/check-directive-metadata.js +70 -561
- package/lib/graph/check-directive-path-target.d.ts +6 -13
- package/lib/graph/check-directive-path-target.js +26 -57
- package/lib/graph/check-directive-value.d.ts +1 -5
- package/lib/graph/check-directive-value.js +40 -180
- package/lib/graph/check-graph.d.ts +5 -5
- package/lib/graph/check-graph.js +8 -6
- package/lib/graph/document-node-identity.d.ts +23 -7
- package/lib/graph/document-node-identity.js +417 -160
- package/lib/graph/graph-node.d.ts +42 -0
- package/lib/graph/graph-node.js +83 -0
- package/lib/graph/inspect-reverse-references.js +16 -11
- package/lib/graph/load-project-graph.d.ts +7 -7
- package/lib/graph/load-project-graph.js +7 -7
- package/lib/graph/parse-where-clause.types.d.ts +3 -2
- package/lib/graph/query/cypher-reader.d.ts +59 -0
- package/lib/graph/query/cypher-reader.js +151 -0
- package/lib/graph/query/cypher-support.d.ts +79 -0
- package/lib/graph/query/cypher-support.js +213 -0
- package/lib/graph/query/cypher-tokenize.d.ts +13 -0
- package/lib/graph/query/cypher-tokenize.js +225 -0
- package/lib/graph/query/cypher.types.d.ts +43 -0
- package/lib/graph/query/execute.d.ts +7 -7
- package/lib/graph/query/execute.js +71 -33
- package/lib/graph/query/inspect.js +58 -24
- package/lib/graph/query/parse-cypher-patterns.d.ts +27 -0
- package/lib/graph/query/parse-cypher-patterns.js +382 -0
- package/lib/graph/query/parse-cypher.d.ts +7 -0
- package/lib/graph/query/parse-cypher.js +580 -0
- package/lib/graph/query/parse-query.d.ts +13 -0
- package/lib/graph/query/parse-query.js +97 -0
- package/lib/graph/query/resolve.d.ts +6 -0
- package/lib/graph/query/resolve.js +81 -24
- package/lib/output/command-output.js +12 -5
- package/lib/output/compact-layout.js +221 -0
- package/lib/output/format-output-item-block.js +31 -1
- package/lib/output/format-output-metadata.js +16 -29
- package/lib/output/format-stored-query-block.js +95 -0
- package/lib/output/layout-incoming-references.js +101 -19
- package/lib/output/layout-stored-queries.js +23 -330
- package/lib/output/list-queries.js +1 -1
- package/lib/output/render-field-discovery.js +11 -2
- package/lib/output/render-output-view.js +9 -5
- package/lib/output/renderers/json.js +5 -26
- package/lib/output/renderers/plain.js +155 -35
- package/lib/output/renderers/rich.js +250 -36
- package/lib/output/resolve-check-target.js +120 -11
- package/lib/output/resolved-link-layout.js +43 -0
- package/lib/output/rich-source/render.js +193 -35
- package/lib/output/show-document.js +25 -18
- package/lib/output/view-model/index.js +124 -103
- package/lib/parse/jsdoc/parse-jsdoc-blocks.js +1 -1
- package/lib/parse/jsdoc/parse-jsdoc-claims.js +12 -6
- package/lib/parse/markdown/parse-markdown-claims.js +99 -62
- package/lib/parse/markdown/parse-markdown-directives.d.ts +10 -6
- package/lib/parse/markdown/parse-markdown-directives.js +104 -18
- package/lib/parse/markdown/parse-markdown-prose.d.ts +27 -0
- package/lib/parse/markdown/parse-markdown-prose.js +243 -0
- package/lib/parse/parse-claims.d.ts +2 -6
- package/lib/parse/parse-claims.js +11 -53
- package/lib/parse/tagged-fenced/tagged-fenced-blocks.d.ts +4 -4
- package/lib/parse/tagged-fenced/tagged-fenced-blocks.js +4 -4
- package/lib/parse/yaml/parse-yaml-claims.js +4 -4
- package/lib/patram.d.ts +9 -3
- package/lib/patram.js +1 -1
- package/lib/scan/discover-fields.js +194 -55
- package/lib/scan/list-source-files.d.ts +4 -4
- package/lib/scan/list-source-files.js +4 -4
- package/package.json +2 -1
- package/lib/directive-validation-test-helpers.js +0 -87
- package/lib/graph/query/parse.d.ts +0 -75
- package/lib/graph/query/parse.js +0 -1064
- package/lib/output/derived-summary.js +0 -280
- package/lib/output/format-derived-summary-row.js +0 -9
package/lib/config/validation.js
CHANGED
|
@@ -1,26 +1,19 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @import {
|
|
3
|
-
* @import {
|
|
4
|
-
* DerivedSummaryFieldConfig,
|
|
5
|
-
* MetadataFieldConfig,
|
|
6
|
-
* PatramRepoConfig,
|
|
7
|
-
* } from './schema.js';
|
|
8
|
-
* @import { PatramDiagnostic } from './load-patram-config.js';
|
|
2
|
+
* @import { MetadataFieldConfig, PatramRepoConfig, PatramDiagnostic } from './load-patram-config.types.ts';
|
|
9
3
|
*/
|
|
10
|
-
/* eslint-disable max-lines */
|
|
11
4
|
|
|
12
|
-
import {
|
|
13
|
-
|
|
14
|
-
import { parsePatramConfig } from './patram-config.js';
|
|
5
|
+
import { CONFIG_FILE_NAME, isReservedStructuralFieldName } from './schema.js';
|
|
15
6
|
import { getQuerySemanticDiagnostics } from '../graph/query/inspect.js';
|
|
16
|
-
import {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
7
|
+
import { parseQueryExpression } from '../graph/query/parse-query.js';
|
|
8
|
+
|
|
9
|
+
const LEGACY_TOP_LEVEL_KEYS = {
|
|
10
|
+
class_schemas:
|
|
11
|
+
'Top-level "class_schemas" is not supported. Use "types" and field rules.',
|
|
12
|
+
classes: 'Top-level "classes" is not supported. Use "types".',
|
|
13
|
+
mappings: 'Top-level "mappings" is not supported.',
|
|
14
|
+
path_classes: 'Top-level "path_classes" is not supported.',
|
|
15
|
+
relations: 'Top-level "relations" is not supported. Use ref fields.',
|
|
16
|
+
};
|
|
24
17
|
|
|
25
18
|
/**
|
|
26
19
|
* @param {unknown} config_value
|
|
@@ -30,47 +23,23 @@ export function validateLegacyConfigShape(config_value) {
|
|
|
30
23
|
if (
|
|
31
24
|
config_value === null ||
|
|
32
25
|
typeof config_value !== 'object' ||
|
|
33
|
-
|
|
26
|
+
Array.isArray(config_value)
|
|
34
27
|
) {
|
|
35
28
|
return [];
|
|
36
29
|
}
|
|
37
30
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
'class_schemas',
|
|
41
|
-
'Top-level "class_schemas" is not supported. Move entries into classes.<name>.schema.',
|
|
42
|
-
),
|
|
43
|
-
];
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* @param {PatramRepoConfig} repo_config
|
|
48
|
-
* @returns {PatramDiagnostic[]}
|
|
49
|
-
*/
|
|
50
|
-
export function validateGraphSchema(repo_config) {
|
|
51
|
-
if (
|
|
52
|
-
repo_config.classes === undefined &&
|
|
53
|
-
repo_config.mappings === undefined &&
|
|
54
|
-
repo_config.relations === undefined
|
|
55
|
-
) {
|
|
56
|
-
return [];
|
|
57
|
-
}
|
|
31
|
+
/** @type {PatramDiagnostic[]} */
|
|
32
|
+
const diagnostics = [];
|
|
58
33
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
mappings: repo_config.mappings ?? {},
|
|
63
|
-
relations: repo_config.relations ?? {},
|
|
64
|
-
});
|
|
65
|
-
} catch (error) {
|
|
66
|
-
if (error instanceof z.ZodError) {
|
|
67
|
-
return error.issues.map(createValidationDiagnostic);
|
|
34
|
+
for (const [legacy_key, message] of Object.entries(LEGACY_TOP_LEVEL_KEYS)) {
|
|
35
|
+
if (!Object.hasOwn(config_value, legacy_key)) {
|
|
36
|
+
continue;
|
|
68
37
|
}
|
|
69
38
|
|
|
70
|
-
|
|
39
|
+
diagnostics.push(createConfigDiagnostic(legacy_key, message));
|
|
71
40
|
}
|
|
72
41
|
|
|
73
|
-
return
|
|
42
|
+
return diagnostics;
|
|
74
43
|
}
|
|
75
44
|
|
|
76
45
|
/**
|
|
@@ -78,19 +47,22 @@ export function validateGraphSchema(repo_config) {
|
|
|
78
47
|
* @returns {PatramDiagnostic[]}
|
|
79
48
|
*/
|
|
80
49
|
export function validateFieldSchemaConfig(repo_config) {
|
|
81
|
-
const path_classes = repo_config.path_classes ?? {};
|
|
82
|
-
const classes = repo_config.classes ?? {};
|
|
83
50
|
const fields = repo_config.fields ?? {};
|
|
51
|
+
const types = repo_config.types ?? {};
|
|
84
52
|
/** @type {PatramDiagnostic[]} */
|
|
85
53
|
const diagnostics = [];
|
|
86
54
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
)
|
|
55
|
+
for (const [field_name, field_definition] of Object.entries(fields)) {
|
|
56
|
+
diagnostics.push(
|
|
57
|
+
...validateFieldDefinition(field_name, field_definition, types),
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
for (const [type_name, type_definition] of Object.entries(types)) {
|
|
62
|
+
diagnostics.push(
|
|
63
|
+
...validateTypeDefinition(type_name, type_definition, fields),
|
|
64
|
+
);
|
|
65
|
+
}
|
|
94
66
|
|
|
95
67
|
return diagnostics;
|
|
96
68
|
}
|
|
@@ -106,510 +78,180 @@ export function validateStoredQueries(repo_config) {
|
|
|
106
78
|
for (const [query_name, stored_query] of Object.entries(
|
|
107
79
|
repo_config.queries,
|
|
108
80
|
)) {
|
|
109
|
-
|
|
110
|
-
diagnostics,
|
|
111
|
-
repo_config,
|
|
112
|
-
stored_query.where,
|
|
113
|
-
`queries.${query_name}.where`,
|
|
114
|
-
);
|
|
115
|
-
}
|
|
81
|
+
const query_text = stored_query.cypher;
|
|
116
82
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
/**
|
|
121
|
-
* @param {PatramRepoConfig} repo_config
|
|
122
|
-
* @returns {PatramDiagnostic[]}
|
|
123
|
-
*/
|
|
124
|
-
export function validateDerivedSummaries(repo_config) {
|
|
125
|
-
if (!repo_config.derived_summaries) {
|
|
126
|
-
return [];
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
const graph_config = resolvePatramGraphConfig(repo_config);
|
|
130
|
-
const known_relation_names = new Set(Object.keys(graph_config.relations));
|
|
131
|
-
/** @type {PatramDiagnostic[]} */
|
|
132
|
-
const diagnostics = [];
|
|
133
|
-
/** @type {Map<string, string>} */
|
|
134
|
-
const class_coverage = new Map();
|
|
83
|
+
if (!query_text) {
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
135
86
|
|
|
136
|
-
|
|
137
|
-
repo_config.derived_summaries,
|
|
138
|
-
)) {
|
|
139
|
-
collectDuplicateClassDiagnostics(
|
|
140
|
-
diagnostics,
|
|
141
|
-
class_coverage,
|
|
142
|
-
summary_definition.classes,
|
|
143
|
-
summary_name,
|
|
144
|
-
);
|
|
145
|
-
collectDerivedSummaryFieldDiagnostics(
|
|
87
|
+
collectWhereClauseDiagnostics(
|
|
146
88
|
diagnostics,
|
|
147
|
-
known_relation_names,
|
|
148
89
|
repo_config,
|
|
149
|
-
|
|
150
|
-
|
|
90
|
+
query_text,
|
|
91
|
+
`queries.${query_name}.cypher`,
|
|
151
92
|
);
|
|
152
93
|
}
|
|
153
94
|
|
|
154
95
|
return diagnostics;
|
|
155
96
|
}
|
|
156
97
|
|
|
157
|
-
/**
|
|
158
|
-
* @param {import('zod').core.$ZodIssue} issue
|
|
159
|
-
* @returns {PatramDiagnostic}
|
|
160
|
-
*/
|
|
161
|
-
function createValidationDiagnostic(issue) {
|
|
162
|
-
const issue_path = formatIssuePath(issue.path);
|
|
163
|
-
|
|
164
|
-
if (issue_path) {
|
|
165
|
-
return {
|
|
166
|
-
code: 'config.invalid',
|
|
167
|
-
column: 1,
|
|
168
|
-
level: 'error',
|
|
169
|
-
line: 1,
|
|
170
|
-
message: `Invalid config at "${issue_path}": ${issue.message}`,
|
|
171
|
-
path: CONFIG_FILE_NAME,
|
|
172
|
-
};
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
return {
|
|
176
|
-
code: 'config.invalid',
|
|
177
|
-
column: 1,
|
|
178
|
-
level: 'error',
|
|
179
|
-
line: 1,
|
|
180
|
-
message: `Invalid config: ${issue.message}`,
|
|
181
|
-
path: CONFIG_FILE_NAME,
|
|
182
|
-
};
|
|
183
|
-
}
|
|
184
|
-
|
|
185
98
|
/**
|
|
186
99
|
* @param {PatramDiagnostic[]} diagnostics
|
|
187
|
-
* @param {
|
|
188
|
-
* @param {string[]}
|
|
189
|
-
* @param {
|
|
100
|
+
* @param {string} field_path
|
|
101
|
+
* @param {string[] | undefined} referenced_types
|
|
102
|
+
* @param {NonNullable<PatramRepoConfig['types']>} types
|
|
190
103
|
*/
|
|
191
|
-
function
|
|
104
|
+
function collectTypeReferenceDiagnostics(
|
|
192
105
|
diagnostics,
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
106
|
+
field_path,
|
|
107
|
+
referenced_types,
|
|
108
|
+
types,
|
|
196
109
|
) {
|
|
197
|
-
for (const
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
if (!existing_summary_name) {
|
|
201
|
-
class_coverage.set(class_name, summary_name);
|
|
110
|
+
for (const referenced_type of referenced_types ?? []) {
|
|
111
|
+
if (types[referenced_type] !== undefined) {
|
|
202
112
|
continue;
|
|
203
113
|
}
|
|
204
114
|
|
|
205
115
|
diagnostics.push(
|
|
206
|
-
createConfigDiagnostic(
|
|
207
|
-
`derived_summaries.${summary_name}.classes`,
|
|
208
|
-
`Class "${class_name}" is already covered by derived summary "${existing_summary_name}".`,
|
|
209
|
-
),
|
|
116
|
+
createConfigDiagnostic(field_path, `Unknown type "${referenced_type}".`),
|
|
210
117
|
);
|
|
211
118
|
}
|
|
212
119
|
}
|
|
213
120
|
|
|
214
121
|
/**
|
|
215
122
|
* @param {PatramDiagnostic[]} diagnostics
|
|
216
|
-
* @param {Set<string>} known_relation_names
|
|
217
123
|
* @param {PatramRepoConfig} repo_config
|
|
218
|
-
* @param {string}
|
|
219
|
-
* @param {DerivedSummaryFieldConfig[]} field_definitions
|
|
220
|
-
*/
|
|
221
|
-
function collectDerivedSummaryFieldDiagnostics(
|
|
222
|
-
diagnostics,
|
|
223
|
-
known_relation_names,
|
|
224
|
-
repo_config,
|
|
225
|
-
summary_name,
|
|
226
|
-
field_definitions,
|
|
227
|
-
) {
|
|
228
|
-
for (const [field_index, field_definition] of field_definitions.entries()) {
|
|
229
|
-
if ('count' in field_definition) {
|
|
230
|
-
collectTraversalDiagnostic(
|
|
231
|
-
diagnostics,
|
|
232
|
-
field_definition.count.traversal,
|
|
233
|
-
known_relation_names,
|
|
234
|
-
`derived_summaries.${summary_name}.fields.${field_index}.count.traversal`,
|
|
235
|
-
);
|
|
236
|
-
collectWhereClauseDiagnostics(
|
|
237
|
-
diagnostics,
|
|
238
|
-
repo_config,
|
|
239
|
-
field_definition.count.where,
|
|
240
|
-
`derived_summaries.${summary_name}.fields.${field_index}.count.where`,
|
|
241
|
-
);
|
|
242
|
-
continue;
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
for (const [case_index, select_case] of field_definition.select.entries()) {
|
|
246
|
-
collectWhereClauseDiagnostics(
|
|
247
|
-
diagnostics,
|
|
248
|
-
repo_config,
|
|
249
|
-
select_case.when,
|
|
250
|
-
`derived_summaries.${summary_name}.fields.${field_index}.select.${case_index}.when`,
|
|
251
|
-
);
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
/**
|
|
257
|
-
* @param {PatramDiagnostic[]} diagnostics
|
|
258
|
-
* @param {string} traversal_text
|
|
259
|
-
* @param {Set<string>} known_relation_names
|
|
124
|
+
* @param {string} where_clause
|
|
260
125
|
* @param {string} diagnostic_path
|
|
261
126
|
*/
|
|
262
|
-
function
|
|
127
|
+
function collectWhereClauseDiagnostics(
|
|
263
128
|
diagnostics,
|
|
264
|
-
|
|
265
|
-
|
|
129
|
+
repo_config,
|
|
130
|
+
where_clause,
|
|
266
131
|
diagnostic_path,
|
|
267
132
|
) {
|
|
268
|
-
const
|
|
269
|
-
/^(?<direction>in|out):(?<relation_name>[a-zA-Z0-9_]+)$/du.exec(
|
|
270
|
-
traversal_text,
|
|
271
|
-
);
|
|
133
|
+
const parse_result = parseQueryExpression(where_clause, repo_config);
|
|
272
134
|
|
|
273
|
-
if (!
|
|
274
|
-
diagnostics.push(
|
|
275
|
-
createConfigDiagnostic(
|
|
276
|
-
diagnostic_path,
|
|
277
|
-
'Derived summary traversal must use "in:<relation>" or "out:<relation>".',
|
|
278
|
-
),
|
|
279
|
-
);
|
|
280
|
-
|
|
281
|
-
return;
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
if (known_relation_names.has(traversal_match.groups.relation_name)) {
|
|
135
|
+
if (!parse_result.success) {
|
|
136
|
+
diagnostics.push(replaceDiagnosticPath(parse_result.diagnostic));
|
|
285
137
|
return;
|
|
286
138
|
}
|
|
287
139
|
|
|
288
140
|
diagnostics.push(
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
);
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
/**
|
|
297
|
-
* @param {PatramDiagnostic[]} diagnostics
|
|
298
|
-
* @param {Record<string, { prefixes: string[] }>} path_classes
|
|
299
|
-
* @param {Record<string, MetadataFieldConfig>} fields
|
|
300
|
-
*/
|
|
301
|
-
function collectFieldConfigDiagnostics(diagnostics, path_classes, fields) {
|
|
302
|
-
for (const [field_name, field_definition] of Object.entries(fields)) {
|
|
303
|
-
if (collectReservedFieldDiagnostic(diagnostics, field_name)) {
|
|
304
|
-
continue;
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
collectDisplayOrderDiagnostic(diagnostics, field_name, field_definition);
|
|
308
|
-
collectFieldPathClassDiagnostic(
|
|
309
|
-
diagnostics,
|
|
310
|
-
path_classes,
|
|
311
|
-
field_name,
|
|
312
|
-
field_definition,
|
|
313
|
-
);
|
|
314
|
-
}
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
/**
|
|
318
|
-
* @param {PatramDiagnostic[]} diagnostics
|
|
319
|
-
* @param {string} field_name
|
|
320
|
-
* @returns {boolean}
|
|
321
|
-
*/
|
|
322
|
-
function collectReservedFieldDiagnostic(diagnostics, field_name) {
|
|
323
|
-
if (
|
|
324
|
-
!field_name.startsWith('$') ||
|
|
325
|
-
!isReservedStructuralFieldName(field_name)
|
|
326
|
-
) {
|
|
327
|
-
return false;
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
diagnostics.push(
|
|
331
|
-
createConfigDiagnostic(
|
|
332
|
-
`fields.${field_name}`,
|
|
333
|
-
'Metadata field names must not start with "$".',
|
|
141
|
+
...getQuerySemanticDiagnostics(
|
|
142
|
+
repo_config,
|
|
143
|
+
{ kind: 'stored_query', name: diagnostic_path.split('.')[1] ?? '' },
|
|
144
|
+
parse_result.expression,
|
|
334
145
|
),
|
|
335
146
|
);
|
|
336
|
-
|
|
337
|
-
return true;
|
|
338
147
|
}
|
|
339
148
|
|
|
340
149
|
/**
|
|
341
|
-
* @param {
|
|
342
|
-
* @param {string}
|
|
343
|
-
* @
|
|
150
|
+
* @param {string} path
|
|
151
|
+
* @param {string} message
|
|
152
|
+
* @returns {PatramDiagnostic}
|
|
344
153
|
*/
|
|
345
|
-
function
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
) {
|
|
355
|
-
return;
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
diagnostics.push(
|
|
359
|
-
createConfigDiagnostic(
|
|
360
|
-
`fields.${field_name}.display.order`,
|
|
361
|
-
'Display order must be a non-negative integer.',
|
|
362
|
-
),
|
|
363
|
-
);
|
|
154
|
+
function createConfigDiagnostic(path, message) {
|
|
155
|
+
return {
|
|
156
|
+
code: 'config.invalid',
|
|
157
|
+
column: 1,
|
|
158
|
+
level: 'error',
|
|
159
|
+
line: 1,
|
|
160
|
+
message: `Invalid config at "${path}": ${message}`,
|
|
161
|
+
path: CONFIG_FILE_NAME,
|
|
162
|
+
};
|
|
364
163
|
}
|
|
365
164
|
|
|
366
165
|
/**
|
|
367
|
-
* @param {PatramDiagnostic[]} diagnostics
|
|
368
|
-
* @param {Record<string, { prefixes: string[] }>} path_classes
|
|
369
166
|
* @param {string} field_name
|
|
370
167
|
* @param {MetadataFieldConfig} field_definition
|
|
168
|
+
* @param {NonNullable<PatramRepoConfig['types']>} types
|
|
169
|
+
* @returns {PatramDiagnostic[]}
|
|
371
170
|
*/
|
|
372
|
-
function
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
field_name,
|
|
376
|
-
field_definition,
|
|
377
|
-
) {
|
|
378
|
-
if (
|
|
379
|
-
!('path_class' in field_definition) ||
|
|
380
|
-
field_definition.path_class === undefined
|
|
381
|
-
) {
|
|
382
|
-
return;
|
|
383
|
-
}
|
|
171
|
+
function validateFieldDefinition(field_name, field_definition, types) {
|
|
172
|
+
/** @type {PatramDiagnostic[]} */
|
|
173
|
+
const diagnostics = [];
|
|
384
174
|
|
|
385
|
-
if (
|
|
175
|
+
if (isReservedStructuralFieldName(field_name)) {
|
|
386
176
|
diagnostics.push(
|
|
387
177
|
createConfigDiagnostic(
|
|
388
|
-
`fields.${field_name}
|
|
389
|
-
'
|
|
178
|
+
`fields.${field_name}`,
|
|
179
|
+
'Metadata field names must not start with "$".',
|
|
390
180
|
),
|
|
391
181
|
);
|
|
392
|
-
|
|
393
|
-
return;
|
|
394
182
|
}
|
|
395
183
|
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
184
|
+
collectTypeReferenceDiagnostics(
|
|
185
|
+
diagnostics,
|
|
186
|
+
`fields.${field_name}.on`,
|
|
187
|
+
field_definition.on,
|
|
188
|
+
types,
|
|
189
|
+
);
|
|
190
|
+
collectTypeReferenceDiagnostics(
|
|
191
|
+
diagnostics,
|
|
192
|
+
`fields.${field_name}.required_on`,
|
|
193
|
+
field_definition.required_on,
|
|
194
|
+
types,
|
|
405
195
|
);
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
/**
|
|
409
|
-
* @param {PatramDiagnostic[]} diagnostics
|
|
410
|
-
* @param {Record<string, { prefixes: string[] }>} path_classes
|
|
411
|
-
* @param {Record<string, MetadataFieldConfig>} fields
|
|
412
|
-
* @param {NonNullable<PatramRepoConfig['classes']>} classes
|
|
413
|
-
*/
|
|
414
|
-
function collectClassSchemaConfigDiagnostics(
|
|
415
|
-
diagnostics,
|
|
416
|
-
path_classes,
|
|
417
|
-
fields,
|
|
418
|
-
classes,
|
|
419
|
-
) {
|
|
420
|
-
for (const [class_name, class_definition] of Object.entries(classes)) {
|
|
421
|
-
const schema_definition = class_definition.schema;
|
|
422
|
-
|
|
423
|
-
if (!schema_definition) {
|
|
424
|
-
continue;
|
|
425
|
-
}
|
|
426
|
-
|
|
427
|
-
collectMarkdownStylesDiagnostic(
|
|
428
|
-
diagnostics,
|
|
429
|
-
`classes.${class_name}.schema.markdown_styles`,
|
|
430
|
-
schema_definition.markdown_styles,
|
|
431
|
-
);
|
|
432
|
-
collectMixedStylesDiagnostic(
|
|
433
|
-
diagnostics,
|
|
434
|
-
`classes.${class_name}.schema.mixed_styles`,
|
|
435
|
-
schema_definition.mixed_styles,
|
|
436
|
-
);
|
|
437
|
-
|
|
438
|
-
for (const field_name of Object.keys(schema_definition.fields)) {
|
|
439
|
-
if (fields[field_name]) {
|
|
440
|
-
collectMarkdownStylesDiagnostic(
|
|
441
|
-
diagnostics,
|
|
442
|
-
`classes.${class_name}.schema.fields.${field_name}.markdown_styles`,
|
|
443
|
-
schema_definition.fields[field_name].markdown_styles,
|
|
444
|
-
);
|
|
445
|
-
} else {
|
|
446
|
-
diagnostics.push(
|
|
447
|
-
createConfigDiagnostic(
|
|
448
|
-
`classes.${class_name}.schema.fields.${field_name}`,
|
|
449
|
-
`Unknown field "${field_name}".`,
|
|
450
|
-
),
|
|
451
|
-
);
|
|
452
|
-
}
|
|
453
|
-
}
|
|
454
|
-
}
|
|
455
|
-
|
|
456
|
-
for (const [class_name, class_definition] of Object.entries(classes)) {
|
|
457
|
-
const schema_definition = class_definition.schema;
|
|
458
|
-
|
|
459
|
-
if (!schema_definition) {
|
|
460
|
-
continue;
|
|
461
|
-
}
|
|
462
|
-
|
|
463
|
-
if (
|
|
464
|
-
schema_definition.document_path_class === undefined ||
|
|
465
|
-
path_classes[schema_definition.document_path_class]
|
|
466
|
-
) {
|
|
467
|
-
continue;
|
|
468
|
-
}
|
|
469
196
|
|
|
197
|
+
if (
|
|
198
|
+
field_definition.type === 'ref' &&
|
|
199
|
+
field_definition.to !== 'document' &&
|
|
200
|
+
types[field_definition.to] === undefined
|
|
201
|
+
) {
|
|
470
202
|
diagnostics.push(
|
|
471
203
|
createConfigDiagnostic(
|
|
472
|
-
`
|
|
473
|
-
`Unknown
|
|
204
|
+
`fields.${field_name}.to`,
|
|
205
|
+
`Unknown target type "${field_definition.to}".`,
|
|
474
206
|
),
|
|
475
207
|
);
|
|
476
208
|
}
|
|
209
|
+
|
|
210
|
+
return diagnostics;
|
|
477
211
|
}
|
|
478
212
|
|
|
479
213
|
/**
|
|
480
|
-
* @param {
|
|
481
|
-
* @param {string}
|
|
482
|
-
* @param {
|
|
214
|
+
* @param {string} type_name
|
|
215
|
+
* @param {NonNullable<PatramRepoConfig['types']>[string]} type_definition
|
|
216
|
+
* @param {NonNullable<PatramRepoConfig['fields']>} fields
|
|
217
|
+
* @returns {PatramDiagnostic[]}
|
|
483
218
|
*/
|
|
484
|
-
function
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
) {
|
|
489
|
-
|
|
490
|
-
return;
|
|
491
|
-
}
|
|
492
|
-
|
|
493
|
-
if (markdown_styles.length === 0) {
|
|
494
|
-
diagnostics.push(
|
|
219
|
+
function validateTypeDefinition(type_name, type_definition, fields) {
|
|
220
|
+
if (
|
|
221
|
+
type_definition.defined_by !== undefined &&
|
|
222
|
+
fields[type_definition.defined_by] === undefined
|
|
223
|
+
) {
|
|
224
|
+
return [
|
|
495
225
|
createConfigDiagnostic(
|
|
496
|
-
|
|
497
|
-
|
|
226
|
+
`types.${type_name}.defined_by`,
|
|
227
|
+
`Unknown field "${type_definition.defined_by}".`,
|
|
498
228
|
),
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
return;
|
|
229
|
+
];
|
|
502
230
|
}
|
|
503
231
|
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
}
|
|
508
|
-
|
|
509
|
-
diagnostics.push(
|
|
510
|
-
createConfigDiagnostic(
|
|
511
|
-
diagnostic_path,
|
|
512
|
-
`Unknown markdown style "${markdown_style}".`,
|
|
513
|
-
),
|
|
514
|
-
);
|
|
515
|
-
}
|
|
516
|
-
}
|
|
232
|
+
const defined_by_field = type_definition.defined_by
|
|
233
|
+
? fields[type_definition.defined_by]
|
|
234
|
+
: undefined;
|
|
517
235
|
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
* @param {string} diagnostic_path
|
|
521
|
-
* @param {string | undefined} mixed_styles
|
|
522
|
-
*/
|
|
523
|
-
function collectMixedStylesDiagnostic(
|
|
524
|
-
diagnostics,
|
|
525
|
-
diagnostic_path,
|
|
526
|
-
mixed_styles,
|
|
527
|
-
) {
|
|
528
|
-
if (mixed_styles === undefined || isMixedStyleValue(mixed_styles)) {
|
|
529
|
-
return;
|
|
236
|
+
if (defined_by_field?.type !== 'ref') {
|
|
237
|
+
return [];
|
|
530
238
|
}
|
|
531
239
|
|
|
532
|
-
|
|
240
|
+
return [
|
|
533
241
|
createConfigDiagnostic(
|
|
534
|
-
|
|
535
|
-
'
|
|
242
|
+
`types.${type_name}.defined_by`,
|
|
243
|
+
'Type identity fields must use a scalar field type.',
|
|
536
244
|
),
|
|
537
|
-
|
|
538
|
-
}
|
|
539
|
-
|
|
540
|
-
/**
|
|
541
|
-
* @param {PatramRepoConfig['classes']} classes
|
|
542
|
-
* @returns {Record<string, ClassDefinition>}
|
|
543
|
-
*/
|
|
544
|
-
function collectGraphClassDefinitions(classes) {
|
|
545
|
-
/** @type {Record<string, ClassDefinition>} */
|
|
546
|
-
const graph_class_definitions = {};
|
|
547
|
-
|
|
548
|
-
for (const [class_name, class_definition] of Object.entries(classes ?? {})) {
|
|
549
|
-
graph_class_definitions[class_name] = {
|
|
550
|
-
builtin: class_definition.builtin,
|
|
551
|
-
label: class_definition.label,
|
|
552
|
-
};
|
|
553
|
-
}
|
|
554
|
-
|
|
555
|
-
return graph_class_definitions;
|
|
556
|
-
}
|
|
557
|
-
|
|
558
|
-
/**
|
|
559
|
-
* @param {PatramDiagnostic[]} diagnostics
|
|
560
|
-
* @param {PatramRepoConfig} repo_config
|
|
561
|
-
* @param {string} where_clause
|
|
562
|
-
* @param {string} diagnostic_path
|
|
563
|
-
*/
|
|
564
|
-
function collectWhereClauseDiagnostics(
|
|
565
|
-
diagnostics,
|
|
566
|
-
repo_config,
|
|
567
|
-
where_clause,
|
|
568
|
-
diagnostic_path,
|
|
569
|
-
) {
|
|
570
|
-
const parse_result = parseWhereClause(where_clause);
|
|
571
|
-
|
|
572
|
-
if (!parse_result.success) {
|
|
573
|
-
diagnostics.push(
|
|
574
|
-
createConfigDiagnostic(diagnostic_path, parse_result.diagnostic.message),
|
|
575
|
-
);
|
|
576
|
-
|
|
577
|
-
return;
|
|
578
|
-
}
|
|
579
|
-
|
|
580
|
-
const semantic_diagnostics = getQuerySemanticDiagnostics(
|
|
581
|
-
repo_config,
|
|
582
|
-
{ kind: 'ad_hoc' },
|
|
583
|
-
parse_result.expression,
|
|
584
|
-
);
|
|
585
|
-
|
|
586
|
-
for (const semantic_diagnostic of semantic_diagnostics) {
|
|
587
|
-
diagnostics.push(
|
|
588
|
-
createConfigDiagnostic(diagnostic_path, semantic_diagnostic.message),
|
|
589
|
-
);
|
|
590
|
-
}
|
|
245
|
+
];
|
|
591
246
|
}
|
|
592
247
|
|
|
593
248
|
/**
|
|
594
|
-
* @param {
|
|
595
|
-
* @param {string} message
|
|
249
|
+
* @param {PatramDiagnostic} diagnostic
|
|
596
250
|
* @returns {PatramDiagnostic}
|
|
597
251
|
*/
|
|
598
|
-
function
|
|
252
|
+
function replaceDiagnosticPath(diagnostic) {
|
|
599
253
|
return {
|
|
600
|
-
|
|
601
|
-
column: 1,
|
|
602
|
-
level: 'error',
|
|
603
|
-
line: 1,
|
|
604
|
-
message: `Invalid config at "${issue_path}": ${message}`,
|
|
254
|
+
...diagnostic,
|
|
605
255
|
path: CONFIG_FILE_NAME,
|
|
606
256
|
};
|
|
607
257
|
}
|
|
608
|
-
|
|
609
|
-
/**
|
|
610
|
-
* @param {(string | number | symbol | undefined)[]} issue_path
|
|
611
|
-
* @returns {string}
|
|
612
|
-
*/
|
|
613
|
-
function formatIssuePath(issue_path) {
|
|
614
|
-
return issue_path.map(String).join('.');
|
|
615
|
-
}
|