satteri 0.11.1 → 0.11.4

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.
@@ -1,4 +1,4 @@
1
- import { ENABLE_DEFINITION_LIST, ENABLE_DIRECTIVE, ENABLE_FOOTNOTES, ENABLE_GFM, ENABLE_HEADING_ATTRIBUTES, ENABLE_MATH, ENABLE_MATH_MULTI_DOLLAR, ENABLE_PLUSES_DELIMITED_METADATA_BLOCKS, ENABLE_SMART_DASHES, ENABLE_SMART_ELLIPSES, ENABLE_SMART_PUNCTUATION, ENABLE_SMART_QUOTES, ENABLE_STRIKETHROUGH, ENABLE_SUBSCRIPT, ENABLE_SUPERSCRIPT, ENABLE_TABLES, ENABLE_TASKLISTS, ENABLE_WIKILINKS, ENABLE_YAML_STYLE_METADATA_BLOCKS, } from "./generated/parse-options.js";
1
+ import { ENABLE_ATTRIBUTES, ENABLE_DEFINITION_LIST, ENABLE_DIRECTIVE, ENABLE_FOOTNOTES, ENABLE_GFM, ENABLE_HEADING_ATTRIBUTES, ENABLE_MATH, ENABLE_MATH_MULTI_DOLLAR, ENABLE_PLUSES_DELIMITED_METADATA_BLOCKS, ENABLE_SMART_DASHES, ENABLE_SMART_ELLIPSES, ENABLE_SMART_PUNCTUATION, ENABLE_SMART_QUOTES, ENABLE_STRIKETHROUGH, ENABLE_SUBSCRIPT, ENABLE_SUPERSCRIPT, ENABLE_TABLES, ENABLE_TASKLISTS, ENABLE_WIKILINKS, ENABLE_YAML_STYLE_METADATA_BLOCKS, } from "./generated/parse-options.js";
2
2
  const GFM_PARSE_OPTIONS = ENABLE_TABLES | ENABLE_STRIKETHROUGH | ENABLE_TASKLISTS | ENABLE_GFM;
3
3
  const FRONTMATTER_PARSE_OPTIONS = ENABLE_YAML_STYLE_METADATA_BLOCKS | ENABLE_PLUSES_DELIMITED_METADATA_BLOCKS;
4
4
  export const DEFAULT_PARSE_OPTIONS = GFM_PARSE_OPTIONS | ENABLE_FOOTNOTES | FRONTMATTER_PARSE_OPTIONS;
@@ -24,6 +24,8 @@ function featuresToParseOptions(features) {
24
24
  }
25
25
  if (features.headingAttributes)
26
26
  options |= ENABLE_HEADING_ATTRIBUTES;
27
+ if (features.attributes)
28
+ options |= ENABLE_ATTRIBUTES;
27
29
  if (features.directive)
28
30
  options |= ENABLE_DIRECTIVE;
29
31
  if (features.superscript)
