@aruzone/aze-forge 0.1.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 (133) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +408 -0
  3. package/dist/acceptance.d.ts +48 -0
  4. package/dist/acceptance.js +180 -0
  5. package/dist/acceptance.js.map +1 -0
  6. package/dist/assets.d.ts +19 -0
  7. package/dist/assets.js +509 -0
  8. package/dist/assets.js.map +1 -0
  9. package/dist/atomic-write.d.ts +1 -0
  10. package/dist/atomic-write.js +23 -0
  11. package/dist/atomic-write.js.map +1 -0
  12. package/dist/callout.d.ts +27 -0
  13. package/dist/callout.js +78 -0
  14. package/dist/callout.js.map +1 -0
  15. package/dist/capabilities-json.d.ts +4 -0
  16. package/dist/capabilities-json.js +225 -0
  17. package/dist/capabilities-json.js.map +1 -0
  18. package/dist/capabilities.d.ts +110 -0
  19. package/dist/capabilities.js +270 -0
  20. package/dist/capabilities.js.map +1 -0
  21. package/dist/cli.d.ts +2 -0
  22. package/dist/cli.js +1096 -0
  23. package/dist/cli.js.map +1 -0
  24. package/dist/compiler.d.ts +3 -0
  25. package/dist/compiler.js +1034 -0
  26. package/dist/compiler.js.map +1 -0
  27. package/dist/configuration-error.d.ts +4 -0
  28. package/dist/configuration-error.js +9 -0
  29. package/dist/configuration-error.js.map +1 -0
  30. package/dist/diagnostics-json.d.ts +23 -0
  31. package/dist/diagnostics-json.js +143 -0
  32. package/dist/diagnostics-json.js.map +1 -0
  33. package/dist/diagnostics.d.ts +16 -0
  34. package/dist/diagnostics.js +154 -0
  35. package/dist/diagnostics.js.map +1 -0
  36. package/dist/equation.d.ts +74 -0
  37. package/dist/equation.js +485 -0
  38. package/dist/equation.js.map +1 -0
  39. package/dist/font.d.ts +19 -0
  40. package/dist/font.js +75 -0
  41. package/dist/font.js.map +1 -0
  42. package/dist/format.d.ts +2 -0
  43. package/dist/format.js +308 -0
  44. package/dist/format.js.map +1 -0
  45. package/dist/hash.d.ts +5 -0
  46. package/dist/hash.js +189 -0
  47. package/dist/hash.js.map +1 -0
  48. package/dist/help.d.ts +9 -0
  49. package/dist/help.js +102 -0
  50. package/dist/help.js.map +1 -0
  51. package/dist/html-fragment.d.ts +7 -0
  52. package/dist/html-fragment.js +70 -0
  53. package/dist/html-fragment.js.map +1 -0
  54. package/dist/index.d.ts +39 -0
  55. package/dist/index.js +32 -0
  56. package/dist/index.js.map +1 -0
  57. package/dist/markdown.d.ts +14 -0
  58. package/dist/markdown.js +425 -0
  59. package/dist/markdown.js.map +1 -0
  60. package/dist/mermaid-browser.d.ts +43 -0
  61. package/dist/mermaid-browser.js +358 -0
  62. package/dist/mermaid-browser.js.map +1 -0
  63. package/dist/mermaid.d.ts +84 -0
  64. package/dist/mermaid.js +740 -0
  65. package/dist/mermaid.js.map +1 -0
  66. package/dist/model.d.ts +406 -0
  67. package/dist/model.js +2 -0
  68. package/dist/model.js.map +1 -0
  69. package/dist/parse.d.ts +3 -0
  70. package/dist/parse.js +1374 -0
  71. package/dist/parse.js.map +1 -0
  72. package/dist/preview-server.d.ts +39 -0
  73. package/dist/preview-server.js +156 -0
  74. package/dist/preview-server.js.map +1 -0
  75. package/dist/preview-shell.d.ts +29 -0
  76. package/dist/preview-shell.js +102 -0
  77. package/dist/preview-shell.js.map +1 -0
  78. package/dist/reference-validation.d.ts +7 -0
  79. package/dist/reference-validation.js +42 -0
  80. package/dist/reference-validation.js.map +1 -0
  81. package/dist/registry.d.ts +29 -0
  82. package/dist/registry.js +384 -0
  83. package/dist/registry.js.map +1 -0
  84. package/dist/render-html.d.ts +24 -0
  85. package/dist/render-html.js +149 -0
  86. package/dist/render-html.js.map +1 -0
  87. package/dist/render-pdf.d.ts +64 -0
  88. package/dist/render-pdf.js +503 -0
  89. package/dist/render-pdf.js.map +1 -0
  90. package/dist/render-png.d.ts +32 -0
  91. package/dist/render-png.js +427 -0
  92. package/dist/render-png.js.map +1 -0
  93. package/dist/render-svg.d.ts +23 -0
  94. package/dist/render-svg.js +243 -0
  95. package/dist/render-svg.js.map +1 -0
  96. package/dist/runtime-support.d.ts +26 -0
  97. package/dist/runtime-support.js +62 -0
  98. package/dist/runtime-support.js.map +1 -0
  99. package/dist/source-map.d.ts +12 -0
  100. package/dist/source-map.js +75 -0
  101. package/dist/source-map.js.map +1 -0
  102. package/dist/table.d.ts +22 -0
  103. package/dist/table.js +90 -0
  104. package/dist/table.js.map +1 -0
  105. package/dist/theme.d.ts +6 -0
  106. package/dist/theme.js +89 -0
  107. package/dist/theme.js.map +1 -0
  108. package/dist/tool-version.d.ts +3 -0
  109. package/dist/tool-version.js +3 -0
  110. package/dist/tool-version.js.map +1 -0
  111. package/dist/type-guards.d.ts +1 -0
  112. package/dist/type-guards.js +4 -0
  113. package/dist/type-guards.js.map +1 -0
  114. package/dist/validate-document.d.ts +2 -0
  115. package/dist/validate-document.js +248 -0
  116. package/dist/validate-document.js.map +1 -0
  117. package/dist/version.d.ts +37 -0
  118. package/dist/version.js +111 -0
  119. package/dist/version.js.map +1 -0
  120. package/dist/watch-dependencies.d.ts +15 -0
  121. package/dist/watch-dependencies.js +93 -0
  122. package/dist/watch-dependencies.js.map +1 -0
  123. package/dist/watch-events.d.ts +51 -0
  124. package/dist/watch-events.js +27 -0
  125. package/dist/watch-events.js.map +1 -0
  126. package/dist/watch-loop.d.ts +39 -0
  127. package/dist/watch-loop.js +117 -0
  128. package/dist/watch-loop.js.map +1 -0
  129. package/package.json +70 -0
  130. package/schemas/acceptance.json +104 -0
  131. package/schemas/capabilities.json +491 -0
  132. package/schemas/diagnostics.json +219 -0
  133. package/schemas/version.json +167 -0
