patram 0.1.1 → 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.
Files changed (49) hide show
  1. package/lib/build-graph-identity.js +57 -24
  2. package/lib/build-graph.js +383 -17
  3. package/lib/build-graph.types.ts +5 -2
  4. package/lib/check-directive-metadata.js +516 -0
  5. package/lib/check-directive-value.js +282 -0
  6. package/lib/check-graph.js +24 -5
  7. package/lib/cli-help-metadata.js +580 -0
  8. package/lib/derived-summary.js +280 -0
  9. package/lib/directive-diagnostics.js +38 -0
  10. package/lib/directive-type-rules.js +133 -0
  11. package/lib/discover-fields.js +427 -0
  12. package/lib/discover-fields.types.ts +52 -0
  13. package/lib/format-derived-summary-row.js +9 -0
  14. package/lib/format-node-header.js +21 -0
  15. package/lib/format-output-item-block.js +22 -0
  16. package/lib/format-output-metadata.js +54 -0
  17. package/lib/layout-stored-queries.js +96 -2
  18. package/lib/load-patram-config.js +754 -18
  19. package/lib/load-patram-config.types.ts +128 -2
  20. package/lib/load-project-graph.js +4 -1
  21. package/lib/output-view.types.ts +29 -6
  22. package/lib/parse-cli-arguments-helpers.js +263 -90
  23. package/lib/parse-cli-arguments.js +160 -8
  24. package/lib/parse-cli-arguments.types.ts +49 -4
  25. package/lib/parse-where-clause.js +670 -209
  26. package/lib/parse-where-clause.types.ts +72 -0
  27. package/lib/patram-cli.js +180 -21
  28. package/lib/patram-config.js +31 -31
  29. package/lib/patram-config.types.ts +10 -4
  30. package/lib/patram.js +6 -0
  31. package/lib/query-graph.js +444 -113
  32. package/lib/query-inspection.js +798 -0
  33. package/lib/render-check-output.js +1 -1
  34. package/lib/render-cli-help.js +419 -0
  35. package/lib/render-field-discovery.js +148 -0
  36. package/lib/render-json-output.js +66 -14
  37. package/lib/render-output-view.js +272 -22
  38. package/lib/render-plain-output.js +31 -86
  39. package/lib/render-rich-output.js +34 -87
  40. package/lib/resolve-patram-graph-config.js +15 -9
  41. package/lib/resolve-where-clause.js +18 -3
  42. package/lib/show-document.js +51 -7
  43. package/lib/tagged-fenced-block-error.js +17 -0
  44. package/lib/tagged-fenced-block-markdown.js +111 -0
  45. package/lib/tagged-fenced-block-metadata.js +97 -0
  46. package/lib/tagged-fenced-block-parser.js +292 -0
  47. package/lib/tagged-fenced-blocks.js +100 -0
  48. package/lib/tagged-fenced-blocks.types.ts +38 -0
  49. package/package.json +12 -7