@@ -102,8 +102,20 @@ export interface Features {
102
102
  * ```
103
103
  */
104
104
  math?: boolean | MathOptions;
105
- /** Heading attributes (`# text { #id .class }`). Default: false. */
105
+ /**
106
+ * Heading attributes (`# text { #id .class }`). Default: false.
107
+ *
108
+ * @deprecated Use {@link Features.attributes}, which reads heading attributes
109
+ * as well as attributes on other elements.
110
+ */
106
111
  headingAttributes?: boolean;
112
+ /**
113
+ * Curly-brace attributes (`{#id .class key=value}`) on fenced code blocks
114
+ * (` ```js {.class} `), inline elements (`*em*{.class}`), and paragraphs,
115
+ * list items, lists, blockquotes, tables, table cells and thematic breaks,
116
+ * as well as on headings. Default: false.
117
+ */
118
+ attributes?: boolean;
107
119
  /** Colon-delimited container directive blocks (`:::`). Default: false. */
108
120
  directive?: boolean;
109
121
  /** Superscript (`^super^`). Default: false. */
package/dist/compile.js CHANGED
@@ -10,8 +10,8 @@ import { MdastReader } from "./mdast/mdast-reader.js";
10
10
  import { normalizePlugins } from "./plugin.js";
11
11
  export function markdownToHtml(source, options = {}) {
12
12
  const { features, fileURL, data = {} } = options;
13
- const mdastPlugins = normalizePlugins(options.mdastPlugins ?? [], "mdastPlugins", source, fileURL, "markdown", data);
14
- const hastPlugins = normalizePlugins(options.hastPlugins ?? [], "hastPlugins", source, fileURL, "markdown", data);
13
+ const mdastPlugins = normalizePlugins(options.mdastPlugins ?? [], "mdastPlugins", source, fileURL, "markdown", data, features);
14
+ const hastPlugins = normalizePlugins(options.hastPlugins ?? [], "hastPlugins", source, fileURL, "markdown", data, features);
15
15
  const hastMayHaveStubs = hastPlugins.length > 0;
16
16
  const { parseOptions, convertOptions: nativeConvertOptions } = featuresToNative(features);
17
17
  // Without plugins, one NAPI call avoids the handle lifecycle’s extra boundary crossings.
@@ -102,8 +102,8 @@ export function markdownToJs(source, options = {}) {
102
102
  function toJsImpl(source, options, mdx) {
103
103
  const { mdastPlugins: mdastInput = [], hastPlugins: hastInput = [], features, fileURL, data = {}, ...mdxFields } = options;
104
104
  const sourceFormat = mdx ? "mdx" : "markdown";
105
- const mdastPlugins = normalizePlugins(mdastInput, "mdastPlugins", source, fileURL, sourceFormat, data);
106
- const hastPlugins = normalizePlugins(hastInput, "hastPlugins", source, fileURL, sourceFormat, data);
105
+ const mdastPlugins = normalizePlugins(mdastInput, "mdastPlugins", source, fileURL, sourceFormat, data, features);
106
+ const hastPlugins = normalizePlugins(hastInput, "hastPlugins", source, fileURL, sourceFormat, data, features);
107
107
  const hastMayHaveStubs = hastPlugins.length > 0;
108
108
  const mdxOptions = mdxOptionsToNative(mdxFields);
109
109
  const { parseOptions, convertOptions: nativeConvertOptions } = featuresToNative(features);
@@ -239,7 +239,7 @@ function applyCollectedHastCommands(handle, collected, invalidateStubs) {
239
239
  function toMdastImpl(source, options, mdx) {
240
240
  const { features, fileURL, data = {} } = options;
241
241
  const sourceFormat = mdx ? "mdx" : "markdown";
242
- const mdastPlugins = normalizePlugins(options.mdastPlugins ?? [], "mdastPlugins", source, fileURL, sourceFormat, data);
242
+ const mdastPlugins = normalizePlugins(options.mdastPlugins ?? [], "mdastPlugins", source, fileURL, sourceFormat, data, features);
243
243
  const { parseOptions } = featuresToNative(features);
244
244
  if (mdastPlugins.length === 0) {
245
245
  const wire = parseMdastWire(source, parseOptions, mdx, options.position);
@@ -268,8 +268,8 @@ function toMdastImpl(source, options, mdx) {
268
268
  function toHastImpl(source, options, mdx) {
269
269
  const { features, fileURL, data = {} } = options;
270
270
  const sourceFormat = mdx ? "mdx" : "markdown";
271
- const mdastPlugins = normalizePlugins(options.mdastPlugins ?? [], "mdastPlugins", source, fileURL, sourceFormat, data);
272
- const hastPlugins = normalizePlugins(options.hastPlugins ?? [], "hastPlugins", source, fileURL, sourceFormat, data);
271
+ const mdastPlugins = normalizePlugins(options.mdastPlugins ?? [], "mdastPlugins", source, fileURL, sourceFormat, data, features);
272
+ const hastPlugins = normalizePlugins(options.hastPlugins ?? [], "hastPlugins", source, fileURL, sourceFormat, data, features);
273
273
  const { parseOptions, convertOptions } = featuresToNative(features);
274
274
  if (mdastPlugins.length === 0 && hastPlugins.length === 0) {
275
275
  const wire = parseHastWire(source, parseOptions, convertOptions, mdx, options.position);
@@ -17,3 +17,4 @@ export declare const ENABLE_SMART_QUOTES: number;
17
17
  export declare const ENABLE_SMART_DASHES: number;
18
18
  export declare const ENABLE_SMART_ELLIPSES: number;
19
19
  export declare const ENABLE_MATH_MULTI_DOLLAR: number;
20
+ export declare const ENABLE_ATTRIBUTES: number;
@@ -18,3 +18,4 @@ export const ENABLE_SMART_QUOTES = 1 << 18;
18
18
  export const ENABLE_SMART_DASHES = 1 << 19;
19
19
  export const ENABLE_SMART_ELLIPSES = 1 << 20;
20
20
  export const ENABLE_MATH_MULTI_DOLLAR = 1 << 23;
21
+ export const ENABLE_ATTRIBUTES = 1 << 24;
@@ -9,6 +9,7 @@ export declare const OP_BOOL = 8;
9
9
  export declare const OP_DATA = 9;
10
10
  export declare const OP_PROP = 10;
11
11
  export declare const OP_ALIGN = 11;
12
+ export declare const OP_POSITION = 12;
12
13
  export declare const OF_VALUE = 0;
13
14
  export declare const OF_URL = 1;
14
15
  export declare const OF_TITLE = 2;
@@ -10,6 +10,7 @@ export const OP_BOOL = 0x08;
10
10
  export const OP_DATA = 0x09;
11
11
  export const OP_PROP = 0x0a;
12
12
  export const OP_ALIGN = 0x0b;
13
+ export const OP_POSITION = 0x0c;
13
14
  export const OF_VALUE = 0;
14
15
  export const OF_URL = 1;
15
16
  export const OF_TITLE = 2;
@@ -126,6 +126,9 @@ function emitHastOp(w, node, isRoot, refs, document = false, allowRootRef = fals
126
126
  if (document && n.type.startsWith("mdx"))
127
127
  return true;
128
128
  w.open(type);
129
+ // A standalone document is only serialized, and has no source for a position to point into.
130
+ if (!document)
131
+ w.position(n.position);
129
132
  if (type === HAST_ELEMENT) {
130
133
  w.str(OF_TAGNAME, typeof n.tagName === "string" ? n.tagName : "div");
131
134
  const props = n.properties;
@@ -393,6 +393,7 @@ function emitMdastOp(w, node, isRoot, forReplace, refs, allowRootRef = false) {
393
393
  isCustom = true;
394
394
  }
395
395
  w.open(type);
396
+ w.position(n.position);
396
397
  if (isCustom)
397
398
  w.str(OF_NAME, n.type);
398
399
  if (typeof n.value === "string")
@@ -13,5 +13,8 @@ export declare class OpWriter extends ByteWriter {
13
13
  ref(id: number): void;
14
14
  align(codes: readonly number[]): void;
15
15
  keepChildren(): void;
16
+ /** Write a node's `position` so the node keeps it. Only lines and columns are
17
+ * sent: Rust derives the byte offsets the arena stores from them. */
18
+ position(value: unknown): void;
16
19
  }
17
20
  export declare function emitMdxAttr(w: OpWriter, a: Record<string, unknown>): void;
package/dist/op-stream.js CHANGED
@@ -1,6 +1,6 @@
1
1
  // Writing directly into CommandBuffer avoids an intermediate op-stream allocation and copy.
2
2
  import { ByteWriter } from "./byte-writer.js";
3
- import { OP_OPEN, OP_CLOSE, OP_REF, OP_KEEP_CHILDREN, OP_STR, OP_U8, OP_U32, OP_BOOL, OP_DATA, OP_PROP, OP_ALIGN, MDX_ATTR_BOOLEAN_PROP, MDX_ATTR_LITERAL_PROP, MDX_ATTR_EXPRESSION_PROP, MDX_ATTR_SPREAD, } from "./generated/wire-constants.js";
3
+ import { OP_OPEN, OP_CLOSE, OP_REF, OP_KEEP_CHILDREN, OP_STR, OP_U8, OP_U32, OP_BOOL, OP_DATA, OP_PROP, OP_ALIGN, OP_POSITION, MDX_ATTR_BOOLEAN_PROP, MDX_ATTR_LITERAL_PROP, MDX_ATTR_EXPRESSION_PROP, MDX_ATTR_SPREAD, } from "./generated/wire-constants.js";
4
4
  export { OF_VALUE, OF_URL, OF_TITLE, OF_ALT, OF_LANG, OF_META, OF_IDENTIFIER, OF_LABEL, OF_NAME, OF_REFERENCE_TYPE, OF_DEPTH, OF_CHECKED, OF_START, OF_ORDERED, OF_SPREAD, OF_TAGNAME, OF_EXPLICIT, PROP_STRING, PROP_BOOL_TRUE, PROP_BOOL_FALSE, PROP_SPACE_SEP, PROP_INT, PROP_NULL, PROP_TOKEN_LIST, } from "./generated/wire-constants.js";
5
5
  export class OpWriter extends ByteWriter {
6
6
  constructor(initialSize = 512) {
@@ -73,6 +73,33 @@ export class OpWriter extends ByteWriter {
73
73
  this.ensure(1);
74
74
  this.buf[this.n++] = OP_KEEP_CHILDREN;
75
75
  }
76
+ /** Write a node's `position` so the node keeps it. Only lines and columns are
77
+ * sent: Rust derives the byte offsets the arena stores from them. */
78
+ position(value) {
79
+ // Most re-encoded nodes are built by plugins and have no position: return before any work.
80
+ if (value == null)
81
+ return;
82
+ const { start, end } = value;
83
+ if (start == null || end == null)
84
+ return;
85
+ const { line: startLine, column: startColumn } = start;
86
+ const { line: endLine, column: endColumn } = end;
87
+ if (!isLineOrColumn(startLine) ||
88
+ !isLineOrColumn(startColumn) ||
89
+ !isLineOrColumn(endLine) ||
90
+ !isLineOrColumn(endColumn)) {
91
+ return;
92
+ }
93
+ this.ensure(17);
94
+ this.buf[this.n++] = OP_POSITION;
95
+ this.writeU32(startLine);
96
+ this.writeU32(startColumn);
97
+ this.writeU32(endLine);
98
+ this.writeU32(endColumn);
99
+ }
100
+ }
101
+ function isLineOrColumn(value) {
102
+ return Number.isInteger(value) && value >= 1 && value <= 4294967295;
76
103
  }
77
104
  export function emitMdxAttr(w, a) {
78
105
  if (a.type === "mdxJsxExpressionAttribute") {
package/dist/plugin.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import type { MdastPluginInstance } from "./mdast/mdast-visitor.js";
2
2
  import type { HastVisitorInstance } from "./hast/hast-visitor.js";
3
+ import type { Features } from "./compile-types.js";
3
4
  import type { Data, SourceFormat } from "./types.js";
4
5
  /**
5
6
  * What a plugin factory is told about the document, before it is parsed.
@@ -17,6 +18,10 @@ export interface PluginFactoryContext {
17
18
  readonly source: string;
18
19
  /** The document-level data bag, before any plugin has run. */
19
20
  readonly data: Data;
21
+ /** The `features` compile option, or `{}` (the defaults) when none was
22
+ * given. Pass it on to compile a fragment with the same syntax as the
23
+ * document. */
24
+ readonly features: Features;
20
25
  }
21
26
  type RequireAtLeastOne<T, K extends keyof T> = Omit<T, K> & {
22
27
  [P in K]-?: Required<Pick<T, P>> & Partial<Pick<T, Exclude<K, P>>>;
@@ -41,7 +46,7 @@ export type MdastPluginInput = MdastPluginEntry;
41
46
  /** Alias for {@link HastPluginEntry}. */
42
47
  export type HastPluginInput = HastPluginEntry;
43
48
  /** Resolve nested entries and invoke each factory once per compile. */
44
- export declare function normalizePlugins<D>(entries: readonly PluginEntry<D>[], option: string, source: string, fileURL: URL | undefined, sourceFormat: SourceFormat, data: Data): D[];
49
+ export declare function normalizePlugins<D>(entries: readonly PluginEntry<D>[], option: string, source: string, fileURL: URL | undefined, sourceFormat: SourceFormat, data: Data, features?: Features): D[];
45
50
  export declare function defineMdastPlugin<P extends MdastPluginDefinition>(definition: P): P;
46
51
  export declare function defineHastPlugin<P extends HastPluginDefinition>(definition: P): P;
47
52
  export {};
package/dist/plugin.js CHANGED
@@ -1,7 +1,7 @@
1
1
  // Bound recursive factories so a self-referential preset cannot overflow the stack.
2
2
  const MAX_FACTORY_DEPTH = 10;
3
3
  /** Resolve nested entries and invoke each factory once per compile. */
4
- export function normalizePlugins(entries, option, source, fileURL, sourceFormat, data) {
4
+ export function normalizePlugins(entries, option, source, fileURL, sourceFormat, data, features) {
5
5
  const out = [];
6
6
  // Built lazily so a list with no factories allocates no context.
7
7
  let ctx;
@@ -25,6 +25,7 @@ export function normalizePlugins(entries, option, source, fileURL, sourceFormat,
25
25
  // The parser drops a leading BOM, so `ctx.source` in a visitor lacks it too.
26
26
  source: source.startsWith("\uFEFF") ? source.slice(1) : source,
27
27
  data,
28
+ features: features ?? {},
28
29
  });
29
30
  walk(entry(ctx), factoryDepth - 1);
30
31
  return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "satteri",
3
- "version": "0.11.1",
3
+ "version": "0.11.4",
4
4
  "description": "High-performance Markdown and MDX processing",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -43,11 +43,14 @@
43
43
  "@emnapi/runtime": "^1.11.1",
44
44
  "@mdx-js/mdx": "^3.1.1",
45
45
  "@napi-rs/wasm-runtime": "^1.1.5",
46
+ "@types/markdown-it": "^14.2.0",
46
47
  "@types/node": "^26.2.0",
47
48
  "@types/react": "^19.2.18",
48
49
  "@types/react-dom": "^19.2.4",
49
50
  "fast-check": "^4.7.0",
50
51
  "hastscript": "^9.0.1",
52
+ "markdown-it": "^15.0.2",
53
+ "markdown-it-attrs": "^5.0.1",
51
54
  "mdast-util-mdx": "^3.0.0",
52
55
  "mdast-util-to-hast": "^13.2.1",
53
56
  "react": "^19.2.5",