diff --git a/.agents/guides/codebase-patterns.md b/.agents/guides/codebase-patterns.md index 9befada..0a02774 100644 --- a/.agents/guides/codebase-patterns.md +++ b/.agents/guides/codebase-patterns.md @@ -34,7 +34,7 @@ Three streaming modes, all event-well-formed (stack discipline): | Progressive | `parseChunked(chunks)` | Async block nodes | Streaming render | All event modes produce **range-first events**: text/token events carry -`startOffset`/`endOffset` into the `TextSource` rather than an extracted +`start_offset`/`end_offset` into the `TextSource` rather than an extracted `value` string. Consumers call `slice(source, evt)` to resolve text on demand. ## TextSource (`text_source.ts`) diff --git a/.agents/memory/GLOSSARY.md b/.agents/memory/GLOSSARY.md index b7e289b..e37dbee 100644 --- a/.agents/memory/GLOSSARY.md +++ b/.agents/memory/GLOSSARY.md @@ -22,7 +22,7 @@ optional `iterSlices`) that abstracts the backing text. Plain `string` satisfies it; rope trees, CRDTs, and append buffers can implement it too. - **Range-first events**: Event design where `text`/`token` events carry - `startOffset`/`endOffset` instead of a `value` string. Text is resolved + `start_offset`/`end_offset` instead of a `value` string. Text is resolved lazily via `slice(source, evt)`. Avoids per-event string allocation. - **Session**: Stateful wrapper (`createSession(source)`) around the stateless pipeline. Caches parse state, exposes streaming and incremental diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index daf9f3e..83a85fa 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -111,6 +111,12 @@ Names should be approachable and succinct, while still capturing: Naming conventions for this project: - AST node types: `WikistRoot`, `WikistNode`, `WikistParent`, `WikistLiteral` +- AST type discriminants: kebab-case (`'thematic-break'`, `'list-item'`, + `'external-link'`); single-word stays lowercase (`'heading'`, `'table'`) +- Event property keys: snake_case (`node_type`, `start_offset`, `end_offset`, + `token_type`) +- Constants/enums: UPPER_SNAKE_CASE keys and values (`TokenType.HEADING_MARKER` + → `'HEADING_MARKER'`) - Events: `WikitextEvent`, `EnterEvent`, `ExitEvent`, `TextEvent` - Tokens: `Token`, `TokenType` - Parsers: `tokenize()`, `blockParse()`, `inlineParse()` diff --git a/.github/instructions/testing.instructions.md b/.github/instructions/testing.instructions.md index de09dc7..a2c0169 100644 --- a/.github/instructions/testing.instructions.md +++ b/.github/instructions/testing.instructions.md @@ -107,8 +107,8 @@ fc.assert( fc.property(fc.string(), (s) => { const stack: string[] = []; for (const evt of events(s)) { - if (evt.type === "enter") stack.push(evt.nodeType); - if (evt.type === "exit") expect(stack.pop()).toBe(evt.nodeType); + if (evt.kind === "enter") stack.push(evt.node_type); + if (evt.kind === "exit") expect(stack.pop()).toBe(evt.node_type); } expect(stack).toEqual([]); }), diff --git a/.github/instructions/typescript.instructions.md b/.github/instructions/typescript.instructions.md index 7c7b95c..736ee7f 100644 --- a/.github/instructions/typescript.instructions.md +++ b/.github/instructions/typescript.instructions.md @@ -46,15 +46,21 @@ applyTo: "**/*.ts,**/*.tsx" ```ts export const TOKEN_KIND = { - TEXT: 'text', - HEADING: 'heading', + TEXT: 'TEXT', + HEADING: 'HEADING', } as const; export type TokenKind = typeof TOKEN_KIND[keyof typeof TOKEN_KIND]; ``` -- For string literal discriminants and object property keys, prefer - `kebab-case` or `snake_case` over `camelCase` when introducing new public - typing and serialized object shapes. + - Constant/enum keys and values can use UPPER_SNAKE_CASE, snake_case, kebab-case, and PascalCase (e.g., + `TokenType.HEADING_MARKER` → `'HEADING_MARKER'`). +- For AST node type discriminant strings (the `type`/`kind` field in interfaces/objects), + prefer `kebab-case` (e.g., `'thematic-break'`, `'list-item'`, + `'external-link'`). Single-word types stay lowercase (e.g., `'heading'`, + `'table'`). +- For object and interface property keys in public interfaces, prefer `snake_case` over + `camelCase` (e.g., `node_type`, `start_offset`, `sort_key`, `tag_name`, + `self_closing`). - Keep existing public keys stable unless a migration is explicitly approved. - Prefer `Iterable` / `AsyncIterable` in public APIs over arrays unless there’s a clear reason (performance counts as a valid reason). @@ -68,9 +74,9 @@ applyTo: "**/*.ts,**/*.tsx" | AST nodes | `Wikist*` | `WikistRoot`, `WikistNode`, `WikistParent`, `WikistLiteral` | | Concrete nodes | PascalCase noun | `Heading`, `Template`, `Wikilink`, `TableCell` | | Events | `*Event` | `WikitextEvent`, `EnterEvent`, `ExitEvent`, `TextEvent` | -| Tokens | `Token`, `TokenType` | `Token`, `TokenType.HEADING_MARKER` | -| Type guards | `is*()` | `isHeading()`, `isTemplate()`, `isParent()` | -| Builders | camelCase noun | `heading(level, children)`, `text(value)` | +| Tokens | `Token`, `TokenType` | `Token`, `TokenType.HEADING_MARKER` (value: `'HEADING_MARKER'`) | +| Type guards | `is*()` | `isHeading()`, `isTemplate()`, `isParent()` | +| Builders | camelCase noun | `heading(level, children)`, `text(value)` | ## Object copying diff --git a/docs/architecture.md b/docs/architecture.md index abc9ddd..cf1e280 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -68,10 +68,10 @@ discipline already used by raw tokens. | Variant | Fields | Meaning | |---------|--------|---------| -| `enter` | `nodeType`, `props`, `position` | Opens a node | -| `exit` | `nodeType`, `position` | Closes the matching node | -| `text` | `startOffset`, `endOffset`, `position` | Literal text range | -| `token` | `tokenType`, `startOffset`, `endOffset`, `position` | Raw token range | +| `enter` | `node_type`, `props`, `position` | Opens a node | +| `exit` | `node_type`, `position` | Closes the matching node | +| `text` | `start_offset`, `end_offset`, `position` | Literal text range | +| `token` | `token_type`, `start_offset`, `end_offset`, `position` | Raw token range | | `error` | `message`, `position` | Recovery diagnostic event | Consumers that need the string value call `slice(source, event)`. This avoids diff --git a/docs/wikist-spec.md b/docs/wikist-spec.md index 9e3c504..331bb42 100644 --- a/docs/wikist-spec.md +++ b/docs/wikist-spec.md @@ -74,14 +74,14 @@ Three base categories, mirroring unist: | `Wikilink` | Parent | (inline context) | `target: string` | | `ExternalLink` | Parent | (inline context) | `url: string` | | `ImageLink` | Parent | (inline context) | `target: string` | -| `CategoryLink` | Parent | (inline context) | `target: string`, `sortKey?: string` | +| `CategoryLink` | Parent | (inline context) | `target: string`, `sort_key?: string` | | `Template` | Parent | (inline context) | `name: string` | | `TemplateArgument` | Parent | Template | `name?: string` (named) or positional | | `Argument` | Parent | (inline context) | `name: string`, `default?: string` | | `ParserFunction` | Parent | (inline context) | `name: string` | | `MagicWord` | Void | (inline context) | `name: string` | | `BehaviorSwitch` | Void | (inline context) | `name: string` | -| `HtmlTag` | Parent | (inline context) | `tagName: string`, `selfClosing: boolean`, `attributes?: Record` | +| `HtmlTag` | Parent | (inline context) | `tag_name: string`, `self_closing: boolean`, `attributes?: Record` | | `HtmlEntity` | Literal | (inline context) | (none) | | `Text` | Literal | (any parent) | (none) | | `Nowiki` | Literal | (inline context) | (none) | @@ -138,7 +138,7 @@ A horizontal rule (`----` or more dashes at line start). ```ts interface ThematicBreak { - type: "thematicBreak"; + type: "thematic-break"; } ``` @@ -166,7 +166,7 @@ interface List { } interface ListItem { - type: "listItem"; + type: "list-item"; marker: string; // the raw marker characters, e.g. "**" or "#*" children: WikistNode[]; } @@ -178,17 +178,17 @@ Definition lists use `;` for terms and `:` for descriptions. ```ts interface DefinitionList { - type: "definitionList"; + type: "definition-list"; children: (DefinitionTerm | DefinitionDescription)[]; } interface DefinitionTerm { - type: "definitionTerm"; + type: "definition-term"; children: WikistNode[]; } interface DefinitionDescription { - type: "definitionDescription"; + type: "definition-description"; children: WikistNode[]; } ``` @@ -206,18 +206,18 @@ interface Table { } interface TableCaption { - type: "tableCaption"; + type: "table-caption"; children: WikistNode[]; } interface TableRow { - type: "tableRow"; + type: "table-row"; attributes?: string; children: TableCell[]; } interface TableCell { - type: "tableCell"; + type: "table-cell"; header: boolean; // true for ! cells, false for | cells attributes?: string; children: WikistNode[]; @@ -241,7 +241,7 @@ interface Italic { } interface BoldItalic { - type: "boldItalic"; + type: "bold-italic"; children: WikistNode[]; } ``` @@ -274,7 +274,7 @@ External links: `[https://example.com text]` or bare URLs. ```ts interface ExternalLink { - type: "externalLink"; + type: "external-link"; url: string; children: WikistNode[]; // display text } @@ -289,7 +289,7 @@ A leading colon (`[[:File:Foo.png]]`) produces a `Wikilink` instead of an ```ts interface ImageLink { - type: "imageLink"; + type: "image-link"; target: string; // filename including namespace children: WikistNode[]; // options and caption parts } @@ -305,9 +305,9 @@ category assignment. ```ts interface CategoryLink { - type: "categoryLink"; + type: "category-link"; target: string; - sortKey?: string; + sort_key?: string; } ``` @@ -327,7 +327,7 @@ interface Template { } interface TemplateArgument { - type: "templateArgument"; + type: "template-argument"; name?: string; // undefined for positional args children: WikistNode[]; // argument value } @@ -356,7 +356,7 @@ alone, without knowing MediaWiki configuration. ```ts interface ParserFunction { - type: "parserFunction"; + type: "parser-function"; name: string; // e.g. "#if", "#switch", "#invoke" children: TemplateArgument[]; } @@ -376,7 +376,7 @@ node type exists for consumers that perform this reclassification. ```ts interface MagicWord { - type: "magicWord"; + type: "magic-word"; name: string; // e.g. "PAGENAME", "CURRENTYEAR" } ``` @@ -388,7 +388,7 @@ Double-underscore switches: `__TOC__`, `__NOTOC__`, `__FORCETOC__`, ```ts interface BehaviorSwitch { - type: "behaviorSwitch"; + type: "behavior-switch"; name: string; // e.g. "TOC", "NOTOC" } ``` @@ -400,9 +400,9 @@ HTML tags in wikitext. Covers both standard HTML and MediaWiki extension tags ```ts interface HtmlTag { - type: "htmlTag"; - tagName: string; - selfClosing: boolean; + type: "html-tag"; + tag_name: string; + self_closing: boolean; attributes?: Record; children: WikistNode[]; // content between open and close tags } @@ -414,7 +414,7 @@ HTML character entities: `&`, `{`, `{`. ```ts interface HtmlEntity { - type: "htmlEntity"; + type: "html-entity"; value: string; // the raw entity text including & and ; } ```