@@ -0,0 +1,282 @@
1
+ /**
2
+ * @import { DirectiveTypeConfig, MetadataDirectiveRuleConfig, PatramDiagnostic, PatramRepoConfig } from './load-patram-config.types.ts';
3
+ * @import { PatramClaim } from './parse-claims.types.ts';
4
+ * @import { MappingDefinition } from './patram-config.types.ts';
5
+ */
6
+
7
+ import { resolveTargetReference } from './build-graph-identity.js';
8
+ import { createOriginDiagnostic } from './directive-diagnostics.js';
9
+ import {
10
+ formatQuotedList,
11
+ getInvalidTypeMessage,
12
+ isDirectiveValueValid,
13
+ } from './directive-type-rules.js';
14
+
15
+ /**
16
+ * Check one directive claim value against typed validation rules.
17
+ *
18
+ * @param {PatramClaim} claim
19
+ * @param {string} directive_name
20
+ * @param {Record<string, MappingDefinition>} mappings
21
+ * @param {PatramRepoConfig} repo_config
22
+ * @param {MetadataDirectiveRuleConfig | undefined} _directive_rule
23
+ * @param {Map<string, string>} document_entity_keys
24
+ * @param {Set<string>} document_paths
25
+ * @returns {PatramDiagnostic[]}
26
+ */
27
+ export function checkDirectiveValue(
28
+ claim,
29
+ directive_name,
30
+ mappings,
31
+ repo_config,
32
+ _directive_rule,
33
+ document_entity_keys,
34
+ document_paths,
35
+ ) {
36
+ const mapping_definition = resolveDirectiveMapping(mappings, claim);
37
+ const validation_field_name = getDirectiveValidationFieldName(
38
+ directive_name,
39
+ mapping_definition,
40
+ );
41
+
42
+ if (!validation_field_name || typeof claim.value !== 'string') {
43
+ return [];
44
+ }
45
+
46
+ if (validation_field_name === '$class') {
47
+ return checkClassValue(claim, directive_name, repo_config);
48
+ }
49
+
50
+ if (isStructuralDirectiveField(validation_field_name)) {
51
+ return [];
52
+ }
53
+
54
+ const type_definition = repo_config.fields?.[validation_field_name];
55
+
56
+ if (!type_definition) {
57
+ return [];
58
+ }
59
+
60
+ if (type_definition.type === 'enum') {
61
+ return checkEnumValue(claim, directive_name, type_definition.values);
62
+ }
63
+
64
+ const type_diagnostic = createInvalidTypeDiagnostic(
65
+ claim,
66
+ directive_name,
67
+ type_definition,
68
+ claim.value,
69
+ );
70
+
71
+ if (type_diagnostic) {
72
+ return [type_diagnostic];
73
+ }
74
+
75
+ return createPathClassDiagnostics(
76
+ claim,
77
+ directive_name,
78
+ mappings,
79
+ repo_config,
80
+ type_definition,
81
+ document_entity_keys,
82
+ document_paths,
83
+ );
84
+ }
85
+
86
+ /**
87
+ * @param {PatramClaim} claim
88
+ * @param {string} directive_name
89
+ * @param {PatramRepoConfig} repo_config
90
+ * @returns {PatramDiagnostic[]}
91
+ */
92
+ function checkClassValue(claim, directive_name, repo_config) {
93
+ if (
94
+ typeof claim.value !== 'string' ||
95
+ repo_config.classes?.[claim.value] !== undefined
96
+ ) {
97
+ return [];
98
+ }
99
+
100
+ return [
101
+ createOriginDiagnostic(
102
+ claim,
103
+ 'directive.invalid_enum',
104
+ `Directive "${directive_name}" must reference a configured class.`,
105
+ ),
106
+ ];
107
+ }
108
+
109
+ /**
110
+ * @param {PatramClaim} claim
111
+ * @param {string} directive_name
112
+ * @param {string[]} allowed_values
113
+ * @returns {PatramDiagnostic[]}
114
+ */
115
+ function checkEnumValue(claim, directive_name, allowed_values) {
116
+ if (typeof claim.value !== 'string' || allowed_values.includes(claim.value)) {
117
+ return [];
118
+ }
119
+
120
+ return [
121
+ createOriginDiagnostic(
122
+ claim,
123
+ 'directive.invalid_enum',
124
+ `Directive "${directive_name}" must be one of ${formatQuotedList(allowed_values)}.`,
125
+ ),
126
+ ];
127
+ }
128
+
129
+ /**
130
+ * @param {PatramClaim} claim
131
+ * @param {string} directive_name
132
+ * @param {Exclude<DirectiveTypeConfig, { type: 'enum' }>} type_definition
133
+ * @param {string} directive_value
134
+ * @returns {PatramDiagnostic | null}
135
+ */
136
+ function createInvalidTypeDiagnostic(
137
+ claim,
138
+ directive_name,
139
+ type_definition,
140
+ directive_value,
141
+ ) {
142
+ if (isDirectiveValueValid(type_definition, directive_value)) {
143
+ return null;
144
+ }
145
+
146
+ return createOriginDiagnostic(
147
+ claim,
148
+ 'directive.invalid_type',
149
+ getInvalidTypeMessage(directive_name, type_definition.type),
150
+ );
151
+ }
152
+
153
+ /**
154
+ * @param {PatramClaim} claim
155
+ * @param {string} directive_name
156
+ * @param {Record<string, MappingDefinition>} mappings
157
+ * @param {PatramRepoConfig} repo_config
158
+ * @param {Exclude<DirectiveTypeConfig, { type: 'enum' }>} type_definition
159
+ * @param {Map<string, string>} document_entity_keys
160
+ * @param {Set<string>} document_paths
161
+ * @returns {PatramDiagnostic[]}
162
+ */
163
+ function createPathClassDiagnostics(
164
+ claim,
165
+ directive_name,
166
+ mappings,
167
+ repo_config,
168
+ type_definition,
169
+ document_entity_keys,
170
+ document_paths,
171
+ ) {
172
+ if (
173
+ type_definition.type !== 'path' ||
174
+ type_definition.path_class === undefined ||
175
+ isDirectivePathInClass(
176
+ mappings,
177
+ claim,
178
+ type_definition.path_class,
179
+ document_entity_keys,
180
+ document_paths,
181
+ repo_config,
182
+ )
183
+ ) {
184
+ return [];
185
+ }
186
+
187
+ return [
188
+ createOriginDiagnostic(
189
+ claim,
190
+ 'directive.invalid_path_class',
191
+ `Directive "${directive_name}" must point to path class "${type_definition.path_class}".`,
192
+ ),
193
+ ];
194
+ }
195
+
196
+ /**
197
+ * @param {Record<string, MappingDefinition>} mappings
198
+ * @param {PatramClaim} claim
199
+ * @param {string} path_class_name
200
+ * @param {Map<string, string>} document_entity_keys
201
+ * @param {Set<string>} document_paths
202
+ * @param {PatramRepoConfig} repo_config
203
+ * @returns {boolean}
204
+ */
205
+ function isDirectivePathInClass(
206
+ mappings,
207
+ claim,
208
+ path_class_name,
209
+ document_entity_keys,
210
+ document_paths,
211
+ repo_config,
212
+ ) {
213
+ const path_class_definition = repo_config.path_classes?.[path_class_name];
214
+
215
+ if (!path_class_definition) {
216
+ return true;
217
+ }
218
+
219
+ const mapping_definition = resolveDirectiveMapping(mappings, claim);
220
+ const target_kind = mapping_definition?.emit?.target_class ?? 'document';
221
+ const resolved_target = resolveTargetReference(
222
+ target_kind,
223
+ 'path',
224
+ claim,
225
+ document_entity_keys,
226
+ document_paths,
227
+ );
228
+
229
+ if (!resolved_target.path) {
230
+ return false;
231
+ }
232
+
233
+ return path_class_definition.prefixes.some((prefix) =>
234
+ resolved_target.path?.startsWith(prefix),
235
+ );
236
+ }
237
+
238
+ /**
239
+ * @param {Record<string, MappingDefinition>} mappings
240
+ * @param {PatramClaim} claim
241
+ * @returns {MappingDefinition | null}
242
+ */
243
+ function resolveDirectiveMapping(mappings, claim) {
244
+ if (!claim.name || !claim.parser) {
245
+ return null;
246
+ }
247
+
248
+ return mappings[`${claim.parser}.directive.${claim.name}`] ?? null;
249
+ }
250
+
251
+ /**
252
+ * @param {string} directive_name
253
+ * @param {MappingDefinition | null} mapping_definition
254
+ * @returns {string}
255
+ */
256
+ function getDirectiveValidationFieldName(directive_name, mapping_definition) {
257
+ if (mapping_definition?.node?.field) {
258
+ return mapping_definition.node.field;
259
+ }
260
+
261
+ return directive_name;
262
+ }
263
+
264
+ /**
265
+ * @param {string} field_name
266
+ * @returns {boolean}
267
+ */
268
+ function isStructuralDirectiveField(field_name) {
269
+ return (
270
+ field_name === '$class' ||
271
+ field_name === '$id' ||
272
+ field_name === '$path' ||
273
+ field_name === 'title'
274
+ );
275
+ }
276
+
277
+ /**
278
+ * @param {PatramClaim} claim
279
+ * @param {string} code
280
+ * @param {string} message
281
+ * @returns {PatramDiagnostic}
282
+ */
@@ -1,8 +1,11 @@
1
1
  /**
2
2
  * @import { BuildGraphResult, GraphEdge, GraphNode } from './build-graph.types.ts';
3
- * @import { PatramDiagnostic } from './load-patram-config.types.ts';
3
+ * @import { PatramDiagnostic, PatramRepoConfig } from './load-patram-config.types.ts';
4
+ * @import { PatramClaim } from './parse-claims.types.ts';
4
5
  */
