@agentshouse/mdmodel 0.1.0-cbce4f1da228f → 0.1.0-cc58fdf3541b7
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/API.md +152 -142
- package/README.md +16 -19
- package/dist/blocks.d.ts.map +1 -1
- package/dist/blocks.js +29 -7
- package/dist/condition.d.ts +42 -10
- package/dist/condition.d.ts.map +1 -1
- package/dist/condition.js +220 -87
- package/dist/definition.d.ts +3 -1
- package/dist/definition.d.ts.map +1 -1
- package/dist/definition.js +105 -21
- package/dist/divide.js +9 -9
- package/dist/document.d.ts +6 -5
- package/dist/document.d.ts.map +1 -1
- package/dist/document.js +309 -187
- package/dist/index.d.ts +9 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -5
- package/dist/links.d.ts +1 -0
- package/dist/links.d.ts.map +1 -1
- package/dist/links.js +85 -39
- package/dist/paths.d.ts +9 -1
- package/dist/paths.d.ts.map +1 -1
- package/dist/paths.js +28 -2
- package/dist/pattern.d.ts +10 -0
- package/dist/pattern.d.ts.map +1 -0
- package/dist/pattern.js +355 -0
- package/dist/project.d.ts.map +1 -1
- package/dist/project.js +7 -7
- package/dist/references.d.ts.map +1 -1
- package/dist/references.js +6 -1
- package/dist/schema.d.ts +1 -0
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +55 -4
- package/dist/types.d.ts +32 -3
- package/dist/types.d.ts.map +1 -1
- package/dist/validate.d.ts +27 -4
- package/dist/validate.d.ts.map +1 -1
- package/dist/validate.js +225 -70
- package/dist/yaml.d.ts.map +1 -1
- package/dist/yaml.js +36 -3
- package/fixtures/README.md +9 -4
- package/fixtures/cases/conditional-section/context.json +1 -0
- package/fixtures/cases/conditional-section/expected.json +6 -0
- package/fixtures/cases/conditional-section/library/questions/hosting.md +9 -0
- package/fixtures/cases/conditional-section/library/questions/payments.md +12 -0
- package/fixtures/cases/conditional-section/schema.yaml +14 -0
- package/fixtures/cases/cross-room-link/context.json +1 -0
- package/fixtures/cases/cross-room-link/expected.json +1 -0
- package/fixtures/cases/cross-room-link/library/clients/acme.md +6 -0
- package/fixtures/cases/cross-room-link/schema.yaml +3 -0
- package/fixtures/cases/cross-room-reference/context.json +1 -0
- package/fixtures/cases/cross-room-reference/expected.json +1 -0
- package/fixtures/cases/cross-room-reference/library/clients/acme.md +9 -0
- package/fixtures/cases/cross-room-reference/schema.yaml +9 -0
- package/fixtures/cases/member-on-an-untyped-document/context.json +1 -0
- package/fixtures/cases/member-on-an-untyped-document/expected.json +6 -0
- package/fixtures/cases/member-on-an-untyped-document/library/note.md +4 -0
- package/fixtures/cases/member-on-another-type/context.json +1 -0
- package/fixtures/cases/member-on-another-type/expected.json +6 -0
- package/fixtures/cases/member-on-another-type/library/note.md +4 -0
- package/fixtures/cases/member-on-another-type/schema.yaml +7 -0
- package/fixtures/division.json +26 -26
- package/package.json +7 -2
package/dist/validate.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../src/validate.ts"],"names":[],"mappings":"AAAA,OAAO,EAAyC,KAAK,UAAU,EAAE,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../src/validate.ts"],"names":[],"mappings":"AAAA,OAAO,EAAyC,KAAK,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAUzF,OAAO,KAAK,EAEV,aAAa,EAEb,OAAO,EACP,iBAAiB,EACjB,OAAO,EACP,aAAa,EACb,UAAU,EACV,iBAAiB,EACjB,cAAc,EACd,aAAa,EACd,MAAM,YAAY,CAAC;AAwDpB,wBAAgB,QAAQ,CACtB,SAAS,EAAE,OAAO,EAClB,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,EAC5C,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,GAChC,OAAO,CAET;AA8BD,wBAAgB,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,aAAa,GAAG,iBAAiB,CAQlF;AA2ED,wBAAgB,aAAa,CAC3B,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,aAAa,EACpB,OAAO,GAAE,IAAI,CAAC,iBAAiB,EAAE,SAAS,CAAM,GAC/C,aAAa,CA4Bf;AAED,wBAAgB,iBAAiB,CAC/B,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,EACf,UAAU,EAAE,UAAU,GACrB,SAAS,OAAO,EAAE,CAiBpB;AAED,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,aAAa,EAAE,CAO3D;AAED,wBAAgB,cAAc,CAC5B,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,EACf,UAAU,CAAC,EAAE,UAAU,GACtB,SAAS,aAAa,EAAE,CAsC1B;AAQD,KAAK,MAAM,GAAG;IAAE,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC;AAEzE,MAAM,MAAM,UAAU,GAAG;IACvB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,CAAC;IAClC,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;IACpC,QAAQ,CAAC,IAAI,EAAE,UAAU,GAAG,IAAI,CAAC;IACjC,QAAQ,CAAC,UAAU,EAAE,UAAU,GAAG,IAAI,CAAC;IACvC,QAAQ,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC;IACnC,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;IACzB,QAAQ,CAAC,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAC;CACtC,CAAC;AAEF,wBAAgB,MAAM,CACpB,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,OAAO,EACf,KAAK,GAAE,aAA6B,EACpC,OAAO,GAAE,IAAI,CAAC,iBAAiB,EAAE,SAAS,CAAM,GAC/C,UAAU,CA2BZ;AAyCD,MAAM,MAAM,QAAQ,GAAG;IACrB,GAAG,CACD,UAAU,EAAE,UAAU,EACtB,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,EAC5C,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,GAChC,MAAM,CAAC;IACV,OAAO,IAAI,UAAU,CAAC;CACvB,CAAC;AAEF,wBAAgB,QAAQ,IAAI,QAAQ,CA+DnC;AAED,wBAAiB,UAAU,CACzB,OAAO,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,EACpC,OAAO,GAAE,iBAAyC,EAClD,KAAK,GAAE,aAA6B,GACnC,SAAS,CAAC,cAAc,EAAE,UAAU,EAAE,IAAI,CAAC,CAsB7C;AAED,wBAAgB,QAAQ,CACtB,OAAO,EAAE,OAAO,EAChB,OAAO,GAAE,iBAAyC,EAClD,KAAK,GAAE,aAA6B,GACnC,UAAU,CAKZ"}
|
package/dist/validate.js
CHANGED
|
@@ -1,15 +1,22 @@
|
|
|
1
1
|
import { ATTACHMENT, BUILT_IN_TYPE, readSchema } from './definition.js';
|
|
2
2
|
import { parse } from './document.js';
|
|
3
|
+
import { MEMBER_FIELD } from './fields.js';
|
|
3
4
|
import { hrefOccurrences, linkInventory } from './links.js';
|
|
4
|
-
import { classifyHref } from './paths.js';
|
|
5
|
+
import { classifyHref, roomTarget } from './paths.js';
|
|
5
6
|
import { references } from './references.js';
|
|
6
7
|
import { refusal } from './refuse.js';
|
|
7
|
-
import { fitsItem, frontmatterRefusal } from './schema.js';
|
|
8
|
+
import { conditionedSections, fitsItem, frontmatterRefusal } from './schema.js';
|
|
9
|
+
import { openPatternBudget, PatternTooCostly } from './pattern.js';
|
|
8
10
|
import { headingSpans, listItems } from './sections.js';
|
|
9
11
|
import { bodyAfterFrontmatter, bodyStartOf } from './yaml.js';
|
|
10
12
|
function refuse(file, rule, extra) {
|
|
11
13
|
return { verdict: 'refuse', ...refusal(file, rule, extra) };
|
|
12
14
|
}
|
|
15
|
+
function unmarkedMember(file, document, definition) {
|
|
16
|
+
return Object.hasOwn(document.fields, MEMBER_FIELD) && definition?.memberDocument !== true
|
|
17
|
+
? refuse(file, 'undeclared-field', { location: `/${MEMBER_FIELD}` })
|
|
18
|
+
: null;
|
|
19
|
+
}
|
|
13
20
|
function instanceType(document) {
|
|
14
21
|
const raw = document.fields.type;
|
|
15
22
|
if (raw === undefined || raw === null)
|
|
@@ -60,8 +67,8 @@ function itemHrefs(content, items, uses) {
|
|
|
60
67
|
}
|
|
61
68
|
return hrefs;
|
|
62
69
|
}
|
|
63
|
-
export function verify(schema) {
|
|
64
|
-
const read = readSchema(schema);
|
|
70
|
+
export function verify(schema, provided) {
|
|
71
|
+
const read = readSchema(schema, provided);
|
|
65
72
|
if ('types' in read)
|
|
66
73
|
return { verdict: 'accept', model: read };
|
|
67
74
|
return {
|
|
@@ -80,10 +87,14 @@ function structure(file, content, document, definition) {
|
|
|
80
87
|
const spans = headingSpans(content, bodyStartOf(content));
|
|
81
88
|
const uses = usesIn(content);
|
|
82
89
|
const sections = [];
|
|
90
|
+
const conditioned = conditionedSections(definition, document.fields);
|
|
83
91
|
for (const [name, spec] of Object.entries(definition.sections)) {
|
|
84
92
|
const occurrences = spans.filter((one) => one.name === name);
|
|
85
93
|
if (occurrences.length === 0 && spec.required)
|
|
86
94
|
return { refusal: refuse(file, 'required-section', { location: name }) };
|
|
95
|
+
if (conditioned.includes(name) &&
|
|
96
|
+
occurrences.every((span) => content.slice(span.bodyStart, span.bodyEnd).trim() === ''))
|
|
97
|
+
return { refusal: refuse(file, 'conditional-section', { location: name }) };
|
|
87
98
|
if (spec.kind !== 'list')
|
|
88
99
|
continue;
|
|
89
100
|
for (const span of occurrences) {
|
|
@@ -96,10 +107,16 @@ function structure(file, content, document, definition) {
|
|
|
96
107
|
const hrefs = itemHrefs(content, items, uses);
|
|
97
108
|
if (hrefs === null)
|
|
98
109
|
return { refusal: refuse(file, 'structured-section', { location: name }) };
|
|
99
|
-
sections.push({
|
|
110
|
+
sections.push({
|
|
111
|
+
section: name,
|
|
112
|
+
to: spec.items.reference,
|
|
113
|
+
crossRoom: spec.items.crossRoom === true,
|
|
114
|
+
hrefs,
|
|
115
|
+
});
|
|
100
116
|
}
|
|
101
117
|
}
|
|
102
|
-
|
|
118
|
+
const member = unmarkedMember(file, document, definition);
|
|
119
|
+
return member === null ? { sections } : { refusal: member };
|
|
103
120
|
}
|
|
104
121
|
function linked(file, content) {
|
|
105
122
|
return hrefOccurrences(bodyAfterFrontmatter(content)).every((occurrence) => classifyHref(file, occurrence.href).kind !== 'invalid');
|
|
@@ -108,18 +125,24 @@ function reached(file, href, to) {
|
|
|
108
125
|
const resolved = classifyHref(file, href);
|
|
109
126
|
return { target: resolved.kind === 'path' ? resolved.path : null, to };
|
|
110
127
|
}
|
|
128
|
+
function crossing(file, href, crossRoom) {
|
|
129
|
+
return crossRoom === true && classifyHref(file, href).kind === 'room';
|
|
130
|
+
}
|
|
111
131
|
function definitionOf(document, model) {
|
|
112
132
|
const kind = instanceType(document);
|
|
113
133
|
return kind === null || !Object.hasOwn(model.types, kind) ? undefined : model.types[kind];
|
|
114
134
|
}
|
|
115
|
-
export function checkDocument(file, content, model) {
|
|
135
|
+
export function checkDocument(file, content, model, context = {}) {
|
|
136
|
+
const anyRoom = context.anyRoom === true;
|
|
116
137
|
const parsed = parse(content, file);
|
|
117
138
|
if (!parsed.ok)
|
|
118
139
|
return { ok: false };
|
|
119
140
|
const document = parsed.document;
|
|
120
141
|
const type = document.type ?? BUILT_IN_TYPE;
|
|
121
142
|
if (instanceType(document) === BUILT_IN_TYPE)
|
|
122
|
-
return
|
|
143
|
+
return unmarkedMember(file, document, undefined) === null
|
|
144
|
+
? { ok: true, type, typed: false, references: [] }
|
|
145
|
+
: { ok: false };
|
|
123
146
|
const definition = definitionOf(document, model);
|
|
124
147
|
const read = structure(file, content, document, definition);
|
|
125
148
|
if ('refusal' in read || definition === undefined || !linked(file, content))
|
|
@@ -129,94 +152,226 @@ export function checkDocument(file, content, model) {
|
|
|
129
152
|
type,
|
|
130
153
|
typed: true,
|
|
131
154
|
references: [
|
|
132
|
-
...references(document, definition).
|
|
133
|
-
...read.sections.flatMap(({ to, hrefs }) => hrefs.
|
|
155
|
+
...references(document, definition).flatMap(({ value, to, crossRoom }) => crossing(file, value, anyRoom || crossRoom) ? [] : [reached(file, value, to)]),
|
|
156
|
+
...read.sections.flatMap(({ to, crossRoom, hrefs }) => hrefs.flatMap((href) => crossing(file, href, anyRoom || crossRoom) ? [] : [reached(file, href, to)])),
|
|
134
157
|
],
|
|
135
158
|
};
|
|
136
159
|
}
|
|
137
160
|
export function sectionReferences(file, content, definition) {
|
|
138
161
|
const referring = (name) => {
|
|
139
162
|
const spec = Object.hasOwn(definition.sections, name) ? definition.sections[name] : undefined;
|
|
140
|
-
return spec?.kind === 'list'
|
|
163
|
+
return spec?.kind === 'list' && spec.items.reference !== undefined ? spec.items : undefined;
|
|
141
164
|
};
|
|
142
165
|
if (!Object.keys(definition.sections).some((name) => referring(name) !== undefined))
|
|
143
166
|
return [];
|
|
144
167
|
const uses = usesIn(content);
|
|
145
168
|
const found = [];
|
|
146
169
|
for (const span of headingSpans(content, bodyStartOf(content))) {
|
|
147
|
-
const
|
|
148
|
-
if (
|
|
170
|
+
const items = referring(span.name);
|
|
171
|
+
if (items === undefined)
|
|
149
172
|
continue;
|
|
150
|
-
const
|
|
151
|
-
const hrefs =
|
|
173
|
+
const listed = listItems(content, span.bodyStart, span.bodyEnd);
|
|
174
|
+
const hrefs = listed === null ? null : itemHrefs(content, listed, uses);
|
|
152
175
|
for (const href of hrefs ?? [])
|
|
153
|
-
|
|
176
|
+
if (!crossing(file, href, items.crossRoom))
|
|
177
|
+
found.push(reached(file, href, items.reference));
|
|
178
|
+
}
|
|
179
|
+
return found;
|
|
180
|
+
}
|
|
181
|
+
export function bodyRoomLinks(body) {
|
|
182
|
+
const found = [];
|
|
183
|
+
for (const href of new Set(hrefOccurrences(body).map((occurrence) => occurrence.href))) {
|
|
184
|
+
const room = roomTarget(href);
|
|
185
|
+
if (room !== null)
|
|
186
|
+
found.push({ kind: 'link', href, ...room });
|
|
187
|
+
}
|
|
188
|
+
return found;
|
|
189
|
+
}
|
|
190
|
+
export function roomReferences(file, content, definition) {
|
|
191
|
+
const found = bodyRoomLinks(bodyAfterFrontmatter(content));
|
|
192
|
+
if (definition === undefined)
|
|
193
|
+
return found;
|
|
194
|
+
const parsed = parse(content, file);
|
|
195
|
+
if (!parsed.ok)
|
|
196
|
+
return found;
|
|
197
|
+
for (const { pointer, value, to, crossRoom } of references(parsed.document, definition)) {
|
|
198
|
+
const room = roomTarget(value);
|
|
199
|
+
if (room !== null)
|
|
200
|
+
found.push({
|
|
201
|
+
kind: 'field',
|
|
202
|
+
pointer,
|
|
203
|
+
value,
|
|
204
|
+
to,
|
|
205
|
+
...room,
|
|
206
|
+
...(crossRoom ? { crossRoom } : {}),
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
const uses = usesIn(content);
|
|
210
|
+
for (const span of headingSpans(content, bodyStartOf(content))) {
|
|
211
|
+
const spec = Object.hasOwn(definition.sections, span.name)
|
|
212
|
+
? definition.sections[span.name]
|
|
213
|
+
: undefined;
|
|
214
|
+
if (spec?.kind !== 'list' || spec.items.reference === undefined)
|
|
215
|
+
continue;
|
|
216
|
+
const listed = listItems(content, span.bodyStart, span.bodyEnd);
|
|
217
|
+
for (const href of (listed === null ? null : itemHrefs(content, listed, uses)) ?? []) {
|
|
218
|
+
const room = roomTarget(href);
|
|
219
|
+
if (room !== null)
|
|
220
|
+
found.push({
|
|
221
|
+
kind: 'item',
|
|
222
|
+
section: span.name,
|
|
223
|
+
href,
|
|
224
|
+
to: spec.items.reference,
|
|
225
|
+
...room,
|
|
226
|
+
...(spec.items.crossRoom === true ? { crossRoom: true } : {}),
|
|
227
|
+
});
|
|
228
|
+
}
|
|
154
229
|
}
|
|
155
230
|
return found;
|
|
156
231
|
}
|
|
232
|
+
function least(refused, file, verdict) {
|
|
233
|
+
return refused === undefined || file < refused.file ? { file, verdict } : refused;
|
|
234
|
+
}
|
|
235
|
+
export function assess(file, content, binary, model = { types: {} }, context = {}) {
|
|
236
|
+
const unread = { file, type: undefined, targets: [], structured: null, fields: [], sections: [] };
|
|
237
|
+
if (binary)
|
|
238
|
+
return { ...unread, read: refuse(file, 'binary-target-document'), linked: true };
|
|
239
|
+
const parsed = parse(content, file);
|
|
240
|
+
if (!parsed.ok)
|
|
241
|
+
return { ...unread, read: { verdict: 'refuse', ...parsed.refusal }, linked: true };
|
|
242
|
+
const document = parsed.document;
|
|
243
|
+
const type = document.type ?? BUILT_IN_TYPE;
|
|
244
|
+
const typed = { file, type, read: null, structured: null };
|
|
245
|
+
if (instanceType(document) === BUILT_IN_TYPE) {
|
|
246
|
+
const structured = unmarkedMember(file, document, undefined);
|
|
247
|
+
return { ...typed, targets: [], structured, fields: [], linked: true, sections: [] };
|
|
248
|
+
}
|
|
249
|
+
const definition = definitionOf(document, model);
|
|
250
|
+
try {
|
|
251
|
+
return structured(file, content, typed, document, definition, context);
|
|
252
|
+
}
|
|
253
|
+
catch (error) {
|
|
254
|
+
if (!(error instanceof PatternTooCostly))
|
|
255
|
+
throw error;
|
|
256
|
+
return {
|
|
257
|
+
...typed,
|
|
258
|
+
targets: [],
|
|
259
|
+
structured: refuse(file, 'pattern', { location: '' }),
|
|
260
|
+
fields: [],
|
|
261
|
+
linked: true,
|
|
262
|
+
sections: [],
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
function structured(file, content, typed, document, definition, context) {
|
|
267
|
+
const read = structure(file, content, document, definition);
|
|
268
|
+
if ('refusal' in read || definition === undefined) {
|
|
269
|
+
return {
|
|
270
|
+
...typed,
|
|
271
|
+
targets: [],
|
|
272
|
+
structured: 'refusal' in read ? read.refusal : null,
|
|
273
|
+
fields: [],
|
|
274
|
+
linked: true,
|
|
275
|
+
sections: [],
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
const anyRoom = context.anyRoom === true;
|
|
279
|
+
const fields = references(document, definition).flatMap(({ pointer, value, to, crossRoom }) => crossing(file, value, anyRoom || crossRoom)
|
|
280
|
+
? []
|
|
281
|
+
: [{ reference: reached(file, value, to), location: pointer }]);
|
|
282
|
+
const sections = read.sections.flatMap(({ section, to, crossRoom, hrefs }) => hrefs.flatMap((href) => crossing(file, href, anyRoom || crossRoom)
|
|
283
|
+
? []
|
|
284
|
+
: [{ reference: reached(file, href, to), location: section }]));
|
|
285
|
+
const targets = new Set();
|
|
286
|
+
for (const { reference } of [...fields, ...sections]) {
|
|
287
|
+
if (reference.target !== null)
|
|
288
|
+
targets.add(reference.target);
|
|
289
|
+
}
|
|
290
|
+
return { ...typed, targets: [...targets], fields, linked: linked(file, content), sections };
|
|
291
|
+
}
|
|
292
|
+
export function verdicts() {
|
|
293
|
+
let read;
|
|
294
|
+
let structured;
|
|
295
|
+
let referred;
|
|
296
|
+
let missed;
|
|
297
|
+
const unreached = (placed, typeOf, binary) => placed.find(({ reference }) => !reaches(reference, typeOf, binary));
|
|
298
|
+
return {
|
|
299
|
+
add(assessment, typeOf, binary) {
|
|
300
|
+
const { file } = assessment;
|
|
301
|
+
if (assessment.read !== null) {
|
|
302
|
+
read = least(read, file, assessment.read);
|
|
303
|
+
return 0;
|
|
304
|
+
}
|
|
305
|
+
if (assessment.structured !== null) {
|
|
306
|
+
structured = least(structured, file, assessment.structured);
|
|
307
|
+
return 0;
|
|
308
|
+
}
|
|
309
|
+
const field = unreached(assessment.fields, typeOf, binary);
|
|
310
|
+
if (field !== undefined) {
|
|
311
|
+
referred = least(referred, file, refuse(file, 'unresolved-reference', { location: field.location }));
|
|
312
|
+
return 0;
|
|
313
|
+
}
|
|
314
|
+
if (!assessment.linked) {
|
|
315
|
+
referred = least(referred, file, refuse(file, 'unresolved-link'));
|
|
316
|
+
return 0;
|
|
317
|
+
}
|
|
318
|
+
const section = unreached(assessment.sections, typeOf, binary);
|
|
319
|
+
if (section !== undefined) {
|
|
320
|
+
referred = least(referred, file, refuse(file, 'unresolved-reference', { location: section.location }));
|
|
321
|
+
return 0;
|
|
322
|
+
}
|
|
323
|
+
const all = [...assessment.fields, ...assessment.sections];
|
|
324
|
+
for (const { reference } of all) {
|
|
325
|
+
if (!missing(reference, typeOf, binary))
|
|
326
|
+
continue;
|
|
327
|
+
const path = reference.target;
|
|
328
|
+
if (missed === undefined || path < missed.path)
|
|
329
|
+
missed = { path, referrers: [] };
|
|
330
|
+
if (path === missed.path && missed.referrers.at(-1) !== file)
|
|
331
|
+
missed.referrers.push(file);
|
|
332
|
+
}
|
|
333
|
+
return all.length;
|
|
334
|
+
},
|
|
335
|
+
verdict() {
|
|
336
|
+
const refused = read ?? structured ?? referred;
|
|
337
|
+
if (refused !== undefined)
|
|
338
|
+
return refused.verdict;
|
|
339
|
+
if (missed !== undefined) {
|
|
340
|
+
return refuse(missed.path, 'referenced-missing-file', {
|
|
341
|
+
referrers: [...missed.referrers].sort(),
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
return { verdict: 'accept' };
|
|
345
|
+
},
|
|
346
|
+
};
|
|
347
|
+
}
|
|
157
348
|
export function* validating(library, context = { binaryTargets: [] }, model = { types: {} }) {
|
|
158
349
|
const binaryTargets = new Set(context.binaryTargets);
|
|
159
350
|
const binary = (path) => binaryTargets.has(path);
|
|
160
|
-
const files = Object.keys(library).sort();
|
|
161
351
|
const types = new Map();
|
|
162
|
-
const
|
|
163
|
-
const
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
if (instanceType(parsed.document) !== BUILT_IN_TYPE)
|
|
172
|
-
documents.set(file, parsed.document);
|
|
173
|
-
yield file;
|
|
174
|
-
}
|
|
175
|
-
const sectionReferences = new Map();
|
|
176
|
-
for (const [file, document] of documents) {
|
|
177
|
-
const read = structure(file, library[file], document, definitionOf(document, model));
|
|
178
|
-
if ('refusal' in read)
|
|
179
|
-
return read.refusal;
|
|
180
|
-
sectionReferences.set(file, read.sections);
|
|
181
|
-
yield file;
|
|
182
|
-
}
|
|
183
|
-
let least;
|
|
184
|
-
for (const [file, document] of documents) {
|
|
185
|
-
const definition = definitionOf(document, model);
|
|
186
|
-
const fields = references(document, definition).map(({ pointer, value, to }) => ({
|
|
187
|
-
reference: reached(file, value, to),
|
|
188
|
-
location: pointer,
|
|
189
|
-
}));
|
|
190
|
-
for (const { reference, location } of fields) {
|
|
191
|
-
if (!reaches(reference, typeOf, binary))
|
|
192
|
-
return refuse(file, 'unresolved-reference', { location });
|
|
352
|
+
const assessments = [];
|
|
353
|
+
const closeBudget = openPatternBudget();
|
|
354
|
+
try {
|
|
355
|
+
for (const [file, content] of library) {
|
|
356
|
+
const assessment = assess(file, content, binary(file), model, context);
|
|
357
|
+
if (assessment.type !== undefined)
|
|
358
|
+
types.set(file, assessment.type);
|
|
359
|
+
assessments.push(assessment);
|
|
360
|
+
yield { path: file, resolved: 0 };
|
|
193
361
|
}
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
.
|
|
198
|
-
.flatMap(({ section, to, hrefs }) => hrefs.map((href) => ({ reference: reached(file, href, to), location: section })));
|
|
199
|
-
for (const { reference, location } of sections) {
|
|
200
|
-
if (!reaches(reference, typeOf, binary))
|
|
201
|
-
return refuse(file, 'unresolved-reference', { location });
|
|
362
|
+
const judged = verdicts();
|
|
363
|
+
for (const assessment of assessments) {
|
|
364
|
+
const resolved = judged.add(assessment, (path) => types.get(path), binary);
|
|
365
|
+
yield { path: assessment.file, resolved };
|
|
202
366
|
}
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
if (least === undefined || path < least.path)
|
|
208
|
-
least = { path, referrers: [] };
|
|
209
|
-
if (path === least.path && least.referrers.at(-1) !== file)
|
|
210
|
-
least.referrers.push(file);
|
|
211
|
-
}
|
|
212
|
-
yield file;
|
|
367
|
+
return judged.verdict();
|
|
368
|
+
}
|
|
369
|
+
finally {
|
|
370
|
+
closeBudget();
|
|
213
371
|
}
|
|
214
|
-
if (least !== undefined)
|
|
215
|
-
return refuse(least.path, 'referenced-missing-file', { referrers: least.referrers });
|
|
216
|
-
return { verdict: 'accept' };
|
|
217
372
|
}
|
|
218
373
|
export function validate(library, context = { binaryTargets: [] }, model = { types: {} }) {
|
|
219
|
-
const steps = validating(library, context, model);
|
|
374
|
+
const steps = validating(new Map(Object.entries(library)), context, model);
|
|
220
375
|
let step = steps.next();
|
|
221
376
|
while (step.done !== true)
|
|
222
377
|
step = steps.next();
|
package/dist/yaml.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"yaml.d.ts","sourceRoot":"","sources":["../src/yaml.ts"],"names":[],"mappings":"AAAA,qBAAa,SAAU,SAAQ,KAAK;IACtB,QAAQ,CAAC,MAAM,EAAE,MAAM;IAAnC,YAAqB,MAAM,EAAE,MAAM,EAGlC;CACF;AAED,MAAM,MAAM,UAAU,GAAG;IACvB,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;IACxB,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,IAAI,CAAC;IACjD,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;CACtB,CAAC;AAEF,MAAM,MAAM,OAAO,GAAG;IACpB,QAAQ,CAAC,IAAI,EAAE,KAAK,CAAC;IACrB,QAAQ,CAAC,KAAK,EAAE,SAAS,QAAQ,EAAE,CAAC;IACpC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;CACtB,CAAC;AAEF,MAAM,MAAM,SAAS,GAAG;IACtB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,KAAK,EAAE,QAAQ,CAAC;CAC1B,CAAC;AAEF,MAAM,MAAM,OAAO,GAAG;IACpB,QAAQ,CAAC,IAAI,EAAE,KAAK,CAAC;IACrB,QAAQ,CAAC,OAAO,EAAE,SAAS,SAAS,EAAE,CAAC;IACvC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;CACtB,CAAC;AAEF,MAAM,MAAM,QAAQ,GAAG,UAAU,GAAG,OAAO,GAAG,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"yaml.d.ts","sourceRoot":"","sources":["../src/yaml.ts"],"names":[],"mappings":"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"}
|
package/dist/yaml.js
CHANGED
|
@@ -6,6 +6,18 @@ export class YamlError extends Error {
|
|
|
6
6
|
this.name = 'YamlError';
|
|
7
7
|
}
|
|
8
8
|
}
|
|
9
|
+
const YAML_DEPTH_MAX = 64;
|
|
10
|
+
function nested(c, parse) {
|
|
11
|
+
if (c.depth >= YAML_DEPTH_MAX)
|
|
12
|
+
throw new YamlError(c.i);
|
|
13
|
+
c.depth += 1;
|
|
14
|
+
try {
|
|
15
|
+
return parse();
|
|
16
|
+
}
|
|
17
|
+
finally {
|
|
18
|
+
c.depth -= 1;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
9
21
|
function peek(c) {
|
|
10
22
|
return c.i < c.end ? c.text[c.i] : '';
|
|
11
23
|
}
|
|
@@ -70,7 +82,10 @@ function indentAt(c) {
|
|
|
70
82
|
}
|
|
71
83
|
return n;
|
|
72
84
|
}
|
|
85
|
+
const UNHELD_TEXT = /[\0\ud800-\udfff]/u;
|
|
73
86
|
function scalar(value, start, end) {
|
|
87
|
+
if (typeof value === 'string' && UNHELD_TEXT.test(value))
|
|
88
|
+
throw new YamlError(start);
|
|
74
89
|
return { kind: 'scalar', value, start, end };
|
|
75
90
|
}
|
|
76
91
|
export function interpretPlain(text) {
|
|
@@ -202,6 +217,9 @@ function parseFlowValue(c) {
|
|
|
202
217
|
return parseScalar(c, true);
|
|
203
218
|
}
|
|
204
219
|
function parseFlowMap(c) {
|
|
220
|
+
return nested(c, () => flowMap(c));
|
|
221
|
+
}
|
|
222
|
+
function flowMap(c) {
|
|
205
223
|
const start = c.i;
|
|
206
224
|
if (peek(c) !== '{')
|
|
207
225
|
throw new YamlError(c.i);
|
|
@@ -240,6 +258,9 @@ function parseFlowMap(c) {
|
|
|
240
258
|
throw new YamlError(c.i);
|
|
241
259
|
}
|
|
242
260
|
function parseFlowSeq(c) {
|
|
261
|
+
return nested(c, () => flowSeq(c));
|
|
262
|
+
}
|
|
263
|
+
function flowSeq(c) {
|
|
243
264
|
const start = c.i;
|
|
244
265
|
if (peek(c) !== '[')
|
|
245
266
|
throw new YamlError(c.i);
|
|
@@ -336,6 +357,9 @@ function parseSeqItem(c, indent) {
|
|
|
336
357
|
return parseValue(c, indent);
|
|
337
358
|
}
|
|
338
359
|
function parseBlockSeq(c, indent) {
|
|
360
|
+
return nested(c, () => blockSeq(c, indent));
|
|
361
|
+
}
|
|
362
|
+
function blockSeq(c, indent) {
|
|
339
363
|
const start = c.i;
|
|
340
364
|
const items = [];
|
|
341
365
|
let end = start;
|
|
@@ -355,6 +379,9 @@ function parseBlockSeq(c, indent) {
|
|
|
355
379
|
return { kind: 'seq', items, start, end };
|
|
356
380
|
}
|
|
357
381
|
function parseBlockMap(c, indent, inline = false) {
|
|
382
|
+
return nested(c, () => blockMap(c, indent, inline));
|
|
383
|
+
}
|
|
384
|
+
function blockMap(c, indent, inline) {
|
|
358
385
|
const start = c.i;
|
|
359
386
|
const entries = [];
|
|
360
387
|
const seen = new Set();
|
|
@@ -387,7 +414,7 @@ function parseBlockMap(c, indent, inline = false) {
|
|
|
387
414
|
return { kind: 'map', entries, start, end };
|
|
388
415
|
}
|
|
389
416
|
export function parseYamlMapping(text, start, end) {
|
|
390
|
-
const c = { text, i: start, end };
|
|
417
|
+
const c = { text, i: start, end, depth: 0 };
|
|
391
418
|
skipBlankAndComments(c);
|
|
392
419
|
if (c.i >= end)
|
|
393
420
|
return { kind: 'map', entries: [], start, end };
|
|
@@ -439,17 +466,23 @@ function listed(value) {
|
|
|
439
466
|
return keys.length === value.length && keys.every((key, index) => key === String(index));
|
|
440
467
|
}
|
|
441
468
|
function heldWithin(value, within) {
|
|
442
|
-
if (
|
|
469
|
+
if (typeof value === 'string')
|
|
470
|
+
return !UNHELD_TEXT.test(value);
|
|
471
|
+
if (value === null || typeof value === 'boolean')
|
|
443
472
|
return true;
|
|
444
473
|
if (typeof value === 'number')
|
|
445
474
|
return Number.isFinite(value);
|
|
446
475
|
if (typeof value !== 'object' || within.includes(value))
|
|
447
476
|
return false;
|
|
477
|
+
if (within.length + 1 >= YAML_DEPTH_MAX)
|
|
478
|
+
return false;
|
|
448
479
|
const path = [...within, value];
|
|
449
480
|
const held = (item) => heldWithin(item, path);
|
|
450
481
|
if (Array.isArray(value))
|
|
451
482
|
return listed(value) && value.every(held);
|
|
452
|
-
return plain(value) &&
|
|
483
|
+
return (plain(value) &&
|
|
484
|
+
Object.keys(value).every((key) => !UNHELD_TEXT.test(key)) &&
|
|
485
|
+
Object.values(value).every(held));
|
|
453
486
|
}
|
|
454
487
|
export function frontmatterValue(value) {
|
|
455
488
|
return heldWithin(value, []);
|
package/fixtures/README.md
CHANGED
|
@@ -51,8 +51,8 @@ change to a value that is not prose leaves the projection alone.
|
|
|
51
51
|
|
|
52
52
|
`division.json` exercises the public `divide` seam separately from the
|
|
53
53
|
validation Libraries. It carries `schema` and `cases` like `projection.json`,
|
|
54
|
-
and each case adds the `
|
|
55
|
-
`divide(document, file, context)` with `{ documentTargets, definition,
|
|
54
|
+
and each case adds the `bound` the division runs under. Parse the source, call
|
|
55
|
+
`divide(document, file, context)` with `{ documentTargets, definition, bound }`,
|
|
56
56
|
and compare against `expected`: either `{ "ok": true, "spans": [...] }`, where
|
|
57
57
|
each span carries the exact `text` to embed and the exact `source` the returned
|
|
58
58
|
`start` and `end` address in that case's source, or `{ "ok": false, "refusal": ... }`. Then
|
|
@@ -107,6 +107,7 @@ schema text. The model's own rules:
|
|
|
107
107
|
| `frontmatter-fence` | A present frontmatter fence does not parse as the accepted YAML subset |
|
|
108
108
|
| `schema-yaml` | A schema text is not a mapping in the accepted YAML subset |
|
|
109
109
|
| `schema-type-name` | A type name is empty, starts with `$`, or is `any` or `attachment` |
|
|
110
|
+
| `provided-type` | A schema names a type its caller provides with a key but its two role keywords |
|
|
110
111
|
| `schema` | A type or subschema is neither a mapping nor a boolean |
|
|
111
112
|
| `schema-keyword` | A schema carries a key that is no JSON Schema 2020-12 keyword the model admits |
|
|
112
113
|
| `$defs` | `$defs` is not a mapping |
|
|
@@ -117,14 +118,18 @@ schema text. The model's own rules:
|
|
|
117
118
|
| `unknown-type` | A file declares a type absent from the governing schema |
|
|
118
119
|
| `undeclared-field` | A closed object carries a key it does not declare |
|
|
119
120
|
| `required-section` | A required section heading is missing |
|
|
121
|
+
| `conditional-section` | A section its type's `if` branch requires is absent or holds no text |
|
|
120
122
|
| `unresolved-reference` | A reference at any depth or a section item reaches no member it admits |
|
|
121
123
|
| `unresolved-link` | A body link is an absolute path or leaves the collection |
|
|
122
124
|
| `structured-section` | A declared list section is not a list of values of its item schema |
|
|
123
125
|
| `referenced-missing-file` | Remaining files still reference an absent path |
|
|
124
126
|
| `unwritable-link` | No Markdown destination form can carry a new link destination |
|
|
125
|
-
| `division-
|
|
127
|
+
| `division-bound` | A division bound is not a whole number of at least 2 |
|
|
126
128
|
|
|
127
|
-
`
|
|
129
|
+
A `then` or `else` of an `if` on a type, in its `allOf` or in such a branch may hold
|
|
130
|
+
`sections`, naming sections the type declares as the type declares them, else `sections`.
|
|
131
|
+
|
|
132
|
+
`unwritable-link` is refused by `apply` and `division-bound` by `divide`, not
|
|
128
133
|
by `validate`, so no case under `cases/` carries either.
|
|
129
134
|
|
|
130
135
|
A reference that admits `attachment` reaches any path in the same Library, so
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{ "binaryTargets": [] }
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
question:
|
|
2
|
+
properties:
|
|
3
|
+
title: { type: string }
|
|
4
|
+
status: { enum: [open, answered, withdrawn] }
|
|
5
|
+
required: [title, status]
|
|
6
|
+
sections:
|
|
7
|
+
Question: { kind: text, required: true }
|
|
8
|
+
Answer: { kind: text }
|
|
9
|
+
if:
|
|
10
|
+
properties:
|
|
11
|
+
status: { enum: [answered, withdrawn] }
|
|
12
|
+
then:
|
|
13
|
+
sections:
|
|
14
|
+
Answer: { kind: text, required: true }
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{ "binaryTargets": [] }
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{ "verdict": "accept" }
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{ "binaryTargets": [] }
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{ "verdict": "accept" }
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
client:
|
|
2
|
+
properties:
|
|
3
|
+
name: { type: string }
|
|
4
|
+
finance: { type: string, reference: invoice, cross_room: true }
|
|
5
|
+
sections:
|
|
6
|
+
Invoices: { kind: list, items: { type: string, reference: invoice, cross_room: true } }
|
|
7
|
+
invoice:
|
|
8
|
+
properties:
|
|
9
|
+
name: { type: string }
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{ "binaryTargets": [] }
|