@effected/markdown 0.19.0 → 0.21.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Frontmatter.js +4 -5
- package/FrontmatterResolver.js +4 -6
- package/FrontmatterSource.js +1 -2
- package/JsonFrontmatter.js +1 -2
- package/Markdown.js +1 -2
- package/MarkdownDiagnostic.js +1 -2
- package/MarkdownDocument.js +23 -15
- package/MarkdownEdit.js +1 -2
- package/MarkdownFormat.js +1 -2
- package/MarkdownNode.js +1 -2
- package/MarkdownTransform.js +1 -2
- package/MarkdownVisitor.js +58 -19
- package/Mdast.js +28 -13
- package/README.md +26 -8
- package/TomlFrontmatter.js +1 -2
- package/YamlFrontmatter.js +1 -2
- package/index.d.ts +87 -54
- package/internal/blockParser.js +1 -2
- package/internal/blockRegistry.js +1 -2
- package/internal/blockTypes.js +1 -2
- package/internal/blocks/atxHeading.js +1 -2
- package/internal/blocks/blockquote.js +1 -2
- package/internal/blocks/code.js +1 -2
- package/internal/blocks/document.js +1 -2
- package/internal/blocks/fencedCode.js +1 -2
- package/internal/blocks/footnoteDefinition.js +1 -2
- package/internal/blocks/frontmatter.js +1 -2
- package/internal/blocks/htmlBlock.js +1 -2
- package/internal/blocks/indentedCode.js +1 -2
- package/internal/blocks/linkReferenceDefinition.js +1 -2
- package/internal/blocks/list.js +1 -2
- package/internal/blocks/paragraph.js +1 -2
- package/internal/blocks/setextHeading.js +1 -2
- package/internal/blocks/table.js +1 -2
- package/internal/blocks/taskListItem.js +1 -2
- package/internal/blocks/thematicBreak.js +1 -2
- package/internal/carriers.js +1 -2
- package/internal/entities.js +1 -2
- package/internal/entityMap.js +1 -2
- package/internal/htmlTags.js +1 -2
- package/internal/inlineNode.js +1 -2
- package/internal/inlineParser.js +1 -2
- package/internal/inlineRegistry.js +1 -2
- package/internal/inlines/autolink.js +1 -2
- package/internal/inlines/autolinkLiteral.js +1 -2
- package/internal/inlines/codeSpan.js +1 -2
- package/internal/inlines/emphasis.js +1 -2
- package/internal/inlines/entity.js +1 -2
- package/internal/inlines/escape.js +1 -2
- package/internal/inlines/footnoteReference.js +1 -2
- package/internal/inlines/lineBreak.js +1 -2
- package/internal/inlines/link.js +1 -2
- package/internal/inlines/rawHtml.js +1 -2
- package/internal/inlines/strikethrough.js +1 -2
- package/internal/inlines/text.js +1 -2
- package/internal/lineIndex.js +1 -2
- package/internal/patterns.js +1 -2
- package/internal/phrasing.js +1 -2
- package/internal/preprocess.js +1 -2
- package/internal/rawInline.js +1 -2
- package/internal/references.js +1 -2
- package/internal/sectionBounds.js +1 -2
- package/internal/segments.js +1 -2
- package/internal/stringify.js +1 -2
- package/internal/unescape.js +1 -2
- package/package.json +4 -4
package/Frontmatter.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { MarkdownEdit } from "./MarkdownEdit.js";
|
|
2
2
|
import { FrontmatterFormat } from "./MarkdownNode.js";
|
|
3
|
-
import { Effect, Schema } from "effect";
|
|
3
|
+
import { Effect, Option, Schema } from "effect";
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
// src/Frontmatter.ts
|
|
6
6
|
/**
|
|
7
7
|
* Indicates that a frontmatter codec was handed a capture of a different
|
|
8
8
|
* format — a yaml codec applied to a `+++` toml capture, for example.
|
|
@@ -212,7 +212,7 @@ var MarkdownFrontmatter = class MarkdownFrontmatter {
|
|
|
212
212
|
*/
|
|
213
213
|
static schema(schema, codec) {
|
|
214
214
|
return (document) => {
|
|
215
|
-
const node = document.frontmatter;
|
|
215
|
+
const node = Option.getOrUndefined(document.frontmatter);
|
|
216
216
|
return node === void 0 ? Effect.fail(new FrontmatterMissingError({ reason: document.hasFrontmatterBlock ? "captureDisabled" : "absent" })) : codec.decode(node).pipe(Effect.flatMap((data) => Schema.decodeUnknownEffect(schema)(data).pipe(Effect.catchTag("SchemaError", (error) => Effect.fail(new FrontmatterValidationError({ issue: error.issue }))))));
|
|
217
217
|
};
|
|
218
218
|
}
|
|
@@ -258,7 +258,7 @@ var MarkdownFrontmatter = class MarkdownFrontmatter {
|
|
|
258
258
|
*/
|
|
259
259
|
static set(schema, codec) {
|
|
260
260
|
return (document, data) => {
|
|
261
|
-
const node = document.frontmatter;
|
|
261
|
+
const node = Option.getOrUndefined(document.frontmatter);
|
|
262
262
|
if (node !== void 0 && node.format !== codec.format) return Effect.fail(new FrontmatterFormatMismatchError({
|
|
263
263
|
expected: codec.format,
|
|
264
264
|
actual: node.format
|
|
@@ -303,5 +303,4 @@ var MarkdownFrontmatter = class MarkdownFrontmatter {
|
|
|
303
303
|
}
|
|
304
304
|
};
|
|
305
305
|
|
|
306
|
-
//#endregion
|
|
307
306
|
export { FrontmatterDecodeError, FrontmatterEncodeError, FrontmatterFormatMismatchError, FrontmatterMissingError, FrontmatterMissingReason, FrontmatterValidationError, MarkdownFrontmatter };
|
package/FrontmatterResolver.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { Effect, Result, Schema } from "effect";
|
|
1
|
+
import { Effect, Predicate, Result, Schema } from "effect";
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
// src/FrontmatterResolver.ts
|
|
4
4
|
/**
|
|
5
5
|
* A `$schema` declaration referencing a schema by URL — any string containing
|
|
6
6
|
* `://`.
|
|
@@ -140,7 +140,6 @@ const parseVersionSegments = (version) => {
|
|
|
140
140
|
if (!/^\d+(\.\d+){0,2}$/.test(version)) return;
|
|
141
141
|
return version.split(".").map((segment) => Number.parseInt(segment, 10));
|
|
142
142
|
};
|
|
143
|
-
const isMapping = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
144
143
|
/**
|
|
145
144
|
* The `$schema` declaration classifier and the package's one built-in
|
|
146
145
|
* resolver implementation.
|
|
@@ -184,7 +183,7 @@ var SchemaResolver = class SchemaResolver {
|
|
|
184
183
|
version
|
|
185
184
|
}));
|
|
186
185
|
}
|
|
187
|
-
if (
|
|
186
|
+
if (Predicate.isObject(value)) return Result.succeed(new SchemaDeclarationInline({ document: value }));
|
|
188
187
|
return Result.fail(new SchemaDeclarationInvalidError({
|
|
189
188
|
reason: "the declaration is neither a string nor a mapping",
|
|
190
189
|
value
|
|
@@ -206,7 +205,7 @@ var SchemaResolver = class SchemaResolver {
|
|
|
206
205
|
* tolerated, or the typed error.
|
|
207
206
|
*/
|
|
208
207
|
static declarationOf(data, options) {
|
|
209
|
-
if (!
|
|
208
|
+
if (!Predicate.isObject(data) || !Object.hasOwn(data, "$schema")) return options?.requireDeclaration === true ? Result.fail(new SchemaDeclarationMissingError()) : Result.succeed(void 0);
|
|
210
209
|
return SchemaResolver.classify(data.$schema);
|
|
211
210
|
}
|
|
212
211
|
/**
|
|
@@ -267,5 +266,4 @@ var SchemaResolver = class SchemaResolver {
|
|
|
267
266
|
}
|
|
268
267
|
};
|
|
269
268
|
|
|
270
|
-
//#endregion
|
|
271
269
|
export { SchemaDeclaration, SchemaDeclarationByName, SchemaDeclarationByPath, SchemaDeclarationByUrl, SchemaDeclarationInline, SchemaDeclarationInvalidError, SchemaDeclarationMissingError, SchemaNameUnknownError, SchemaResolver, SchemaVersionUnresolvableError };
|
package/FrontmatterSource.js
CHANGED
|
@@ -2,7 +2,7 @@ import { FrontmatterFormat } from "./MarkdownNode.js";
|
|
|
2
2
|
import { scanRawFrontmatter } from "./internal/blocks/frontmatter.js";
|
|
3
3
|
import { Effect, Schema } from "effect";
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
// src/FrontmatterSource.ts
|
|
6
6
|
/**
|
|
7
7
|
* The three line-terminator spellings a fence line may end with. Recorded by
|
|
8
8
|
* {@link FrontmatterSource.split} as fidelity — what the source's fence
|
|
@@ -169,5 +169,4 @@ var FrontmatterSource = class {
|
|
|
169
169
|
}
|
|
170
170
|
};
|
|
171
171
|
|
|
172
|
-
//#endregion
|
|
173
172
|
export { FrontmatterNewline, FrontmatterSource, FrontmatterSourceBlock, FrontmatterSourceSplit };
|
package/JsonFrontmatter.js
CHANGED
|
@@ -2,7 +2,7 @@ import { FrontmatterDecodeError, FrontmatterEncodeError, FrontmatterFormatMismat
|
|
|
2
2
|
import { Effect } from "effect";
|
|
3
3
|
import { Jsonc } from "@effected/jsonc";
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
// src/JsonFrontmatter.ts
|
|
6
6
|
/**
|
|
7
7
|
* The json frontmatter codec, over `@effected/jsonc`.
|
|
8
8
|
*
|
|
@@ -42,5 +42,4 @@ const JsonFrontmatter = {
|
|
|
42
42
|
})))
|
|
43
43
|
};
|
|
44
44
|
|
|
45
|
-
//#endregion
|
|
46
45
|
export { JsonFrontmatter };
|
package/Markdown.js
CHANGED
|
@@ -6,7 +6,7 @@ import { stringifyBlocks } from "./internal/stringify.js";
|
|
|
6
6
|
import { MarkdownDiagnostic } from "./MarkdownDiagnostic.js";
|
|
7
7
|
import { Effect, Result, Schema, SchemaIssue, SchemaTransformation } from "effect";
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
// src/Markdown.ts
|
|
10
10
|
/**
|
|
11
11
|
* The markdown dialects the parser can be pointed at. `"gfm"` — CommonMark
|
|
12
12
|
* 0.31.2 plus the GitHub extensions (tables, strikethrough, autolink
|
|
@@ -426,5 +426,4 @@ var Markdown = class Markdown {
|
|
|
426
426
|
static MarkdownFromString = Markdown.fromString();
|
|
427
427
|
};
|
|
428
428
|
|
|
429
|
-
//#endregion
|
|
430
429
|
export { Markdown, MarkdownDialect, MarkdownParseError, MarkdownParseOptions, MarkdownStringifyError, parsePassResult };
|
package/MarkdownDiagnostic.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { MARKDOWN_PARSE_ERROR_CODES } from "./internal/carriers.js";
|
|
2
2
|
import { Schema } from "effect";
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
// src/MarkdownDiagnostic.ts
|
|
5
5
|
/**
|
|
6
6
|
* Error codes `Markdown.parse`/`MarkdownDocument.parse` can fail with.
|
|
7
7
|
* Currently exactly one, `"NestingDepthExceeded"`, the hardening-guard trip;
|
|
@@ -84,5 +84,4 @@ function lineChar(text, offset) {
|
|
|
84
84
|
};
|
|
85
85
|
}
|
|
86
86
|
|
|
87
|
-
//#endregion
|
|
88
87
|
export { MarkdownDiagnostic, MarkdownParseErrorCode };
|
package/MarkdownDocument.js
CHANGED
|
@@ -6,9 +6,9 @@ import { normalizeLabelText } from "./internal/references.js";
|
|
|
6
6
|
import { MarkdownDiagnostic } from "./MarkdownDiagnostic.js";
|
|
7
7
|
import { parsePassResult } from "./Markdown.js";
|
|
8
8
|
import { rootHeadingBounds } from "./internal/sectionBounds.js";
|
|
9
|
-
import { Effect, Result, Schema } from "effect";
|
|
9
|
+
import { Effect, Option, Result, Schema } from "effect";
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
// src/MarkdownDocument.ts
|
|
12
12
|
/**
|
|
13
13
|
* A heading-delimited span from {@link MarkdownDocument.sections}: the
|
|
14
14
|
* heading, its depth, the source `range` the section occupies and the
|
|
@@ -167,19 +167,26 @@ var MarkdownDocument = class MarkdownDocument extends Schema.Class("MarkdownDocu
|
|
|
167
167
|
definitions: Schema.ReadonlyMap(Schema.String, Definition)
|
|
168
168
|
}) {
|
|
169
169
|
/**
|
|
170
|
-
* The document's frontmatter capture, or `
|
|
170
|
+
* The document's frontmatter capture, or `Option.none()` when there is
|
|
171
|
+
* none.
|
|
171
172
|
*
|
|
172
173
|
* @remarks
|
|
173
174
|
* Derived from the tree rather than stored: a {@link Frontmatter} node can
|
|
174
175
|
* only ever sit at the head of `root.children` (the capture fires at most
|
|
175
176
|
* once, at offset 0), so the tree is the single source of truth and the
|
|
176
|
-
* accessor can never disagree with it. `
|
|
177
|
-
* with no frontmatter block and one parsed with the capture toggle
|
|
178
|
-
* {@link MarkdownDocument.hasFrontmatterBlock} tells the two apart.
|
|
177
|
+
* accessor can never disagree with it. `Option.none()` covers both a
|
|
178
|
+
* document with no frontmatter block and one parsed with the capture toggle
|
|
179
|
+
* off — {@link MarkdownDocument.hasFrontmatterBlock} tells the two apart.
|
|
180
|
+
*
|
|
181
|
+
* An `Option` rather than `Frontmatter | undefined` because this is a
|
|
182
|
+
* lookup that can miss, and the kit's lookups return `Option` — the same
|
|
183
|
+
* shape as the sibling format packages' tree lookups (`JsoncNode.find`,
|
|
184
|
+
* `YamlNode.find`). A derived getter is a lookup, not a data field: it
|
|
185
|
+
* recomputes from the tree on every access.
|
|
179
186
|
*/
|
|
180
187
|
get frontmatter() {
|
|
181
188
|
const head = this.root.children[0];
|
|
182
|
-
return head !== void 0 && head.type === "frontmatter" ? head :
|
|
189
|
+
return head !== void 0 && head.type === "frontmatter" ? Option.some(head) : Option.none();
|
|
183
190
|
}
|
|
184
191
|
/**
|
|
185
192
|
* Whether the source opens with a well-formed frontmatter block —
|
|
@@ -187,10 +194,10 @@ var MarkdownDocument = class MarkdownDocument extends Schema.Class("MarkdownDocu
|
|
|
187
194
|
*
|
|
188
195
|
* @remarks
|
|
189
196
|
* The structural signal that disambiguates the `frontmatter` accessor's
|
|
190
|
-
* `
|
|
197
|
+
* `Option.none()`: capture is opt-in (`MarkdownParseOptions.frontmatter`
|
|
191
198
|
* defaults off), so a document whose source visibly starts with `---` can
|
|
192
199
|
* still carry no capture node — spec-correct CommonMark reads the fences as
|
|
193
|
-
* a thematic break plus content.
|
|
200
|
+
* a thematic break plus content. An empty `frontmatter` with
|
|
194
201
|
* `hasFrontmatterBlock === true` means exactly "parsed with capture off";
|
|
195
202
|
* with `false` it means the source genuinely has no block.
|
|
196
203
|
*
|
|
@@ -274,11 +281,12 @@ var MarkdownDocument = class MarkdownDocument extends Schema.Class("MarkdownDocu
|
|
|
274
281
|
* document repeatedly should bind `document.sections` once instead.
|
|
275
282
|
*
|
|
276
283
|
* @param options - Depth restriction.
|
|
277
|
-
* @returns The first matching section, or `
|
|
284
|
+
* @returns The first matching section, or `Option.none()` when no section
|
|
285
|
+
* matches.
|
|
278
286
|
*/
|
|
279
287
|
firstSection(options) {
|
|
280
288
|
const depth = options?.depth;
|
|
281
|
-
return this.sections.find((section) => depth === void 0 || section.depth === depth);
|
|
289
|
+
return Option.fromUndefinedOr(this.sections.find((section) => depth === void 0 || section.depth === depth));
|
|
282
290
|
}
|
|
283
291
|
/**
|
|
284
292
|
* The first section whose heading matches, in document order.
|
|
@@ -295,7 +303,8 @@ var MarkdownDocument = class MarkdownDocument extends Schema.Class("MarkdownDocu
|
|
|
295
303
|
*
|
|
296
304
|
* @param match - A string, a RegExp, or a predicate.
|
|
297
305
|
* @param options - Depth restriction, applied before matching.
|
|
298
|
-
* @returns The first matching section, or `
|
|
306
|
+
* @returns The first matching section, or `Option.none()` when no section
|
|
307
|
+
* matches.
|
|
299
308
|
*/
|
|
300
309
|
sectionByHeading(match, options) {
|
|
301
310
|
const depth = options?.depth;
|
|
@@ -303,7 +312,7 @@ var MarkdownDocument = class MarkdownDocument extends Schema.Class("MarkdownDocu
|
|
|
303
312
|
match.lastIndex = 0;
|
|
304
313
|
return match.test(section.text.trim());
|
|
305
314
|
} : (section) => match(section.text, section);
|
|
306
|
-
return this.sections.find((section) => (depth === void 0 || section.depth === depth) && matches(section));
|
|
315
|
+
return Option.fromUndefinedOr(this.sections.find((section) => (depth === void 0 || section.depth === depth) && matches(section)));
|
|
307
316
|
}
|
|
308
317
|
/**
|
|
309
318
|
* Every link-bearing node in the document, in document order: links,
|
|
@@ -343,7 +352,7 @@ var MarkdownDocument = class MarkdownDocument extends Schema.Class("MarkdownDocu
|
|
|
343
352
|
return entries;
|
|
344
353
|
}
|
|
345
354
|
find(selector) {
|
|
346
|
-
return findInTree(this.root, 0, selectorPredicate(selector));
|
|
355
|
+
return Option.fromUndefinedOr(findInTree(this.root, 0, selectorPredicate(selector)));
|
|
347
356
|
}
|
|
348
357
|
findAll(selector) {
|
|
349
358
|
const predicate = selectorPredicate(selector);
|
|
@@ -391,5 +400,4 @@ var MarkdownDocument = class MarkdownDocument extends Schema.Class("MarkdownDocu
|
|
|
391
400
|
static parse = Effect.fn("MarkdownDocument.parse")((text, options) => Effect.fromResult(MarkdownDocument.parseResult(text, options)));
|
|
392
401
|
};
|
|
393
402
|
|
|
394
|
-
//#endregion
|
|
395
403
|
export { DocumentSection, MarkdownDocument };
|
package/MarkdownEdit.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Schema } from "effect";
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
// src/MarkdownEdit.ts
|
|
4
4
|
/**
|
|
5
5
|
* A range within a markdown document, expressed as a zero-based character
|
|
6
6
|
* `offset` and a `length` in UTF-16 code units. Pass to `MarkdownFormat.format`
|
|
@@ -52,5 +52,4 @@ var MarkdownEdit = class extends Schema.Class("MarkdownEdit")({
|
|
|
52
52
|
}
|
|
53
53
|
};
|
|
54
54
|
|
|
55
|
-
//#endregion
|
|
56
55
|
export { MarkdownEdit, MarkdownRange };
|
package/MarkdownFormat.js
CHANGED
|
@@ -3,7 +3,7 @@ import { BulletChar, EmphasisChar, FenceChar, HeadingStyle, Paragraph, Root, Tex
|
|
|
3
3
|
import { Markdown, MarkdownDialect, MarkdownParseOptions } from "./Markdown.js";
|
|
4
4
|
import { Effect, Result, Schema } from "effect";
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
// src/MarkdownFormat.ts
|
|
7
7
|
/**
|
|
8
8
|
* The two ways CommonMark spells a code block: `fenced` (a backtick or tilde
|
|
9
9
|
* fence) and `indented` (four-space indentation).
|
|
@@ -511,5 +511,4 @@ var MarkdownFormat = class MarkdownFormat {
|
|
|
511
511
|
});
|
|
512
512
|
};
|
|
513
513
|
|
|
514
|
-
//#endregion
|
|
515
514
|
export { CodeBlockStyle, MarkdownFormat, MarkdownFormattingOptions, MarkdownModificationError, MarkdownModificationErrorCode };
|
package/MarkdownNode.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Effect, Schema } from "effect";
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
// src/MarkdownNode.ts
|
|
4
4
|
/**
|
|
5
5
|
* A single point in a source document: 1-based `line` and `column`, 0-based
|
|
6
6
|
* `offset`.
|
|
@@ -854,5 +854,4 @@ const MarkdownNode = Schema.suspend(() => Schema.Union([
|
|
|
854
854
|
TableContent
|
|
855
855
|
]));
|
|
856
856
|
|
|
857
|
-
//#endregion
|
|
858
857
|
export { Blockquote, Break, BreakStyle, BulletChar, Code, Definition, Delete, Emphasis, EmphasisChar, FenceChar, FlowContent, FootnoteDefinition, FootnoteReference, Frontmatter, FrontmatterContent, FrontmatterFormat, Heading, HeadingDepth, HeadingStyle, Html, Image, ImageReference, InlineCode, Link, LinkReference, List, ListContent, ListDelimiter, ListItem, MarkdownNode, MdxFlowExpression, MdxJsxAttribute, MdxJsxAttributeContent, MdxJsxAttributeValueExpression, MdxJsxExpressionAttribute, MdxJsxFlowElement, MdxJsxTextElement, MdxTextExpression, MdxjsEsm, Paragraph, PhrasingContent, Point, Position, ReferenceType, Root, RowContent, Strong, Table, TableAlign, TableCell, TableContent, TableRow, Text, ThematicBreak, ThematicBreakChar };
|
package/MarkdownTransform.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { rootHeadingBounds } from "./internal/sectionBounds.js";
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
// src/MarkdownTransform.ts
|
|
4
4
|
/**
|
|
5
5
|
* Pure, total tree-to-tree transforms over the markdown node classes. Not
|
|
6
6
|
* instantiable.
|
|
@@ -163,5 +163,4 @@ function mapNode(node, handlers) {
|
|
|
163
163
|
return handler === void 0 ? current : handler(current);
|
|
164
164
|
}
|
|
165
165
|
|
|
166
|
-
//#endregion
|
|
167
166
|
export { MarkdownTransform };
|
package/MarkdownVisitor.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { MarkdownDiagnostic } from "./MarkdownDiagnostic.js";
|
|
2
2
|
import { Data, Stream } from "effect";
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
// src/MarkdownVisitor.ts
|
|
5
5
|
/**
|
|
6
6
|
* Constructors and matchers for the `MarkdownVisitorEvent` union (e.g.
|
|
7
7
|
* `MarkdownVisitorEvent.Enter({ node, path, depth })`,
|
|
@@ -11,8 +11,9 @@ import { Data, Stream } from "effect";
|
|
|
11
11
|
*/
|
|
12
12
|
const MarkdownVisitorEvent = Data.taggedEnum();
|
|
13
13
|
/**
|
|
14
|
-
* Walks a parsed markdown tree as a lazy
|
|
15
|
-
* document pre-order.
|
|
14
|
+
* Walks a parsed markdown tree as a lazy sequence of `Enter`/`Exit` events in
|
|
15
|
+
* document pre-order — synchronously with {@link MarkdownVisitor.walk}, or as
|
|
16
|
+
* a `Stream` with {@link MarkdownVisitor.visit}. Not instantiable.
|
|
16
17
|
*
|
|
17
18
|
* @example
|
|
18
19
|
* ```ts
|
|
@@ -32,9 +33,52 @@ const MarkdownVisitorEvent = Data.taggedEnum();
|
|
|
32
33
|
*
|
|
33
34
|
* @public
|
|
34
35
|
*/
|
|
35
|
-
var MarkdownVisitor = class {
|
|
36
|
+
var MarkdownVisitor = class MarkdownVisitor {
|
|
36
37
|
constructor() {}
|
|
37
38
|
/**
|
|
39
|
+
* Walk `root` synchronously, as a lazy `Iterable` of `MarkdownVisitorEvent`
|
|
40
|
+
* in document pre-order: `Enter` and `Exit` for every node including the
|
|
41
|
+
* root and every leaf. The primitive {@link MarkdownVisitor.visit} is
|
|
42
|
+
* defined in terms of, so the two yield the same events in the same order.
|
|
43
|
+
*
|
|
44
|
+
* @remarks
|
|
45
|
+
* For callers that cannot suspend — a lint rule whose host calls it
|
|
46
|
+
* synchronously, a getter, a non-Effect adapter. Events are produced on
|
|
47
|
+
* demand: a `for…of` that `break`s stops the walk without visiting the
|
|
48
|
+
* rest of the tree. The iterable is re-iterable — every `[Symbol.iterator]()`
|
|
49
|
+
* call starts a fresh walk from the root, so binding the result once and
|
|
50
|
+
* iterating it twice walks the tree twice, independently.
|
|
51
|
+
*
|
|
52
|
+
* Not named `walkResult`: it returns no `Result`, and the walk has no
|
|
53
|
+
* failure channel to put in one. A hand-constructed tree nested past the
|
|
54
|
+
* depth cap surfaces as a terminal `MarkdownVisitorEvent` `Error` event
|
|
55
|
+
* carrying a `NestingDepthExceeded` {@link MarkdownDiagnostic}, after which
|
|
56
|
+
* nothing is yielded — the same posture as `visit`. A tree produced by
|
|
57
|
+
* `Markdown.parse` or decoded by `Mdast.fromMdast` can never trip it.
|
|
58
|
+
*
|
|
59
|
+
* @example
|
|
60
|
+
* ```ts
|
|
61
|
+
* import { Markdown, MarkdownVisitor } from "@effected/markdown";
|
|
62
|
+
* import { Result } from "effect";
|
|
63
|
+
*
|
|
64
|
+
* const root = Result.getOrThrow(Markdown.parseResult("# Hi\n\ntext\n"));
|
|
65
|
+
* const entered: Array<string> = [];
|
|
66
|
+
* for (const event of MarkdownVisitor.walk(root)) {
|
|
67
|
+
* if (event._tag === "Enter") entered.push(event.node.type);
|
|
68
|
+
* }
|
|
69
|
+
* console.log(entered);
|
|
70
|
+
* // => ["root", "heading", "text", "paragraph", "text"]
|
|
71
|
+
* ```
|
|
72
|
+
*
|
|
73
|
+
* @param root - The tree to walk.
|
|
74
|
+
* @returns A lazy, re-iterable `Iterable` of `MarkdownVisitorEvent`.
|
|
75
|
+
*/
|
|
76
|
+
static walk(root) {
|
|
77
|
+
return { [Symbol.iterator]() {
|
|
78
|
+
return walkNode(root, [], 0);
|
|
79
|
+
} };
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
38
82
|
* Create a lazy `Stream` of `MarkdownVisitorEvent` walking `root` in
|
|
39
83
|
* document pre-order: `Enter` and `Exit` for every node including the
|
|
40
84
|
* root and every leaf. Events are produced on demand, so combining with
|
|
@@ -44,28 +88,24 @@ var MarkdownVisitor = class {
|
|
|
44
88
|
* @remarks
|
|
45
89
|
* Infallible at the type level. A tree produced by `Markdown.parse` can
|
|
46
90
|
* never trip the walk's depth guard — the parser refuses deeper input —
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
91
|
+
* and neither can a decoded foreign tree (`Mdast.fromMdast`), because the
|
|
92
|
+
* decode boundary refuses it first; only a hand-constructed tree can trip
|
|
93
|
+
* the guard. The trip surfaces as a terminal `MarkdownVisitorEvent`
|
|
94
|
+
* `Error` event carrying a `NestingDepthExceeded` {@link MarkdownDiagnostic},
|
|
95
|
+
* mirroring the typed failure `Markdown.stringify` produces for the same
|
|
96
|
+
* tree.
|
|
97
|
+
*
|
|
98
|
+
* Defined as `Stream.fromIterable(MarkdownVisitor.walk(root))`: every run
|
|
99
|
+
* of the stream walks afresh, and the two forms never diverge.
|
|
51
100
|
*
|
|
52
101
|
* @param root - The tree to walk.
|
|
53
102
|
* @returns A lazy `Stream` of `MarkdownVisitorEvent`, infallible at the type
|
|
54
103
|
* level.
|
|
55
104
|
*/
|
|
56
105
|
static visit(root) {
|
|
57
|
-
return Stream.fromIterable(
|
|
106
|
+
return Stream.fromIterable(MarkdownVisitor.walk(root));
|
|
58
107
|
}
|
|
59
108
|
};
|
|
60
|
-
/** Lazy per-subscription iterable so each run of the stream walks afresh. */
|
|
61
|
-
function walkIterable(root) {
|
|
62
|
-
return { [Symbol.iterator]() {
|
|
63
|
-
return walk(root)[Symbol.iterator]();
|
|
64
|
-
} };
|
|
65
|
-
}
|
|
66
|
-
function* walk(root) {
|
|
67
|
-
yield* walkNode(root, [], 0);
|
|
68
|
-
}
|
|
69
109
|
function* walkNode(node, path, depth) {
|
|
70
110
|
if (depth > 256) {
|
|
71
111
|
yield MarkdownVisitorEvent.Error({
|
|
@@ -105,5 +145,4 @@ function* walkNode(node, path, depth) {
|
|
|
105
145
|
});
|
|
106
146
|
}
|
|
107
147
|
|
|
108
|
-
//#endregion
|
|
109
148
|
export { MarkdownVisitor, MarkdownVisitorEvent };
|
package/Mdast.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { Root } from "./MarkdownNode.js";
|
|
2
2
|
import { unescapeString } from "./internal/unescape.js";
|
|
3
|
-
import {
|
|
3
|
+
import { GuardExceeded, isGuardExceeded } from "./internal/carriers.js";
|
|
4
|
+
import { Effect, Predicate, Result, Schema, SchemaIssue } from "effect";
|
|
4
5
|
|
|
5
|
-
|
|
6
|
+
// src/Mdast.ts
|
|
6
7
|
/**
|
|
7
8
|
* Indicates that foreign mdast input failed to decode into the package's
|
|
8
9
|
* node classes.
|
|
@@ -286,10 +287,9 @@ const admittedFields = {
|
|
|
286
287
|
mdxTextExpression: ["value"],
|
|
287
288
|
mdxjsEsm: ["value"]
|
|
288
289
|
};
|
|
289
|
-
const
|
|
290
|
-
const completePoint = (value) => isRecord(value) && typeof value.line === "number" && typeof value.column === "number" && typeof value.offset === "number";
|
|
290
|
+
const completePoint = (value) => Predicate.isObject(value) && typeof value.line === "number" && typeof value.column === "number" && typeof value.offset === "number";
|
|
291
291
|
const normalizePosition = (value) => {
|
|
292
|
-
if (
|
|
292
|
+
if (Predicate.isObject(value) && completePoint(value.start) && completePoint(value.end)) {
|
|
293
293
|
const start = value.start;
|
|
294
294
|
const end = value.end;
|
|
295
295
|
return {
|
|
@@ -307,8 +307,9 @@ const normalizePosition = (value) => {
|
|
|
307
307
|
}
|
|
308
308
|
return sentinelPosition;
|
|
309
309
|
};
|
|
310
|
-
const normalizeNode = (value) => {
|
|
311
|
-
if (!
|
|
310
|
+
const normalizeNode = (value, depth = 0) => {
|
|
311
|
+
if (!Predicate.isObject(value) || typeof value.type !== "string") return value;
|
|
312
|
+
if (depth > 256) throw new GuardExceeded("NestingDepthExceeded", 256, depth, 0);
|
|
312
313
|
const type = value.type;
|
|
313
314
|
const format = frontmatterTypes.get(type);
|
|
314
315
|
if (format !== void 0) return {
|
|
@@ -321,7 +322,7 @@ const normalizeNode = (value) => {
|
|
|
321
322
|
type,
|
|
322
323
|
name: value.name,
|
|
323
324
|
attributes: Array.isArray(value.attributes) ? value.attributes.map(normalizeMdxAttribute) : value.attributes,
|
|
324
|
-
children: Array.isArray(value.children) ? value.children.map(normalizeNode) : value.children,
|
|
325
|
+
children: Array.isArray(value.children) ? value.children.map((child) => normalizeNode(child, depth + 1)) : value.children,
|
|
325
326
|
position: normalizePosition(value.position)
|
|
326
327
|
};
|
|
327
328
|
const admitted = admittedFields[type];
|
|
@@ -335,12 +336,12 @@ const normalizeNode = (value) => {
|
|
|
335
336
|
const code = normalized.value;
|
|
336
337
|
normalized.value = code.endsWith("\n") ? code : `${code}\n`;
|
|
337
338
|
}
|
|
338
|
-
if (Array.isArray(value.children)) normalized.children = value.children.map(normalizeNode);
|
|
339
|
+
if (Array.isArray(value.children)) normalized.children = value.children.map((child) => normalizeNode(child, depth + 1));
|
|
339
340
|
normalized.position = normalizePosition(value.position);
|
|
340
341
|
return normalized;
|
|
341
342
|
};
|
|
342
343
|
const normalizeMdxAttribute = (value) => {
|
|
343
|
-
if (!
|
|
344
|
+
if (!Predicate.isObject(value) || typeof value.type !== "string") return value;
|
|
344
345
|
if (value.type === "mdxJsxExpressionAttribute") return {
|
|
345
346
|
type: "mdxJsxExpressionAttribute",
|
|
346
347
|
value: value.value,
|
|
@@ -353,7 +354,7 @@ const normalizeMdxAttribute = (value) => {
|
|
|
353
354
|
name: value.name,
|
|
354
355
|
position: normalizePosition(value.position)
|
|
355
356
|
};
|
|
356
|
-
if (raw !== void 0) normalized.value =
|
|
357
|
+
if (raw !== void 0) normalized.value = Predicate.isObject(raw) && raw.type === "mdxJsxAttributeValueExpression" ? {
|
|
357
358
|
type: "mdxJsxAttributeValueExpression",
|
|
358
359
|
value: raw.value,
|
|
359
360
|
position: normalizePosition(raw.position)
|
|
@@ -452,12 +453,27 @@ var Mdast = class Mdast {
|
|
|
452
453
|
* plain tree built for `Markdown.stringify` can carry it straight in. A
|
|
453
454
|
* value outside `"canonical" | "literal"` fails the decode typed.
|
|
454
455
|
*
|
|
456
|
+
* **Nesting is capped at 256 levels below the root**, the same cap the
|
|
457
|
+
* parser and `Markdown.stringify` enforce. A tree with any node deeper
|
|
458
|
+
* than that, leaves included, fails typed with a `SchemaIssue.InvalidValue`
|
|
459
|
+
* issue before the schema decode runs, so hostile depth is never a defect.
|
|
460
|
+
* Because leaves count, a document the parser accepts can still be
|
|
461
|
+
* refused here: `"> ".repeat(255) + "x"` parses, but its `text` leaf sits
|
|
462
|
+
* at depth 257, and `Markdown.stringify` refuses that tree too.
|
|
463
|
+
*
|
|
455
464
|
* @param input - A plain mdast tree, typically a `root`.
|
|
456
465
|
* @returns A `Result` succeeding with the decoded {@link Root}, or
|
|
457
466
|
* failing with {@link MdastDecodeError} carrying the structured issue.
|
|
458
467
|
*/
|
|
459
468
|
static fromMdastResult(input) {
|
|
460
|
-
|
|
469
|
+
let normalized;
|
|
470
|
+
try {
|
|
471
|
+
normalized = normalizeNode(input);
|
|
472
|
+
} catch (error) {
|
|
473
|
+
if (!isGuardExceeded(error)) throw error;
|
|
474
|
+
return Result.fail(new MdastDecodeError({ issue: new SchemaIssue.InvalidValue({ message: error.message }) }));
|
|
475
|
+
}
|
|
476
|
+
return Result.mapError(decodeRoot(normalized), (error) => new MdastDecodeError({ issue: error.issue }));
|
|
461
477
|
}
|
|
462
478
|
/**
|
|
463
479
|
* Decode foreign plain mdast into the package's node classes. Defined in
|
|
@@ -520,5 +536,4 @@ function nodeText(node, includeImageAlt, includeHtml) {
|
|
|
520
536
|
return "";
|
|
521
537
|
}
|
|
522
538
|
|
|
523
|
-
//#endregion
|
|
524
539
|
export { Mdast, MdastDecodeError };
|
package/README.md
CHANGED
|
@@ -85,7 +85,7 @@ Every parse has a synchronous twin — `MarkdownDocument.parseResult` and `Markd
|
|
|
85
85
|
|
|
86
86
|
```ts
|
|
87
87
|
import { MarkdownDocument, MarkdownFormat } from "@effected/markdown";
|
|
88
|
-
import { Effect } from "effect";
|
|
88
|
+
import { Effect, Option } from "effect";
|
|
89
89
|
|
|
90
90
|
const source = `# Release notes
|
|
91
91
|
|
|
@@ -94,7 +94,7 @@ See the [changelog](./CHANGELOG.md).
|
|
|
94
94
|
|
|
95
95
|
const program = Effect.gen(function* () {
|
|
96
96
|
const doc = yield* MarkdownDocument.parse(source);
|
|
97
|
-
const label = doc.find("text")
|
|
97
|
+
const label = Option.getOrThrow(doc.find("text"));
|
|
98
98
|
return yield* MarkdownFormat.modifyToString(doc, label, "Release notes (2026)");
|
|
99
99
|
});
|
|
100
100
|
|
|
@@ -104,7 +104,7 @@ Effect.runPromise(program).then(console.log);
|
|
|
104
104
|
// See the [changelog](./CHANGELOG.md).
|
|
105
105
|
```
|
|
106
106
|
|
|
107
|
-
`find` walks the tree in document pre-order and returns the document's own node
|
|
107
|
+
`find` walks the tree in document pre-order and returns the document's own node inside an `Option` — `Option.none()` when nothing matches — so the match feeds `modify` by identity. Replacements are literal strings or node fragments, and both render through the canonical stringifier, so a modified document re-parses cleanly by construction — you cannot splice in raw markdown that reopens a fence or breaks a table.
|
|
108
108
|
|
|
109
109
|
`MarkdownFormat.format` handles the other half: conservative marker normalization, never content rewriting. It converts heading style, bullet character, emphasis marker, fence character, thematic-break character and code-block style, and skips any conversion that would not be safe rather than attempting it cleverly:
|
|
110
110
|
|
|
@@ -198,11 +198,11 @@ Effect.runPromise(program).then(console.log);
|
|
|
198
198
|
// [ "./CHANGELOG.md", "https://example.com/docs" ]
|
|
199
199
|
```
|
|
200
200
|
|
|
201
|
-
`headings` lists every heading wherever it sits, including inside blockquotes and list items. `sections` are delimited by root-level headings only, and each section's range spans its subsections, so the edit layer can splice a whole section out in one edit. `firstSection` and `sectionByHeading` find one `DocumentSection` without scanning `sections` yourself — the second matches on an exact trimmed heading string, a `RegExp`, or a predicate, and both accept `{ depth }` to restrict to one heading level:
|
|
201
|
+
`headings` lists every heading wherever it sits, including inside blockquotes and list items. `sections` are delimited by root-level headings only, and each section's range spans its subsections, so the edit layer can splice a whole section out in one edit. `firstSection` and `sectionByHeading` find one `DocumentSection` without scanning `sections` yourself — the second matches on an exact trimmed heading string, a `RegExp`, or a predicate, and both accept `{ depth }` to restrict to one heading level. Both return an `Option`, as every lookup in the kit does (the `frontmatter` accessor and `find` too):
|
|
202
202
|
|
|
203
203
|
```ts
|
|
204
204
|
import { MarkdownDocument } from "@effected/markdown";
|
|
205
|
-
import { Effect } from "effect";
|
|
205
|
+
import { Effect, Option } from "effect";
|
|
206
206
|
|
|
207
207
|
const source = `# Changelog
|
|
208
208
|
|
|
@@ -217,7 +217,10 @@ Initial release.
|
|
|
217
217
|
|
|
218
218
|
const program = Effect.gen(function* () {
|
|
219
219
|
const doc = yield* MarkdownDocument.parse(source);
|
|
220
|
-
return doc.sectionByHeading("1.2.3", { depth: 2 })
|
|
220
|
+
return Option.match(doc.sectionByHeading("1.2.3", { depth: 2 }), {
|
|
221
|
+
onNone: () => "no such release",
|
|
222
|
+
onSome: (section) => section.body.trim(),
|
|
223
|
+
});
|
|
221
224
|
});
|
|
222
225
|
|
|
223
226
|
Effect.runPromise(program).then(console.log);
|
|
@@ -226,7 +229,7 @@ Effect.runPromise(program).then(console.log);
|
|
|
226
229
|
|
|
227
230
|
`DocumentSection.body` is the section without its heading, and it is deliberately untrimmed — exactly the bytes `bodyRange` describes — so callers write `.body.trim()` when they want the tidy form.
|
|
228
231
|
|
|
229
|
-
For anything the accessors do not cover, `find` and `findAll` walk the whole tree in document pre-order. A string selector matches the node's `type` and narrows the result — `doc.findAll("heading")` is `ReadonlyArray<Heading>`, `doc.find("table")` is `Table
|
|
232
|
+
For anything the accessors do not cover, `find` and `findAll` walk the whole tree in document pre-order. A string selector matches the node's `type` and narrows the result — `doc.findAll("heading")` is `ReadonlyArray<Heading>`, `doc.find("table")` is `Option<Table>` — and a type-guard predicate narrows the same way. The nodes come back by identity, so `doc.findAll("heading")[1]` addresses the second heading for `MarkdownFormat.modify` without raw child indexing.
|
|
230
233
|
|
|
231
234
|
`MarkdownVisitor.visit` streams the same walk as `Enter`/`Exit` events carrying the node, its child-index path and its depth:
|
|
232
235
|
|
|
@@ -246,6 +249,21 @@ Effect.runPromise(program).then(console.log);
|
|
|
246
249
|
// "Exit:text", "Exit:emphasis", "Enter:text", "Exit:text", "Exit:paragraph", "Exit:root" ]
|
|
247
250
|
```
|
|
248
251
|
|
|
252
|
+
`MarkdownVisitor.walk` is the same walk as a synchronous, lazy `Iterable` — the primitive `visit` is defined in terms of — for code that cannot suspend, such as a lint rule a host calls synchronously. Each iteration walks afresh from the root, and a `for…of` that `break`s stops the walk:
|
|
253
|
+
|
|
254
|
+
```ts
|
|
255
|
+
import { Markdown, MarkdownVisitor } from "@effected/markdown";
|
|
256
|
+
import { Result } from "effect";
|
|
257
|
+
|
|
258
|
+
const root = Result.getOrThrow(Markdown.parseResult("# Title\n\nA *b* c\n"));
|
|
259
|
+
const headings: Array<number> = [];
|
|
260
|
+
for (const event of MarkdownVisitor.walk(root)) {
|
|
261
|
+
if (event._tag === "Enter" && event.node.type === "heading") headings.push(event.node.depth);
|
|
262
|
+
}
|
|
263
|
+
console.log(headings);
|
|
264
|
+
// [ 1 ]
|
|
265
|
+
```
|
|
266
|
+
|
|
249
267
|
## mdast interop
|
|
250
268
|
|
|
251
269
|
The node classes already use mdast's type names and field shapes; `Mdast.toMdast` strips the fidelity fields this package adds — bullet characters, fence style, ATX-versus-setext spelling — (keeping only a `Text` node's `escapeStyle` when set, see [Literal text](#literal-text)) and emits plain spec-valid mdast JSON that the remark ecosystem consumes directly, including `mdast-util-to-hast` if you want hast:
|
|
@@ -364,7 +382,7 @@ To *normalize* an existing document to different choices, use `MarkdownFormat` w
|
|
|
364
382
|
- `FrontmatterSource` — string-level frontmatter `split`/`join` over the same closed fence grammar, byte-exact boundaries, no parsing of value or body; for bodies the CommonMark engine should not touch.
|
|
365
383
|
- `MarkdownEdit` / `MarkdownRange` (with `applyAll`) — the non-mutating text-edit vocabulary, field-identical to `@effected/jsonc`'s, `@effected/yaml`'s and `@effected/toml`'s.
|
|
366
384
|
- `MarkdownFormat` — `format`/`formatToString` compute conservative marker-normalization edits; `modify`/`modifyToString` replace a node by identity through the canonical stringifier.
|
|
367
|
-
- `MarkdownVisitor` — walk a parsed tree as
|
|
385
|
+
- `MarkdownVisitor` — walk a parsed tree as `Enter`/`Exit` events with child-index paths and depth: synchronously as a lazy `Iterable` (`walk`), or as a `Stream` (`visit`).
|
|
368
386
|
- `Mdast` — `toMdast` projects to plain mdast JSON; `fromMdast`/`fromMdastResult` admit a foreign mdast tree with validation.
|
|
369
387
|
- `MarkdownFrontmatter.schema` plus the `YamlFrontmatter`, `TomlFrontmatter` and `JsonFrontmatter` codecs — typed frontmatter decoding over optional per-format peers, with four separately catchable failure modes and a `FrontmatterMissingReason` on the absent one.
|
|
370
388
|
- `SchemaResolver` — classify a frontmatter `$schema` declaration into `ByUrl`/`ByPath`/`Inline`/`ByName` and resolve names against a registry, with no IO and no dependencies.
|
package/TomlFrontmatter.js
CHANGED
|
@@ -2,7 +2,7 @@ import { FrontmatterDecodeError, FrontmatterEncodeError, FrontmatterFormatMismat
|
|
|
2
2
|
import { Effect } from "effect";
|
|
3
3
|
import { Toml } from "@effected/toml";
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
// src/TomlFrontmatter.ts
|
|
6
6
|
/**
|
|
7
7
|
* The toml frontmatter codec, over `@effected/toml`.
|
|
8
8
|
*
|
|
@@ -40,5 +40,4 @@ const TomlFrontmatter = {
|
|
|
40
40
|
})))
|
|
41
41
|
};
|
|
42
42
|
|
|
43
|
-
//#endregion
|
|
44
43
|
export { TomlFrontmatter };
|
package/YamlFrontmatter.js
CHANGED
|
@@ -2,7 +2,7 @@ import { FrontmatterDecodeError, FrontmatterEncodeError, FrontmatterFormatMismat
|
|
|
2
2
|
import { Effect } from "effect";
|
|
3
3
|
import { Yaml } from "@effected/yaml";
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
// src/YamlFrontmatter.ts
|
|
6
6
|
/**
|
|
7
7
|
* The yaml frontmatter codec, over `@effected/yaml`.
|
|
8
8
|
*
|
|
@@ -41,5 +41,4 @@ const YamlFrontmatter = {
|
|
|
41
41
|
})))
|
|
42
42
|
};
|
|
43
43
|
|
|
44
|
-
//#endregion
|
|
45
44
|
export { YamlFrontmatter };
|