5
6
 
7
+ import { checkDirectiveMetadata } from './check-directive-metadata.js';
8
+
6
9
  /**
7
10
  * Graph validation.
8
11
  *
@@ -24,9 +27,11 @@
24
27
  *
25
28
  * @param {BuildGraphResult} graph
26
29
  * @param {string[]} existing_file_paths
30
+ * @param {PatramRepoConfig} [repo_config]
31
+ * @param {PatramClaim[]} [claims]
27
32
  * @returns {PatramDiagnostic[]}
28
33
  */
29
- export function checkGraph(graph, existing_file_paths) {
34
+ export function checkGraph(graph, existing_file_paths, repo_config, claims) {
30
35
  /** @type {PatramDiagnostic[]} */
31
36
  const diagnostics = [];
32
37
  const existing_file_path_set = new Set(existing_file_paths);
@@ -54,6 +59,17 @@ export function checkGraph(graph, existing_file_paths) {
54
59
  );
55
60
  }
56
61
 
62
+ if (repo_config && claims) {
63
+ diagnostics.push(
64
+ ...checkDirectiveMetadata(
65
+ graph,
66
+ repo_config,
67
+ claims,
68
+ existing_file_paths,
69
+ ),
70
+ );
71
+ }
72
+
57
73
  return diagnostics;
58
74
  }
59
75
 
@@ -102,15 +118,18 @@ function collectBrokenLinkDiagnostics(
102
118
  target_node,
103
119
  existing_file_path_set,
104
120
  ) {
121
+ const target_class = target_node.$class ?? target_node.kind;
122
+ const target_path = target_node.$path ?? target_node.path;
123
+
105
124
  if (graph_edge.relation !== 'links_to') {
106
125
  return;
107
126
  }
108
127
 
109
- if (target_node.kind !== 'document' || !target_node.path) {
128
+ if (target_class !== 'document' || !target_path) {
110
129
  return;
111
130
  }
112
131
 
113
- if (existing_file_path_set.has(target_node.path)) {
132
+ if (existing_file_path_set.has(target_path)) {
114
133
  return;
115
134
  }
116
135
 
@@ -118,7 +137,7 @@ function collectBrokenLinkDiagnostics(
118
137
  createDiagnostic(
119
138
  graph_edge,
120
139
  'graph.link_broken',
121
- `Document link target "${target_node.path}" was not found.`,
140
+ `Document link target "${target_path}" was not found.`,
122
141
  ),
123
142
  );
124
143
  }