patram 0.5.0 → 0.6.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 +42 -1
- package/lib/build-graph.js +1 -1
- package/lib/check-directive-metadata.js +160 -5
- package/lib/check-directive-path-target.js +173 -0
- package/lib/check-directive-value.js +122 -125
- package/lib/directive-validation-test-helpers.js +87 -0
- package/lib/load-patram-config.js +95 -8
- package/lib/load-project-graph.js +16 -6
- package/lib/parse-claims.js +95 -8
- package/lib/parse-claims.types.ts +7 -0
- package/lib/parse-markdown-claims.js +9 -3
- package/lib/parse-markdown-directives.js +48 -25
- package/lib/parse-yaml-claims.js +472 -0
- package/lib/source-file-defaults.js +3 -0
- package/package.json +2 -1
|
@@ -201,7 +201,14 @@ function resolveDocumentTargetReference(target_path, document_node_references) {
|
|
|
201
201
|
function resolveDirectiveAwareTargetPath(claim, raw_target, document_paths) {
|
|
202
202
|
const normalized_raw_target = normalizeRepoRelativePath(raw_target);
|
|
203
203
|
|
|
204
|
-
if (
|
|
204
|
+
if (
|
|
205
|
+
claim.type === 'directive' &&
|
|
206
|
+
shouldKeepDirectiveTargetRepoRelative(
|
|
207
|
+
raw_target,
|
|
208
|
+
normalized_raw_target,
|
|
209
|
+
document_paths,
|
|
210
|
+
)
|
|
211
|
+
) {
|
|
205
212
|
return normalized_raw_target;
|
|
206
213
|
}
|
|
207
214
|
|
|
@@ -212,6 +219,40 @@ function resolveDirectiveAwareTargetPath(claim, raw_target, document_paths) {
|
|
|
212
219
|
return normalizeRepoRelativePath(posix.join(source_directory, raw_target));
|
|
213
220
|
}
|
|
214
221
|
|
|
222
|
+
/**
|
|
223
|
+
* @param {string} raw_target
|
|
224
|
+
* @param {string} normalized_raw_target
|
|
225
|
+
* @param {Set<string>} document_paths
|
|
226
|
+
* @returns {boolean}
|
|
227
|
+
*/
|
|
228
|
+
function shouldKeepDirectiveTargetRepoRelative(
|
|
229
|
+
raw_target,
|
|
230
|
+
normalized_raw_target,
|
|
231
|
+
document_paths,
|
|
232
|
+
) {
|
|
233
|
+
if (raw_target.startsWith('./') || raw_target.startsWith('../')) {
|
|
234
|
+
return false;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
if (document_paths.has(normalized_raw_target)) {
|
|
238
|
+
return true;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
const target_root_segment = normalized_raw_target.split('/')[0];
|
|
242
|
+
|
|
243
|
+
if (!target_root_segment) {
|
|
244
|
+
return false;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
for (const document_path of document_paths) {
|
|
248
|
+
if (document_path.split('/')[0] === target_root_segment) {
|
|
249
|
+
return true;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
return false;
|
|
254
|
+
}
|
|
255
|
+
|
|
215
256
|
/**
|
|
216
257
|
* @param {PatramClaim} claim
|
|
217
258
|
* @returns {string}
|
package/lib/build-graph.js
CHANGED
|
@@ -30,7 +30,7 @@ import {
|
|
|
30
30
|
* Uses Term: ../docs/reference/terms/graph.md
|
|
31
31
|
* Uses Term: ../docs/reference/terms/mapping.md
|
|
32
32
|
* Uses Term: ../docs/reference/terms/relation.md
|
|
33
|
-
* Tracked in: ../docs/plans/v0/source-anchor-
|
|
33
|
+
* Tracked in: ../docs/plans/v0/source-anchor-dogfooding.md
|
|
34
34
|
* Decided by: ../docs/decisions/graph-materialization.md
|
|
35
35
|
* Implements: ../docs/tasks/v0/materialize-graph.md
|
|
36
36
|
* @patram
|
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
createDocumentDiagnostic,
|
|
18
18
|
createOriginDiagnostic,
|
|
19
19
|
} from './directive-diagnostics.js';
|
|
20
|
+
import { formatQuotedList } from './directive-type-rules.js';
|
|
20
21
|
import { resolvePatramGraphConfig } from './resolve-patram-graph-config.js';
|
|
21
22
|
|
|
22
23
|
/**
|
|
@@ -44,11 +45,7 @@ export function checkDirectiveMetadata(
|
|
|
44
45
|
claims,
|
|
45
46
|
existing_file_paths,
|
|
46
47
|
) {
|
|
47
|
-
if (
|
|
48
|
-
claims.length === 0 ||
|
|
49
|
-
(repo_config.fields === undefined &&
|
|
50
|
-
!hasConfiguredClassSchemas(repo_config))
|
|
51
|
-
) {
|
|
48
|
+
if (claims.length === 0 || !hasDirectiveValidationConfig(repo_config)) {
|
|
52
49
|
return [];
|
|
53
50
|
}
|
|
54
51
|
|
|
@@ -93,6 +90,19 @@ export function checkDirectiveMetadata(
|
|
|
93
90
|
return diagnostics;
|
|
94
91
|
}
|
|
95
92
|
|
|
93
|
+
/**
|
|
94
|
+
* @param {PatramRepoConfig} repo_config
|
|
95
|
+
* @returns {boolean}
|
|
96
|
+
*/
|
|
97
|
+
function hasDirectiveValidationConfig(repo_config) {
|
|
98
|
+
return (
|
|
99
|
+
repo_config.fields !== undefined ||
|
|
100
|
+
hasMarkdownStyleValidationConfig(repo_config) ||
|
|
101
|
+
hasConfiguredClassSchemas(repo_config) ||
|
|
102
|
+
hasPathTargetDirectiveMappings(repo_config)
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
|
|
96
106
|
/**
|
|
97
107
|
* @param {PatramClaim[]} claims
|
|
98
108
|
* @returns {Map<string, PatramClaim[]>}
|
|
@@ -146,6 +156,8 @@ function collectDocumentDiagnostics(
|
|
|
146
156
|
const schema_definition = repo_config.classes?.[document_kind]?.schema;
|
|
147
157
|
/** @type {Map<string, number>} */
|
|
148
158
|
const directive_counts = new Map();
|
|
159
|
+
/** @type {Set<string>} */
|
|
160
|
+
const seen_markdown_styles = new Set();
|
|
149
161
|
|
|
150
162
|
for (const claim of document_claims) {
|
|
151
163
|
if (!claim.name) {
|
|
@@ -160,6 +172,7 @@ function collectDocumentDiagnostics(
|
|
|
160
172
|
document_kind,
|
|
161
173
|
schema_definition,
|
|
162
174
|
directive_counts,
|
|
175
|
+
seen_markdown_styles,
|
|
163
176
|
document_entity_keys,
|
|
164
177
|
document_node_references,
|
|
165
178
|
document_paths,
|
|
@@ -185,6 +198,7 @@ function collectDocumentDiagnostics(
|
|
|
185
198
|
* @param {string} document_kind
|
|
186
199
|
* @param {MetadataSchemaConfig | undefined} schema_definition
|
|
187
200
|
* @param {Map<string, number>} directive_counts
|
|
201
|
+
* @param {Set<string>} seen_markdown_styles
|
|
188
202
|
* @param {Map<string, string>} document_entity_keys
|
|
189
203
|
* @param {Map<string, import('./document-node-identity.js').DocumentNodeReference>} document_node_references
|
|
190
204
|
* @param {Set<string>} document_paths
|
|
@@ -197,6 +211,7 @@ function collectClaimDiagnostics(
|
|
|
197
211
|
document_kind,
|
|
198
212
|
schema_definition,
|
|
199
213
|
directive_counts,
|
|
214
|
+
seen_markdown_styles,
|
|
200
215
|
document_entity_keys,
|
|
201
216
|
document_node_references,
|
|
202
217
|
document_paths,
|
|
@@ -227,6 +242,12 @@ function collectClaimDiagnostics(
|
|
|
227
242
|
schema_definition,
|
|
228
243
|
next_count,
|
|
229
244
|
),
|
|
245
|
+
...collectMarkdownStyleDiagnostics(
|
|
246
|
+
claim,
|
|
247
|
+
document_kind,
|
|
248
|
+
schema_definition,
|
|
249
|
+
seen_markdown_styles,
|
|
250
|
+
),
|
|
230
251
|
...checkDirectiveValue(
|
|
231
252
|
claim,
|
|
232
253
|
claim.name,
|
|
@@ -254,6 +275,54 @@ function hasConfiguredClassSchemas(repo_config) {
|
|
|
254
275
|
return false;
|
|
255
276
|
}
|
|
256
277
|
|
|
278
|
+
/**
|
|
279
|
+
* @param {PatramRepoConfig} repo_config
|
|
280
|
+
* @returns {boolean}
|
|
281
|
+
*/
|
|
282
|
+
function hasMarkdownStyleValidationConfig(repo_config) {
|
|
283
|
+
for (const class_definition of Object.values(repo_config.classes ?? {})) {
|
|
284
|
+
const schema_definition = class_definition.schema;
|
|
285
|
+
|
|
286
|
+
if (!schema_definition) {
|
|
287
|
+
continue;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
if (
|
|
291
|
+
schema_definition.markdown_styles !== undefined ||
|
|
292
|
+
schema_definition.mixed_styles !== undefined
|
|
293
|
+
) {
|
|
294
|
+
return true;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
for (const field_rule of Object.values(schema_definition.fields)) {
|
|
298
|
+
if (field_rule.markdown_styles !== undefined) {
|
|
299
|
+
return true;
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
return false;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* @param {PatramRepoConfig} repo_config
|
|
309
|
+
* @returns {boolean}
|
|
310
|
+
*/
|
|
311
|
+
function hasPathTargetDirectiveMappings(repo_config) {
|
|
312
|
+
for (const [mapping_name, mapping_definition] of Object.entries(
|
|
313
|
+
repo_config.mappings ?? {},
|
|
314
|
+
)) {
|
|
315
|
+
if (
|
|
316
|
+
mapping_name.includes('.directive.') &&
|
|
317
|
+
mapping_definition.emit?.target === 'path'
|
|
318
|
+
) {
|
|
319
|
+
return true;
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
return false;
|
|
324
|
+
}
|
|
325
|
+
|
|
257
326
|
/**
|
|
258
327
|
* @param {PatramRepoConfig} repo_config
|
|
259
328
|
* @param {string} document_path
|
|
@@ -546,3 +615,89 @@ function collectPlacementDiagnostics(
|
|
|
546
615
|
),
|
|
547
616
|
];
|
|
548
617
|
}
|
|
618
|
+
|
|
619
|
+
/**
|
|
620
|
+
* @param {PatramClaim} claim
|
|
621
|
+
* @param {string} document_kind
|
|
622
|
+
* @param {MetadataSchemaConfig | undefined} schema_definition
|
|
623
|
+
* @param {Set<string>} seen_markdown_styles
|
|
624
|
+
* @returns {PatramDiagnostic[]}
|
|
625
|
+
*/
|
|
626
|
+
function collectMarkdownStyleDiagnostics(
|
|
627
|
+
claim,
|
|
628
|
+
document_kind,
|
|
629
|
+
schema_definition,
|
|
630
|
+
seen_markdown_styles,
|
|
631
|
+
) {
|
|
632
|
+
const markdown_style = resolveClaimMarkdownStyle(claim);
|
|
633
|
+
|
|
634
|
+
if (!markdown_style || !claim.name) {
|
|
635
|
+
return [];
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
/** @type {PatramDiagnostic[]} */
|
|
639
|
+
const diagnostics = [];
|
|
640
|
+
const allowed_styles =
|
|
641
|
+
schema_definition?.fields[claim.name]?.markdown_styles ??
|
|
642
|
+
schema_definition?.markdown_styles;
|
|
643
|
+
|
|
644
|
+
if (allowed_styles && !allowed_styles.includes(markdown_style)) {
|
|
645
|
+
diagnostics.push(
|
|
646
|
+
createOriginDiagnostic(
|
|
647
|
+
claim,
|
|
648
|
+
'directive.invalid_style',
|
|
649
|
+
`Directive "${claim.name}" uses markdown style "${markdown_style}" but only ${formatQuotedList(allowed_styles)} are allowed.`,
|
|
650
|
+
),
|
|
651
|
+
);
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
if (
|
|
655
|
+
shouldReportMixedStyles(
|
|
656
|
+
schema_definition,
|
|
657
|
+
seen_markdown_styles,
|
|
658
|
+
markdown_style,
|
|
659
|
+
)
|
|
660
|
+
) {
|
|
661
|
+
diagnostics.push(
|
|
662
|
+
createOriginDiagnostic(
|
|
663
|
+
claim,
|
|
664
|
+
'document.mixed_styles',
|
|
665
|
+
`Document mixes markdown directive style "${markdown_style}" with ${formatQuotedList([...seen_markdown_styles])} while class "${document_kind}" sets mixed_styles="error".`,
|
|
666
|
+
),
|
|
667
|
+
);
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
seen_markdown_styles.add(markdown_style);
|
|
671
|
+
|
|
672
|
+
return diagnostics;
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
/**
|
|
676
|
+
* @param {PatramClaim} claim
|
|
677
|
+
* @returns {string | null}
|
|
678
|
+
*/
|
|
679
|
+
function resolveClaimMarkdownStyle(claim) {
|
|
680
|
+
if (claim.parser !== 'markdown' || claim.markdown_style === undefined) {
|
|
681
|
+
return null;
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
return claim.markdown_style;
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
/**
|
|
688
|
+
* @param {MetadataSchemaConfig | undefined} schema_definition
|
|
689
|
+
* @param {Set<string>} seen_markdown_styles
|
|
690
|
+
* @param {string} markdown_style
|
|
691
|
+
* @returns {boolean}
|
|
692
|
+
*/
|
|
693
|
+
function shouldReportMixedStyles(
|
|
694
|
+
schema_definition,
|
|
695
|
+
seen_markdown_styles,
|
|
696
|
+
markdown_style,
|
|
697
|
+
) {
|
|
698
|
+
return (
|
|
699
|
+
schema_definition?.mixed_styles === 'error' &&
|
|
700
|
+
seen_markdown_styles.size === 1 &&
|
|
701
|
+
!seen_markdown_styles.has(markdown_style)
|
|
702
|
+
);
|
|
703
|
+
}
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @import { DirectiveTypeConfig, 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 { isPathLikeTarget } from './claim-helpers.js';
|
|
8
|
+
import { resolveTargetReference } from './build-graph-identity.js';
|
|
9
|
+
import { createOriginDiagnostic } from './directive-diagnostics.js';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* @param {PatramClaim} claim
|
|
13
|
+
* @param {string} directive_name
|
|
14
|
+
* @param {MappingDefinition | null} mapping_definition
|
|
15
|
+
* @param {Exclude<DirectiveTypeConfig, { type: 'enum' }> | undefined} type_definition
|
|
16
|
+
* @param {Map<string, string>} document_entity_keys
|
|
17
|
+
* @param {Map<string, import('./document-node-identity.js').DocumentNodeReference>} document_node_references
|
|
18
|
+
* @param {Set<string>} document_paths
|
|
19
|
+
* @returns {PatramDiagnostic[]}
|
|
20
|
+
*/
|
|
21
|
+
export function createPathExistenceDiagnostics(
|
|
22
|
+
claim,
|
|
23
|
+
directive_name,
|
|
24
|
+
mapping_definition,
|
|
25
|
+
type_definition,
|
|
26
|
+
document_entity_keys,
|
|
27
|
+
document_node_references,
|
|
28
|
+
document_paths,
|
|
29
|
+
) {
|
|
30
|
+
if (
|
|
31
|
+
typeof claim.value !== 'string' ||
|
|
32
|
+
!isPathLikeTarget(claim.value) ||
|
|
33
|
+
!shouldCheckDirectivePathExistence(mapping_definition, type_definition)
|
|
34
|
+
) {
|
|
35
|
+
return [];
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const resolved_target = resolveDirectiveTargetPath(
|
|
39
|
+
claim,
|
|
40
|
+
mapping_definition,
|
|
41
|
+
document_entity_keys,
|
|
42
|
+
document_node_references,
|
|
43
|
+
document_paths,
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
if (!resolved_target || document_paths.has(resolved_target)) {
|
|
47
|
+
return [];
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return [
|
|
51
|
+
createOriginDiagnostic(
|
|
52
|
+
claim,
|
|
53
|
+
'directive.path_not_found',
|
|
54
|
+
`Directive "${directive_name}" points to missing file "${resolved_target}".`,
|
|
55
|
+
),
|
|
56
|
+
];
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* @param {PatramClaim} claim
|
|
61
|
+
* @param {string} directive_name
|
|
62
|
+
* @param {Record<string, MappingDefinition>} mappings
|
|
63
|
+
* @param {PatramRepoConfig} repo_config
|
|
64
|
+
* @param {Exclude<DirectiveTypeConfig, { type: 'enum' }>} type_definition
|
|
65
|
+
* @param {Map<string, string>} document_entity_keys
|
|
66
|
+
* @param {Map<string, import('./document-node-identity.js').DocumentNodeReference>} document_node_references
|
|
67
|
+
* @param {Set<string>} document_paths
|
|
68
|
+
* @returns {PatramDiagnostic[]}
|
|
69
|
+
*/
|
|
70
|
+
export function createPathClassDiagnostics(
|
|
71
|
+
claim,
|
|
72
|
+
directive_name,
|
|
73
|
+
mappings,
|
|
74
|
+
repo_config,
|
|
75
|
+
type_definition,
|
|
76
|
+
document_entity_keys,
|
|
77
|
+
document_node_references,
|
|
78
|
+
document_paths,
|
|
79
|
+
) {
|
|
80
|
+
if (type_definition.type !== 'path' || !type_definition.path_class) {
|
|
81
|
+
return [];
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const path_class_definition =
|
|
85
|
+
repo_config.path_classes?.[type_definition.path_class];
|
|
86
|
+
|
|
87
|
+
if (!path_class_definition) {
|
|
88
|
+
return [];
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const mapping_definition = resolveDirectiveMapping(mappings, claim);
|
|
92
|
+
const resolved_target = resolveDirectiveTargetPath(
|
|
93
|
+
claim,
|
|
94
|
+
mapping_definition,
|
|
95
|
+
document_entity_keys,
|
|
96
|
+
document_node_references,
|
|
97
|
+
document_paths,
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
if (
|
|
101
|
+
!resolved_target ||
|
|
102
|
+
path_class_definition.prefixes.some((prefix) =>
|
|
103
|
+
resolved_target.startsWith(prefix),
|
|
104
|
+
)
|
|
105
|
+
) {
|
|
106
|
+
return [];
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return [
|
|
110
|
+
createOriginDiagnostic(
|
|
111
|
+
claim,
|
|
112
|
+
'directive.invalid_path_class',
|
|
113
|
+
`Directive "${directive_name}" must point to path class "${type_definition.path_class}".`,
|
|
114
|
+
),
|
|
115
|
+
];
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* @param {PatramClaim} claim
|
|
120
|
+
* @param {MappingDefinition | null} mapping_definition
|
|
121
|
+
* @param {Map<string, string>} document_entity_keys
|
|
122
|
+
* @param {Map<string, import('./document-node-identity.js').DocumentNodeReference>} document_node_references
|
|
123
|
+
* @param {Set<string>} document_paths
|
|
124
|
+
* @returns {string | undefined}
|
|
125
|
+
*/
|
|
126
|
+
function resolveDirectiveTargetPath(
|
|
127
|
+
claim,
|
|
128
|
+
mapping_definition,
|
|
129
|
+
document_entity_keys,
|
|
130
|
+
document_node_references,
|
|
131
|
+
document_paths,
|
|
132
|
+
) {
|
|
133
|
+
const target_kind = mapping_definition?.emit?.target_class ?? 'document';
|
|
134
|
+
const resolved_target = resolveTargetReference(
|
|
135
|
+
target_kind,
|
|
136
|
+
'path',
|
|
137
|
+
claim,
|
|
138
|
+
document_entity_keys,
|
|
139
|
+
document_node_references,
|
|
140
|
+
document_paths,
|
|
141
|
+
);
|
|
142
|
+
|
|
143
|
+
return resolved_target.path;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* @param {Record<string, MappingDefinition>} mappings
|
|
148
|
+
* @param {PatramClaim} claim
|
|
149
|
+
* @returns {MappingDefinition | null}
|
|
150
|
+
*/
|
|
151
|
+
function resolveDirectiveMapping(mappings, claim) {
|
|
152
|
+
if (!claim.name || !claim.parser) {
|
|
153
|
+
return null;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
return mappings[`${claim.parser}.directive.${claim.name}`] ?? null;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* @param {MappingDefinition | null} mapping_definition
|
|
161
|
+
* @param {Exclude<DirectiveTypeConfig, { type: 'enum' }> | undefined} type_definition
|
|
162
|
+
* @returns {boolean}
|
|
163
|
+
*/
|
|
164
|
+
function shouldCheckDirectivePathExistence(
|
|
165
|
+
mapping_definition,
|
|
166
|
+
type_definition,
|
|
167
|
+
) {
|
|
168
|
+
if (type_definition?.type === 'path') {
|
|
169
|
+
return true;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
return mapping_definition?.emit?.target === 'path';
|
|
173
|
+
}
|