patram 0.4.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.
@@ -4,7 +4,10 @@
4
4
  * @import { MappingDefinition } from './patram-config.types.ts';
5
5
  */
6
6
 
7
- import { resolveTargetReference } from './build-graph-identity.js';
7
+ import {
8
+ createPathClassDiagnostics,
9
+ createPathExistenceDiagnostics,
10
+ } from './check-directive-path-target.js';
8
11
  import { createOriginDiagnostic } from './directive-diagnostics.js';
9
12
  import {
10
13
  formatQuotedList,
@@ -40,45 +43,31 @@ export function checkDirectiveValue(
40
43
  directive_name,
41
44
  mapping_definition,
42
45
  );
43
-
44
46
  if (!validation_field_name || typeof claim.value !== 'string') {
45
47
  return [];
46
48
  }
47
-
49
+ const directive_value = claim.value;
48
50
  if (validation_field_name === '$class') {
49
- return checkClassValue(claim, directive_name, repo_config);
50
- }
51
-
52
- if (isStructuralDirectiveField(validation_field_name)) {
53
- return [];
51
+ return checkClassValue(claim, directive_name, directive_value, repo_config);
54
52
  }
55
-
56
53
  const type_definition = repo_config.fields?.[validation_field_name];
57
-
58
- if (!type_definition) {
59
- return [];
60
- }
61
-
62
- if (type_definition.type === 'enum') {
63
- return checkEnumValue(claim, directive_name, type_definition.values);
64
- }
65
-
66
- const type_diagnostic = createInvalidTypeDiagnostic(
67
- claim,
68
- directive_name,
69
- type_definition,
70
- claim.value,
71
- );
72
-
73
- if (type_diagnostic) {
74
- return [type_diagnostic];
54
+ if (isStructuralDirectiveField(validation_field_name) || !type_definition) {
55
+ return collectUntypedPathDiagnostics(
56
+ claim,
57
+ directive_name,
58
+ mapping_definition,
59
+ document_entity_keys,
60
+ document_node_references,
61
+ document_paths,
62
+ );
75
63
  }
76
-
77
- return createPathClassDiagnostics(
64
+ return checkTypedDirectiveValue(
78
65
  claim,
79
66
  directive_name,
67
+ directive_value,
80
68
  mappings,
81
69
  repo_config,
70
+ mapping_definition,
82
71
  type_definition,
83
72
  document_entity_keys,
84
73
  document_node_references,
@@ -89,14 +78,12 @@ export function checkDirectiveValue(
89
78
  /**
90
79
  * @param {PatramClaim} claim
91
80
  * @param {string} directive_name
81
+ * @param {string} class_name
92
82
  * @param {PatramRepoConfig} repo_config
93
83
  * @returns {PatramDiagnostic[]}
94
84
  */
95
- function checkClassValue(claim, directive_name, repo_config) {
96
- if (
97
- typeof claim.value !== 'string' ||
98
- repo_config.classes?.[claim.value] !== undefined
99
- ) {
85
+ function checkClassValue(claim, directive_name, class_name, repo_config) {
86
+ if (repo_config.classes?.[class_name] !== undefined) {
100
87
  return [];
101
88
  }
102
89
 
@@ -112,44 +99,85 @@ function checkClassValue(claim, directive_name, repo_config) {
112
99
  /**
113
100
  * @param {PatramClaim} claim
114
101
  * @param {string} directive_name
115
- * @param {string[]} allowed_values
102
+ * @param {string} directive_value
103
+ * @param {Record<string, MappingDefinition>} mappings
104
+ * @param {PatramRepoConfig} repo_config
105
+ * @param {MappingDefinition | null} mapping_definition
106
+ * @param {DirectiveTypeConfig} type_definition
107
+ * @param {Map<string, string>} document_entity_keys
108
+ * @param {Map<string, import('./document-node-identity.js').DocumentNodeReference>} document_node_references
109
+ * @param {Set<string>} document_paths
116
110
  * @returns {PatramDiagnostic[]}
117
111
  */
118
- function checkEnumValue(claim, directive_name, allowed_values) {
119
- if (typeof claim.value !== 'string' || allowed_values.includes(claim.value)) {
120
- return [];
112
+ function checkTypedDirectiveValue(
113
+ claim,
114
+ directive_name,
115
+ directive_value,
116
+ mappings,
117
+ repo_config,
118
+ mapping_definition,
119
+ type_definition,
120
+ document_entity_keys,
121
+ document_node_references,
122
+ document_paths,
123
+ ) {
124
+ if (type_definition.type === 'enum') {
125
+ return checkEnumValue(
126
+ claim,
127
+ directive_name,
128
+ directive_value,
129
+ type_definition.values,
130
+ );
121
131
  }
122
132
 
123
- return [
124
- createOriginDiagnostic(
125
- claim,
126
- 'directive.invalid_enum',
127
- `Directive "${directive_name}" must be one of ${formatQuotedList(allowed_values)}.`,
128
- ),
129
- ];
133
+ if (!isDirectiveValueValid(type_definition, directive_value)) {
134
+ return [
135
+ createOriginDiagnostic(
136
+ claim,
137
+ 'directive.invalid_type',
138
+ getInvalidTypeMessage(directive_name, type_definition.type),
139
+ ),
140
+ ];
141
+ }
142
+
143
+ return collectPathDiagnostics(
144
+ claim,
145
+ directive_name,
146
+ mappings,
147
+ repo_config,
148
+ mapping_definition,
149
+ type_definition,
150
+ document_entity_keys,
151
+ document_node_references,
152
+ document_paths,
153
+ );
130
154
  }
131
155
 
132
156
  /**
133
157
  * @param {PatramClaim} claim
134
158
  * @param {string} directive_name
135
- * @param {Exclude<DirectiveTypeConfig, { type: 'enum' }>} type_definition
136
- * @param {string} directive_value
137
- * @returns {PatramDiagnostic | null}
159
+ * @param {MappingDefinition | null} mapping_definition
160
+ * @param {Map<string, string>} document_entity_keys
161
+ * @param {Map<string, import('./document-node-identity.js').DocumentNodeReference>} document_node_references
162
+ * @param {Set<string>} document_paths
163
+ * @returns {PatramDiagnostic[]}
138
164
  */
139
- function createInvalidTypeDiagnostic(
165
+ function collectUntypedPathDiagnostics(
140
166
  claim,
141
167
  directive_name,
142
- type_definition,
143
- directive_value,
168
+ mapping_definition,
169
+ document_entity_keys,
170
+ document_node_references,
171
+ document_paths,
144
172
  ) {
145
- if (isDirectiveValueValid(type_definition, directive_value)) {
146
- return null;
147
- }
148
-
149
- return createOriginDiagnostic(
173
+ return createPathExistenceDiagnostics(
150
174
  claim,
151
- 'directive.invalid_type',
152
- getInvalidTypeMessage(directive_name, type_definition.type),
175
+ directive_name,
176
+ mapping_definition,
177
+ undefined,
178
+ document_entity_keys,
179
+ document_node_references,
180
+ document_paths,
153
181
  );
154
182
  }
155
183
 
@@ -158,90 +186,70 @@ function createInvalidTypeDiagnostic(
158
186
  * @param {string} directive_name
159
187
  * @param {Record<string, MappingDefinition>} mappings
160
188
  * @param {PatramRepoConfig} repo_config
189
+ * @param {MappingDefinition | null} mapping_definition
161
190
  * @param {Exclude<DirectiveTypeConfig, { type: 'enum' }>} type_definition
162
191
  * @param {Map<string, string>} document_entity_keys
163
192
  * @param {Map<string, import('./document-node-identity.js').DocumentNodeReference>} document_node_references
164
193
  * @param {Set<string>} document_paths
165
194
  * @returns {PatramDiagnostic[]}
166
195
  */
167
- function createPathClassDiagnostics(
196
+ function collectPathDiagnostics(
168
197
  claim,
169
198
  directive_name,
170
199
  mappings,
171
200
  repo_config,
201
+ mapping_definition,
172
202
  type_definition,
173
203
  document_entity_keys,
174
204
  document_node_references,
175
205
  document_paths,
176
206
  ) {
177
- if (
178
- type_definition.type !== 'path' ||
179
- type_definition.path_class === undefined ||
180
- isDirectivePathInClass(
181
- mappings,
207
+ return createPathClassDiagnostics(
208
+ claim,
209
+ directive_name,
210
+ mappings,
211
+ repo_config,
212
+ type_definition,
213
+ document_entity_keys,
214
+ document_node_references,
215
+ document_paths,
216
+ ).concat(
217
+ createPathExistenceDiagnostics(
182
218
  claim,
183
- type_definition.path_class,
219
+ directive_name,
220
+ mapping_definition,
221
+ type_definition,
184
222
  document_entity_keys,
185
223
  document_node_references,
186
224
  document_paths,
187
- repo_config,
188
- )
189
- ) {
190
- return [];
191
- }
192
-
193
- return [
194
- createOriginDiagnostic(
195
- claim,
196
- 'directive.invalid_path_class',
197
- `Directive "${directive_name}" must point to path class "${type_definition.path_class}".`,
198
225
  ),
199
- ];
226
+ );
200
227
  }
201
228
 
202
229
  /**
203
- * @param {Record<string, MappingDefinition>} mappings
204
230
  * @param {PatramClaim} claim
205
- * @param {string} path_class_name
206
- * @param {Map<string, string>} document_entity_keys
207
- * @param {Map<string, import('./document-node-identity.js').DocumentNodeReference>} document_node_references
208
- * @param {Set<string>} document_paths
209
- * @param {PatramRepoConfig} repo_config
210
- * @returns {boolean}
231
+ * @param {string} directive_name
232
+ * @param {string} directive_value
233
+ * @param {string[]} allowed_values
234
+ * @returns {PatramDiagnostic[]}
211
235
  */
212
- function isDirectivePathInClass(
213
- mappings,
236
+ function checkEnumValue(
214
237
  claim,
215
- path_class_name,
216
- document_entity_keys,
217
- document_node_references,
218
- document_paths,
219
- repo_config,
238
+ directive_name,
239
+ directive_value,
240
+ allowed_values,
220
241
  ) {
221
- const path_class_definition = repo_config.path_classes?.[path_class_name];
222
-
223
- if (!path_class_definition) {
224
- return true;
225
- }
226
-
227
- const mapping_definition = resolveDirectiveMapping(mappings, claim);
228
- const target_kind = mapping_definition?.emit?.target_class ?? 'document';
229
- const resolved_target = resolveTargetReference(
230
- target_kind,
231
- 'path',
232
- claim,
233
- document_entity_keys,
234
- document_node_references,
235
- document_paths,
236
- );
237
-
238
- if (!resolved_target.path) {
239
- return false;
242
+ if (allowed_values.includes(directive_value)) {
243
+ return [];
240
244
  }
241
245
 
242
- return path_class_definition.prefixes.some((prefix) =>
243
- resolved_target.path?.startsWith(prefix),
244
- );
246
+ return [
247
+ createOriginDiagnostic(
248
+ claim,
249
+ 'directive.invalid_enum',
250
+ `Directive "${directive_name}" must be one of ${formatQuotedList(allowed_values)}.`,
251
+ ),
252
+ ];
245
253
  }
246
254
 
247
255
  /**
@@ -263,11 +271,7 @@ function resolveDirectiveMapping(mappings, claim) {
263
271
  * @returns {string}
264
272
  */
265
273
  function getDirectiveValidationFieldName(directive_name, mapping_definition) {
266
- if (mapping_definition?.node?.field) {
267
- return mapping_definition.node.field;
268
- }
269
-
270
- return directive_name;
274
+ return mapping_definition?.node?.field ?? directive_name;
271
275
  }
272
276
 
273
277
  /**
@@ -282,10 +286,3 @@ function isStructuralDirectiveField(field_name) {
282
286
  field_name === 'title'
283
287
  );
284
288
  }
285
-
286
- /**
287
- * @param {PatramClaim} claim
288
- * @param {string} code
289
- * @param {string} message
290
- * @returns {PatramDiagnostic}
291
- */
@@ -0,0 +1,87 @@
1
+ /**
2
+ * @import { MappingDefinition } from './patram-config.types.ts';
3
+ */
4
+
5
+ /**
6
+ * @returns {Record<string, { prefixes: string[] }>}
7
+ */
8
+ export function createDirectivePathClasses() {
9
+ return {
10
+ decision_docs: {
11
+ prefixes: ['docs/decisions/'],
12
+ },
13
+ plan_docs: {
14
+ prefixes: ['docs/plans/'],
15
+ },
16
+ task_docs: {
17
+ prefixes: ['docs/tasks/'],
18
+ },
19
+ };
20
+ }
21
+
22
+ /**
23
+ * @returns {Record<string, { from: string[], to: string[] }>}
24
+ */
25
+ export function createDirectiveRelations() {
26
+ return {
27
+ decided_by: {
28
+ from: ['document'],
29
+ to: ['document'],
30
+ },
31
+ tracked_in: {
32
+ from: ['document'],
33
+ to: ['document'],
34
+ },
35
+ };
36
+ }
37
+
38
+ /**
39
+ * @returns {Record<string, MappingDefinition>}
40
+ */
41
+ export function createMarkdownDirectiveMappings() {
42
+ return {
43
+ 'markdown.directive.decided_by': createRelationMapping('decided_by'),
44
+ 'markdown.directive.kind': createNodeMapping('$class'),
45
+ 'markdown.directive.status': createNodeMapping('status'),
46
+ 'markdown.directive.tracked_in': createRelationMapping('tracked_in'),
47
+ };
48
+ }
49
+
50
+ /**
51
+ * @returns {Record<string, MappingDefinition>}
52
+ */
53
+ export function createJsdocDirectiveMappings() {
54
+ return {
55
+ 'jsdoc.directive.decided_by': createRelationMapping('decided_by'),
56
+ 'jsdoc.directive.kind': createNodeMapping('$class'),
57
+ 'jsdoc.directive.status': createNodeMapping('status'),
58
+ 'jsdoc.directive.tracked_in': createRelationMapping('tracked_in'),
59
+ };
60
+ }
61
+
62
+ /**
63
+ * @param {string} field_name
64
+ * @returns {MappingDefinition}
65
+ */
66
+ function createNodeMapping(field_name) {
67
+ return {
68
+ node: {
69
+ class: 'document',
70
+ field: field_name,
71
+ },
72
+ };
73
+ }
74
+
75
+ /**
76
+ * @param {string} relation_name
77
+ * @returns {MappingDefinition}
78
+ */
79
+ function createRelationMapping(relation_name) {
80
+ return {
81
+ emit: {
82
+ relation: relation_name,
83
+ target: 'path',
84
+ target_class: 'document',
85
+ },
86
+ };
87
+ }