package/dist/parse.js ADDED
@@ -0,0 +1,1374 @@
1
+ import { isAlias, isScalar, parseDocument, visit } from "yaml";
2
+ import { createDiagnostic } from "./diagnostics.js";
3
+ import { CALLOUT_PLUGIN_TYPE, CALLOUT_PLUGIN_VERSION, CALLOUT_VARIANTS, calloutPlugin } from "./callout.js";
4
+ import { TABLE_PLUGIN_TYPE, TABLE_PLUGIN_VERSION, tablePlugin } from "./table.js";
5
+ import { EQUATION_PLUGIN_TYPE, equationPlugin, parseEquationHeader, validateEquationBody, } from "./equation.js";
6
+ import { isGfmTableStart, parseInlineFragment, splitTableRow, tryParseGfmTable } from "./markdown.js";
7
+ import { MERMAID_PLUGIN_TYPE, mermaidPlugin, parseMermaidHeader, validateMermaidBody, } from "./mermaid.js";
8
+ import { rangeFromLineSlice, rangeFromLines, sourceLines, } from "./source-map.js";
9
+ import { validateBlockIds, } from "./reference-validation.js";
10
+ const OUTPUT_FORMATS = {
11
+ html: true,
12
+ svg: true,
13
+ png: true,
14
+ pdf: true,
15
+ };
16
+ const KNOWN_METADATA_KEYS = {
17
+ azemark: true,
18
+ author: true,
19
+ title: true,
20
+ theme: true,
21
+ outputs: true,
22
+ };
23
+ const EXTENSION_KEY = /^x-[a-z0-9]+(?:-[a-z0-9]+)*$/;
24
+ function metadataKeyDistance(left, right) {
25
+ let previous = Array.from({ length: right.length + 1 }, (_, index) => index);
26
+ for (let leftIndex = 0; leftIndex < left.length; leftIndex += 1) {
27
+ const current = [leftIndex + 1];
28
+ for (let rightIndex = 0; rightIndex < right.length; rightIndex += 1) {
29
+ const substitution = (previous[rightIndex] ?? Number.POSITIVE_INFINITY) +
30
+ (left[leftIndex] === right[rightIndex] ? 0 : 1);
31
+ current.push(Math.min((previous[rightIndex + 1] ?? Number.POSITIVE_INFINITY) + 1, (current[rightIndex] ?? Number.POSITIVE_INFINITY) + 1, substitution));
32
+ }
33
+ previous = current;
34
+ }
35
+ return previous[right.length] ?? right.length;
36
+ }
37
+ function nearestMetadataKey(value) {
38
+ let nearest = "azemark";
39
+ let distance = Number.POSITIVE_INFINITY;
40
+ for (const candidate of Object.keys(KNOWN_METADATA_KEYS)) {
41
+ const candidateDistance = metadataKeyDistance(value, candidate);
42
+ if (candidateDistance < distance) {
43
+ nearest = candidate;
44
+ distance = candidateDistance;
45
+ }
46
+ }
47
+ return nearest;
48
+ }
49
+ function diagnostic(code, message, options, line, severity = "error", range = line === undefined ? undefined : rangeFromLines(line, line), details = {}) {
50
+ const location = options.sourceName === undefined && range === undefined
51
+ ? undefined
52
+ : options.sourceName === undefined
53
+ ? { range: range }
54
+ : range === undefined
55
+ ? { source: options.sourceName }
56
+ : { source: options.sourceName, range };
57
+ return createDiagnostic(code, severity, message, {
58
+ ...(details.data === undefined ? {} : { data: details.data }),
59
+ ...(location === undefined ? {} : { location }),
60
+ ...(details.suggestion === undefined
61
+ ? {}
62
+ : { suggestion: details.suggestion }),
63
+ ...(details.fix === undefined ? {} : { fix: details.fix }),
64
+ ...(details.relatedLocations === undefined
65
+ ? {}
66
+ : { relatedLocations: details.relatedLocations }),
67
+ });
68
+ }
69
+ function emptyMetadata() {
70
+ return { authors: [], extensions: {} };
71
+ }
72
+ function asJsonValue(value) {
73
+ if (value === null || typeof value === "string" || typeof value === "boolean") {
74
+ return value;
75
+ }
76
+ if (typeof value === "number") {
77
+ return Number.isFinite(value) ? value : undefined;
78
+ }
79
+ if (Array.isArray(value)) {
80
+ const converted = value.map(asJsonValue);
81
+ return converted.every((item) => item !== undefined)
82
+ ? converted
83
+ : undefined;
84
+ }
85
+ if (typeof value === "object") {
86
+ const converted = {};
87
+ for (const [key, item] of Object.entries(value)) {
88
+ const jsonItem = asJsonValue(item);
89
+ if (jsonItem === undefined)
90
+ return undefined;
91
+ converted[key] = jsonItem;
92
+ }
93
+ return converted;
94
+ }
95
+ return undefined;
96
+ }
97
+ function parseAuthors(value, options, line, diagnostics) {
98
+ if (typeof value === "string")
99
+ return [value];
100
+ if (Array.isArray(value) && value.every((author) => typeof author === "string")) {
101
+ return value;
102
+ }
103
+ diagnostics.push(diagnostic("azeforge.metadata#invalid-author", 'Front matter "author" must be a string or an array of strings.', options, line));
104
+ return [];
105
+ }
106
+ function parseOutputs(value, options, line, diagnostics) {
107
+ if (!Array.isArray(value) || !value.every((item) => typeof item === "string")) {
108
+ diagnostics.push(diagnostic("azeforge.metadata#invalid-outputs", 'Front matter "outputs" must be an array of Artifact formats.', options, line));
109
+ return undefined;
110
+ }
111
+ const formats = value.filter((item) => OUTPUT_FORMATS[item] === true);
112
+ if (formats.length !== value.length || new Set(formats).size !== formats.length) {
113
+ diagnostics.push(diagnostic("azeforge.metadata#invalid-outputs", 'Front matter "outputs" must contain unique html, svg, png, or pdf values.', options, line));
114
+ return undefined;
115
+ }
116
+ return formats;
117
+ }
118
+ function parseFrontMatter(lines, options) {
119
+ const firstLine = lines[0];
120
+ if (firstLine === undefined) {
121
+ return {
122
+ metadata: emptyMetadata(),
123
+ diagnostics: [],
124
+ bodyStart: 0,
125
+ versionDeclared: false,
126
+ };
127
+ }
128
+ const openingText = firstLine.text.startsWith("\uFEFF")
129
+ ? firstLine.text.slice(1)
130
+ : firstLine.text;
131
+ if (openingText !== "---") {
132
+ return {
133
+ metadata: emptyMetadata(),
134
+ diagnostics: [],
135
+ bodyStart: 0,
136
+ versionDeclared: false,
137
+ };
138
+ }
139
+ const closingIndex = lines.findIndex((line, index) => index > 0 && line.text === "---");
140
+ if (closingIndex < 0) {
141
+ return {
142
+ metadata: emptyMetadata(),
143
+ diagnostics: [
144
+ diagnostic("azeforge.metadata#unclosed", "Front matter must end with a --- delimiter.", options, firstLine),
145
+ ],
146
+ bodyStart: Math.max(1, lines.findIndex((line, index) => index > 0 && /^[ \t]*$/.test(line.text)) + 1),
147
+ versionDeclared: false,
148
+ };
149
+ }
150
+ const yamlSource = lines
151
+ .slice(1, closingIndex)
152
+ .map((line) => line.text)
153
+ .join("\n");
154
+ const parsed = parseDocument(yamlSource, {
155
+ prettyErrors: false,
156
+ strict: true,
157
+ uniqueKeys: true,
158
+ });
159
+ const diagnostics = parsed.errors.map(() => diagnostic("azeforge.metadata#invalid-yaml", "Front matter contains invalid YAML.", options, lines[1]));
160
+ visit(parsed, {
161
+ Alias() {
162
+ diagnostics.push(diagnostic("azeforge.metadata#alias-disabled", "Front matter aliases are not supported.", options, lines[1]));
163
+ },
164
+ Pair(_key, pair) {
165
+ if (isScalar(pair.key) &&
166
+ pair.key.value === "<<" &&
167
+ pair.key.type === "PLAIN") {
168
+ diagnostics.push(diagnostic("azeforge.metadata#merge-key-disabled", "Front matter merge keys are not supported.", options, lines[1]));
169
+ }
170
+ },
171
+ Node(_key, node) {
172
+ if (!isAlias(node) && node.anchor !== undefined) {
173
+ diagnostics.push(diagnostic("azeforge.metadata#anchor-disabled", "Front matter anchors are not supported.", options, lines[1]));
174
+ }
175
+ if (node.tag !== undefined) {
176
+ diagnostics.push(diagnostic("azeforge.metadata#tag-disabled", "Front matter tags are not supported.", options, lines[1]));
177
+ }
178
+ },
179
+ });
180
+ let value;
181
+ try {
182
+ value = parsed.toJS({ maxAliasCount: 0 });
183
+ }
184
+ catch {
185
+ diagnostics.push(diagnostic("azeforge.metadata#invalid-yaml", "Front matter contains invalid YAML.", options, lines[1]));
186
+ }
187
+ if (value === null || typeof value !== "object" || Array.isArray(value)) {
188
+ diagnostics.push(diagnostic("azeforge.metadata#mapping-required", "Front matter must be a mapping.", options, lines[1] ?? firstLine));
189
+ return {
190
+ metadata: emptyMetadata(),
191
+ diagnostics,
192
+ bodyStart: closingIndex + 1,
193
+ versionDeclared: false,
194
+ };
195
+ }
196
+ const record = value;
197
+ const metadata = { authors: [], extensions: {} };
198
+ const metadataLine = lines[1] ?? firstLine;
199
+ if (record.azemark !== undefined && record.azemark !== 1) {
200
+ diagnostics.push(diagnostic("azeforge.source#version-unsupported", 'Front matter "azemark" must be the integer 1.', options, metadataLine));
201
+ }
202
+ if (record.author !== undefined) {
203
+ metadata.authors = parseAuthors(record.author, options, metadataLine, diagnostics);
204
+ }
205
+ for (const key of ["title", "theme"]) {
206
+ const item = record[key];
207
+ if (item === undefined)
208
+ continue;
209
+ if (typeof item !== "string") {
210
+ diagnostics.push(diagnostic(`azeforge.metadata#invalid-${key}`, `Front matter "${key}" must be a string.`, options, metadataLine));
211
+ }
212
+ else {
213
+ metadata[key] = item;
214
+ }
215
+ }
216
+ if (record.outputs !== undefined) {
217
+ const outputs = parseOutputs(record.outputs, options, metadataLine, diagnostics);
218
+ if (outputs !== undefined)
219
+ metadata.outputs = outputs;
220
+ }
221
+ for (const [key, item] of Object.entries(record)) {
222
+ if (KNOWN_METADATA_KEYS[key] === true)
223
+ continue;
224
+ if (!EXTENSION_KEY.test(key)) {
225
+ const malformedExtension = key.startsWith("x-");
226
+ diagnostics.push(diagnostic(malformedExtension
227
+ ? "azeforge.metadata#malformed-extension-key"
228
+ : "azeforge.metadata#unknown-key", malformedExtension
229
+ ? `Malformed front matter extension key "${key}".`
230
+ : `Unknown front matter key "${key}".`, options, metadataLine, malformedExtension ? "error" : "warning", rangeFromLines(metadataLine, metadataLine), {
231
+ data: { key },
232
+ ...(malformedExtension
233
+ ? {}
234
+ : { suggestion: `Did you mean "${nearestMetadataKey(key)}"?` }),
235
+ }));
236
+ continue;
237
+ }
238
+ const extension = asJsonValue(item);
239
+ if (extension === undefined) {
240
+ diagnostics.push(diagnostic("azeforge.metadata#invalid-extension", `Front matter extension "${key}" must contain JSON-compatible values.`, options, metadataLine));
241
+ }
242
+ else {
243
+ metadata.extensions[key] = extension;
244
+ }
245
+ }
246
+ return {
247
+ metadata,
248
+ diagnostics,
249
+ bodyStart: closingIndex + 1,
250
+ versionDeclared: Object.prototype.hasOwnProperty.call(record, "azemark"),
251
+ };
252
+ }
253
+ function lineTextStartIndex(line) {
254
+ return line.number === 1 && line.text.startsWith("\uFEFF") ? 1 : 0;
255
+ }
256
+ function lineText(line) {
257
+ return line.text.slice(lineTextStartIndex(line));
258
+ }
259
+ function parseAtxHeading(text) {
260
+ const match = /^ {0,3}(#{1,6})(?:[ \t]+(.*)|[ \t]*)$/.exec(text);
261
+ const marker = match?.[1];
262
+ if (marker === undefined)
263
+ return undefined;
264
+ const rawHeadingText = (match?.[2] ?? "").replace(/[ \t]+$/, "");
265
+ const headingText = /^#+$/.test(rawHeadingText)
266
+ ? ""
267
+ : rawHeadingText.replace(/[ \t]+#+$/, "");
268
+ return {
269
+ level: marker.length,
270
+ text: headingText,
271
+ };
272
+ }
273
+ function findRawHtml(lines) {
274
+ const rawHtmlStart = /^(?:<!--|<![A-Za-z]|<\?|<\/?[A-Za-z][A-Za-z0-9-]*(?=[\s/>]))/;
275
+ for (const line of lines) {
276
+ const text = lineText(line);
277
+ if (/^ {0,3}(?:`{3,}|~{3,})/.test(text))
278
+ continue;
279
+ for (let index = 0; index < text.length; index += 1) {
280
+ if (text[index] === "\\") {
281
+ index += 1;
282
+ continue;
283
+ }
284
+ if (text[index] === "`") {
285
+ let delimiterLength = 1;
286
+ while (text[index + delimiterLength] === "`")
287
+ delimiterLength += 1;
288
+ const delimiter = "`".repeat(delimiterLength);
289
+ const closingIndex = text.indexOf(delimiter, index + delimiterLength);
290
+ if (closingIndex >= 0) {
291
+ index = closingIndex + delimiterLength - 1;
292
+ }
293
+ else {
294
+ index += delimiterLength - 1;
295
+ }
296
+ continue;
297
+ }
298
+ if (text[index] !== "<")
299
+ continue;
300
+ const match = rawHtmlStart.exec(text.slice(index));
301
+ if (match === null)
302
+ continue;
303
+ const closeIndex = text.indexOf(">", index + match[0].length);
304
+ const contentStartIndex = lineTextStartIndex(line);
305
+ return {
306
+ line,
307
+ startIndex: contentStartIndex + index,
308
+ endIndex: contentStartIndex +
309
+ (closeIndex < 0 ? index + match[0].length : closeIndex + 1),
310
+ };
311
+ }
312
+ }
313
+ return undefined;
314
+ }
315
+ function rawHtmlInvalidBlock(source, lines, match, options, diagnostics) {
316
+ const first = lines[0] ?? match.line;
317
+ const last = lines.at(-1) ?? match.line;
318
+ const diagnosticIndex = diagnostics.length;
319
+ diagnostics.push(diagnostic("azeforge.security#raw-html-disabled", "Raw HTML is disabled in AzeMark Source.", options, match.line, "error", rangeFromLineSlice(match.line, match.startIndex, match.endIndex)));
320
+ return {
321
+ kind: "invalid",
322
+ raw: source.slice(first.startIndex, last.endIndex),
323
+ range: rangeFromLines(first, last),
324
+ diagnosticIndexes: [diagnosticIndex],
325
+ };
326
+ }
327
+ function damerauDistance(left, right) {
328
+ const a = [...left];
329
+ const b = [...right];
330
+ const m = a.length;
331
+ const n = b.length;
332
+ const INF = m + n;
333
+ const d = Array.from({ length: m + 2 }, () => new Array(n + 2).fill(0));
334
+ const first = d[0];
335
+ const second = d[1];
336
+ if (first !== undefined && second !== undefined) {
337
+ first[0] = INF;
338
+ second[0] = INF;
339
+ }
340
+ for (let i = 0; i <= m; i += 1) {
341
+ const row = d[i + 1];
342
+ const next = d[i + 2];
343
+ if (row !== undefined) {
344
+ row[0] = INF;
345
+ row[1] = i;
346
+ }
347
+ if (next !== undefined)
348
+ next[0] = INF;
349
+ }
350
+ for (let j = 0; j <= n; j += 1) {
351
+ const row0 = d[0];
352
+ const row1 = d[1];
353
+ if (row0 !== undefined)
354
+ row0[j + 1] = INF;
355
+ if (row1 !== undefined)
356
+ row1[j + 1] = j;
357
+ }
358
+ const lastRow = new Map();
359
+ for (let i = 1; i <= m; i += 1) {
360
+ let db = 0;
361
+ for (let j = 1; j <= n; j += 1) {
362
+ const i1 = lastRow.get(b[j - 1] ?? "") ?? 0;
363
+ const j1 = db;
364
+ let cost = 1;
365
+ if ((a[i - 1] ?? "") === (b[j - 1] ?? "")) {
366
+ cost = 0;
367
+ db = j;
368
+ }
369
+ const substitution = (d[i]?.[j] ?? 0) + cost;
370
+ const insertion = (d[i + 1]?.[j] ?? 0) + 1;
371
+ const deletion = (d[i]?.[j + 1] ?? 0) + 1;
372
+ const transposition = (d[i1]?.[j1] ?? INF) + (i - i1 - 1) + 1 + (j - j1 - 1);
373
+ const cell = d[i + 1]?.[j + 1];
374
+ if (cell !== undefined) {
375
+ const row = d[i + 1];
376
+ if (row !== undefined) {
377
+ row[j + 1] = Math.min(substitution, insertion, deletion, transposition);
378
+ }
379
+ }
380
+ }
381
+ lastRow.set(a[i - 1] ?? "", i);
382
+ }
383
+ return (d[m + 1]?.[n + 1] ?? INF);
384
+ }
385
+ function unknownDirectiveCandidates(type, availableTypes) {
386
+ const typeLength = [...type].length;
387
+ return availableTypes
388
+ .map((candidate) => {
389
+ const absolute = damerauDistance(type, candidate);
390
+ const normalized = absolute / Math.max(typeLength, [...candidate].length, 1);
391
+ return { candidate, absolute, normalized };
392
+ })
393
+ .filter(({ absolute, normalized }) => absolute <= 3 && normalized <= 0.34)
394
+ .sort((x, y) => x.normalized === y.normalized
395
+ ? x.absolute === y.absolute
396
+ ? x.candidate < y.candidate
397
+ ? -1
398
+ : 1
399
+ : x.absolute - y.absolute
400
+ : x.normalized - y.normalized)
401
+ .map(({ candidate }) => candidate);
402
+ }
403
+ function parseEquationEnvelope(source, lines, openIndex, closingIndex, first, last, options, allowRawLatex, diagnostics) {
404
+ const blockRange = rangeFromLines(first, last);
405
+ const raw = source.slice(first.startIndex, last.endIndex);
406
+ const startIndex = diagnostics.length;
407
+ const finishInvalid = () => ({
408
+ kind: "invalid",
409
+ raw,
410
+ range: blockRange,
411
+ diagnosticIndexes: Array.from({ length: diagnostics.length - startIndex }, (_, offset) => startIndex + offset),
412
+ originalType: EQUATION_PLUGIN_TYPE,
413
+ });
414
+ const entries = [];
415
+ let cursor = openIndex + 1;
416
+ while (cursor < closingIndex) {
417
+ const header = lines[cursor];
418
+ if (header === undefined)
419
+ break;
420
+ if (/^[ \t]*$/.test(lineText(header))) {
421
+ cursor += 1;
422
+ continue;
423
+ }
424
+ const match = /^[ \t]*([A-Za-z][A-Za-z0-9-]*)[ \t]*:(.*)$/.exec(lineText(header));
425
+ if (match === null)
426
+ break;
427
+ const key = match[1] ?? "";
428
+ const rawValue = match[2] ?? "";
429
+ const value = rawValue.trim();
430
+ const colonIndex = header.text.indexOf(":");
431
+ const valueStart = colonIndex + 1 + (rawValue.length - rawValue.trimStart().length);
432
+ entries.push({
433
+ key,
434
+ value,
435
+ range: rangeFromLineSlice(header, valueStart, valueStart + value.length),
436
+ });
437
+ cursor += 1;
438
+ }
439
+ while (cursor < closingIndex) {
440
+ const blank = lines[cursor];
441
+ if (blank !== undefined && !/^[ \t]*$/.test(lineText(blank)))
442
+ break;
443
+ cursor += 1;
444
+ }
445
+ const bodyLines = lines.slice(cursor, closingIndex);
446
+ const body = bodyLines.map((entry) => lineText(entry)).join("\n");
447
+ const bodyRanges = bodyLines.map((entry) => rangeFromLines(entry, entry));
448
+ const syntaxRange = entries.find((entry) => entry.key === "syntax")?.range;
449
+ const header = parseEquationHeader(entries, blockRange, options.sourceName);
450
+ if (header.diagnostics.length > 0) {
451
+ diagnostics.push(...header.diagnostics);
452
+ return finishInvalid();
453
+ }
454
+ const validated = validateEquationBody({
455
+ header,
456
+ body,
457
+ bodyRanges,
458
+ blockRange,
459
+ sourceName: options.sourceName,
460
+ allowRawLatex,
461
+ ...(syntaxRange === undefined ? {} : { syntaxRange }),
462
+ });
463
+ if (validated.block !== undefined)
464
+ return validated.block;
465
+ diagnostics.push(...validated.diagnostics);
466
+ return finishInvalid();
467
+ }
468
+ export const MAX_NESTING_DEPTH = 8;
469
+ function inlineSourceFromLines(lines) {
470
+ let out = "";
471
+ let hardBreak = false;
472
+ for (let index = 0; index < lines.length; index += 1) {
473
+ const raw = lineText(lines[index]);
474
+ const last = index === lines.length - 1;
475
+ let stripped = raw;
476
+ let hard = false;
477
+ if (!last) {
478
+ if (/\\[ \t]*$/.test(raw)) {
479
+ hard = true;
480
+ stripped = raw.replace(/\\[ \t]*$/, "");
481
+ }
482
+ else if (/[ \t]{2,}$/.test(raw)) {
483
+ hard = true;
484
+ stripped = raw.replace(/[ \t]+$/, "");
485
+ }
486
+ }
487
+ stripped = stripped.replace(/^[ \t]+|[ \t]+$/g, "");
488
+ out += (index === 0 ? "" : hardBreak ? "\n" : " ") + stripped;
489
+ hardBreak = hard;
490
+ }
491
+ return out;
492
+ }
493
+ function parseInlineNodes(text, first, last, options, diagnostics) {
494
+ const parsed = parseInlineFragment(text);
495
+ if (parsed.unsafeTargets.length > 0) {
496
+ const target = parsed.unsafeTargets[0] ?? "";
497
+ diagnostics.push(diagnostic("azeforge.link#unsafe-protocol", `Link target "${target}" uses a disallowed protocol.`, options, first, "error", rangeFromLines(first, last), {
498
+ data: { href: target },
499
+ suggestion: "Use an https:, http:, mailto:, or #fragment link.",
500
+ }));
501
+ return undefined;
502
+ }
503
+ return [...parsed.nodes];
504
+ }
505
+ function ensureInlineTargetsSafe(texts, first, last, options, diagnostics) {
506
+ for (const text of texts) {
507
+ const parsed = parseInlineFragment(text);
508
+ if (parsed.unsafeTargets.length > 0) {
509
+ const target = parsed.unsafeTargets[0] ?? "";
510
+ diagnostics.push(diagnostic("azeforge.link#unsafe-protocol", `Link target "${target}" uses a disallowed protocol.`, options, first, "error", rangeFromLines(first, last), {
511
+ data: { href: target },
512
+ suggestion: "Use an https:, http:, mailto:, or #fragment link.",
513
+ }));
514
+ return false;
515
+ }
516
+ }
517
+ return true;
518
+ }
519
+ function invalidBlockFor(source, first, last, startIndex, diagnostics, originalType) {
520
+ return {
521
+ kind: "invalid",
522
+ raw: source.slice(first.startIndex, last.endIndex),
523
+ range: rangeFromLines(first, last),
524
+ diagnosticIndexes: Array.from({ length: diagnostics.length - startIndex }, (_, offset) => startIndex + offset),
525
+ ...(originalType === undefined ? {} : { originalType }),
526
+ };
527
+ }
528
+ function isThematicBreakText(text) {
529
+ return /^ {0,3}(?:(?:\*[ \t]*){3,}|(?:-[ \t]*){3,}|(?:_[ \t]*){3,})$/.test(text);
530
+ }
531
+ function fenceOpen(text) {
532
+ const match = /^ {0,3}(`{3,}|~{3,})(.*)$/.exec(text);
533
+ if (match === null)
534
+ return undefined;
535
+ const fence = match[1] ?? "";
536
+ const char = fence[0] === "~" ? "~" : "`";
537
+ const info = (match[2] ?? "").trim();
538
+ if (char === "`" && info.includes("`"))
539
+ return undefined;
540
+ return { char, length: fence.length, info };
541
+ }
542
+ function isFenceClose(text, char, length) {
543
+ const match = /^ {0,3}(`+|~+)[ \t]*$/.exec(text);
544
+ if (match === null)
545
+ return false;
546
+ const run = match[1] ?? "";
547
+ return run[0] === char && run.length >= length;
548
+ }
549
+ function parseFencedCode(source, lines, index, options, diagnostics) {
550
+ const open = lines[index];
551
+ if (open === undefined)
552
+ return undefined;
553
+ const fence = fenceOpen(lineText(open));
554
+ if (fence === undefined)
555
+ return undefined;
556
+ let closing = -1;
557
+ for (let cursor = index + 1; cursor < lines.length; cursor += 1) {
558
+ const candidate = lines[cursor];
559
+ if (candidate !== undefined && isFenceClose(lineText(candidate), fence.char, fence.length)) {
560
+ closing = cursor;
561
+ break;
562
+ }
563
+ }
564
+ if (closing < 0) {
565
+ const startIndex = diagnostics.length;
566
+ const last = lines.at(-1) ?? open;
567
+ diagnostics.push(diagnostic("azeforge.source#unclosed-fence", "A fenced code Block must end with a matching closing fence.", options, open, "error", rangeFromLines(open, open), { suggestion: "Close the fenced code Block with a matching fence." }));
568
+ return {
569
+ block: invalidBlockFor(source, open, last, startIndex, diagnostics),
570
+ next: lines.length,
571
+ };
572
+ }
573
+ const last = lines[closing] ?? open;
574
+ const value = lines
575
+ .slice(index + 1, closing)
576
+ .map((line) => lineText(line))
577
+ .join("\n");
578
+ const language = fence.info.split(/[ \t]+/, 1)[0] ?? "";
579
+ return {
580
+ block: {
581
+ kind: "code",
582
+ value,
583
+ range: rangeFromLines(open, last),
584
+ ...(language === "" ? {} : { language }),
585
+ },
586
+ next: closing + 1,
587
+ };
588
+ }
589
+ function stripQuotePrefix(text) {
590
+ const match = /^ {0,3}>[ \t]?/.exec(text);
591
+ if (match === null)
592
+ return undefined;
593
+ return text.slice(match[0].length);
594
+ }
595
+ function syntheticLine(original, text, prefixChars) {
596
+ return {
597
+ number: original.number,
598
+ text,
599
+ startIndex: original.startIndex + prefixChars,
600
+ endIndex: original.endIndex,
601
+ startOffset: original.startOffset + Buffer.byteLength(original.text.slice(0, prefixChars), "utf8"),
602
+ endOffset: original.endOffset,
603
+ };
604
+ }
605
+ function parseNestedBlocks(source, nested, first, last, options, diagnostics, activeTypes, allowRawLatex, depth) {
606
+ if (depth + 1 > MAX_NESTING_DEPTH) {
607
+ diagnostics.push(diagnostic("azeforge.source#nesting-too-deep", "Nested content exceeds the maximum supported depth.", options, first, "error", rangeFromLines(first, last)));
608
+ return undefined;
609
+ }
610
+ return parseBlocks(source, nested, 0, options, diagnostics, activeTypes, allowRawLatex, depth + 1);
611
+ }
612
+ function parseBlockquote(source, lines, index, options, diagnostics, activeTypes, allowRawLatex, depth) {
613
+ const first = lines[index];
614
+ if (first === undefined || stripQuotePrefix(lineText(first)) === undefined)
615
+ return undefined;
616
+ const consumed = [];
617
+ const inner = [];
618
+ let cursor = index;
619
+ while (cursor < lines.length) {
620
+ const line = lines[cursor];
621
+ if (line === undefined)
622
+ break;
623
+ const text = lineText(line);
624
+ if (/^[ \t]*$/.test(text)) {
625
+ consumed.push(line);
626
+ inner.push(syntheticLine(line, "", 0));
627
+ cursor += 1;
628
+ continue;
629
+ }
630
+ const stripped = stripQuotePrefix(text);
631
+ if (stripped === undefined)
632
+ break;
633
+ consumed.push(line);
634
+ const prefixChars = text.length - stripped.length;
635
+ inner.push(syntheticLine(line, stripped, prefixChars));
636
+ cursor += 1;
637
+ }
638
+ while (inner.length > 0 && /^[ \t]*$/.test(inner[inner.length - 1]?.text ?? "")) {
639
+ inner.pop();
640
+ consumed.pop();
641
+ cursor -= 1;
642
+ }
643
+ const last = consumed.at(-1) ?? first;
644
+ const startIndex = diagnostics.length;
645
+ const children = parseNestedBlocks(source, inner, first, last, options, diagnostics, activeTypes, allowRawLatex, depth);
646
+ if (children === undefined) {
647
+ return {
648
+ block: invalidBlockFor(source, first, last, startIndex, diagnostics),
649
+ next: cursor,
650
+ };
651
+ }
652
+ return {
653
+ block: { kind: "blockquote", children, range: rangeFromLines(first, last) },
654
+ next: cursor,
655
+ };
656
+ }
657
+ function parseListMarker(text) {
658
+ const unordered = /^([ \t]*)([*+-])(?:[ \t]+|$)/.exec(text);
659
+ if (unordered !== null) {
660
+ const lead = (unordered[1] ?? "").replaceAll("\t", " ").length;
661
+ const markerEnd = (unordered[1] ?? "").length + 1 + (/^[ \t]/.test(text.slice((unordered[1] ?? "").length + 1)) ? 1 : 0);
662
+ if (lead > 3 && markerEnd <= 0)
663
+ return undefined;
664
+ return { ordered: false, lead, markerEnd };
665
+ }
666
+ const ordered = /^([ \t]*)(\d{1,9})([.)])(?:[ \t]+|$)/.exec(text);
667
+ if (ordered !== null) {
668
+ const lead = (ordered[1] ?? "").replaceAll("\t", " ").length;
669
+ const digits = ordered[2] ?? "";
670
+ const markerEnd = (ordered[1] ?? "").length + digits.length + 1 + (/^[ \t]/.test(text.slice((ordered[1] ?? "").length + digits.length + 1)) ? 1 : 0);
671
+ if (lead > 3)
672
+ return undefined;
673
+ return { ordered: true, lead, markerEnd, start: Number.parseInt(digits, 10) };
674
+ }
675
+ return undefined;
676
+ }
677
+ function leadingSpaces(text) {
678
+ const match = /^[ \t]*/.exec(text);
679
+ return (match?.[0] ?? "").length;
680
+ }
681
+ function parseList(source, lines, index, options, diagnostics, activeTypes, allowRawLatex, depth) {
682
+ const first = lines[index];
683
+ if (first === undefined)
684
+ return undefined;
685
+ const opener = parseListMarker(lineText(first));
686
+ if (opener === undefined || opener.lead > 3)
687
+ return undefined;
688
+ const items = [];
689
+ const itemLineRuns = [];
690
+ const itemOriginals = [];
691
+ let cursor = index;
692
+ let contentIndent = opener.markerEnd;
693
+ let failed = false;
694
+ while (cursor < lines.length) {
695
+ const line = lines[cursor];
696
+ if (line === undefined)
697
+ break;
698
+ const text = lineText(line);
699
+ if (/^[ \t]*$/.test(text)) {
700
+ if (itemLineRuns.length === 0)
701
+ break;
702
+ itemLineRuns[itemLineRuns.length - 1]?.push(syntheticLine(line, "", 0));
703
+ itemOriginals[itemOriginals.length - 1]?.push(line);
704
+ cursor += 1;
705
+ continue;
706
+ }
707
+ const marker = parseListMarker(text);
708
+ if (marker !== undefined &&
709
+ marker.ordered === opener.ordered &&
710
+ marker.lead < contentIndent &&
711
+ itemLineRuns.length > 0) {
712
+ const innerFirst = text.slice(marker.markerEnd);
713
+ const prefixChars = text.length - innerFirst.length;
714
+ itemLineRuns.push([syntheticLine(line, innerFirst, prefixChars)]);
715
+ itemOriginals.push([line]);
716
+ contentIndent = marker.markerEnd;
717
+ cursor += 1;
718
+ continue;
719
+ }
720
+ if (itemLineRuns.length === 0) {
721
+ const innerFirst = text.slice(opener.markerEnd);
722
+ const prefixChars = text.length - innerFirst.length;
723
+ itemLineRuns.push([syntheticLine(line, innerFirst, prefixChars)]);
724
+ itemOriginals.push([line]);
725
+ cursor += 1;
726
+ continue;
727
+ }
728
+ if (leadingSpaces(text) >= contentIndent) {
729
+ itemLineRuns[itemLineRuns.length - 1]?.push(syntheticLine(line, text.slice(contentIndent), contentIndent));
730
+ itemOriginals[itemOriginals.length - 1]?.push(line);
731
+ cursor += 1;
732
+ continue;
733
+ }
734
+ break;
735
+ }
736
+ const last = lines[cursor - 1] ?? first;
737
+ const startIndex = diagnostics.length;
738
+ for (let itemIndex = 0; itemIndex < itemLineRuns.length; itemIndex += 1) {
739
+ const run = (itemLineRuns[itemIndex] ?? []).filter((line, position, all) => {
740
+ if (position < all.length - 1)
741
+ return true;
742
+ return !/^[ \t]*$/.test(line.text);
743
+ });
744
+ const originals = itemOriginals[itemIndex] ?? [];
745
+ const itemFirst = originals[0] ?? first;
746
+ const itemLast = originals.at(-1) ?? itemFirst;
747
+ const children = parseNestedBlocks(source, run, itemFirst, itemLast, options, diagnostics, activeTypes, allowRawLatex, depth);
748
+ if (children === undefined) {
749
+ failed = true;
750
+ break;
751
+ }
752
+ items.push({ blocks: children, range: rangeFromLines(itemFirst, itemLast) });
753
+ }
754
+ if (failed || items.length === 0) {
755
+ return {
756
+ block: invalidBlockFor(source, first, last, startIndex, diagnostics),
757
+ next: cursor,
758
+ };
759
+ }
760
+ return {
761
+ block: {
762
+ kind: "list",
763
+ ordered: opener.ordered,
764
+ items,
765
+ range: rangeFromLines(first, last),
766
+ ...(opener.ordered && opener.start !== undefined && opener.start !== 1 ? { start: opener.start } : {}),
767
+ },
768
+ next: cursor,
769
+ };
770
+ }
771
+ function tryGfmTableAt(lines, index) {
772
+ const headerLine = lines[index];
773
+ const delimiterLine = lines[index + 1];
774
+ if (headerLine === undefined || delimiterLine === undefined)
775
+ return undefined;
776
+ if (!isGfmTableStart(lineText(headerLine), lineText(delimiterLine)))
777
+ return undefined;
778
+ const texts = [];
779
+ let cursor = index;
780
+ while (cursor < lines.length) {
781
+ const line = lines[cursor];
782
+ if (line === undefined)
783
+ break;
784
+ const text = lineText(line);
785
+ if (/^[ \t]*$/.test(text))
786
+ break;
787
+ if (cursor > index + 1 && !text.includes("|"))
788
+ break;
789
+ texts.push(text);
790
+ cursor += 1;
791
+ }
792
+ const parsed = tryParseGfmTable(texts);
793
+ if (parsed === undefined)
794
+ return undefined;
795
+ return { data: parsed.data, consumed: parsed.consumed };
796
+ }
797
+ function splitHeaderEntries(lines, openIndex, closingIndex) {
798
+ const entries = [];
799
+ let cursor = openIndex + 1;
800
+ while (cursor < closingIndex) {
801
+ const header = lines[cursor];
802
+ if (header === undefined)
803
+ break;
804
+ if (/^[ \t]*$/.test(lineText(header))) {
805
+ cursor += 1;
806
+ continue;
807
+ }
808
+ const match = /^[ \t]*([A-Za-z][A-Za-z0-9-]*)[ \t]*:(.*)$/.exec(lineText(header));
809
+ if (match === null)
810
+ break;
811
+ const key = match[1] ?? "";
812
+ const rawValue = match[2] ?? "";
813
+ const value = rawValue.trim();
814
+ const colonIndex = header.text.indexOf(":");
815
+ const valueStart = colonIndex + 1 + (rawValue.length - rawValue.trimStart().length);
816
+ entries.push({
817
+ key,
818
+ value,
819
+ range: rangeFromLineSlice(header, valueStart, valueStart + value.length),
820
+ });
821
+ cursor += 1;
822
+ }
823
+ while (cursor < closingIndex) {
824
+ const blank = lines[cursor];
825
+ if (blank !== undefined && !/^[ \t]*$/.test(lineText(blank)))
826
+ break;
827
+ cursor += 1;
828
+ }
829
+ return { entries, bodyStart: cursor };
830
+ }
831
+ function parseMermaidEnvelope(source, lines, openIndex, closingIndex, first, last, options, diagnostics) {
832
+ const blockRange = rangeFromLines(first, last);
833
+ const startIndex = diagnostics.length;
834
+ const finishInvalid = () => invalidBlockFor(source, first, last, startIndex, diagnostics, MERMAID_PLUGIN_TYPE);
835
+ const { entries, bodyStart } = splitHeaderEntries(lines, openIndex, closingIndex);
836
+ const header = parseMermaidHeader(entries, blockRange, options.sourceName);
837
+ if (header.diagnostics.length > 0) {
838
+ diagnostics.push(...header.diagnostics);
839
+ return finishInvalid();
840
+ }
841
+ const bodyLines = lines.slice(bodyStart, closingIndex);
842
+ const validated = validateMermaidBody({
843
+ header,
844
+ body: bodyLines.map((line) => lineText(line)).join("\n"),
845
+ bodyRanges: bodyLines.map((line) => rangeFromLines(line, line)),
846
+ blockRange,
847
+ sourceName: options.sourceName,
848
+ });
849
+ if (validated.block !== undefined)
850
+ return validated.block;
851
+ diagnostics.push(...validated.diagnostics);
852
+ return finishInvalid();
853
+ }
854
+ function parseCalloutEnvelope(source, lines, openIndex, closingIndex, first, last, options, diagnostics, activeTypes, allowRawLatex, depth) {
855
+ const blockRange = rangeFromLines(first, last);
856
+ const startIndex = diagnostics.length;
857
+ const finishInvalid = () => invalidBlockFor(source, first, last, startIndex, diagnostics, CALLOUT_PLUGIN_TYPE);
858
+ const { entries, bodyStart } = splitHeaderEntries(lines, openIndex, closingIndex);
859
+ let variant = "note";
860
+ let title;
861
+ let id;
862
+ for (const entry of entries) {
863
+ if (entry.key === "variant") {
864
+ if (!CALLOUT_VARIANTS.includes(entry.value)) {
865
+ diagnostics.push(diagnostic("azeforge.callout#unknown-variant", `Callout variant "${entry.value}" is not supported.`, options, first, "error", entry.range, {
866
+ data: { variant: entry.value },
867
+ suggestion: `Use one of ${CALLOUT_VARIANTS.join(", ")}.`,
868
+ }));
869
+ return finishInvalid();
870
+ }
871
+ variant = entry.value;
872
+ }
873
+ else if (entry.key === "title") {
874
+ const nodes = parseInlineNodes(entry.value, first, last, options, diagnostics);
875
+ if (nodes === undefined)
876
+ return finishInvalid();
877
+ title = nodes;
878
+ }
879
+ else if (entry.key === "id") {
880
+ id = entry.value;
881
+ }
882
+ else {
883
+ diagnostics.push(diagnostic("azeforge.callout#unknown-header", `Callout header key "${entry.key}" is not supported.`, options, first, "error", entry.range, {
884
+ data: { key: entry.key },
885
+ suggestion: "Use variant, title, or id.",
886
+ }));
887
+ return finishInvalid();
888
+ }
889
+ }
890
+ const bodyLines = lines.slice(bodyStart, closingIndex).filter((line) => !/^[ \t]*$/.test(lineText(line)));
891
+ const children = bodyLines.length === 0
892
+ ? []
893
+ : parseNestedBlocks(source, bodyLines, first, last, options, diagnostics, activeTypes, allowRawLatex, depth);
894
+ if (children === undefined)
895
+ return finishInvalid();
896
+ const block = {
897
+ kind: "callout",
898
+ variant,
899
+ children,
900
+ range: blockRange,
901
+ ...(id === undefined || id === "" ? {} : { id }),
902
+ ...(title === undefined ? {} : { title }),
903
+ pluginVersion: CALLOUT_PLUGIN_VERSION,
904
+ };
905
+ return block;
906
+ }
907
+ function parseTableEnvelope(source, lines, openIndex, closingIndex, first, last, options, diagnostics) {
908
+ const blockRange = rangeFromLines(first, last);
909
+ const startIndex = diagnostics.length;
910
+ const finishInvalid = () => invalidBlockFor(source, first, last, startIndex, diagnostics, TABLE_PLUGIN_TYPE);
911
+ const { entries, bodyStart } = splitHeaderEntries(lines, openIndex, closingIndex);
912
+ let caption;
913
+ let id;
914
+ for (const entry of entries) {
915
+ if (entry.key === "caption") {
916
+ if (entry.value === "") {
917
+ diagnostics.push(diagnostic("azeforge.table#empty-caption", "Table caption must not be empty.", options, first, "error", entry.range));
918
+ return finishInvalid();
919
+ }
920
+ const nodes = parseInlineNodes(entry.value, first, last, options, diagnostics);
921
+ if (nodes === undefined)
922
+ return finishInvalid();
923
+ caption = nodes;
924
+ }
925
+ else if (entry.key === "id") {
926
+ id = entry.value;
927
+ }
928
+ else {
929
+ diagnostics.push(diagnostic("azeforge.table#unknown-header", `Table header key "${entry.key}" is not supported.`, options, first, "error", entry.range, { data: { key: entry.key }, suggestion: "Use caption or id." }));
930
+ return finishInvalid();
931
+ }
932
+ }
933
+ const bodyTexts = lines
934
+ .slice(bodyStart, closingIndex)
935
+ .map((line) => lineText(line))
936
+ .filter((text) => !/^[ \t]*$/.test(text));
937
+ const bodySourceLines = lines.slice(bodyStart, closingIndex).filter((line) => !/^[ \t]*$/.test(lineText(line)));
938
+ const bodyRawHtml = findRawHtml(bodySourceLines);
939
+ if (bodyRawHtml !== undefined) {
940
+ diagnostics.push(diagnostic("azeforge.security#raw-html-disabled", "Raw HTML is disabled in AzeMark Source.", options, bodyRawHtml.line, "error", rangeFromLineSlice(bodyRawHtml.line, bodyRawHtml.startIndex, bodyRawHtml.endIndex)));
941
+ return invalidBlockFor(source, first, last, startIndex, diagnostics, TABLE_PLUGIN_TYPE);
942
+ }
943
+ if (bodyTexts.length < 2) {
944
+ diagnostics.push(diagnostic("azeforge.table#body-must-be-table", "A table Block body must contain a GFM table with a header and delimiter row.", options, first, "error", blockRange, { suggestion: "Add a header row followed by a delimiter row such as `| --- |`." }));
945
+ return finishInvalid();
946
+ }
947
+ const parsed = tryParseGfmTable(bodyTexts);
948
+ if (parsed === undefined || parsed.consumed !== bodyTexts.length) {
949
+ diagnostics.push(diagnostic("azeforge.table#body-must-be-table", "A table Block body must contain only a GFM table.", options, first, "error", blockRange, { suggestion: "Keep only the GFM header, delimiter, and body rows in the table body." }));
950
+ return finishInvalid();
951
+ }
952
+ const safetyTexts = [];
953
+ const headerCells = splitTableRow(bodyTexts[0]);
954
+ const bodyCellRows = bodyTexts.slice(2).map((line) => splitTableRow(line));
955
+ if (headerCells !== undefined)
956
+ safetyTexts.push(...headerCells);
957
+ for (const row of bodyCellRows) {
958
+ if (row !== undefined)
959
+ safetyTexts.push(...row);
960
+ }
961
+ if (!ensureInlineTargetsSafe(safetyTexts, first, last, options, diagnostics)) {
962
+ return finishInvalid();
963
+ }
964
+ const block = {
965
+ kind: "table",
966
+ data: parsed.data,
967
+ range: blockRange,
968
+ ...(id === undefined || id === "" ? {} : { id }),
969
+ ...(caption === undefined ? {} : { caption }),
970
+ pluginVersion: TABLE_PLUGIN_VERSION,
971
+ };
972
+ return block;
973
+ }
974
+ function parseBlocks(source, lines, bodyStart, options, diagnostics, activeTypes, allowRawLatex, depth = 0) {
975
+ const blocks = [];
976
+ let index = bodyStart;
977
+ while (index < lines.length) {
978
+ const line = lines[index];
979
+ if (line === undefined || /^[ \t]*$/.test(lineText(line))) {
980
+ index += 1;
981
+ continue;
982
+ }
983
+ const directive = /^ {0,3}:{4,}[ \t]*([^ \t:]*)/.exec(lineText(line));
984
+ if (directive !== null) {
985
+ const first = line;
986
+ const openIndex = index;
987
+ const originalType = directive[1] === "" ? undefined : directive[1];
988
+ let closingIndex = -1;
989
+ {
990
+ let depth = 1;
991
+ for (let scan = openIndex + 1; scan < lines.length; scan += 1) {
992
+ const text = lineText(lines[scan]);
993
+ if (/^ {0,3}:{4,}[ \t]*$/.test(text)) {
994
+ depth -= 1;
995
+ if (depth === 0) {
996
+ closingIndex = scan;
997
+ break;
998
+ }
999
+ }
1000
+ else if (/^ {0,3}:{4,}[ \t]*[^ \t:]/.test(text)) {
1001
+ depth += 1;
1002
+ }
1003
+ }
1004
+ }
1005
+ const closed = closingIndex >= 0;
1006
+ let unambiguousEnd = false;
1007
+ if (closed) {
1008
+ index = closingIndex + 1;
1009
+ }
1010
+ else {
1011
+ const recoveryIndex = lines.findIndex((candidate, candidateIndex) => candidateIndex > index && /^[ \t]*$/.test(lineText(candidate)));
1012
+ unambiguousEnd =
1013
+ recoveryIndex < 0 ||
1014
+ lines
1015
+ .slice(recoveryIndex)
1016
+ .every((candidate) => /^[ \t]*$/.test(lineText(candidate)));
1017
+ index = recoveryIndex < 0 ? lines.length : recoveryIndex;
1018
+ }
1019
+ const last = closed
1020
+ ? (lines[closingIndex] ?? first)
1021
+ : (lines[index - 1] ?? first);
1022
+ if (closed &&
1023
+ originalType === EQUATION_PLUGIN_TYPE &&
1024
+ activeTypes.includes(EQUATION_PLUGIN_TYPE)) {
1025
+ blocks.push(parseEquationEnvelope(source, lines, openIndex, closingIndex, first, last, options, allowRawLatex, diagnostics));
1026
+ continue;
1027
+ }
1028
+ if (closed &&
1029
+ originalType === MERMAID_PLUGIN_TYPE &&
1030
+ activeTypes.includes(MERMAID_PLUGIN_TYPE)) {
1031
+ blocks.push(parseMermaidEnvelope(source, lines, openIndex, closingIndex, first, last, options, diagnostics));
1032
+ continue;
1033
+ }
1034
+ if (closed &&
1035
+ originalType === CALLOUT_PLUGIN_TYPE &&
1036
+ activeTypes.includes(CALLOUT_PLUGIN_TYPE)) {
1037
+ blocks.push(parseCalloutEnvelope(source, lines, openIndex, closingIndex, first, last, options, diagnostics, activeTypes, allowRawLatex, depth));
1038
+ continue;
1039
+ }
1040
+ if (closed &&
1041
+ originalType === TABLE_PLUGIN_TYPE &&
1042
+ activeTypes.includes(TABLE_PLUGIN_TYPE)) {
1043
+ blocks.push(parseTableEnvelope(source, lines, openIndex, closingIndex, first, last, options, diagnostics));
1044
+ continue;
1045
+ }
1046
+ const diagnosticIndex = diagnostics.length;
1047
+ const typeStartIndex = originalType === undefined
1048
+ ? lineTextStartIndex(first)
1049
+ : lineTextStartIndex(first) + lineText(first).indexOf(originalType);
1050
+ const insertionLine = lines.at(-1) ?? last;
1051
+ const insertionRange = rangeFromLineSlice(insertionLine, insertionLine.text.length, insertionLine.text.length);
1052
+ const newline = source.includes("\r\n") ? "\r\n" : "\n";
1053
+ diagnostics.push(diagnostic(closed
1054
+ ? originalType === undefined
1055
+ ? "azeforge.source#missing-directive-type"
1056
+ : "azeforge.source#unknown-directive"
1057
+ : "azeforge.source#unclosed-directive", closed
1058
+ ? originalType === undefined
1059
+ ? "A directive Block must name a type."
1060
+ : `Directive Block type "${originalType}" is not available.`
1061
+ : "A directive Block must end with a closing `::::` delimiter.", options, first, "error", originalType === undefined
1062
+ ? rangeFromLines(first, first)
1063
+ : rangeFromLineSlice(first, typeStartIndex, typeStartIndex + originalType.length), {
1064
+ data: !closed || originalType === undefined
1065
+ ? {}
1066
+ : { type: originalType, availableTypes: [...activeTypes] },
1067
+ ...(closed && originalType !== undefined
1068
+ ? (() => {
1069
+ const candidates = unknownDirectiveCandidates(originalType, activeTypes);
1070
+ const lowered = originalType.toLowerCase();
1071
+ const caseMatch = lowered !== originalType &&
1072
+ activeTypes.includes(lowered)
1073
+ ? lowered
1074
+ : undefined;
1075
+ const suggestion = caseMatch !== undefined
1076
+ ? `Did you mean "${caseMatch}"?`
1077
+ : candidates.length > 0
1078
+ ? `Did you mean ${candidates
1079
+ .slice(0, 3)
1080
+ .map((candidate) => `"${candidate}"`)
1081
+ .join(", ")}?`
1082
+ : activeTypes.length > 0
1083
+ ? `Available directive types: ${activeTypes
1084
+ .slice(0, 5)
1085
+ .join(", ")}.`
1086
+ : "No directive types are available.";
1087
+ return {
1088
+ suggestion,
1089
+ ...(caseMatch === undefined
1090
+ ? {}
1091
+ : {
1092
+ fix: {
1093
+ title: `Use directive type "${caseMatch}".`,
1094
+ applicability: "safe",
1095
+ edits: [
1096
+ {
1097
+ range: rangeFromLineSlice(first, typeStartIndex, typeStartIndex + originalType.length),
1098
+ expectedText: originalType,
1099
+ replacementText: caseMatch,
1100
+ },
1101
+ ],
1102
+ },
1103
+ }),
1104
+ };
1105
+ })()
1106
+ : {}),
1107
+ ...(!closed
1108
+ ? {
1109
+ suggestion: "Add a closing `::::` delimiter before the next Block.",
1110
+ ...(unambiguousEnd
1111
+ ? {
1112
+ fix: {
1113
+ title: "Add the closing directive delimiter.",
1114
+ applicability: "safe",
1115
+ edits: [
1116
+ {
1117
+ range: insertionRange,
1118
+ expectedText: "",
1119
+ replacementText: source.endsWith("\n") || source.endsWith("\r")
1120
+ ? `::::${newline}`
1121
+ : `${newline}::::`,
1122
+ },
1123
+ ],
1124
+ },
1125
+ }
1126
+ : {}),
1127
+ }
1128
+ : {}),
1129
+ }));
1130
+ blocks.push({
1131
+ kind: "invalid",
1132
+ raw: source.slice(first.startIndex, last.endIndex),
1133
+ range: rangeFromLines(first, last),
1134
+ diagnosticIndexes: [diagnosticIndex],
1135
+ ...(originalType === undefined ? {} : { originalType }),
1136
+ });
1137
+ continue;
1138
+ }
1139
+ const fenced = parseFencedCode(source, lines, index, options, diagnostics);
1140
+ if (fenced !== undefined) {
1141
+ blocks.push(fenced.block);
1142
+ index = fenced.next;
1143
+ continue;
1144
+ }
1145
+ if (isThematicBreakText(lineText(line))) {
1146
+ blocks.push({ kind: "thematicBreak", range: rangeFromLines(line, line) });
1147
+ index += 1;
1148
+ continue;
1149
+ }
1150
+ const quoted = parseBlockquote(source, lines, index, options, diagnostics, activeTypes, allowRawLatex, depth);
1151
+ if (quoted !== undefined) {
1152
+ blocks.push(quoted.block);
1153
+ index = quoted.next;
1154
+ continue;
1155
+ }
1156
+ const listed = parseList(source, lines, index, options, diagnostics, activeTypes, allowRawLatex, depth);
1157
+ if (listed !== undefined) {
1158
+ blocks.push(listed.block);
1159
+ index = listed.next;
1160
+ continue;
1161
+ }
1162
+ const gfmTable = tryGfmTableAt(lines, index);
1163
+ if (gfmTable !== undefined) {
1164
+ const tableLines = lines.slice(index, index + gfmTable.consumed);
1165
+ const tableRawHtml = findRawHtml(tableLines);
1166
+ if (tableRawHtml !== undefined) {
1167
+ blocks.push(rawHtmlInvalidBlock(source, tableLines, tableRawHtml, options, diagnostics));
1168
+ index += gfmTable.consumed;
1169
+ continue;
1170
+ }
1171
+ const tableLast = lines[index + gfmTable.consumed - 1] ?? line;
1172
+ const startIndex = diagnostics.length;
1173
+ const safetyTexts = [];
1174
+ for (let rowOffset = 0; rowOffset < gfmTable.consumed; rowOffset += 1) {
1175
+ if (rowOffset === 1)
1176
+ continue;
1177
+ const cells = splitTableRow(lineText(lines[index + rowOffset]));
1178
+ if (cells !== undefined)
1179
+ safetyTexts.push(...cells);
1180
+ }
1181
+ if (!ensureInlineTargetsSafe(safetyTexts, line, tableLast, options, diagnostics)) {
1182
+ blocks.push(invalidBlockFor(source, line, tableLast, startIndex, diagnostics));
1183
+ index += gfmTable.consumed;
1184
+ continue;
1185
+ }
1186
+ blocks.push({
1187
+ kind: "table",
1188
+ data: gfmTable.data,
1189
+ range: rangeFromLines(line, tableLast),
1190
+ });
1191
+ index += gfmTable.consumed;
1192
+ continue;
1193
+ }
1194
+ const headingRawHtml = findRawHtml([line]);
1195
+ if (headingRawHtml !== undefined) {
1196
+ blocks.push(rawHtmlInvalidBlock(source, [line], headingRawHtml, options, diagnostics));
1197
+ index += 1;
1198
+ continue;
1199
+ }
1200
+ const atxHeading = parseAtxHeading(lineText(line));
1201
+ if (atxHeading !== undefined) {
1202
+ const startIndex = diagnostics.length;
1203
+ const children = parseInlineNodes(atxHeading.text, line, line, options, diagnostics);
1204
+ if (children === undefined) {
1205
+ blocks.push(invalidBlockFor(source, line, line, startIndex, diagnostics));
1206
+ index += 1;
1207
+ continue;
1208
+ }
1209
+ blocks.push({
1210
+ kind: "heading",
1211
+ level: atxHeading.level,
1212
+ children,
1213
+ range: rangeFromLines(line, line),
1214
+ });
1215
+ index += 1;
1216
+ continue;
1217
+ }
1218
+ const paragraphLines = [line];
1219
+ index += 1;
1220
+ let setextLevel;
1221
+ let setextUnderline;
1222
+ while (index < lines.length) {
1223
+ const next = lines[index];
1224
+ if (next === undefined || /^[ \t]*$/.test(lineText(next)))
1225
+ break;
1226
+ const setext = /^ {0,3}(=+|-+)[ \t]*$/.exec(lineText(next));
1227
+ if (setext !== null) {
1228
+ setextLevel = setext[1]?.startsWith("=") === true ? 1 : 2;
1229
+ setextUnderline = next;
1230
+ index += 1;
1231
+ break;
1232
+ }
1233
+ const nextText = lineText(next);
1234
+ const after = lines[index + 1];
1235
+ if (parseAtxHeading(nextText) !== undefined ||
1236
+ /^ {0,3}:{4,}/.test(nextText) ||
1237
+ fenceOpen(nextText) !== undefined ||
1238
+ isThematicBreakText(nextText) ||
1239
+ stripQuotePrefix(nextText) !== undefined ||
1240
+ (parseListMarker(nextText) !== undefined && (parseListMarker(nextText)?.lead ?? 4) <= 3) ||
1241
+ (after !== undefined && isGfmTableStart(nextText, lineText(after)))) {
1242
+ break;
1243
+ }
1244
+ paragraphLines.push(next);
1245
+ index += 1;
1246
+ }
1247
+ const paragraphRawHtml = findRawHtml(paragraphLines);
1248
+ if (paragraphRawHtml !== undefined) {
1249
+ blocks.push(rawHtmlInvalidBlock(source, paragraphLines, paragraphRawHtml, options, diagnostics));
1250
+ continue;
1251
+ }
1252
+ const text = inlineSourceFromLines(paragraphLines);
1253
+ const inlineStart = diagnostics.length;
1254
+ if (setextLevel !== undefined && setextUnderline !== undefined) {
1255
+ const headingChildren = parseInlineNodes(text, line, setextUnderline, options, diagnostics);
1256
+ if (headingChildren === undefined) {
1257
+ blocks.push(invalidBlockFor(source, line, setextUnderline, inlineStart, diagnostics));
1258
+ continue;
1259
+ }
1260
+ blocks.push({
1261
+ kind: "heading",
1262
+ level: setextLevel,
1263
+ children: headingChildren,
1264
+ range: rangeFromLines(line, setextUnderline),
1265
+ });
1266
+ continue;
1267
+ }
1268
+ const last = paragraphLines.at(-1) ?? line;
1269
+ const children = parseInlineNodes(text, line, last, options, diagnostics);
1270
+ if (children === undefined) {
1271
+ blocks.push(invalidBlockFor(source, line, last, inlineStart, diagnostics));
1272
+ continue;
1273
+ }
1274
+ blocks.push({
1275
+ kind: "paragraph",
1276
+ children,
1277
+ range: rangeFromLines(line, last),
1278
+ });
1279
+ }
1280
+ return blocks;
1281
+ }
1282
+ function directiveIdOccurrences(lines, bodyStart, options) {
1283
+ const occurrences = [];
1284
+ for (let index = bodyStart; index < lines.length; index += 1) {
1285
+ const opening = lines[index];
1286
+ if (opening === undefined ||
1287
+ !/^ {0,3}:{4,}[ \t]*[^ \t:]+/.test(lineText(opening))) {
1288
+ continue;
1289
+ }
1290
+ for (let headerIndex = index + 1; headerIndex < lines.length; headerIndex += 1) {
1291
+ const header = lines[headerIndex];
1292
+ if (header === undefined ||
1293
+ /^ {0,3}:{4,}[ \t]*$/.test(lineText(header))) {
1294
+ break;
1295
+ }
1296
+ if (/^[ \t]*$/.test(header.text))
1297
+ continue;
1298
+ if (!/^[ \t]*[A-Za-z][A-Za-z0-9-]*[ \t]*:/.test(header.text))
1299
+ break;
1300
+ const idMatch = /^[ \t]*id[ \t]*:[ \t]*(.*?)[ \t]*$/.exec(header.text);
1301
+ const id = idMatch?.[1];
1302
+ if (id === undefined)
1303
+ continue;
1304
+ const startIndex = header.text.indexOf(id, header.text.indexOf(":") + 1);
1305
+ occurrences.push({
1306
+ id,
1307
+ range: rangeFromLineSlice(header, startIndex, startIndex + id.length),
1308
+ ...(options.sourceName === undefined
1309
+ ? {}
1310
+ : { source: options.sourceName }),
1311
+ });
1312
+ break;
1313
+ }
1314
+ }
1315
+ return occurrences;
1316
+ }
1317
+ function requiredVersionDiagnostic(source, lines, bodyStart, versionDeclared, options) {
1318
+ const containsDirective = lines
1319
+ .slice(bodyStart)
1320
+ .some((line) => /^ {0,3}:{4,}/.test(lineText(line)));
1321
+ if (!containsDirective)
1322
+ return undefined;
1323
+ if (versionDeclared)
1324
+ return undefined;
1325
+ const first = lines[0];
1326
+ if (first === undefined)
1327
+ return undefined;
1328
+ const newline = source.includes("\r\n") ? "\r\n" : "\n";
1329
+ const insertionLine = bodyStart === 0 ? first : (lines[1] ?? first);
1330
+ const insertionIndex = bodyStart === 0 && insertionLine.text.startsWith("\uFEFF") ? 1 : 0;
1331
+ const insertionRange = rangeFromLineSlice(insertionLine, insertionIndex, insertionIndex);
1332
+ return diagnostic("azeforge.source#version-required", "Source with directive Blocks must declare AzeMark version 1.", options, insertionLine, "error", insertionRange, {
1333
+ fix: {
1334
+ title: "Declare AzeMark version 1.",
1335
+ applicability: "safe",
1336
+ edits: [
1337
+ {
1338
+ range: insertionRange,
1339
+ expectedText: "",
1340
+ replacementText: bodyStart === 0
1341
+ ? `---${newline}azemark: 1${newline}---${newline}${newline}`
1342
+ : `azemark: 1${newline}`,
1343
+ },
1344
+ ],
1345
+ },
1346
+ });
1347
+ }
1348
+ export function parseSource(source, options = {}) {
1349
+ const lines = sourceLines(source);
1350
+ const frontMatter = parseFrontMatter(lines, options);
1351
+ const diagnostics = [...frontMatter.diagnostics];
1352
+ const versionDiagnostic = requiredVersionDiagnostic(source, lines, frontMatter.bodyStart, frontMatter.versionDeclared, options);
1353
+ if (versionDiagnostic !== undefined)
1354
+ diagnostics.push(versionDiagnostic);
1355
+ const activePlugins = options.plugins ?? [
1356
+ equationPlugin,
1357
+ calloutPlugin,
1358
+ mermaidPlugin,
1359
+ tablePlugin,
1360
+ ];
1361
+ const activeTypes = [...new Set(activePlugins.map((plugin) => plugin.descriptor.type))].sort();
1362
+ const blocks = parseBlocks(source, lines, frontMatter.bodyStart, options, diagnostics, activeTypes, options.allowRawLatex ?? false);
1363
+ diagnostics.push(...validateBlockIds(directiveIdOccurrences(lines, frontMatter.bodyStart, options)));
1364
+ return {
1365
+ document: {
1366
+ azemarkVersion: 1,
1367
+ schemaVersion: 1,
1368
+ metadata: frontMatter.metadata,
1369
+ blocks,
1370
+ },
1371
+ diagnostics,
1372
+ };
1373
+ }
1374
+ //# sourceMappingURL=parse.js.map