From 6ed5578d99448e9c20baadec6f72aa14bc740537 Mon Sep 17 00:00:00 2001 From: Fuyeor Date: Mon, 21 Sep 2026 14:49:39 +0800 Subject: [PATCH 1/7] refactor: rename directory to singular form and update scope to @ffm --- .gitignore | 6 +- .localerc.json | 8 +- .vscode/settings.json | 7 +- .../markdown-parser-rust => crate}/Cargo.toml | 1 - .../markdown-parser-rust => crate}/src/ast.rs | 2 +- .../markdown-parser-rust => crate}/src/lib.rs | 2 +- .../src/linkify.rs | 2 +- .../src/parser.rs | 2 +- .../src/plain_text.rs | 2 +- .../src/render.rs | 2 +- .../src/rules.rs | 2 +- .../src/safety.rs | 2 +- .../src/state.rs | 2 +- .../vscode/.gitignore | 0 .../vscode/.vscode/launch.json | 0 LICENSE => extension/vscode/LICENSE | 2 +- .../vscode/example}/en.ffm | 0 .../vscode/example}/zh-hans.ffm | 0 .../vscode/extension/icon.png | Bin .../vscode/language-configuration.json | 0 .../vscode/package.json | 12 +- .../README.md => extension/vscode/readme.md | 0 .../vscode/src/extension.spec.ts | 2 +- .../vscode/src/extension.ts | 4 +- .../vscode/src/ffm-grammar.spec.ts | 2 +- .../vscode/src/vscode.mock.ts | 2 +- .../vscode/syntax}/ffm.tmLanguage.json | 0 .../vscode/tsconfig.json | 0 {fixtures => fixture}/ffm.json | 0 {fixtures => fixture}/linkify.json | 0 {fixtures => fixture}/markdown.json | 0 {fixtures => fixture}/safety.json | 0 packages/extensions/vscode/LICENSE => license | 2 +- package.json | 6 +- .../benchmark/fixture/linkify-it/link.txt | 0 .../benchmark/fixture/linkify-it/not-link.txt | 0 .../benchmark}/linkify.ts | 67 +++-- .../benchmark}/memory.ts | 20 +- .../benchmark}/package.json | 6 +- .../samples.ts => package/benchmark/sample.ts | 10 +- package/benchmark/speed.ts | 34 +++ .../converter}/bench/index.ts | 4 +- .../converter}/package.json | 4 +- .../README.md => package/converter/readme.md | 12 +- .../converter}/src/color.ts | 6 +- .../converter/src/constant.ts | 4 +- .../converter/src/fixture/spec.json | 0 .../converter}/src/index.spec.ts | 10 +- .../converter}/src/index.ts | 10 +- .../converter}/src/render.ts | 6 +- .../converter}/src/style.ts | 6 +- .../types.ts => package/converter/src/type.ts | 6 +- .../html2ffm => package/converter}/test.ts | 2 +- .../converter}/tsconfig.json | 0 .../converter}/tsconfig.test.json | 1 - .../formatter}/package.json | 2 +- .../README.md => package/formatter/readme.md | 6 +- .../formatter/src/block.ts | 15 +- .../formatter/src/constant.ts | 6 +- .../formatter/src/fixture/spec.json | 0 .../formatter}/src/index.spec.ts | 14 +- .../formatter}/src/index.ts | 18 +- .../formatter}/src/text.ts | 4 +- .../types.ts => package/formatter/src/type.ts | 5 +- .../formatter}/tsconfig.json | 2 +- {packages => package}/linkify/benchmark.js | 5 +- .../linkify/build/generate-tlds.ts | 23 +- {packages => package}/linkify/package.json | 2 +- {packages => package}/linkify/src/cases.json | 0 .../linkify/src/cc-tld-bitmap.ts | 5 +- .../linkify/src/index.spec.ts | 4 +- {packages => package}/linkify/src/index.ts | 11 +- {packages => package}/linkify/tsconfig.json | 0 .../lit-renderer}/package.json | 4 +- package/lit-renderer/src/index.ts | 3 + .../lit-renderer}/src/render.ts | 16 +- .../parser}/package.json | 12 +- .../parser}/src/core/color.ts | 2 +- .../parser}/src/core/parser.ts | 10 +- .../parser}/src/core/plain-text.ts | 24 +- .../parser}/src/core/render.ts | 4 +- .../parser}/src/core/state.ts | 2 +- .../parser}/src/core/url.ts | 2 +- .../parser}/src/default.ts | 24 +- .../parser/src/index.spec.ts | 7 +- .../parser}/src/index.ts | 18 +- .../parser/src/plugin}/ast.ts | 4 +- .../parser/src/plugin}/async-blocks.ts | 9 +- .../parser/src/plugin}/extensions.ts | 4 +- .../parser/src/plugin}/highlight.ts | 4 +- .../parser/src/plugin}/latex.ts | 4 +- .../parser/src/plugin}/twemoji.ts | 4 +- .../parser/src/rule}/blocks.ts | 4 +- .../rules => package/parser/src/rule}/ffm.ts | 16 +- .../parser/src/rule}/inlines.ts | 4 +- .../parser}/src/test/index.spec.ts | 9 +- .../parser/src/test/spec}/commonMark.json | 0 .../parser/src/test/spec}/githubMark.json | 0 .../types.ts => package/parser/src/type.ts | 7 +- .../parser}/tsconfig.json | 0 .../parser}/tsconfig.test.json | 1 - {packages => package}/playground/index.html | 0 {packages => package}/playground/package.json | 8 +- .../playground/public/assets/css/style.css | 0 .../playground/public/assets/example}/en.ffm | 0 .../playground/public/assets/example}/fr.ffm | 0 .../playground/public/assets/example}/ja.ffm | 0 .../public/assets/example}/zh-hans.ffm | 0 .../playground/public/favicon.svg | 0 .../playground/public/robots.txt | 0 .../src/@app/component/left-sidebar.vue | 20 +- .../playground/src/@app/config/locale.ts | 8 +- .../src/@app/config/sidebar/menu.ts | 4 +- .../playground/src/@app/http.ts | 0 .../playground/src/@app}/locale.json | 0 .../playground/src/@app}/router/index.ts | 8 +- {packages => package}/playground/src/App.vue | 2 +- .../src/home/component/campaign.vue | 6 +- .../playground/src/home/component/feature.vue | 4 +- .../playground/src/home/component/hero.vue | 4 +- .../src/home/component/image-placehoder.vue | 0 .../playground/src/home/component/intro.vue | 0 .../src/home/component/section-label.vue | 0 .../src/home/component/statement.vue | 4 +- .../playground/src/home/index.vue | 12 +- package/playground/src/main.ts | 21 ++ package/playground/src/playground/api.ts | 11 + .../src/playground/component/editor.vue | 17 +- .../src/playground/component/history.vue | 7 +- .../src/playground/component/preview.vue | 14 +- .../src/playground/component/share-modal.vue | 40 +-- .../src/playground/component/stats-bar.vue | 20 +- .../src/playground/component/toolbar.vue | 14 +- .../playground/composable}/useCompression.ts | 2 +- .../composable}/useDocumentStats.ts | 2 +- .../playground/composable}/useIndexedDb.ts | 4 +- .../composable}/useMarkdownEditor.ts | 4 +- .../composable}/useMarkdownHighlighter.ts | 2 +- .../composable}/usePlaygroundLink.ts | 2 +- .../composable}/usePlaygroundSource.ts | 2 +- .../playground/src/playground/index.vue | 40 +-- .../playground/tsconfig.json | 9 +- .../playground/vite.config.ts | 6 + .../vue-renderer}/package.json | 4 +- package/vue-renderer/src/index.ts | 2 + .../vue-renderer}/src/render.ts | 8 +- packages/benchmarks/speed.ts | 34 --- packages/markdown-parser-lit/src/index.ts | 3 - packages/markdown-parser-vue/src/index.ts | 2 - packages/playground/src/api/examples.ts | 11 - packages/playground/src/main.ts | 25 -- pnpm-lock.yaml | 267 +++++------------- pnpm-workspace.yaml | 3 +- README.md => readme.md | 12 +- vitest.config.ts | 2 +- 155 files changed, 569 insertions(+), 701 deletions(-) rename {packages/markdown-parser-rust => crate}/Cargo.toml (91%) rename {packages/markdown-parser-rust => crate}/src/ast.rs (99%) rename {packages/markdown-parser-rust => crate}/src/lib.rs (99%) rename {packages/markdown-parser-rust => crate}/src/linkify.rs (99%) rename {packages/markdown-parser-rust => crate}/src/parser.rs (99%) rename {packages/markdown-parser-rust => crate}/src/plain_text.rs (98%) rename {packages/markdown-parser-rust => crate}/src/render.rs (99%) rename {packages/markdown-parser-rust => crate}/src/rules.rs (99%) rename {packages/markdown-parser-rust => crate}/src/safety.rs (97%) rename {packages/markdown-parser-rust => crate}/src/state.rs (98%) rename {packages/extensions => extension}/vscode/.gitignore (100%) rename {packages/extensions => extension}/vscode/.vscode/launch.json (100%) rename LICENSE => extension/vscode/LICENSE (94%) rename {packages/extensions/vscode/examples => extension/vscode/example}/en.ffm (100%) rename {packages/extensions/vscode/examples => extension/vscode/example}/zh-hans.ffm (100%) rename {packages/extensions => extension}/vscode/extension/icon.png (100%) rename {packages/extensions => extension}/vscode/language-configuration.json (100%) rename {packages/extensions => extension}/vscode/package.json (87%) rename packages/extensions/vscode/README.md => extension/vscode/readme.md (100%) rename {packages/extensions => extension}/vscode/src/extension.spec.ts (98%) rename {packages/extensions => extension}/vscode/src/extension.ts (89%) rename {packages/extensions => extension}/vscode/src/ffm-grammar.spec.ts (98%) rename {packages/extensions => extension}/vscode/src/vscode.mock.ts (87%) rename {packages/extensions/vscode/syntaxes => extension/vscode/syntax}/ffm.tmLanguage.json (100%) rename {packages/extensions => extension}/vscode/tsconfig.json (100%) rename {fixtures => fixture}/ffm.json (100%) rename {fixtures => fixture}/linkify.json (100%) rename {fixtures => fixture}/markdown.json (100%) rename {fixtures => fixture}/safety.json (100%) rename packages/extensions/vscode/LICENSE => license (94%) rename packages/benchmarks/fixtures/linkify-it/links.txt => package/benchmark/fixture/linkify-it/link.txt (100%) rename packages/benchmarks/fixtures/linkify-it/not_links.txt => package/benchmark/fixture/linkify-it/not-link.txt (100%) rename {packages/benchmarks => package/benchmark}/linkify.ts (70%) rename {packages/benchmarks => package/benchmark}/memory.ts (74%) rename {packages/benchmarks => package/benchmark}/package.json (74%) rename packages/benchmarks/samples.ts => package/benchmark/sample.ts (92%) create mode 100644 package/benchmark/speed.ts rename {packages/html2ffm => package/converter}/bench/index.ts (94%) rename {packages/html2ffm => package/converter}/package.json (89%) rename packages/html2ffm/README.md => package/converter/readme.md (89%) rename {packages/html2ffm => package/converter}/src/color.ts (98%) rename packages/html2ffm/src/constants.ts => package/converter/src/constant.ts (98%) rename packages/html2ffm/src/fixtures/conversions.json => package/converter/src/fixture/spec.json (100%) rename {packages/html2ffm => package/converter}/src/index.spec.ts (80%) rename {packages/html2ffm => package/converter}/src/index.ts (80%) rename {packages/html2ffm => package/converter}/src/render.ts (99%) rename {packages/html2ffm => package/converter}/src/style.ts (96%) rename packages/html2ffm/src/types.ts => package/converter/src/type.ts (86%) rename {packages/html2ffm => package/converter}/test.ts (91%) rename {packages/html2ffm => package/converter}/tsconfig.json (100%) rename {packages/html2ffm => package/converter}/tsconfig.test.json (81%) rename {packages/markdown-formatter => package/formatter}/package.json (92%) rename packages/markdown-formatter/README.md => package/formatter/readme.md (81%) rename packages/markdown-formatter/src/blocks.ts => package/formatter/src/block.ts (96%) rename packages/markdown-formatter/src/constants.ts => package/formatter/src/constant.ts (79%) rename packages/markdown-formatter/src/fixtures/format.json => package/formatter/src/fixture/spec.json (100%) rename {packages/markdown-formatter => package/formatter}/src/index.spec.ts (65%) rename {packages/markdown-formatter => package/formatter}/src/index.ts (85%) rename {packages/markdown-formatter => package/formatter}/src/text.ts (98%) rename packages/markdown-formatter/src/types.ts => package/formatter/src/type.ts (83%) rename {packages/markdown-formatter => package/formatter}/tsconfig.json (70%) rename {packages => package}/linkify/benchmark.js (96%) rename {packages => package}/linkify/build/generate-tlds.ts (72%) rename {packages => package}/linkify/package.json (93%) rename {packages => package}/linkify/src/cases.json (100%) rename {packages => package}/linkify/src/cc-tld-bitmap.ts (78%) rename packages/linkify/src/cross-language-fixtures.spec.ts => package/linkify/src/index.spec.ts (88%) rename {packages => package}/linkify/src/index.ts (93%) rename {packages => package}/linkify/tsconfig.json (100%) rename {packages/markdown-parser-lit => package/lit-renderer}/package.json (79%) create mode 100644 package/lit-renderer/src/index.ts rename {packages/markdown-parser-lit => package/lit-renderer}/src/render.ts (93%) rename {packages/markdown-parser => package/parser}/package.json (55%) rename {packages/markdown-parser => package/parser}/src/core/color.ts (86%) rename {packages/markdown-parser => package/parser}/src/core/parser.ts (98%) rename {packages/markdown-parser => package/parser}/src/core/plain-text.ts (78%) rename {packages/markdown-parser => package/parser}/src/core/render.ts (98%) rename {packages/markdown-parser => package/parser}/src/core/state.ts (97%) rename {packages/markdown-parser => package/parser}/src/core/url.ts (92%) rename {packages/markdown-parser => package/parser}/src/default.ts (75%) rename packages/markdown-parser/src/cross-language-fixtures.spec.ts => package/parser/src/index.spec.ts (96%) rename {packages/markdown-parser => package/parser}/src/index.ts (76%) rename {packages/markdown-parser/src/plugins => package/parser/src/plugin}/ast.ts (92%) rename {packages/markdown-parser/src/plugins => package/parser/src/plugin}/async-blocks.ts (95%) rename {packages/markdown-parser/src/plugins => package/parser/src/plugin}/extensions.ts (95%) rename {packages/markdown-parser/src/plugins => package/parser/src/plugin}/highlight.ts (95%) rename {packages/markdown-parser/src/plugins => package/parser/src/plugin}/latex.ts (98%) rename {packages/markdown-parser/src/plugins => package/parser/src/plugin}/twemoji.ts (97%) rename {packages/markdown-parser/src/rules => package/parser/src/rule}/blocks.ts (99%) rename {packages/markdown-parser/src/rules => package/parser/src/rule}/ffm.ts (90%) rename {packages/markdown-parser/src/rules => package/parser/src/rule}/inlines.ts (98%) rename {packages/markdown-parser => package/parser}/src/test/index.spec.ts (93%) rename {packages/markdown-parser/src/test/specs => package/parser/src/test/spec}/commonMark.json (100%) rename {packages/markdown-parser/src/test/specs => package/parser/src/test/spec}/githubMark.json (100%) rename packages/markdown-parser/src/types.ts => package/parser/src/type.ts (94%) rename {packages/markdown-parser => package/parser}/tsconfig.json (100%) rename {packages/markdown-parser => package/parser}/tsconfig.test.json (78%) rename {packages => package}/playground/index.html (100%) rename {packages => package}/playground/package.json (79%) rename {packages => package}/playground/public/assets/css/style.css (100%) rename {packages/playground/public/assets/examples => package/playground/public/assets/example}/en.ffm (100%) rename {packages/playground/public/assets/examples => package/playground/public/assets/example}/fr.ffm (100%) rename {packages/playground/public/assets/examples => package/playground/public/assets/example}/ja.ffm (100%) rename {packages/playground/public/assets/examples => package/playground/public/assets/example}/zh-hans.ffm (100%) rename {packages => package}/playground/public/favicon.svg (100%) rename {packages => package}/playground/public/robots.txt (100%) rename packages/playground/src/layout/Left.vue => package/playground/src/@app/component/left-sidebar.vue (74%) rename packages/playground/src/config/locales.ts => package/playground/src/@app/config/locale.ts (51%) rename packages/playground/src/config/sidebar/menu.config.ts => package/playground/src/@app/config/sidebar/menu.ts (67%) rename packages/playground/src/api/index.ts => package/playground/src/@app/http.ts (100%) rename {packages/playground/src/locale => package/playground/src/@app}/locale.json (100%) rename {packages/playground/src => package/playground/src/@app}/router/index.ts (92%) rename {packages => package}/playground/src/App.vue (87%) rename packages/playground/src/components/Home/HomeCampaign.vue => package/playground/src/home/component/campaign.vue (94%) rename packages/playground/src/components/Home/HomeFeatures.vue => package/playground/src/home/component/feature.vue (97%) rename packages/playground/src/components/Home/HomeHero.vue => package/playground/src/home/component/hero.vue (97%) rename packages/playground/src/components/Home/HomeImagePlaceholder.vue => package/playground/src/home/component/image-placehoder.vue (100%) rename packages/playground/src/components/Home/HomeIntro.vue => package/playground/src/home/component/intro.vue (100%) rename packages/playground/src/components/Home/HomeSectionLabel.vue => package/playground/src/home/component/section-label.vue (100%) rename packages/playground/src/components/Home/HomeStatement.vue => package/playground/src/home/component/statement.vue (94%) rename packages/playground/src/views/Home.vue => package/playground/src/home/index.vue (90%) create mode 100644 package/playground/src/main.ts create mode 100644 package/playground/src/playground/api.ts rename packages/playground/src/components/Playground/PlaygroundEditor.vue => package/playground/src/playground/component/editor.vue (92%) rename packages/playground/src/components/Playground/DocumentHistory.vue => package/playground/src/playground/component/history.vue (98%) rename packages/playground/src/components/Playground/PlaygroundPreview.vue => package/playground/src/playground/component/preview.vue (88%) rename packages/playground/src/components/Playground/PlaygroundShare.vue => package/playground/src/playground/component/share-modal.vue (75%) rename packages/playground/src/components/Playground/DocumentStatsBar.vue => package/playground/src/playground/component/stats-bar.vue (80%) rename packages/playground/src/components/Playground/MarkdownToolbar.vue => package/playground/src/playground/component/toolbar.vue (92%) rename {packages/playground/src/composables => package/playground/src/playground/composable}/useCompression.ts (97%) rename {packages/playground/src/composables => package/playground/src/playground/composable}/useDocumentStats.ts (96%) rename {packages/playground/src/composables => package/playground/src/playground/composable}/useIndexedDb.ts (98%) rename {packages/playground/src/composables => package/playground/src/playground/composable}/useMarkdownEditor.ts (98%) rename {packages/playground/src/composables => package/playground/src/playground/composable}/useMarkdownHighlighter.ts (98%) rename {packages/playground/src/composables => package/playground/src/playground/composable}/usePlaygroundLink.ts (90%) rename {packages/playground/src/composables => package/playground/src/playground/composable}/usePlaygroundSource.ts (71%) rename packages/playground/src/views/Playground.vue => package/playground/src/playground/index.vue (86%) rename {packages => package}/playground/tsconfig.json (56%) rename {packages => package}/playground/vite.config.ts (68%) rename {packages/markdown-parser-vue => package/vue-renderer}/package.json (80%) create mode 100644 package/vue-renderer/src/index.ts rename {packages/markdown-parser-vue => package/vue-renderer}/src/render.ts (97%) delete mode 100644 packages/benchmarks/speed.ts delete mode 100644 packages/markdown-parser-lit/src/index.ts delete mode 100644 packages/markdown-parser-vue/src/index.ts delete mode 100644 packages/playground/src/api/examples.ts delete mode 100644 packages/playground/src/main.ts rename README.md => readme.md (77%) diff --git a/.gitignore b/.gitignore index efc251d..465a3ed 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,6 @@ **/node_modules **/dist **/out -**/tests.failed.json -packages/playground/public/assets/locale/* -**/target \ No newline at end of file +**/test.failed.json +**/target +package/playground/public/assets/locale/* \ No newline at end of file diff --git a/.localerc.json b/.localerc.json index dfa6dda..d2e3e4f 100644 --- a/.localerc.json +++ b/.localerc.json @@ -1,10 +1,10 @@ { "basic": { - "source": "packages/playground/node_modules/@fuyeor/locale/src/common.json", - "outDir": "packages/playground/public/assets/locale" + "source": "package/playground/node_modules/@fuyeor/locale/src/common.json", + "outDir": "package/playground/public/assets/locale" }, "main": { - "source": "packages/playground/src/locale/locale.json", - "outDir": "packages/playground/public/assets/locale" + "source": "package/playground/src/@app/locale.json", + "outDir": "package/playground/public/assets/locale" } } diff --git a/.vscode/settings.json b/.vscode/settings.json index af9c146..1bc62cf 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -15,9 +15,14 @@ "[javascript]": { "editor.defaultFormatter": "esbenp.prettier-vscode" }, + "[markdown]": { + "editor.formatOnSave": false, + "editor.formatOnType": false + }, "search.exclude": { "**/node_modules": true, "**/dist": true, - "**/build": true + "**/build": true, + "**/target": true } } diff --git a/packages/markdown-parser-rust/Cargo.toml b/crate/Cargo.toml similarity index 91% rename from packages/markdown-parser-rust/Cargo.toml rename to crate/Cargo.toml index 1a5fb92..3ddaa61 100644 --- a/packages/markdown-parser-rust/Cargo.toml +++ b/crate/Cargo.toml @@ -1,4 +1,3 @@ -# packages/markdown-parser-rust/Cargo.toml [package] name = "ffm-parser" version = "0.1.0" diff --git a/packages/markdown-parser-rust/src/ast.rs b/crate/src/ast.rs similarity index 99% rename from packages/markdown-parser-rust/src/ast.rs rename to crate/src/ast.rs index 02a88e9..b6cf84a 100644 --- a/packages/markdown-parser-rust/src/ast.rs +++ b/crate/src/ast.rs @@ -1,4 +1,4 @@ -// packages/markdown-parser-rust/src/ast.rs +// src/ast.rs use std::collections::BTreeMap; #[cfg(feature = "serde")] diff --git a/packages/markdown-parser-rust/src/lib.rs b/crate/src/lib.rs similarity index 99% rename from packages/markdown-parser-rust/src/lib.rs rename to crate/src/lib.rs index e795c1d..901b88e 100644 --- a/packages/markdown-parser-rust/src/lib.rs +++ b/crate/src/lib.rs @@ -1,4 +1,4 @@ -// packages/markdown-parser-rust/src/lib.rs +// src/lib.rs //! Fuyeor Flavored Markdown parser and safe HTML renderer for Rust. //! //! The implementation mirrors the rule order and AST semantics of the diff --git a/packages/markdown-parser-rust/src/linkify.rs b/crate/src/linkify.rs similarity index 99% rename from packages/markdown-parser-rust/src/linkify.rs rename to crate/src/linkify.rs index 9ac1581..a86a602 100644 --- a/packages/markdown-parser-rust/src/linkify.rs +++ b/crate/src/linkify.rs @@ -1,4 +1,4 @@ -// packages/markdown-parser-rust/src/linkify.rs +// src/linkify.rs /// A link match with byte offsets into the original UTF-8 string. #[derive(Clone, Debug, PartialEq, Eq)] diff --git a/packages/markdown-parser-rust/src/parser.rs b/crate/src/parser.rs similarity index 99% rename from packages/markdown-parser-rust/src/parser.rs rename to crate/src/parser.rs index aba575c..dd49196 100644 --- a/packages/markdown-parser-rust/src/parser.rs +++ b/crate/src/parser.rs @@ -1,4 +1,4 @@ -// packages/markdown-parser-rust/src/parser.rs +// src/parser.rs use std::cell::RefCell; use std::collections::HashMap; diff --git a/packages/markdown-parser-rust/src/plain_text.rs b/crate/src/plain_text.rs similarity index 98% rename from packages/markdown-parser-rust/src/plain_text.rs rename to crate/src/plain_text.rs index 0d44eaa..4d8df76 100644 --- a/packages/markdown-parser-rust/src/plain_text.rs +++ b/crate/src/plain_text.rs @@ -1,4 +1,4 @@ -// packages/markdown-parser-rust/src/plain_text.rs +// src/plain_text.rs use std::fmt::Write; use crate::ast::{AstNode, NodeType}; diff --git a/packages/markdown-parser-rust/src/render.rs b/crate/src/render.rs similarity index 99% rename from packages/markdown-parser-rust/src/render.rs rename to crate/src/render.rs index 58bf07f..1f9f691 100644 --- a/packages/markdown-parser-rust/src/render.rs +++ b/crate/src/render.rs @@ -1,4 +1,4 @@ -// packages/markdown-parser-rust/src/render.rs +// src/render.rs use std::fmt::Write; use crate::ast::{AstNode, NodeType}; diff --git a/packages/markdown-parser-rust/src/rules.rs b/crate/src/rules.rs similarity index 99% rename from packages/markdown-parser-rust/src/rules.rs rename to crate/src/rules.rs index 0d5646e..049751b 100644 --- a/packages/markdown-parser-rust/src/rules.rs +++ b/crate/src/rules.rs @@ -1,4 +1,4 @@ -// packages/markdown-parser-rust/src/rules.rs +// src/rules.rs use crate::ast::{Alignment, AstNode, NodeType}; use crate::parser::ParserContext; use crate::safety::{is_safe_color_value, is_safe_link_url}; diff --git a/packages/markdown-parser-rust/src/safety.rs b/crate/src/safety.rs similarity index 97% rename from packages/markdown-parser-rust/src/safety.rs rename to crate/src/safety.rs index 7d78909..9680bb6 100644 --- a/packages/markdown-parser-rust/src/safety.rs +++ b/crate/src/safety.rs @@ -1,4 +1,4 @@ -// packages/markdown-parser-rust/src/safety.rs +// src/safety.rs /// Validates links using the same relative-URL and protocol policy as FFM. pub fn is_safe_link_url(value: &str) -> bool { diff --git a/packages/markdown-parser-rust/src/state.rs b/crate/src/state.rs similarity index 98% rename from packages/markdown-parser-rust/src/state.rs rename to crate/src/state.rs index 0a94e09..0a05288 100644 --- a/packages/markdown-parser-rust/src/state.rs +++ b/crate/src/state.rs @@ -1,4 +1,4 @@ -// packages/markdown-parser-rust/src/state.rs +// src/state.rs use std::collections::HashMap; /// Holds normalized lines and the current block-parser position. diff --git a/packages/extensions/vscode/.gitignore b/extension/vscode/.gitignore similarity index 100% rename from packages/extensions/vscode/.gitignore rename to extension/vscode/.gitignore diff --git a/packages/extensions/vscode/.vscode/launch.json b/extension/vscode/.vscode/launch.json similarity index 100% rename from packages/extensions/vscode/.vscode/launch.json rename to extension/vscode/.vscode/launch.json diff --git a/LICENSE b/extension/vscode/LICENSE similarity index 94% rename from LICENSE rename to extension/vscode/LICENSE index f91ef59..e9c7e0d 100644 --- a/LICENSE +++ b/extension/vscode/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2026 Fuyeor +Copyright (c) 2026 Fuyeor Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the โ€œSoftwareโ€), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: diff --git a/packages/extensions/vscode/examples/en.ffm b/extension/vscode/example/en.ffm similarity index 100% rename from packages/extensions/vscode/examples/en.ffm rename to extension/vscode/example/en.ffm diff --git a/packages/extensions/vscode/examples/zh-hans.ffm b/extension/vscode/example/zh-hans.ffm similarity index 100% rename from packages/extensions/vscode/examples/zh-hans.ffm rename to extension/vscode/example/zh-hans.ffm diff --git a/packages/extensions/vscode/extension/icon.png b/extension/vscode/extension/icon.png similarity index 100% rename from packages/extensions/vscode/extension/icon.png rename to extension/vscode/extension/icon.png diff --git a/packages/extensions/vscode/language-configuration.json b/extension/vscode/language-configuration.json similarity index 100% rename from packages/extensions/vscode/language-configuration.json rename to extension/vscode/language-configuration.json diff --git a/packages/extensions/vscode/package.json b/extension/vscode/package.json similarity index 87% rename from packages/extensions/vscode/package.json rename to extension/vscode/package.json index 645ae78..9d4bd61 100644 --- a/packages/extensions/vscode/package.json +++ b/extension/vscode/package.json @@ -9,7 +9,7 @@ "repository": { "type": "git", "url": "https://github.com/Fuyeor/markdown-parser.git", - "directory": "packages/extensions/vscode" + "directory": "extension/vscode" }, "engines": { "vscode": "^1.80.0" @@ -37,7 +37,7 @@ { "language": "ffm", "scopeName": "text.ffm", - "path": "./syntaxes/ffm.tmLanguage.json" + "path": "./syntax/ffm.tmLanguage.json" } ], "configurationDefaults": { @@ -50,11 +50,9 @@ }, "files": [ "out", - "examples", - "syntaxes", + "syntax", "extension", - "language-configuration.json", - "LICENSE" + "language-configuration.json" ], "scripts": { "compile": "esbuild src/extension.ts --bundle --platform=node --format=cjs --external:vscode --outfile=out/extension.js", @@ -63,7 +61,7 @@ "test": "vitest run --config ../../vitest.config.ts src/*.spec.ts" }, "dependencies": { - "@fuyeor/markdown-formatter": "workspace:*" + "@ffm/formatter": "workspace:*" }, "devDependencies": { "@types/vscode": "^1.80.0", diff --git a/packages/extensions/vscode/README.md b/extension/vscode/readme.md similarity index 100% rename from packages/extensions/vscode/README.md rename to extension/vscode/readme.md diff --git a/packages/extensions/vscode/src/extension.spec.ts b/extension/vscode/src/extension.spec.ts similarity index 98% rename from packages/extensions/vscode/src/extension.spec.ts rename to extension/vscode/src/extension.spec.ts index 4a4c235..b8ac0b6 100644 --- a/packages/extensions/vscode/src/extension.spec.ts +++ b/extension/vscode/src/extension.spec.ts @@ -1,4 +1,4 @@ -// packages/vscode-extension/src/extension.spec.ts +// extension/vscode/src/extension.spec.ts import { beforeEach, describe, expect, it, vi } from 'vitest'; import * as vscode from 'vscode'; import { activate } from './extension'; diff --git a/packages/extensions/vscode/src/extension.ts b/extension/vscode/src/extension.ts similarity index 89% rename from packages/extensions/vscode/src/extension.ts rename to extension/vscode/src/extension.ts index ae9d4ad..4ad14a6 100644 --- a/packages/extensions/vscode/src/extension.ts +++ b/extension/vscode/src/extension.ts @@ -1,6 +1,6 @@ -// packages/vscode-extension/src/extension.ts +// extension/vscode/src/extension.ts import * as vscode from 'vscode'; -import { format } from '@fuyeor/markdown-formatter'; +import { format } from '@ffm/formatter'; /** Replace the complete document only when formatting changes its content. */ const formatter: vscode.DocumentFormattingEditProvider = { diff --git a/packages/extensions/vscode/src/ffm-grammar.spec.ts b/extension/vscode/src/ffm-grammar.spec.ts similarity index 98% rename from packages/extensions/vscode/src/ffm-grammar.spec.ts rename to extension/vscode/src/ffm-grammar.spec.ts index 8489701..f22375a 100644 --- a/packages/extensions/vscode/src/ffm-grammar.spec.ts +++ b/extension/vscode/src/ffm-grammar.spec.ts @@ -1,4 +1,4 @@ -// packages/vscode-extension/src/ffm-grammar.spec.ts +// extension/vscode/src/ffm-grammar.spec.ts import { readFileSync } from 'node:fs'; import { resolve } from 'node:path'; diff --git a/packages/extensions/vscode/src/vscode.mock.ts b/extension/vscode/src/vscode.mock.ts similarity index 87% rename from packages/extensions/vscode/src/vscode.mock.ts rename to extension/vscode/src/vscode.mock.ts index 61537ae..3721009 100644 --- a/packages/extensions/vscode/src/vscode.mock.ts +++ b/extension/vscode/src/vscode.mock.ts @@ -1,4 +1,4 @@ -// packages/vscode-extension/src/vscode.mock.ts +// extension/vscode/src/vscode.mock.ts import { vi } from 'vitest'; export class Range { diff --git a/packages/extensions/vscode/syntaxes/ffm.tmLanguage.json b/extension/vscode/syntax/ffm.tmLanguage.json similarity index 100% rename from packages/extensions/vscode/syntaxes/ffm.tmLanguage.json rename to extension/vscode/syntax/ffm.tmLanguage.json diff --git a/packages/extensions/vscode/tsconfig.json b/extension/vscode/tsconfig.json similarity index 100% rename from packages/extensions/vscode/tsconfig.json rename to extension/vscode/tsconfig.json diff --git a/fixtures/ffm.json b/fixture/ffm.json similarity index 100% rename from fixtures/ffm.json rename to fixture/ffm.json diff --git a/fixtures/linkify.json b/fixture/linkify.json similarity index 100% rename from fixtures/linkify.json rename to fixture/linkify.json diff --git a/fixtures/markdown.json b/fixture/markdown.json similarity index 100% rename from fixtures/markdown.json rename to fixture/markdown.json diff --git a/fixtures/safety.json b/fixture/safety.json similarity index 100% rename from fixtures/safety.json rename to fixture/safety.json diff --git a/packages/extensions/vscode/LICENSE b/license similarity index 94% rename from packages/extensions/vscode/LICENSE rename to license index f91ef59..e9c7e0d 100644 --- a/packages/extensions/vscode/LICENSE +++ b/license @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2026 Fuyeor +Copyright (c) 2026 Fuyeor Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the โ€œSoftwareโ€), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: diff --git a/package.json b/package.json index 6786f01..5537265 100644 --- a/package.json +++ b/package.json @@ -1,12 +1,12 @@ { - "name": "fuyeor-markdown-parser", + "name": "fuyeor-flavored-markdown", "license": "MIT", "author": "Fuyeor ", "packageManager": "pnpm@11.22.0+sha512.1ff870c4c6133dfd88fb2afc46dd13d47f09c9794b438c6fdb47ca98caf3bc16381ee0be93a091b8e3824cf01f889f46d7d9e20910fb0be1ab0fb5baa80dd621", "scripts": { "format": "prettier --write \"**/*.ts\"", - "test": "pnpm -F @fuyeor/markdown-parser test", - "playground": "pnpm -F @fuyeor/markdown-playground dev", + "test": "pnpm -r --parallel test", + "playground": "pnpm -F @ffm/playground dev", "locale": "locale-cli" }, "devDependencies": { diff --git a/packages/benchmarks/fixtures/linkify-it/links.txt b/package/benchmark/fixture/linkify-it/link.txt similarity index 100% rename from packages/benchmarks/fixtures/linkify-it/links.txt rename to package/benchmark/fixture/linkify-it/link.txt diff --git a/packages/benchmarks/fixtures/linkify-it/not_links.txt b/package/benchmark/fixture/linkify-it/not-link.txt similarity index 100% rename from packages/benchmarks/fixtures/linkify-it/not_links.txt rename to package/benchmark/fixture/linkify-it/not-link.txt diff --git a/packages/benchmarks/linkify.ts b/package/benchmark/linkify.ts similarity index 70% rename from packages/benchmarks/linkify.ts rename to package/benchmark/linkify.ts index f1180f5..9e97ea2 100644 --- a/packages/benchmarks/linkify.ts +++ b/package/benchmark/linkify.ts @@ -1,7 +1,7 @@ -// @fuyeor/markdown-parser-benchmark/linkify.ts +// @ffm/benchmark/linkify.ts import { readFileSync } from 'node:fs'; import { bench, group, run } from 'mitata'; -import { linkify } from '@fuyeor/linkify'; +import { linkify } from '@ffm/linkify'; import { LinkifyIt } from 'linkify-it'; type PositiveFixture = { @@ -28,36 +28,40 @@ type FixtureReport = { negative: FixtureChecks; }; -const linksFixture = readFileSync( - new URL('./fixtures/linkify-it/links.txt', import.meta.url), +const linkFixture = readFileSync( + new URL('./fixture/linkify-it/link.txt', import.meta.url), 'utf8', ); -const notLinksFixture = readFileSync( - new URL('./fixtures/linkify-it/not_links.txt', import.meta.url), +const notLinkFixture = readFileSync( + new URL('./fixture/linkify-it/not-link.txt', import.meta.url), 'utf8', ); -const linkifyIt = new LinkifyIt({ fuzzyLink: true, fuzzyIP: true, urlAuth: true }); +const linkifyIt = new LinkifyIt({ + fuzzyLink: true, + fuzzyIP: true, + urlAuth: true, +}); linkifyIt.normalize = () => {}; // Parse linkify-it's alternating input/expected-output fixture format. function parsePositiveFixtures(source: string): PositiveFixture[] { - const lines = source.split(/\r?\n/g); - const fixtures: PositiveFixture[] = []; + const line = source.split(/\r?\n/g); + const fixture: PositiveFixture[] = []; - for (let index = 0; index < lines.length; index++) { - const text = lines[index].replace(/^%.*/, ''); - const next = (lines[index + 1] ?? '').replace(/^%.*/, ''); + for (let index = 0; index < line.length; index++) { + const text = line[index].replace(/^%.*/, ''); + const next = (line[index + 1] ?? '').replace(/^%.*/, ''); if (!text.trim()) continue; if (next.trim()) { - fixtures.push({ line: index + 1, text, expected: next }); + fixture.push({ line: index + 1, text, expected: next }); index++; } else { - fixtures.push({ line: index + 1, text, expected: text }); + fixture.push({ line: index + 1, text, expected: text }); } } - return fixtures; + return fixture; } // Convert a fixture file into the exact text corpus used by both benchmarks. @@ -68,15 +72,15 @@ function fixtureCorpus(source: string): string { .join('\n'); } -// Check one implementation against a list of positive fixtures. +// Check one implementation against a list of positive fixture. function evaluatePositiveFixtures( - fixtures: PositiveFixture[], + fixture: PositiveFixture[], matcher: (text: string) => string[], ): FixtureChecks { const failures: FixtureFailure[] = []; let passed = 0; - for (const fixture of fixtures) { + for (const fixture of fixture) { const actual = matcher(fixture.text); if (actual.includes(fixture.expected)) { passed++; @@ -90,10 +94,10 @@ function evaluatePositiveFixtures( } } - return { total: fixtures.length, passed, failures }; + return { total: fixture.length, passed, failures }; } -// Check one implementation against the official negative fixtures. +// Check one implementation against the official negative fixture. function evaluateNegativeFixtures( source: string, matcher: (text: string) => string[], @@ -120,8 +124,11 @@ function evaluateNegativeFixtures( // Evaluate both official fixture files without requiring behavioral parity. function evaluateFixtures(matcher: (text: string) => string[]): FixtureReport { return { - positive: evaluatePositiveFixtures(parsePositiveFixtures(linksFixture), matcher), - negative: evaluateNegativeFixtures(notLinksFixture, matcher), + positive: evaluatePositiveFixtures( + parsePositiveFixtures(linkFixture), + matcher, + ), + negative: evaluateNegativeFixtures(notLinkFixture, matcher), }; } @@ -143,8 +150,8 @@ function summarizeReport(report: FixtureReport) { }; } -const positiveFixtures = parsePositiveFixtures(linksFixture); -const fixtureText = `${fixtureCorpus(linksFixture)}\n${fixtureCorpus(notLinksFixture)}`; +const positiveFixtures = parsePositiveFixtures(linkFixture); +const fixtureText = `${fixtureCorpus(linkFixture)}\n${fixtureCorpus(notLinkFixture)}`; const syntheticText = ` Here is a test with multiple links. Visit http://example.com or https://example.org. @@ -156,11 +163,11 @@ Links with punctuation: https://example.com/path., (https://example.org)! console.log( JSON.stringify( { - fixtureSource: 'markdown-it/linkify-it/test/fixtures', + fixtureSource: 'markdown-it/linkify-it/test/fixture', positiveCases: positiveFixtures.length, - negativeCases: evaluateNegativeFixtures(notLinksFixture, () => []).total, + negativeCases: evaluateNegativeFixtures(notLinkFixture, () => []).total, corpusBytes: Buffer.byteLength(fixtureText), - '@fuyeor/linkify': summarizeReport( + '@ffm/linkify': summarizeReport( evaluateFixtures((text) => linkify(text).map((match) => match.text)), ), 'linkify-it': summarizeReport( @@ -175,7 +182,7 @@ console.log( ); group('Synthetic corpus (276 KB)', () => { - bench('@fuyeor/linkify', () => { + bench('@ffm/linkify', () => { linkify(syntheticText); }); @@ -184,8 +191,8 @@ group('Synthetic corpus (276 KB)', () => { }); }); -group('Official linkify-it fixtures', () => { - bench('@fuyeor/linkify', () => { +group('Official linkify-it fixture', () => { + bench('@ffm/linkify', () => { linkify(fixtureText); }); diff --git a/packages/benchmarks/memory.ts b/package/benchmark/memory.ts similarity index 74% rename from packages/benchmarks/memory.ts rename to package/benchmark/memory.ts index 8545332..5b131bb 100644 --- a/packages/benchmarks/memory.ts +++ b/package/benchmark/memory.ts @@ -1,10 +1,10 @@ -// @fuyeor/markdown-parser-benchmark/memory.ts -// pnpm -F @fuyeor/markdown-parser-benchmark bench:memory +// @ffm/benchmark/memory.ts +// pnpm -F @ffm/benchmark bench:memory // @ts-nocheck -import { createFuyeorMarkdownParser, render } from '@fuyeor/markdown-parser'; +import { createFuyeorMarkdownParser, render } from '@ffm/parser'; import MarkdownIt from 'markdown-it'; import { marked } from 'marked'; -import { socialSample, blogSample } from './samples'; +import { social, article } from './samples'; // Polyfill for window if needed by some specific logic if (typeof window === 'undefined') global.window = globalThis; @@ -32,7 +32,7 @@ async function measure(name: string, content: string) { `\n--- Test Scenario: ${name} (${(content.length / 1024).toFixed(1)} KB) ---`, ); - // @fuyeor/markdown-parser + // @ffm/parser const ffmStart = getMemory(); let ffmResult = ffmIt(content); const ffmEnd = getMemory(); @@ -54,21 +54,21 @@ async function measure(name: string, content: string) { const mkEnd = getMemory(); const mkImpact = Math.max(0, mkEnd - mkStart); - console.log(`[@fuyeor/markdown-parser] Impact: ${ffmImpact.toFixed(2)} MB`); + console.log(`[@ffm/parser] Impact: ${ffmImpact.toFixed(2)} MB`); console.log(`[markdown-it] Impact: ${mdImpact.toFixed(2)} MB`); console.log(`[marked] Impact: ${mkImpact.toFixed(2)} MB`); const ratio = (mdImpact / (ffmImpact || 0.01)).toFixed(2); - console.log(`๐Ÿ“Š markdown-it relative to @fuyeor/markdown-parser: ${ratio}x`); + console.log(`๐Ÿ“Š markdown-it relative to @ffm/parser: ${ratio}x`); const mkRatio = (mkImpact / (ffmImpact || 0.01)).toFixed(2); - console.log(`๐Ÿ“Š marked relative to @fuyeor/markdown-parser: ${mkRatio}x`); + console.log(`๐Ÿ“Š marked relative to @ffm/parser: ${mkRatio}x`); } const runTests = async () => { console.log('๐Ÿš€ Starting Memory Benchmark...'); - await measure('Social', socialSample); - await measure('Blog', blogSample); + await measure('Social', social); + await measure('Blog', article); }; runTests(); diff --git a/packages/benchmarks/package.json b/package/benchmark/package.json similarity index 74% rename from packages/benchmarks/package.json rename to package/benchmark/package.json index 417d502..a16f689 100644 --- a/packages/benchmarks/package.json +++ b/package/benchmark/package.json @@ -1,10 +1,10 @@ { - "name": "@fuyeor/markdown-parser-benchmark", + "name": "@ffm/benchmark", "private": true, "type": "module", "dependencies": { - "@fuyeor/linkify": "workspace:*", - "@fuyeor/markdown-parser": "workspace:*", + "@ffm/linkify": "workspace:*", + "@ffm/parser": "workspace:*", "linkify-it": "^6.1.0", "markdown-it": "^14.0.2", "marked": "^18.0.2", diff --git a/packages/benchmarks/samples.ts b/package/benchmark/sample.ts similarity index 92% rename from packages/benchmarks/samples.ts rename to package/benchmark/sample.ts index 4e00ce7..48fe4c6 100644 --- a/packages/benchmarks/samples.ts +++ b/package/benchmark/sample.ts @@ -1,13 +1,13 @@ -// @fuyeor/markdown-parser-benchmark/samples.ts +// @ffm/benchmark/sample.ts /** * Social Media Scenario * Focus: Short, heavy formatting (bold, italic), many links, and mentions. */ -export const socialSample = ` -I'm currently focused on refining the benchmarking process for **@fuyeor/markdown-parser**. +export const social = ` +I'm currently focused on refining the benchmarking process for **@ffm/parser**. -I've initiated *memory* and *speed tests*, using files like \`@fuyeor/markdown-parser-benchmark/memory.ts\` and \`@fuyeor/markdown-parser-benchmark/speed.ts\`. +I've initiated *memory* and *speed tests*, using files like \`@ffm/benchmark/memory.ts\` and \`@ffm/benchmark/speed.ts\`. Further, I'm working to __create content scenarios__ like [social](https://fuyeor.com). @@ -18,7 +18,7 @@ Further, I'm working to __create content scenarios__ like [social](https://fuyeo * Blog Post Scenario * Focus: Structured headers, nested lists, code blocks, and tables. */ -export const blogSample = ` +export const article = ` ### The \`translate\` attribute [link](https://html.spec.whatwg.org/multipage/dom.html#the-translate-attribute) > The **\`translate\` attribute** is used to specify whether an element's attribute values and the text content of its \`Text\` node children should be translated when the page is localized, or left unchanged. It is an **[enumerated attribute](https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#enumerated-attribute)** with the following keywords and states: diff --git a/package/benchmark/speed.ts b/package/benchmark/speed.ts new file mode 100644 index 0000000..c143668 --- /dev/null +++ b/package/benchmark/speed.ts @@ -0,0 +1,34 @@ +// @ffm/benchmark/speed.ts +// pnpm -F @ffm/benchmark bench:speed +// @ts-nocheck +import { run, bench, group } from 'mitata'; +import { createFuyeorMarkdownParser, render } from '@ffm/parser'; +import MarkdownIt from 'markdown-it'; +import { marked } from 'marked'; +import { social, article } from './samples'; + +const ffm = createFuyeorMarkdownParser(); +const mdIt = new MarkdownIt({ linkify: true }); + +const ffmIt = (content: string) => { + const ast = ffm(content); + return render(ast); +}; + +// Benchmarking Social scenario +group('50 Social Post (Small/Frequent)', () => { + bench('@ffm/parser (AST)', () => ffm(social)); + bench('@ffm/parser (HTML)', () => ffmIt(social)); + bench('markdown-it (HTML)', () => mdIt.render(social)); + bench('marked (HTML)', () => marked.parse(social)); +}); + +// Benchmarking Blog scenario +group('20 Blog Post (Structured/Nested)', () => { + bench('@ffm/parser (AST)', () => ffm(article)); + bench('@ffm/parser (HTML)', () => ffmIt(article)); + bench('markdown-it (HTML)', () => mdIt.render(article)); + bench('marked (HTML)', () => marked.parse(article)); +}); + +await run(); diff --git a/packages/html2ffm/bench/index.ts b/package/converter/bench/index.ts similarity index 94% rename from packages/html2ffm/bench/index.ts rename to package/converter/bench/index.ts index cc711c0..8646a8d 100644 --- a/packages/html2ffm/bench/index.ts +++ b/package/converter/bench/index.ts @@ -1,5 +1,5 @@ -// packages/html2ffm/bench/index.ts -import { toFFM } from '@fuyeor/html2ffm'; +// @ffm/converter/bench/index.ts +import { toFFM } from '@ffm/converter'; const ITERATIONS = 1_000; const html = ` diff --git a/packages/html2ffm/package.json b/package/converter/package.json similarity index 89% rename from packages/html2ffm/package.json rename to package/converter/package.json index 29450d0..1af09d8 100644 --- a/packages/html2ffm/package.json +++ b/package/converter/package.json @@ -1,5 +1,5 @@ { - "name": "@fuyeor/html2ffm", + "name": "@ffm/converter", "version": "0.1.3", "description": "Convert HTML fragments to Fuyeor Flavored Markdown.", "license": "MIT", @@ -18,7 +18,7 @@ ".": "./src/index.ts" }, "dependencies": { - "@fuyeor/markdown-formatter": "workspace:*", + "@ffm/formatter": "workspace:*", "htmlparser2": "^10.1.0" }, "devDependencies": { diff --git a/packages/html2ffm/README.md b/package/converter/readme.md similarity index 89% rename from packages/html2ffm/README.md rename to package/converter/readme.md index 2810896..dd28b42 100644 --- a/packages/html2ffm/README.md +++ b/package/converter/readme.md @@ -1,11 +1,11 @@ -# @fuyeor/html2ffm +# @ffm/converter Convert HTML fragments into formatted [Fuyeor Flavored Markdown](https://reference.fuyeor.com/zh-hans/ffm/tutorials/syntax/basic) (FFM). ## Installation ```sh -pnpm add @fuyeor/html2ffm +pnpm add @ffm/converter ``` The package is source-first and can be consumed by modern Node.js or browser applications through a normal ESM import. @@ -13,7 +13,7 @@ The package is source-first and can be consumed by modern Node.js or browser app ## Usage ```ts -import { toFFM } from '@fuyeor/html2ffm'; +import { toFFM } from '@ffm/converter'; const output = toFFM('

Hello

World

'); @@ -43,17 +43,17 @@ The parser decodes HTML entities. Unknown ordinary element wrappers are removed Run the package tests: ```sh -pnpm --filter @fuyeor/html2ffm test +pnpm --filter @ffm/converter test ``` Run the package typecheck: ```sh -pnpm --filter @fuyeor/html2ffm typecheck +pnpm --filter @ffm/converter typecheck ``` Run the standalone benchmark: ```sh -pnpm --filter @fuyeor/html2ffm bench +pnpm --filter @ffm/converter bench ``` diff --git a/packages/html2ffm/src/color.ts b/package/converter/src/color.ts similarity index 98% rename from packages/html2ffm/src/color.ts rename to package/converter/src/color.ts index d8bb0b0..21c9150 100644 --- a/packages/html2ffm/src/color.ts +++ b/package/converter/src/color.ts @@ -1,6 +1,6 @@ -// @fuyeor/html2ffm/src/color.ts -import { cssNamedColors } from './constants'; -import type { Rgba } from './types'; +// @ffm/converter/src/color.ts +import { cssNamedColors } from './constant'; +import type { Rgba } from './type'; // Convert one clamped color channel to a two-digit lowercase hexadecimal value. function normalizeHexChannel(channel: number): string { diff --git a/packages/html2ffm/src/constants.ts b/package/converter/src/constant.ts similarity index 98% rename from packages/html2ffm/src/constants.ts rename to package/converter/src/constant.ts index c40d15d..ffbab2a 100644 --- a/packages/html2ffm/src/constants.ts +++ b/package/converter/src/constant.ts @@ -1,5 +1,5 @@ -// @fuyeor/html2ffm/src/constants.ts -import type { Marks } from './types'; +// @ffm/converter/src/constant.ts +import type { Marks } from './type'; export const blockElements = new Set([ 'article', diff --git a/packages/html2ffm/src/fixtures/conversions.json b/package/converter/src/fixture/spec.json similarity index 100% rename from packages/html2ffm/src/fixtures/conversions.json rename to package/converter/src/fixture/spec.json diff --git a/packages/html2ffm/src/index.spec.ts b/package/converter/src/index.spec.ts similarity index 80% rename from packages/html2ffm/src/index.spec.ts rename to package/converter/src/index.spec.ts index afe858d..27884ad 100644 --- a/packages/html2ffm/src/index.spec.ts +++ b/package/converter/src/index.spec.ts @@ -1,13 +1,13 @@ -// @fuyeor/html2ffm/src/index.spec.ts -// pnpm --filter @fuyeor/html2ffm test +// @ffm/converter/src/index.spec.ts +// pnpm --filter @ffm/converter test import { describe, expect, it } from 'vitest'; import { toFFM } from './index'; -import fixtures from './fixtures/conversions.json' with { type: 'json' }; +import fixtures from './fixture/spec.json' with { type: 'json' }; describe('toFFM conversions', () => { - for (const [section, cases] of Object.entries(fixtures)) { + for (const [section, item] of Object.entries(fixtures)) { describe(section, () => { - for (const [index, { desc, text, expect: expected }] of cases.entries()) { + for (const [index, { desc, text, expect: expected }] of item.entries()) { it(desc ?? `case ${index + 1}: ${text.slice(0, 30)}`, () => { expect(toFFM(text)).toBe(expected); }); diff --git a/packages/html2ffm/src/index.ts b/package/converter/src/index.ts similarity index 80% rename from packages/html2ffm/src/index.ts rename to package/converter/src/index.ts index 2633e6e..474a70d 100644 --- a/packages/html2ffm/src/index.ts +++ b/package/converter/src/index.ts @@ -1,14 +1,14 @@ -// @fuyeor/html2ffm/src/index.ts -import { format } from '@fuyeor/markdown-formatter'; +// @ffm/converter/src/index.ts +import { format } from '@ffm/formatter'; import { parseDocument } from 'htmlparser2'; import { renderFlow } from './render'; -import type { ParsedDocument, ToFFMOptions } from './types'; +import type { ParsedDocument, ToFFMOptions } from './type'; export * from './color'; -export * from './constants'; +export * from './constant'; export * from './render'; export * from './style'; -export * from './types'; +export * from './type'; /** Convert an HTML fragment into formatted Fuyeor Flavored Markdown. */ export function toFFM(input: string, options?: ToFFMOptions): string { diff --git a/packages/html2ffm/src/render.ts b/package/converter/src/render.ts similarity index 99% rename from packages/html2ffm/src/render.ts rename to package/converter/src/render.ts index 2986695..5a18fd8 100644 --- a/packages/html2ffm/src/render.ts +++ b/package/converter/src/render.ts @@ -1,4 +1,4 @@ -// @fuyeor/html2ffm/src/render.ts +// @ffm/converter/src/render.ts import { blockElements, unsafeSchemePattern, @@ -6,7 +6,7 @@ import { emptyMarks, headingPattern, safeSchemePattern, -} from './constants'; +} from './constant'; import { cloneMarks, cloneStyle, @@ -24,7 +24,7 @@ import type { Style, TableRow, TextNode, -} from './types'; +} from './type'; export function isElement(node: ChildNode): node is ElementNode { return 'name' in node && 'attribs' in node && 'children' in node; diff --git a/packages/html2ffm/src/style.ts b/package/converter/src/style.ts similarity index 96% rename from packages/html2ffm/src/style.ts rename to package/converter/src/style.ts index c6ee144..2c9f42a 100644 --- a/packages/html2ffm/src/style.ts +++ b/package/converter/src/style.ts @@ -1,7 +1,7 @@ -// @fuyeor/html2ffm/src/style.ts +// @ffm/converter/src/style.ts import { isTransparentColor, parseColor } from './color'; -import { cssLengthPattern } from './constants'; -import type { Marks, Style } from './types'; +import { cssLengthPattern } from './constant'; +import type { Marks, Style } from './type'; export function parseFontSize(value: string): string | null { const normalized = value.trim().replace(/\s*!important\s*$/iu, ''); diff --git a/packages/html2ffm/src/types.ts b/package/converter/src/type.ts similarity index 86% rename from packages/html2ffm/src/types.ts rename to package/converter/src/type.ts index d265d99..b17806f 100644 --- a/packages/html2ffm/src/types.ts +++ b/package/converter/src/type.ts @@ -1,5 +1,5 @@ -// @fuyeor/html2ffm/src/types.ts -import type { FormatOptions } from '@fuyeor/markdown-formatter'; +// @ffm/converter/src/type.ts +import type { FormatOption } from '@ffm/formatter'; import type { parseDocument } from 'htmlparser2'; export type ParsedDocument = ReturnType; @@ -48,4 +48,4 @@ export type TableRow = { }; /** Options for HTML to FFM conversion. */ -export type ToFFMOptions = FormatOptions; +export type ToFFMOptions = FormatOption; diff --git a/packages/html2ffm/test.ts b/package/converter/test.ts similarity index 91% rename from packages/html2ffm/test.ts rename to package/converter/test.ts index 3310bb8..d92fbdf 100644 --- a/packages/html2ffm/test.ts +++ b/package/converter/test.ts @@ -1,4 +1,4 @@ -// @fuyeor/html2ffm/src/test.ts +// @ffm/converter/src/test.ts // npx tsx test.ts import { toFFM } from './src/index'; diff --git a/packages/html2ffm/tsconfig.json b/package/converter/tsconfig.json similarity index 100% rename from packages/html2ffm/tsconfig.json rename to package/converter/tsconfig.json diff --git a/packages/html2ffm/tsconfig.test.json b/package/converter/tsconfig.test.json similarity index 81% rename from packages/html2ffm/tsconfig.test.json rename to package/converter/tsconfig.test.json index cd95de0..fac772b 100644 --- a/packages/html2ffm/tsconfig.test.json +++ b/package/converter/tsconfig.test.json @@ -1,4 +1,3 @@ -// packages/html2ffm/tsconfig.test.json { "extends": "./tsconfig.json", "compilerOptions": { diff --git a/packages/markdown-formatter/package.json b/package/formatter/package.json similarity index 92% rename from packages/markdown-formatter/package.json rename to package/formatter/package.json index 5e5b8ee..b55a719 100644 --- a/packages/markdown-formatter/package.json +++ b/package/formatter/package.json @@ -1,5 +1,5 @@ { - "name": "@fuyeor/markdown-formatter", + "name": "@ffm/formatter", "version": "0.1.2", "description": "Formatter for Fuyeor Flavored Markdown documents.", "license": "MIT", diff --git a/packages/markdown-formatter/README.md b/package/formatter/readme.md similarity index 81% rename from packages/markdown-formatter/README.md rename to package/formatter/readme.md index 07dae16..f53f55e 100644 --- a/packages/markdown-formatter/README.md +++ b/package/formatter/readme.md @@ -1,4 +1,4 @@ -@fuyeor/markdown-formatter โ€” A lightweight, deterministic Markdown and FFM (FuYeor Flavored Markdown) formatter with built-in CJK typographic spacing, table alignment, list indentation normalization, and semantic fence processing. +@ffm/formatter โ€” A lightweight, deterministic Markdown and FFM (Fuyeor Flavored Markdown) formatter with built-in CJK typographic spacing, table alignment, list indentation normalization, and semantic fence processing. ## Features @@ -12,7 +12,7 @@ ## Quick Start ```ts -import { format } from '@fuyeor/markdown-formatter'; +import { format } from '@ffm/formatter'; const markdown = ` # Title @@ -49,7 +49,7 @@ console.log(formatted); ### Example with Options ```ts -import { format } from '@fuyeor/markdown-formatter'; +import { format } from '@ffm/formatter'; const source = 'First paragraph\n\n\n\nSecond paragraph'; diff --git a/packages/markdown-formatter/src/blocks.ts b/package/formatter/src/block.ts similarity index 96% rename from packages/markdown-formatter/src/blocks.ts rename to package/formatter/src/block.ts index 1f3cfa6..591f722 100644 --- a/packages/markdown-formatter/src/blocks.ts +++ b/package/formatter/src/block.ts @@ -1,12 +1,7 @@ -// @fuyeor/markdown-formatter/src/blocks.ts -import { semanticFenceLanguages } from './constants'; +// @ffm/formatter/src/block.ts +import { semanticFenceLanguages } from './constant'; import { formatText } from './text'; -import type { - Fence, - FormatOptions, - ListIndentContext, - QuoteLine, -} from './types'; +import type { Fence, FormatOption, ListIndentContext, QuoteLine } from './type'; /** Normalize line endings before applying deterministic line-based formatting. */ export function normalizeLineEndings(content: string): string { @@ -235,8 +230,8 @@ export function formatSemanticFence( lines: readonly string[], start: number, fence: Fence, - formatFn: (content: string, options?: FormatOptions) => string, - options?: FormatOptions, + formatFn: (content: string, options?: FormatOption) => string, + options?: FormatOption, ): { lines: string[]; next: number } | null { if (!fence.language || !semanticFenceLanguages.has(fence.language)) { return null; diff --git a/packages/markdown-formatter/src/constants.ts b/package/formatter/src/constant.ts similarity index 79% rename from packages/markdown-formatter/src/constants.ts rename to package/formatter/src/constant.ts index c4ac029..aa0c0a4 100644 --- a/packages/markdown-formatter/src/constants.ts +++ b/package/formatter/src/constant.ts @@ -1,6 +1,6 @@ -// @fuyeor/markdown-formatter/src/constants.ts +// @ffm/formatter/src/constant.ts // NOTE: Avoid medieval SCREAMING_SNAKE_CASE; use camelCase for modern readability. -import type { FormatOptions } from './types'; +import type { FormatOption } from './type'; export const cjkCharacter = '\\p{Script=Han}'; export const latinOrDigit = 'A-Za-z0-9'; @@ -18,6 +18,6 @@ export const semanticFenceLanguages = new Set([ ]); /** Default formatting configuration options. */ -export const defaultFormatOptions: Readonly> = { +export const defaultFormatOption: Readonly> = { maxConsecutiveBlankLines: 1, }; diff --git a/packages/markdown-formatter/src/fixtures/format.json b/package/formatter/src/fixture/spec.json similarity index 100% rename from packages/markdown-formatter/src/fixtures/format.json rename to package/formatter/src/fixture/spec.json diff --git a/packages/markdown-formatter/src/index.spec.ts b/package/formatter/src/index.spec.ts similarity index 65% rename from packages/markdown-formatter/src/index.spec.ts rename to package/formatter/src/index.spec.ts index 91912d7..db098be 100644 --- a/packages/markdown-formatter/src/index.spec.ts +++ b/package/formatter/src/index.spec.ts @@ -1,13 +1,13 @@ -// @fuyeor/markdown-formatter/src/index.spec.ts -// pnpm --filter @fuyeor/markdown-formatter test +// @ffm/formatter/src/index.spec.ts +// pnpm --filter @ffm/formatter test import { describe, expect, it } from 'vitest'; import { format } from './index'; -import fixtures from './fixtures/format.json' with { type: 'json' }; +import fixture from './fixture/spec.json' with { type: 'json' }; -describe('format fixtures', () => { - for (const fixture of fixtures) { - it(fixture.section, () => { - expect(format(fixture.origin)).toBe(fixture.formatted); +describe('format fixture', () => { + for (const item of fixture) { + it(item.section, () => { + expect(format(item.origin)).toBe(item.formatted); }); } diff --git a/packages/markdown-formatter/src/index.ts b/package/formatter/src/index.ts similarity index 85% rename from packages/markdown-formatter/src/index.ts rename to package/formatter/src/index.ts index 0177602..7cbdc4f 100644 --- a/packages/markdown-formatter/src/index.ts +++ b/package/formatter/src/index.ts @@ -1,4 +1,4 @@ -// @fuyeor/markdown-formatter/src/index.ts +// @ffm/formatter/src/index.ts import { formatDeepQuote, formatOrdinaryLine, @@ -8,23 +8,23 @@ import { isFenceClose, normalizeLineEndings, trimDocumentBoundary, -} from './blocks'; -import { defaultFormatOptions } from './constants'; -import type { Fence, FormatOptions, ListIndentContext } from './types'; +} from './block'; +import { defaultFormatOption } from './constant'; +import type { Fence, FormatOption, ListIndentContext } from './type'; -export * from './blocks'; -export * from './constants'; +export * from './block'; +export * from './constant'; export * from './text'; -export * from './types'; +export * from './type'; /** Format a complete FFM document according to the editor's canonical style. */ -export function format(content: string, options?: FormatOptions): string { +export function format(content: string, options?: FormatOption): string { if (typeof content !== 'string') throw new TypeError('content must be a string'); const maxBlank = options?.maxConsecutiveBlankLines ?? - defaultFormatOptions.maxConsecutiveBlankLines; + defaultFormatOption.maxConsecutiveBlankLines; if (!Number.isInteger(maxBlank) || maxBlank < 0) throw new TypeError( diff --git a/packages/markdown-formatter/src/text.ts b/package/formatter/src/text.ts similarity index 98% rename from packages/markdown-formatter/src/text.ts rename to package/formatter/src/text.ts index 09cf329..503eee4 100644 --- a/packages/markdown-formatter/src/text.ts +++ b/package/formatter/src/text.ts @@ -1,9 +1,9 @@ -// @fuyeor/markdown-formatter/src/text.ts +// @ffm/formatter/src/text.ts import { cjkLatinBoundary, inlineMarkupPattern, linkTargetPattern, -} from './constants'; +} from './constant'; /** Check whether a single character is a Han-script character. */ export function isCjkCharacter(character: string | undefined): boolean { diff --git a/packages/markdown-formatter/src/types.ts b/package/formatter/src/type.ts similarity index 83% rename from packages/markdown-formatter/src/types.ts rename to package/formatter/src/type.ts index 020e910..4c80934 100644 --- a/packages/markdown-formatter/src/types.ts +++ b/package/formatter/src/type.ts @@ -1,5 +1,4 @@ -// @fuyeor/markdown-formatter/src/types.ts - +// @ffm/formatter/src/type.ts export type Fence = { character: '`' | '~'; length: number; @@ -15,7 +14,7 @@ export type ListIndentContext = { }; /** Configuration options for the Markdown formatter. */ -export type FormatOptions = { +export type FormatOption = { /** Maximum allowable consecutive blank lines between blocks. @default 1 */ maxConsecutiveBlankLines?: number; }; diff --git a/packages/markdown-formatter/tsconfig.json b/package/formatter/tsconfig.json similarity index 70% rename from packages/markdown-formatter/tsconfig.json rename to package/formatter/tsconfig.json index 6c7b3e6..7257dd9 100644 --- a/packages/markdown-formatter/tsconfig.json +++ b/package/formatter/tsconfig.json @@ -1,5 +1,5 @@ { - "extends": "../markdown-parser/tsconfig.json", + "extends": "../parser/tsconfig.json", "compilerOptions": { "rootDir": "src", "noEmit": true diff --git a/packages/linkify/benchmark.js b/package/linkify/benchmark.js similarity index 96% rename from packages/linkify/benchmark.js rename to package/linkify/benchmark.js index e75d6ba..172eb2f 100644 --- a/packages/linkify/benchmark.js +++ b/package/linkify/benchmark.js @@ -1,4 +1,4 @@ -// packages/linkify/benchmark.js +// @ffm/linkify/benchmark.js import { statSync } from 'node:fs'; const { linkify } = await import('./dist/index.js'); @@ -40,6 +40,7 @@ console.log( p95Ms: Number(percentile(0.95).toFixed(3)), averageMs: Number((sum / samples.length).toFixed(3)), distBytes: statSync(new URL('./dist/index.js', import.meta.url)).size, - declarationBytes: statSync(new URL('./dist/index.d.ts', import.meta.url)).size, + declarationBytes: statSync(new URL('./dist/index.d.ts', import.meta.url)) + .size, }), ); diff --git a/packages/linkify/build/generate-tlds.ts b/package/linkify/build/generate-tlds.ts similarity index 72% rename from packages/linkify/build/generate-tlds.ts rename to package/linkify/build/generate-tlds.ts index 063c4b2..1631855 100644 --- a/packages/linkify/build/generate-tlds.ts +++ b/package/linkify/build/generate-tlds.ts @@ -3,7 +3,7 @@ import { mkdirSync, writeFileSync } from 'node:fs'; import { dirname, resolve } from 'node:path'; // md is intentionally omitted so Markdown filenames are not fuzzy links. -const CC_TLDS = ` +const ccTld = ` ac ad ae af ag ai al am ao aq ar as at au aw ax az ba bb bd be bf bg bh bi bj bm bn bo br bs bt bv bw by bz ca cc cd cf cg ch ci ck cl cm cn co cr cu cv cw cx cy cz @@ -33,12 +33,12 @@ za zm zw .trim() .split(/\s+/); -const OUTPUT_PATH = resolve(import.meta.dirname, '../src/cc-tld-bitmap.ts'); -const BIT_COUNT = 26 * 26; -const WORD_COUNT = Math.ceil(BIT_COUNT / 32); -const bitmap = new Uint32Array(WORD_COUNT); +const bitCount = 26 * 26; +const wordCount = Math.ceil(bitCount / 32); +const bitmap = new Uint32Array(wordCount); +const outputPath = resolve(import.meta.dirname, '../src/cc-tld-bitmap.ts'); -for (const tld of CC_TLDS) { +for (const tld of ccTld) { if (!/^[a-z]{2}$/.test(tld) || tld === 'md') { throw new Error(`Invalid ccTLD entry: ${tld}`); } @@ -48,12 +48,11 @@ for (const tld of CC_TLDS) { } const words = Array.from(bitmap, (word) => `0x${word.toString(16)}`); -const source = `// packages/linkify/src/cc-tld-bitmap.ts +const source = `// @ffm/linkify/src/cc-tld-bitmap.ts // This file is generated by build/generate-tlds.ts. Do not edit manually. - -export const CC_TLD_BITMAP = new Uint32Array([${words.join(', ')}]); +export const ccBitmap = new Uint32Array([${words.join(', ')}]); `; -mkdirSync(dirname(OUTPUT_PATH), { recursive: true }); -writeFileSync(OUTPUT_PATH, source); -console.log(`Generated ${OUTPUT_PATH} from ${CC_TLDS.length} ccTLDs.`); +mkdirSync(dirname(outputPath), { recursive: true }); +writeFileSync(outputPath, source); +console.log(`Generated ${outputPath} from ${ccTld.length} ccTLDs.`); diff --git a/packages/linkify/package.json b/package/linkify/package.json similarity index 93% rename from packages/linkify/package.json rename to package/linkify/package.json index 0db404b..e2b7b96 100644 --- a/packages/linkify/package.json +++ b/package/linkify/package.json @@ -1,5 +1,5 @@ { - "name": "@fuyeor/linkify", + "name": "@ffm/linkify", "version": "1.0.0", "description": "High performance and minimal size linkifier", "type": "module", diff --git a/packages/linkify/src/cases.json b/package/linkify/src/cases.json similarity index 100% rename from packages/linkify/src/cases.json rename to package/linkify/src/cases.json diff --git a/packages/linkify/src/cc-tld-bitmap.ts b/package/linkify/src/cc-tld-bitmap.ts similarity index 78% rename from packages/linkify/src/cc-tld-bitmap.ts rename to package/linkify/src/cc-tld-bitmap.ts index 4e7a7bb..77e62f7 100644 --- a/packages/linkify/src/cc-tld-bitmap.ts +++ b/package/linkify/src/cc-tld-bitmap.ts @@ -1,7 +1,6 @@ -// packages/linkify/src/cc-tld-bitmap.ts +// @ffm/linkify/src/cc-tld-bitmap.ts // This file is generated by build/generate-tlds.ts. Do not edit manually. - -export const CC_TLD_BITMAP = new Uint32Array([ +export const ccBitmap = new Uint32Array([ 0xeedf597c, 0xdeddb9cf, 0x15843f27, 0x1e005480, 0xb0095c00, 0x15fb9f, 0x7818068d, 0x340400f, 0xf42b1d00, 0xd54f8141, 0x25d7fffc, 0x100084b, 0x538f3c40, 0x40000001, 0xfdf15100, 0x9fbb3be7, 0x404419a, 0x408557, 0x4002, diff --git a/packages/linkify/src/cross-language-fixtures.spec.ts b/package/linkify/src/index.spec.ts similarity index 88% rename from packages/linkify/src/cross-language-fixtures.spec.ts rename to package/linkify/src/index.spec.ts index 035c605..0ff6a99 100644 --- a/packages/linkify/src/cross-language-fixtures.spec.ts +++ b/package/linkify/src/index.spec.ts @@ -1,4 +1,4 @@ -// packages/linkify/src/cross-language-fixtures.spec.ts +// @ffm/linkify/src/index.spec.ts import { readFileSync } from 'node:fs'; import { resolve } from 'node:path'; import { describe, expect, it } from 'vitest'; @@ -22,7 +22,7 @@ type LinkFixtureFile = { const fixtures = JSON.parse( readFileSync( - resolve(import.meta.dirname, '../../../fixtures/linkify.json'), + resolve(import.meta.dirname, '../../../fixture/linkify.json'), 'utf8', ), ) as LinkFixtureFile; diff --git a/packages/linkify/src/index.ts b/package/linkify/src/index.ts similarity index 93% rename from packages/linkify/src/index.ts rename to package/linkify/src/index.ts index 42c4c4d..a7a6e79 100644 --- a/packages/linkify/src/index.ts +++ b/package/linkify/src/index.ts @@ -1,6 +1,5 @@ -// src/index.ts - -import { CC_TLD_BITMAP } from './cc-tld-bitmap.js'; +// @ffm/linkify/src/index.ts +import { ccBitmap } from './cc-tld-bitmap.js'; export interface LinkMatch { url: string; @@ -10,7 +9,7 @@ export interface LinkMatch { } // ๅ€™้€‰ๆ‰ซๆๅช่ดŸ่ดฃๆ‰พๅ‡บๅŸŸๅๅฝข็Šถ๏ผŒๅฎž้™…ไธคๅญ—ๆฏ TLD ๅˆๆณ•ๆ€ง็”ฑไฝๅ›พๆ ก้ชŒใ€‚ -const LINKIFY_REGEX = +const linkifyRegex = /(https?:\/\/[^\s]+|(?> 5] & (1 << (bitIndex & 31))) !== 0; + return (ccBitmap[bitIndex >> 5] & (1 << (bitIndex & 31))) !== 0; } // Validate only fuzzy two-letter TLDs; explicit URLs and hand-written TLDs are already gated. @@ -61,7 +60,7 @@ export function linkify(text: string): LinkMatch[] { const matches: LinkMatch[] = []; const punctuation = '.,:;?!'; - for (const match of text.matchAll(LINKIFY_REGEX)) { + for (const match of text.matchAll(linkifyRegex)) { let urlStr = match[0]; const matchIdx = match.index!; const isExplicitUrl = diff --git a/packages/linkify/tsconfig.json b/package/linkify/tsconfig.json similarity index 100% rename from packages/linkify/tsconfig.json rename to package/linkify/tsconfig.json diff --git a/packages/markdown-parser-lit/package.json b/package/lit-renderer/package.json similarity index 79% rename from packages/markdown-parser-lit/package.json rename to package/lit-renderer/package.json index 57c861e..d95e333 100644 --- a/packages/markdown-parser-lit/package.json +++ b/package/lit-renderer/package.json @@ -1,5 +1,5 @@ { - "name": "@fuyeor/markdown-parser-lit", + "name": "@ffm/lit-renderer", "license": "MIT", "author": "Fuyeor ", "type": "module", @@ -8,7 +8,7 @@ ".": "./src/index.ts" }, "dependencies": { - "@fuyeor/markdown-parser": "workspace:*" + "@ffm/parser": "workspace:*" }, "peerDependencies": { "lit": "*" diff --git a/package/lit-renderer/src/index.ts b/package/lit-renderer/src/index.ts new file mode 100644 index 0000000..635c5a9 --- /dev/null +++ b/package/lit-renderer/src/index.ts @@ -0,0 +1,3 @@ +// @ffm/lit-renderer/src/index.ts +export * from '@ffm/parser'; +export { render } from './render'; diff --git a/packages/markdown-parser-lit/src/render.ts b/package/lit-renderer/src/render.ts similarity index 93% rename from packages/markdown-parser-lit/src/render.ts rename to package/lit-renderer/src/render.ts index f055ad7..62453f5 100644 --- a/packages/markdown-parser-lit/src/render.ts +++ b/package/lit-renderer/src/render.ts @@ -1,19 +1,15 @@ -// @fuyeor/markdown-parser-lit/src/render.ts +// @ffm/lit-renderer/src/render.ts import { html, type TemplateResult } from 'lit'; import { html as staticHtml, unsafeStatic } from 'lit/static-html.js'; -import { - isSafeColorValue, - isSafeLinkUrl, - type ASTNode, -} from '@fuyeor/markdown-parser'; +import { isSafeColorValue, isSafeLinkUrl, type ASTNode } from '@ffm/parser'; /** - * recursively render AST nodes into Lit TemplateResult + * recursively render AST node into Lit TemplateResult */ -export function render(nodes?: ASTNode[]): (TemplateResult | string | null)[] { - if (!nodes || nodes.length === 0) return []; +export function render(node?: ASTNode[]): (TemplateResult | string | null)[] { + if (!node || node.length === 0) return []; - return nodes.map((node) => { + return node.map((node) => { switch (node.type) { case 'heading': { const level = diff --git a/packages/markdown-parser/package.json b/package/parser/package.json similarity index 55% rename from packages/markdown-parser/package.json rename to package/parser/package.json index c48c2b3..88d8473 100644 --- a/packages/markdown-parser/package.json +++ b/package/parser/package.json @@ -1,15 +1,15 @@ { - "name": "@fuyeor/markdown-parser", + "name": "@ffm/parser", "version": "0.1.0", "license": "MIT", "author": "Fuyeor ", "type": "module", "sideEffects": false, "scripts": { - "test": "vitest run src/cross-language-fixtures.spec.ts", - "test:unit": "vitest run src/cross-language-fixtures.spec.ts -t \"standard Markdown fixtures\"", - "test:ffm": "vitest run src/cross-language-fixtures.spec.ts -t \"ffm fixtures\"", - "test:safety": "vitest run src/cross-language-fixtures.spec.ts -t \"safety fixtures\"", + "test": "vitest run src/index.spec.ts", + "test:unit": "vitest run src/index.spec.ts -t \"cfm fixture\"", + "test:ffm": "vitest run src/index.spec.ts -t \"ffm fixture\"", + "test:safety": "vitest run src/index.spec.ts -t \"safety fixture\"", "test:compat": "vitest run src/test/index.spec.ts", "typecheck": "tsc -p tsconfig.json --noEmit && tsc -p tsconfig.test.json --noEmit" }, @@ -20,7 +20,7 @@ ".": "./src/index.ts" }, "dependencies": { - "@fuyeor/linkify": "workspace:*" + "@ffm/linkify": "workspace:*" }, "devDependencies": { "@types/node": "^25.6.0", diff --git a/packages/markdown-parser/src/core/color.ts b/package/parser/src/core/color.ts similarity index 86% rename from packages/markdown-parser/src/core/color.ts rename to package/parser/src/core/color.ts index 0389697..db1ca23 100644 --- a/packages/markdown-parser/src/core/color.ts +++ b/package/parser/src/core/color.ts @@ -1,4 +1,4 @@ -// @fuyeor/markdown-parser/src/core/color.ts +// @ffm/parser/src/core/color.ts const COLOR_VALUE_REGEX = /^(?:#(?:[0-9a-fA-F]{3,4}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})|(?:rgb|hsl)a?\([\d\s,%.]+\))$/; diff --git a/packages/markdown-parser/src/core/parser.ts b/package/parser/src/core/parser.ts similarity index 98% rename from packages/markdown-parser/src/core/parser.ts rename to package/parser/src/core/parser.ts index c105f8a..7f4b043 100644 --- a/packages/markdown-parser/src/core/parser.ts +++ b/package/parser/src/core/parser.ts @@ -1,6 +1,6 @@ -// @fuyeor/markdown-parser/src/core/parser.ts +// @ffm/parser/src/core/parser.ts import { BlockState, InlineState } from './state'; -import { linkify } from '@fuyeor/linkify'; +import { linkify } from '@ffm/linkify'; import type { ASTNode, ASTTransform, @@ -8,9 +8,9 @@ import type { InlineRule, Linkifier, MarkdownPlugin, - MarkdownParserOptions, + MarkdownParserOption, ParserContext, -} from '#/types'; +} from '#/type'; import { isSafeLinkUrl } from '#/core/url'; const LINKIFY_CANDIDATE_REGEX = /(?:https?:\/\/|[A-Za-z0-9-]+\.)/u; @@ -30,7 +30,7 @@ export class MarkdownParser { readonly #maxNestingDepth: number; readonly #linkifier: Linkifier; - constructor(options: MarkdownParserOptions = {}) { + constructor(options: MarkdownParserOption = {}) { const maxNestingDepth = options.maxNestingDepth ?? 64; if (!Number.isInteger(maxNestingDepth) || maxNestingDepth < 1) throw new RangeError('maxNestingDepth must be a positive integer'); diff --git a/packages/markdown-parser/src/core/plain-text.ts b/package/parser/src/core/plain-text.ts similarity index 78% rename from packages/markdown-parser/src/core/plain-text.ts rename to package/parser/src/core/plain-text.ts index 1b62e3c..ee71b96 100644 --- a/packages/markdown-parser/src/core/plain-text.ts +++ b/package/parser/src/core/plain-text.ts @@ -1,9 +1,9 @@ -// @fuyeor/markdown-parser/src/core/plain-text.ts -import type { ASTNode } from '../types'; +// @ffm/parser/src/core/plain-text.ts +import type { ASTNode } from '#/type'; -/// Converts Markdown/FFM AST nodes into search-oriented plain text. -export function toPlainText(nodes: readonly ASTNode[]): string { - return blockTextList(nodes).replace(/\n+$/u, ''); +/// Converts Markdown/FFM AST node into search-oriented plain text. +export function toPlainText(node: readonly ASTNode[]): string { + return blockTextList(node).replace(/\n+$/u, ''); } /// Converts one AST node without interpreting HTML or CSS content. @@ -38,7 +38,8 @@ function blockText(node: ASTNode): string { case 'chain_item': return joinTitleAndBody(node.title ?? [], node.children ?? []); case 'table': { - const header = node.headers === undefined ? '' : blockTextList(node.headers); + const header = + node.headers === undefined ? '' : blockTextList(node.headers); const body = blockTextList(node.children ?? []); return header.length > 0 && body.length > 0 ? `${header}\n${body}` @@ -56,15 +57,16 @@ function blockText(node: ASTNode): string { } /// Converts inline children without inserting formatting boundaries. -function inlineText(nodes: readonly ASTNode[]): string { - return nodes.map(blockText).join(''); +function inlineText(node: readonly ASTNode[]): string { + return node.map(blockText).join(''); } /// Converts block children with a stable newline boundary. function blockTextList(nodes: readonly ASTNode[]): string { let output = ''; for (const [index, node] of nodes.entries()) { - if (index > 0 && output.length > 0 && !output.endsWith('\n')) output += '\n'; + if (index > 0 && output.length > 0 && !output.endsWith('\n')) + output += '\n'; output += blockText(node); } return output; @@ -83,6 +85,6 @@ function joinTitleAndBody( } /// Joins table cells with boundaries that preserve searchable word separation. -function joinNodes(nodes: readonly ASTNode[]): string { - return nodes.map(blockText).join('\n'); +function joinNodes(node: readonly ASTNode[]): string { + return node.map(blockText).join('\n'); } diff --git a/packages/markdown-parser/src/core/render.ts b/package/parser/src/core/render.ts similarity index 98% rename from packages/markdown-parser/src/core/render.ts rename to package/parser/src/core/render.ts index 5ce79d7..9ce5509 100644 --- a/packages/markdown-parser/src/core/render.ts +++ b/package/parser/src/core/render.ts @@ -1,5 +1,5 @@ -// @fuyeor/markdown-parser/src/core/render.ts -import type { ASTNode } from '#/types'; +// @ffm/parser/src/core/render.ts +import type { ASTNode } from '#/type'; import { isSafeColorValue } from '#/core/color'; import { isSafeLinkUrl } from '#/core/url'; diff --git a/packages/markdown-parser/src/core/state.ts b/package/parser/src/core/state.ts similarity index 97% rename from packages/markdown-parser/src/core/state.ts rename to package/parser/src/core/state.ts index e942df0..bb1073b 100644 --- a/packages/markdown-parser/src/core/state.ts +++ b/package/parser/src/core/state.ts @@ -1,4 +1,4 @@ -// @fuyeor/markdown-parser/src/core/state.ts +// @ffm/parser/src/core/state.ts export class BlockState { lines: string[]; lineIndex: number = 0; diff --git a/packages/markdown-parser/src/core/url.ts b/package/parser/src/core/url.ts similarity index 92% rename from packages/markdown-parser/src/core/url.ts rename to package/parser/src/core/url.ts index 133f4ee..aa583b2 100644 --- a/packages/markdown-parser/src/core/url.ts +++ b/package/parser/src/core/url.ts @@ -1,4 +1,4 @@ -// @fuyeor/markdown-parser/src/core/url.ts +// @ffm/parser/src/core/url.ts const SAFE_PROTOCOLS = new Set(['http:', 'https:', 'mailto:', 'tel:']); diff --git a/packages/markdown-parser/src/default.ts b/package/parser/src/default.ts similarity index 75% rename from packages/markdown-parser/src/default.ts rename to package/parser/src/default.ts index e44aa12..085b929 100644 --- a/packages/markdown-parser/src/default.ts +++ b/package/parser/src/default.ts @@ -1,6 +1,6 @@ -// @fuyeor/markdown-parser/src/default.ts +// @ffm/parser/src/default.ts import { MarkdownParser } from './core/parser'; -import type { MarkdownParserOptions } from './types'; +import type { MarkdownParserOption } from './type'; import { headingRule, tableRule, @@ -8,7 +8,7 @@ import { listRule, hrRule, blockquoteRule, -} from './rules/blocks'; +} from './rule/blocks'; import { hardBreakRule, inlineCodeRule, @@ -17,14 +17,14 @@ import { underlineRule, italicRule, strikeRule, -} from './rules/inlines'; -import { ffmBlockRule } from './rules/ffm'; -import { latexPlugin } from './plugins/latex'; -import { extensionsPlugin } from './plugins/extensions'; +} from './rule/inlines'; +import { ffmBlockRule } from './rule/ffm'; +import { latexPlugin } from './plugin/latex'; +import { extensionsPlugin } from './plugin/extensions'; -export function createMarkdownParser(options: MarkdownParserOptions = {}) { +export function createMarkdownParser(option: MarkdownParserOption = {}) { return ( - new MarkdownParser(options) + new MarkdownParser(option) // block order: Code block -> List -> Title -> Table -> Delete line -> Quote .addBlockRule(codeBlockRule) .addBlockRule(listRule) @@ -46,11 +46,9 @@ export function createMarkdownParser(options: MarkdownParserOptions = {}) { ); } -export function createFuyeorMarkdownParser( - options: MarkdownParserOptions = {}, -) { +export function createFuyeorMarkdownParser(option: MarkdownParserOption = {}) { return ( - new MarkdownParser(options) + new MarkdownParser(option) // block order: Code block -> List -> Title -> Table -> Delete line -> Quote .addBlockRule(ffmBlockRule) .addBlockRule(codeBlockRule) diff --git a/packages/markdown-parser/src/cross-language-fixtures.spec.ts b/package/parser/src/index.spec.ts similarity index 96% rename from packages/markdown-parser/src/cross-language-fixtures.spec.ts rename to package/parser/src/index.spec.ts index 1100248..1982881 100644 --- a/packages/markdown-parser/src/cross-language-fixtures.spec.ts +++ b/package/parser/src/index.spec.ts @@ -1,4 +1,4 @@ -// packages/markdown-parser/src/cross-language-fixtures.spec.ts +// @ffm/parser/src/index.spec.ts import { readFileSync } from 'node:fs'; import { resolve } from 'node:path'; import { describe, expect, it } from 'vitest'; @@ -41,7 +41,10 @@ type SafetyFixtureFile = { const loadJson = (name: string): T => JSON.parse( - readFileSync(resolve(import.meta.dirname, `../../../fixtures/${name}`), 'utf8'), + readFileSync( + resolve(import.meta.dirname, `../../../fixture/${name}`), + 'utf8', + ), ) as T; const standardFixtures = loadJson('markdown.json'); diff --git a/packages/markdown-parser/src/index.ts b/package/parser/src/index.ts similarity index 76% rename from packages/markdown-parser/src/index.ts rename to package/parser/src/index.ts index 1ae0548..e59dfa1 100644 --- a/packages/markdown-parser/src/index.ts +++ b/package/parser/src/index.ts @@ -1,4 +1,4 @@ -// @fuyeor/markdown-parser/src/index.ts +// @ffm/parser/src/index.ts export { MarkdownParser } from './core/parser'; export { render } from './core/render'; export { toPlainText } from './core/plain-text'; @@ -11,7 +11,7 @@ export { listRule, hrRule, blockquoteRule, -} from './rules/blocks'; +} from './rule/blocks'; export { hardBreakRule, inlineCodeRule, @@ -19,16 +19,16 @@ export { linkRule, underlineRule, strikeRule, -} from './rules/inlines'; -export { latexPlugin, latexInlineRule, latexBlockRule } from './plugins/latex'; +} from './rule/inlines'; +export { latexPlugin, latexInlineRule, latexBlockRule } from './plugin/latex'; export { twemojiPlugin, nativeEmojiPlugin, twemojiTransform, transformTwemojiNode, getTwemojiUrl, -} from './plugins/twemoji'; -export { highlightPlugin, highlightTransform } from './plugins/highlight'; +} from './plugin/twemoji'; +export { highlightPlugin, highlightTransform } from './plugin/highlight'; export { abcPlugin, abcTransform, @@ -43,8 +43,8 @@ export { specialBlockPlugin, specialBlockTransform, transformSpecialBlockNode, -} from './plugins/async-blocks'; -export { extensionsPlugin, extensionsTransform } from './plugins/extensions'; +} from './plugin/async-blocks'; +export { extensionsPlugin, extensionsTransform } from './plugin/extensions'; export { createMarkdownParser, createFuyeorMarkdownParser } from './default'; export type { @@ -55,6 +55,6 @@ export type { BlockRule, InlineRule, Linkifier, - MarkdownParserOptions, + MarkdownParserOption, MarkdownPlugin, } from './types'; diff --git a/packages/markdown-parser/src/plugins/ast.ts b/package/parser/src/plugin/ast.ts similarity index 92% rename from packages/markdown-parser/src/plugins/ast.ts rename to package/parser/src/plugin/ast.ts index db9fd36..94c568c 100644 --- a/packages/markdown-parser/src/plugins/ast.ts +++ b/package/parser/src/plugin/ast.ts @@ -1,5 +1,5 @@ -// @fuyeor/markdown-parser/src/plugins/ast.ts -import type { ASTNode } from '#/types'; +// @ffm/parser/src/plugin/ast.ts +import type { ASTNode } from '#/type'; type ASTNodeReplacement = ASTNode | readonly ASTNode[]; diff --git a/packages/markdown-parser/src/plugins/async-blocks.ts b/package/parser/src/plugin/async-blocks.ts similarity index 95% rename from packages/markdown-parser/src/plugins/async-blocks.ts rename to package/parser/src/plugin/async-blocks.ts index 1af6b5a..eac7bd2 100644 --- a/packages/markdown-parser/src/plugins/async-blocks.ts +++ b/package/parser/src/plugin/async-blocks.ts @@ -1,11 +1,6 @@ -// @fuyeor/markdown-parser/src/plugins/async-blocks.ts +// @ffm/parser/src/plugin/async-blocks.ts import { InlineState } from '#/core/state'; -import type { - ASTNode, - ASTTransform, - InlineRule, - MarkdownPlugin, -} from '#/types'; +import type { ASTNode, ASTTransform, InlineRule, MarkdownPlugin } from '#/type'; import { mapAstNodes } from './ast'; import { normalizeHighlightLanguage } from './highlight'; diff --git a/packages/markdown-parser/src/plugins/extensions.ts b/package/parser/src/plugin/extensions.ts similarity index 95% rename from packages/markdown-parser/src/plugins/extensions.ts rename to package/parser/src/plugin/extensions.ts index 0086a1e..400f131 100644 --- a/packages/markdown-parser/src/plugins/extensions.ts +++ b/package/parser/src/plugin/extensions.ts @@ -1,5 +1,5 @@ -// @fuyeor/markdown-parser/src/plugins/extensions.ts -import type { ASTNode, ASTTransform, MarkdownPlugin } from '#/types'; +// @ffm/parser/src/plugin/extensions.ts +import type { ASTNode, ASTTransform, MarkdownPlugin } from '#/type'; import { mapAstNodes } from './ast'; import { transformTwemojiNode } from './twemoji'; import { diff --git a/packages/markdown-parser/src/plugins/highlight.ts b/package/parser/src/plugin/highlight.ts similarity index 95% rename from packages/markdown-parser/src/plugins/highlight.ts rename to package/parser/src/plugin/highlight.ts index 442c76d..348d9ff 100644 --- a/packages/markdown-parser/src/plugins/highlight.ts +++ b/package/parser/src/plugin/highlight.ts @@ -1,5 +1,5 @@ -// @fuyeor/markdown-parser/src/plugins/highlight.ts -import type { ASTNode, ASTTransform, MarkdownPlugin } from '#/types'; +// @ffm/parser/src/plugin/highlight.ts +import type { ASTNode, ASTTransform, MarkdownPlugin } from '#/type'; import { mapAstNodes } from './ast'; const languageAliases: Record = { diff --git a/packages/markdown-parser/src/plugins/latex.ts b/package/parser/src/plugin/latex.ts similarity index 98% rename from packages/markdown-parser/src/plugins/latex.ts rename to package/parser/src/plugin/latex.ts index 8e561c7..4042326 100644 --- a/packages/markdown-parser/src/plugins/latex.ts +++ b/package/parser/src/plugin/latex.ts @@ -1,6 +1,6 @@ -// @fuyeor/markdown-parser/src/plugins/latex.ts +// @ffm/parser/src/plugin/latex.ts import { BlockState, InlineState } from '#/core/state'; -import type { BlockRule, InlineRule, MarkdownPlugin } from '#/types'; +import type { BlockRule, InlineRule, MarkdownPlugin } from '#/type'; const MATH_INLINE_MARKER = '$'; const MATH_BLOCK_MARKER = '$$'; diff --git a/packages/markdown-parser/src/plugins/twemoji.ts b/package/parser/src/plugin/twemoji.ts similarity index 97% rename from packages/markdown-parser/src/plugins/twemoji.ts rename to package/parser/src/plugin/twemoji.ts index cd311db..7621318 100644 --- a/packages/markdown-parser/src/plugins/twemoji.ts +++ b/package/parser/src/plugin/twemoji.ts @@ -1,5 +1,5 @@ -// @fuyeor/markdown-parser/src/plugins/twemoji.ts -import type { ASTNode, ASTTransform, MarkdownPlugin } from '#/types'; +// @ffm/parser/src/plugin/twemoji.ts +import type { ASTNode, ASTTransform, MarkdownPlugin } from '#/type'; import { mapAstNodes } from './ast'; const r = String.raw; diff --git a/packages/markdown-parser/src/rules/blocks.ts b/package/parser/src/rule/blocks.ts similarity index 99% rename from packages/markdown-parser/src/rules/blocks.ts rename to package/parser/src/rule/blocks.ts index 40364df..627f3f4 100644 --- a/packages/markdown-parser/src/rules/blocks.ts +++ b/package/parser/src/rule/blocks.ts @@ -1,5 +1,5 @@ -// @fuyeor/markdown-parser/src/rules/blocks.ts -import type { ASTNode, BlockRule } from '#/types'; +// @ffm/parser/src/rule/blocks.ts +import type { ASTNode, BlockRule } from '#/type'; import { BlockState } from '#/core/state'; type FencedBlock = { diff --git a/packages/markdown-parser/src/rules/ffm.ts b/package/parser/src/rule/ffm.ts similarity index 90% rename from packages/markdown-parser/src/rules/ffm.ts rename to package/parser/src/rule/ffm.ts index b1e8cb9..bf5ef1e 100644 --- a/packages/markdown-parser/src/rules/ffm.ts +++ b/package/parser/src/rule/ffm.ts @@ -1,16 +1,16 @@ -// @fuyeor/markdown-parser/src/rules/ffm.ts +// @ffm/parser/src/rule/ffm.ts import { BlockState } from '#/core/state'; import { extractFencedBlock } from './blocks'; -import type { BlockRule, ASTNode } from '#/types'; +import type { BlockRule, ASTNode } from '#/type'; -const FFM_KEYWORDS = new Set(['quote', 'slide', 'chain', 'accordion']); +const ffmKeyword = new Set(['quote', 'slide', 'chain', 'accordion']); // matches two horizontal newline characters before and after the match // allows spaces in between, e.g., \n\n --- \n\n -const SLIDE_SEPARATOR_REGEX = /\n\n\s*---\s*\n\n/g; +const slideSeparatorRegex = /\n\n\s*---\s*\n\n/g; // ffm block title -const TITLE_REGEX = /^\s*\*\*(?:\[([ xX])\]\s*)?(.+?)\*\*\s*$/; +const nodeTitleRegex = /^\s*\*\*(?:\[([ xX])\]\s*)?(.+?)\*\*\s*$/; /** * intercept special fuyeor flavored markdown blocks (accordion, slide, chain, quote) @@ -26,7 +26,7 @@ export const ffmBlockRule: BlockRule = { const type = block.lang.split(/\s+/)[0]; // skip if it's not a specific keyword, leave it to codeBlockRule. - if (!FFM_KEYWORDS.has(type)) return null; + if (!ffmKeyword.has(type)) return null; const { content: rawContent, consumedLines } = block; @@ -41,7 +41,7 @@ export const ffmBlockRule: BlockRule = { // ffm slide if (type === 'slide') { const slideContents = rawContent - .split(SLIDE_SEPARATOR_REGEX) + .split(slideSeparatorRegex) .filter((s) => s.trim().length > 0); const slides = slideContents.map((s) => ({ @@ -65,7 +65,7 @@ export const ffmBlockRule: BlockRule = { type === 'accordion' ? ctx.createId('acc') : undefined; for (const l of rawContent.split('\n')) { - const titleMatch = l.match(TITLE_REGEX); + const titleMatch = l.match(nodeTitleRegex); if (titleMatch) { // archive the previous item, or archive free content const previousItem = currentItem; diff --git a/packages/markdown-parser/src/rules/inlines.ts b/package/parser/src/rule/inlines.ts similarity index 98% rename from packages/markdown-parser/src/rules/inlines.ts rename to package/parser/src/rule/inlines.ts index d5c83fb..bc27bfc 100644 --- a/packages/markdown-parser/src/rules/inlines.ts +++ b/package/parser/src/rule/inlines.ts @@ -1,5 +1,5 @@ -// @fuyeor/markdown-parser/src/rules/inlines.ts -import type { InlineRule } from '#/types'; +// @ffm/parser/src/rule/inlines.ts +import type { InlineRule } from '#/type'; import { InlineState } from '#/core/state'; import { isSafeColorValue } from '#/core/color'; import { isSafeLinkUrl } from '#/core/url'; diff --git a/packages/markdown-parser/src/test/index.spec.ts b/package/parser/src/test/index.spec.ts similarity index 93% rename from packages/markdown-parser/src/test/index.spec.ts rename to package/parser/src/test/index.spec.ts index 44250d0..5fe6b60 100644 --- a/packages/markdown-parser/src/test/index.spec.ts +++ b/package/parser/src/test/index.spec.ts @@ -1,4 +1,4 @@ -// @fuyeor/markdown-parser/test/index.spec.ts +// @ffm/parser/test/index.spec.ts // pnpm test import { readFileSync, writeFileSync } from 'node:fs'; import { resolve } from 'node:path'; @@ -12,11 +12,11 @@ const failures: any[] = []; // load test JSON const commonMark = JSON.parse( // commonMark: 0.13.2 edition - readFileSync(resolve(__dirname, './specs/commonMark.json'), 'utf-8'), + readFileSync(resolve(__dirname, './spec/commonMark.json'), 'utf-8'), ); const githubMark = JSON.parse( // githubMark: 0.29 edition - readFileSync(resolve(__dirname, './specs/githubMark.json'), 'utf-8'), + readFileSync(resolve(__dirname, './spec/githubMark.json'), 'utf-8'), ); /** * skip unimplemented test cases @@ -113,12 +113,11 @@ describe('markdown compatibility test', () => { }); }); - // after test complete, write error details into JSON afterAll(() => { if (failures.length > 0) { writeFileSync( - resolve(__dirname, './tests.failed.json'), + resolve(__dirname, './test.failed.json'), JSON.stringify(failures, null, 2), 'utf-8', ); diff --git a/packages/markdown-parser/src/test/specs/commonMark.json b/package/parser/src/test/spec/commonMark.json similarity index 100% rename from packages/markdown-parser/src/test/specs/commonMark.json rename to package/parser/src/test/spec/commonMark.json diff --git a/packages/markdown-parser/src/test/specs/githubMark.json b/package/parser/src/test/spec/githubMark.json similarity index 100% rename from packages/markdown-parser/src/test/specs/githubMark.json rename to package/parser/src/test/spec/githubMark.json diff --git a/packages/markdown-parser/src/types.ts b/package/parser/src/type.ts similarity index 94% rename from packages/markdown-parser/src/types.ts rename to package/parser/src/type.ts index bec0595..88c18aa 100644 --- a/packages/markdown-parser/src/types.ts +++ b/package/parser/src/type.ts @@ -1,6 +1,5 @@ -// @fuyeor/markdown-parser/src/types.ts - -import type { LinkMatch } from '@fuyeor/linkify'; +// @ffm/parser/src/type.ts +import type { LinkMatch } from '@ffm/linkify'; import type { MarkdownParser } from './core/parser'; import type { BlockState, InlineState } from './core/state'; @@ -83,7 +82,7 @@ export interface InlineRule { export type Linkifier = (text: string) => readonly LinkMatch[]; -export interface MarkdownParserOptions { +export interface MarkdownParserOption { maxNestingDepth?: number; linkifier?: Linkifier; } diff --git a/packages/markdown-parser/tsconfig.json b/package/parser/tsconfig.json similarity index 100% rename from packages/markdown-parser/tsconfig.json rename to package/parser/tsconfig.json diff --git a/packages/markdown-parser/tsconfig.test.json b/package/parser/tsconfig.test.json similarity index 78% rename from packages/markdown-parser/tsconfig.test.json rename to package/parser/tsconfig.test.json index f8ad5a9..b59851f 100644 --- a/packages/markdown-parser/tsconfig.test.json +++ b/package/parser/tsconfig.test.json @@ -1,4 +1,3 @@ -// packages/markdown-parser/tsconfig.test.json { "extends": "./tsconfig.json", "compilerOptions": { diff --git a/packages/playground/index.html b/package/playground/index.html similarity index 100% rename from packages/playground/index.html rename to package/playground/index.html diff --git a/packages/playground/package.json b/package/playground/package.json similarity index 79% rename from packages/playground/package.json rename to package/playground/package.json index 6dd0db8..78baea6 100644 --- a/packages/playground/package.json +++ b/package/playground/package.json @@ -1,5 +1,5 @@ { - "name": "@fuyeor/markdown-playground", + "name": "@ffm/playground", "license": "MIT", "author": "Fuyeor ", "type": "module", @@ -16,9 +16,9 @@ "@fuyeor/config": "^0.1.1", "@fuyeor/interactify": "^0.1.1", "@fuyeor/locale": "^0.1.0", - "@fuyeor/markdown-formatter": "workspace:*", - "@fuyeor/markdown-parser": "workspace:*", - "@fuyeor/markdown-parser-vue": "workspace:*", + "@ffm/formatter": "workspace:*", + "@ffm/parser": "workspace:*", + "@ffm/vue-renderer": "workspace:*", "@fuyeor/vue-router": "^0.0.9", "vue": "^3.5.41" }, diff --git a/packages/playground/public/assets/css/style.css b/package/playground/public/assets/css/style.css similarity index 100% rename from packages/playground/public/assets/css/style.css rename to package/playground/public/assets/css/style.css diff --git a/packages/playground/public/assets/examples/en.ffm b/package/playground/public/assets/example/en.ffm similarity index 100% rename from packages/playground/public/assets/examples/en.ffm rename to package/playground/public/assets/example/en.ffm diff --git a/packages/playground/public/assets/examples/fr.ffm b/package/playground/public/assets/example/fr.ffm similarity index 100% rename from packages/playground/public/assets/examples/fr.ffm rename to package/playground/public/assets/example/fr.ffm diff --git a/packages/playground/public/assets/examples/ja.ffm b/package/playground/public/assets/example/ja.ffm similarity index 100% rename from packages/playground/public/assets/examples/ja.ffm rename to package/playground/public/assets/example/ja.ffm diff --git a/packages/playground/public/assets/examples/zh-hans.ffm b/package/playground/public/assets/example/zh-hans.ffm similarity index 100% rename from packages/playground/public/assets/examples/zh-hans.ffm rename to package/playground/public/assets/example/zh-hans.ffm diff --git a/packages/playground/public/favicon.svg b/package/playground/public/favicon.svg similarity index 100% rename from packages/playground/public/favicon.svg rename to package/playground/public/favicon.svg diff --git a/packages/playground/public/robots.txt b/package/playground/public/robots.txt similarity index 100% rename from packages/playground/public/robots.txt rename to package/playground/public/robots.txt diff --git a/packages/playground/src/layout/Left.vue b/package/playground/src/@app/component/left-sidebar.vue similarity index 74% rename from packages/playground/src/layout/Left.vue rename to package/playground/src/@app/component/left-sidebar.vue index d317117..d21f6ba 100644 --- a/packages/playground/src/layout/Left.vue +++ b/package/playground/src/@app/component/left-sidebar.vue @@ -1,8 +1,8 @@ - + " } ], @@ -45,15 +45,15 @@ "value": "table" }, { - "path": "/0/headers/0/type", + "path": "/0/header/0/type", "value": "table_cell" }, { - "path": "/0/headers/1/type", + "path": "/0/header/1/type", "value": "table_cell" }, { - "path": "/0/children/0/type", + "path": "/0/content/0/type", "value": "table_row" } ], diff --git a/package/lit-renderer/src/render.ts b/package/lit-renderer/src/render.ts index 62453f5..5bea102 100644 --- a/package/lit-renderer/src/render.ts +++ b/package/lit-renderer/src/render.ts @@ -19,46 +19,46 @@ export function render(node?: ASTNode[]): (TemplateResult | string | null)[] { node.level <= 6 ? node.level : null; - if (level === null) return html`${render(node.children)}`; + if (level === null) return html`${render(node.content)}`; const tagName = `h${level}`; return staticHtml` <${unsafeStatic(tagName)}> - ${render(node.children)} + ${render(node.content)} `; } case 'paragraph': - return html`

${render(node.children)}

`; + return html`

${render(node.content)}

`; case 'text': - return node.content || ''; + return node.value || ''; case 'bold': - return html`${render(node.children)}`; + return html`${render(node.content)}`; case 'italic': - return html`${render(node.children)}`; + return html`${render(node.content)}`; case 'underline': - return html`${render(node.children)}`; + return html`${render(node.content)}`; case 'strike': - return html`${render(node.children)}`; + return html`${render(node.content)}`; case 'link': { const url = String(node.url ?? '').trim(); return isSafeLinkUrl(url) - ? html`${render(node.children)}` - : html`${render(node.children)}`; + ? html`${render(node.content)}` + : html`${render(node.content)}`; } case 'inline_code': - return html`${node.content}`; + return html`${node.value}`; case 'color_code': { - const color = String(node.content ?? ''); + const color = String(node.value ?? ''); if (!isSafeColorValue(color)) return html`${color}`; return html` @@ -86,7 +86,7 @@ export function render(node?: ASTNode[]): (TemplateResult | string | null)[] { ${node.lang ? html`
${node.lang}
` : ''}
${node.content}
+ : ''}">${node.value}
`; @@ -98,16 +98,16 @@ export function render(node?: ASTNode[]): (TemplateResult | string | null)[] { : undefined; return staticHtml` <${unsafeStatic(tag)} start="${startValue || ''}"> - ${render(node.children)} + ${render(node.content)} `; } case 'list_item': - return html`
  • ${render(node.children)}
  • `; + return html`
  • ${render(node.content)}
  • `; case 'blockquote': - return html`
    ${render(node.children)}
    `; + return html`
    ${render(node.content)}
    `; case 'hr': return html`
    `; @@ -121,17 +121,17 @@ export function render(node?: ASTNode[]): (TemplateResult | string | null)[] { - ${(node.headers ?? []).map( - (cell) => html``, + ${(node.header ?? []).map( + (cell) => html``, )} - ${node.children?.map( + ${node.content?.map( (row) => html` - ${(row.children ?? []).map( - (cell) => html``, + ${(row.content ?? []).map( + (cell) => html``, )} `, @@ -142,13 +142,13 @@ export function render(node?: ASTNode[]): (TemplateResult | string | null)[] { `; case 'accordion': - return html`
    ${render(node.children)}
    `; + return html`
    ${render(node.content)}
    `; case 'accordion_item': return html`
    ${render(node.title)} -
    ${render(node.children)}
    +
    ${render(node.content)}
    `; @@ -156,18 +156,16 @@ export function render(node?: ASTNode[]): (TemplateResult | string | null)[] { case 'slide': return html`
    -
    ${render(node.children)}
    +
    ${render(node.content)}
    `; case 'slide_item': - return html`
    ${render(node.children)}
    `; + return html`
    ${render(node.content)}
    `; // FFM Chain case 'chain': - return html`
    - ${render(node.children)} -
    `; + return html`
    ${render(node.content)}
    `; case 'chain_item': { const statusClass = node.hasCheckbox @@ -183,7 +181,7 @@ export function render(node?: ASTNode[]): (TemplateResult | string | null)[] { ${node.title && node.title.length > 0 ? html`
    ${render(node.title)}
    ` : ''} -
    ${render(node.children)}
    +
    ${render(node.content)}
    `; @@ -191,9 +189,7 @@ export function render(node?: ASTNode[]): (TemplateResult | string | null)[] { default: // fallback render - return node.children - ? html`${render(node.children)}` - : null; + return node.content ? html`${render(node.content)}` : null; } }); } diff --git a/package/parser/src/core/parser.ts b/package/parser/src/core/parser.ts index 7f4b043..004e986 100644 --- a/package/parser/src/core/parser.ts +++ b/package/parser/src/core/parser.ts @@ -107,10 +107,10 @@ export class MarkdownParser { return [ { type: 'paragraph', - children: [ + content: [ { type: 'text', - content: state.remainingLines.join('\n'), + value: state.remainingLines.join('\n'), }, ], }, @@ -223,7 +223,7 @@ export class MarkdownParser { if (paragraphLines.length > 0) { nodes.push({ type: 'paragraph', - children: this.#parseInline( + content: this.#parseInline( new InlineState(paragraphLines.join('\n')), depth, ), @@ -236,7 +236,7 @@ export class MarkdownParser { #parseInline(state: InlineState, depth = 0): ASTNode[] { if (depth > this.#maxNestingDepth) { - return [{ type: 'text', content: state.content.slice(state.pos) }]; + return [{ type: 'text', value: state.content.slice(state.pos) }]; } const context = depth === 0 ? this.#context : this.#createContext(depth); @@ -256,20 +256,20 @@ export class MarkdownParser { if (match.index > lastIdx) { nodes.push({ type: 'text', - content: textBuffer.slice(lastIdx, match.index), + value: textBuffer.slice(lastIdx, match.index), }); } nodes.push({ type: 'link', url: match.url, - children: [{ type: 'text', content: match.text }], + content: [{ type: 'text', value: match.text }], }); lastIdx = match.lastIndex; } } if (lastIdx < textBuffer.length) { - nodes.push({ type: 'text', content: textBuffer.slice(lastIdx) }); + nodes.push({ type: 'text', value: textBuffer.slice(lastIdx) }); } } }; diff --git a/package/parser/src/core/plain-text.ts b/package/parser/src/core/plain-text.ts index ee71b96..231fc1d 100644 --- a/package/parser/src/core/plain-text.ts +++ b/package/parser/src/core/plain-text.ts @@ -12,9 +12,9 @@ function blockText(node: ASTNode): string { case 'text': case 'inline_code': case 'color_code': - return node.content ?? ''; + return node.value ?? ''; case 'code_block': - return node.content ?? ''; + return node.value ?? ''; case 'heading': case 'paragraph': case 'bold': @@ -23,7 +23,7 @@ function blockText(node: ASTNode): string { case 'strike': case 'link': case 'table_cell': - return inlineText(node.children ?? []); + return inlineText(node.content ?? []); case 'blockquote': case 'list': case 'list_item': @@ -32,27 +32,27 @@ function blockText(node: ASTNode): string { case 'accordion': case 'chain': case 'root': - return blockTextList(node.children ?? []); + return blockTextList(node.content ?? []); case 'accordion_item': - return joinTitleAndBody(node.title ?? [], node.children ?? []); + return joinTitleAndBody(node.title ?? [], node.content ?? []); case 'chain_item': - return joinTitleAndBody(node.title ?? [], node.children ?? []); + return joinTitleAndBody(node.title ?? [], node.content ?? []); case 'table': { const header = - node.headers === undefined ? '' : blockTextList(node.headers); - const body = blockTextList(node.children ?? []); + node.header === undefined ? '' : blockTextList(node.header); + const body = blockTextList(node.content ?? []); return header.length > 0 && body.length > 0 ? `${header}\n${body}` : header || body; } case 'table_row': - return joinNodes(node.children ?? []); + return joinNodes(node.content ?? []); case 'hardbreak': return '\n'; case 'hr': return ''; default: - return blockTextList(node.children ?? []); + return blockTextList(node.content ?? []); } } diff --git a/package/parser/src/core/render.ts b/package/parser/src/core/render.ts index 9ce5509..ebce92e 100644 --- a/package/parser/src/core/render.ts +++ b/package/parser/src/core/render.ts @@ -65,46 +65,46 @@ export function render(nodes?: ASTNode[]): string { const level = getHeadingLevel(node.level); html += level === null - ? `${render(node.children)}` - : `${render(node.children)}\n`; + ? `${render(node.content)}` + : `${render(node.content)}\n`; break; } case 'paragraph': - html += `

    ${render(node.children)}

    \n`; + html += `

    ${render(node.content)}

    \n`; break; case 'text': - html += node.content ? escapeHtml(node.content) : ''; + html += node.value ? escapeHtml(node.value) : ''; break; case 'emoji': { - const emoji = String(node.content ?? ''); + const emoji = String(node.value ?? ''); const alt = String(node.alt ?? emoji); const src = String(node.src ?? ''); html += `${escapeHtml(alt)}`; break; } case 'math_inline': - html += `${escapeHtml(String(node.content ?? ''))}`; + html += `${escapeHtml(String(node.value ?? ''))}`; break; case 'math_block': - html += `
    ${escapeHtml(String(node.content ?? ''))}
    \n`; + html += `
    ${escapeHtml(String(node.value ?? ''))}
    \n`; break; case 'bold': - html += `${render(node.children)}`; + html += `${render(node.content)}`; break; case 'italic': - html += `${render(node.children)}`; + html += `${render(node.content)}`; break; case 'underline': - html += `${render(node.children)}`; + html += `${render(node.content)}`; break; case 'strike': - html += `${render(node.children)}`; + html += `${render(node.content)}`; break; case 'inline_code': - html += `${node.content ? escapeHtml(node.content) : ''}`; + html += `${node.value ? escapeHtml(node.value) : ''}`; break; case 'color_code': { - const color = String(node.content ?? ''); + const color = String(node.value ?? ''); if (!isSafeColorValue(color)) { html += escapeHtml(color); break; @@ -115,8 +115,8 @@ export function render(nodes?: ASTNode[]): string { case 'link': { const url = String(node.url ?? '').trim(); html += isSafeLinkUrl(url) - ? `${render(node.children)}` - : render(node.children); + ? `${render(node.content)}` + : render(node.content); break; } case 'code_block': { @@ -129,7 +129,7 @@ export function render(nodes?: ASTNode[]): string { if (language) { html += `
    ${escapeHtml(displayLanguage)}
    `; } - html += `
    ${escapeHtml(String(node.content ?? ''))}\n
    \n`; + html += `
    ${escapeHtml(String(node.value ?? ''))}\n
    \n`; break; } case 'list': { @@ -138,23 +138,23 @@ export function render(nodes?: ASTNode[]): string { node.ordered && node.start && node.start !== 1 ? ` start="${node.start}"` : ''; - html += `<${tag}${start}>\n${render(node.children)}\n`; + html += `<${tag}${start}>\n${render(node.content)}\n`; break; } case 'list_item': - html += `
  • ${render(node.children)}
  • \n`; + html += `
  • ${render(node.content)}
  • \n`; break; case 'mermaid': - html += `
    ${escapeHtml(String(node.content ?? ''))}
    \n`; + html += `
    ${escapeHtml(String(node.value ?? ''))}
    \n`; break; case 'abc': - html += `
    ${escapeHtml(String(node.content ?? ''))}
    \n`; + html += `
    ${escapeHtml(String(node.value ?? ''))}
    \n`; break; case 'smiles_inline': - html += ``; + html += ``; break; case 'smiles_block': { - const blocks = String(node.content ?? '') + const blocks = String(node.value ?? '') .split('\n') .filter((line) => line.trim().length > 0) .map( @@ -166,19 +166,19 @@ export function render(nodes?: ASTNode[]): string { } case 'table': { html += '
    ${render(cell.children)}${render(cell.content)}
    ${render(cell.children)}${render(cell.content)}
    \n'; - if (node.headers) { + if (node.header) { html += '\n\n'; - node.headers.forEach((cell) => { - html += `${render(cell.children)}\n`; + node.header.forEach((cell) => { + html += `${render(cell.content)}\n`; }); html += '\n\n'; } - if (node.children && node.children.length > 0) { + if (node.content && node.content.length > 0) { html += '\n'; - node.children.forEach((row) => { + node.content.forEach((row) => { html += '\n'; - row.children?.forEach((cell) => { - html += `${render(cell.children)}\n`; + row.content?.forEach((cell) => { + html += `${render(cell.content)}\n`; }); html += '\n'; }); @@ -191,16 +191,16 @@ export function render(nodes?: ASTNode[]): string { html += '
    \n'; break; case 'blockquote': - html += `
    \n${render(node.children)}
    \n`; + html += `
    \n${render(node.content)}
    \n`; break; case 'accordion': - html += `
    ${render(node.children)}
    `; + html += `
    ${render(node.content)}
    `; break; case 'accordion_item': - html += `
    ${render(node.title)}
    ${render(node.children)}
    `; + html += `
    ${render(node.title)}
    ${render(node.content)}
    `; break; case 'chain': - html += `
    ${render(node.children)}
    `; + html += `
    ${render(node.content)}
    `; break; case 'chain_item': { const statusClass = node.hasCheckbox @@ -212,20 +212,20 @@ export function render(nodes?: ASTNode[]): string { node.title && node.title.length > 0 ? `
    ${render(node.title)}
    ` : ''; - html += `
    ${title}
    ${render(node.children)}
    `; + html += `
    ${title}
    ${render(node.content)}
    `; break; } case 'slide': - html += `
    ${render(node.children)}
    `; + html += `
    ${render(node.content)}
    `; break; case 'slide_item': - html += `
    ${render(node.children)}
    `; + html += `
    ${render(node.content)}
    `; break; case 'hardbreak': html += '
    \n'; break; default: - html += `${render(node.children)}`; + html += `${render(node.content)}`; break; } } diff --git a/package/parser/src/index.spec.ts b/package/parser/src/index.spec.ts index 1982881..a8d73f1 100644 --- a/package/parser/src/index.spec.ts +++ b/package/parser/src/index.spec.ts @@ -123,6 +123,44 @@ function executeMarkdownSuite( executeMarkdownSuite('standard Markdown fixtures', standardFixtures, false); executeMarkdownSuite('FFM fixtures', ffmFixtures, true); +// Verify the public AST shape uses the modern scalar and structural field names. +describe('AST field names', () => { + it('uses value, content, and header fields', () => { + const ast = createMarkdownParser()('# Title\n\nhello'); + expect(ast).toEqual([ + { + type: 'heading', + level: 1, + content: [{ type: 'text', value: 'Title' }], + }, + { + type: 'paragraph', + content: [{ type: 'text', value: 'hello' }], + }, + ]); + + const table = createMarkdownParser()( + '\n| A | B |\n|---|---|\n| C | D |', + ); + expect(table[0]).toMatchObject({ + type: 'table', + header: [ + { type: 'table_cell', content: [{ type: 'text', value: 'A' }] }, + { type: 'table_cell', content: [{ type: 'text', value: 'B' }] }, + ], + content: [ + { + type: 'table_row', + content: [ + { type: 'table_cell', content: [{ type: 'text', value: 'C' }] }, + { type: 'table_cell', content: [{ type: 'text', value: 'D' }] }, + ], + }, + ], + }); + }); +}); + describe('safety fixtures', () => { expect(safetyFixtures.schema_version).toBe(1); for (const fixture of safetyFixtures.links) diff --git a/package/parser/src/index.ts b/package/parser/src/index.ts index e59dfa1..bfe848b 100644 --- a/package/parser/src/index.ts +++ b/package/parser/src/index.ts @@ -57,4 +57,4 @@ export type { Linkifier, MarkdownParserOption, MarkdownPlugin, -} from './types'; +} from './type'; diff --git a/package/parser/src/plugin/ast.ts b/package/parser/src/plugin/ast.ts index 94c568c..781dce1 100644 --- a/package/parser/src/plugin/ast.ts +++ b/package/parser/src/plugin/ast.ts @@ -12,12 +12,12 @@ export const mapAstNodes = ( for (let index = 0; index < nodes.length; index++) { const node = nodes[index]; - const transformedChildren = node.children - ? mapAstNodes(node.children, transformNode) - : node.children; + const transformedChildren = node.content + ? mapAstNodes(node.content, transformNode) + : node.content; const transformedNode = - transformedChildren && transformedChildren !== node.children - ? { ...node, children: transformedChildren } + transformedChildren && transformedChildren !== node.content + ? { ...node, content: transformedChildren } : node; const replacement = transformNode(transformedNode); const replacementNodes = Array.isArray(replacement) diff --git a/package/parser/src/plugin/async-blocks.ts b/package/parser/src/plugin/async-blocks.ts index eac7bd2..ec79c44 100644 --- a/package/parser/src/plugin/async-blocks.ts +++ b/package/parser/src/plugin/async-blocks.ts @@ -18,7 +18,7 @@ export const smilesInlineRule: InlineRule = { if (!match) return null; return { - node: { type: 'smiles_inline', content: match[1].trim() }, + node: { type: 'smiles_inline', value: match[1].trim() }, consumedChars: match[0].length, }; }, @@ -36,7 +36,7 @@ export const transformSpecialBlockNode = ( return { type: language === 'smiles' ? 'smiles_block' : language, - content: String(node.content ?? ''), + value: String(node.value ?? ''), } satisfies ASTNode; }; diff --git a/package/parser/src/plugin/latex.ts b/package/parser/src/plugin/latex.ts index 4042326..092cf98 100644 --- a/package/parser/src/plugin/latex.ts +++ b/package/parser/src/plugin/latex.ts @@ -44,7 +44,7 @@ export const latexInlineRule: InlineRule = { return { node: { type: 'math_inline', - content: state.content + value: state.content .slice(state.pos + MATH_INLINE_MARKER.length, endIndex) .trim(), }, @@ -86,7 +86,7 @@ export const latexBlockRule: BlockRule = { return { node: { type: 'math_block', - content: contentLines.join('\n').trim(), + value: contentLines.join('\n').trim(), }, consumedLines, }; diff --git a/package/parser/src/plugin/twemoji.ts b/package/parser/src/plugin/twemoji.ts index 7621318..fb01ac1 100644 --- a/package/parser/src/plugin/twemoji.ts +++ b/package/parser/src/plugin/twemoji.ts @@ -18,7 +18,7 @@ export const getTwemojiUrl = (emoji: string): string => { // Split text nodes into plain text and emoji image nodes without touching code. export const transformTwemojiNode = (node: ASTNode): ASTNode[] => { - const content = node.content; + const content = node.value; if (typeof content !== 'string' || content.length === 0) return [node]; emojiRegex.lastIndex = 0; @@ -35,12 +35,12 @@ export const transformTwemojiNode = (node: ASTNode): ASTNode[] => { if (matchIndex > lastIndex) { nodes.push({ type: 'text', - content: content.slice(lastIndex, matchIndex), + value: content.slice(lastIndex, matchIndex), }); } nodes.push({ type: 'emoji', - content: emoji, + value: emoji, alt: emoji, src: getTwemojiUrl(emoji), }); @@ -48,7 +48,7 @@ export const transformTwemojiNode = (node: ASTNode): ASTNode[] => { } if (lastIndex < content.length) { - nodes.push({ type: 'text', content: content.slice(lastIndex) }); + nodes.push({ type: 'text', value: content.slice(lastIndex) }); } return nodes; }; diff --git a/package/parser/src/rule/blocks.ts b/package/parser/src/rule/blocks.ts index 627f3f4..0c9a372 100644 --- a/package/parser/src/rule/blocks.ts +++ b/package/parser/src/rule/blocks.ts @@ -30,7 +30,7 @@ export const headingRule: BlockRule = { node: { type: 'heading', level: match[1].length, - children: match[2] ? ctx.parseInline(match[2].trim()) : [], + content: match[2] ? ctx.parseInline(match[2].trim()) : [], }, consumedLines: 1, }; @@ -48,7 +48,7 @@ export const codeBlockRule: BlockRule = { if (!block) return null; return { - node: { type: 'code_block', lang: block.lang, content: block.content }, + node: { type: 'code_block', lang: block.lang, value: block.content }, consumedLines: block.consumedLines, }; }, @@ -191,7 +191,7 @@ const createTableCell = ( ): ASTNode => { const cell: ASTNode = { type: 'table_cell', - children: parseInline(content), + content: parseInline(content), }; if (alignment) cell.align = alignment; return cell; @@ -238,7 +238,7 @@ export const tableRule: BlockRule = { rows.push({ type: 'table_row', - children: normalizeTableCells( + content: normalizeTableCells( extractTableCells(rowLine), columnCount, ).map((cell, index) => @@ -253,12 +253,12 @@ export const tableRule: BlockRule = { type: 'table', ...(headers ? { - headers: headers.map((header, index) => + header: headers.map((header, index) => createTableCell(header, alignments[index], ctx.parseInline), ), } : {}), - children: rows, + content: rows, }, consumedLines, }; @@ -319,7 +319,7 @@ export const blockquoteRule: BlockRule = { return { node: { type: 'blockquote', - children: ctx.parseBlocks(contentLines.join('\n')), + content: ctx.parseBlocks(contentLines.join('\n')), }, consumedLines, }; @@ -418,7 +418,7 @@ export const listRule: BlockRule = { items.push({ type: 'list_item', - children: ctx.parseBlocks(itemLines.join('\n')), + content: ctx.parseBlocks(itemLines.join('\n')), }); consumedLines += itemConsumedLines; } else { @@ -431,7 +431,7 @@ export const listRule: BlockRule = { type: 'list', ordered: isOrdered, start: startNumber, - children: items, + content: items, }, consumedLines, }; diff --git a/package/parser/src/rule/ffm.ts b/package/parser/src/rule/ffm.ts index bf5ef1e..53a0b22 100644 --- a/package/parser/src/rule/ffm.ts +++ b/package/parser/src/rule/ffm.ts @@ -33,7 +33,7 @@ export const ffmBlockRule: BlockRule = { // ffm quote: recursively render the internal Markdown if (type === 'quote') { return { - node: { type: 'blockquote', children: ctx.parseBlocks(rawContent) }, + node: { type: 'blockquote', content: ctx.parseBlocks(rawContent) }, consumedLines, }; } @@ -46,10 +46,10 @@ export const ffmBlockRule: BlockRule = { const slides = slideContents.map((s) => ({ type: 'slide_item', - children: ctx.parseBlocks(s.trim()), + content: ctx.parseBlocks(s.trim()), })); - return { node: { type: 'slide', children: slides }, consumedLines }; + return { node: { type: 'slide', content: slides }, consumedLines }; } // ffm chain and accordion @@ -78,7 +78,7 @@ export const ffmBlockRule: BlockRule = { preambleLines = currentLines; } } else { - previousItem.children = ctx.parseBlocks( + previousItem.content = ctx.parseBlocks( currentLines.join('\n').trim(), ); } @@ -88,7 +88,7 @@ export const ffmBlockRule: BlockRule = { type: type === 'accordion' ? 'accordion_item' : 'chain_item', name: accordionName, title: ctx.parseInline(titleMatch[2]), - children: [], + content: [], ...(type === 'chain' ? { isCompleted: checkboxMark @@ -108,7 +108,7 @@ export const ffmBlockRule: BlockRule = { // archive the last item or go back const lastItem = currentItem; if (lastItem) { - lastItem.children = ctx.parseBlocks(currentLines.join('\n').trim()); + lastItem.content = ctx.parseBlocks(currentLines.join('\n').trim()); } else { // if no valid title is found from beginning to end // revert to displaying regular content @@ -117,18 +117,18 @@ export const ffmBlockRule: BlockRule = { } } - const children: ASTNode[] = []; + const content: ASTNode[] = []; if (preambleLines.length > 0) { - children.push(...ctx.parseBlocks(preambleLines.join('\n').trim())); + content.push(...ctx.parseBlocks(preambleLines.join('\n').trim())); } - children.push(...items); + content.push(...items); - return { node: { type, name: accordionName, children }, consumedLines }; + return { node: { type, name: accordionName, content }, consumedLines }; } // fallback return { - node: { type: 'code_block', lang: type, content: rawContent }, + node: { type: 'code_block', lang: type, value: rawContent }, consumedLines, }; }, diff --git a/package/parser/src/rule/inlines.ts b/package/parser/src/rule/inlines.ts index bc27bfc..c9acf35 100644 --- a/package/parser/src/rule/inlines.ts +++ b/package/parser/src/rule/inlines.ts @@ -40,7 +40,7 @@ export const boldRule: InlineRule = { node: { type: 'bold', // supports nested parsing - children: ctx.parseInline(innerContent), + content: ctx.parseInline(innerContent), }, // +2 is because the followed `**` was consumed consumedChars: endIdx - state.pos + 2, @@ -68,7 +68,7 @@ export const italicRule: InlineRule = { return { node: { type: 'italic', - children: ctx.parseInline(state.content.slice(state.pos + 1, endIdx)), + content: ctx.parseInline(state.content.slice(state.pos + 1, endIdx)), }, consumedChars: endIdx - state.pos + 1, }; @@ -111,7 +111,7 @@ export const inlineCodeRule: InlineRule = { if (endIdx === -1) { return { - node: { type: 'text', content: marker }, + node: { type: 'text', value: marker }, consumedChars: markerLength, }; } @@ -132,7 +132,7 @@ export const inlineCodeRule: InlineRule = { return { node: { type: isColor ? 'color_code' : 'inline_code', - content: rawContent, + value: rawContent, }, consumedChars: endIdx + markerLength - state.pos, }; @@ -168,7 +168,7 @@ export const linkRule: InlineRule = { node: { type: 'link', url, - children: ctx.parseInline(innerText), + content: ctx.parseInline(innerText), }, consumedChars: urlEnd - state.pos + 1, }; @@ -190,7 +190,7 @@ export const underlineRule: InlineRule = { return { node: { type: 'underline', - children: ctx.parseInline(state.content.slice(state.pos + 2, endIdx)), + content: ctx.parseInline(state.content.slice(state.pos + 2, endIdx)), }, consumedChars: endIdx - state.pos + 2, }; @@ -212,7 +212,7 @@ export const strikeRule: InlineRule = { return { node: { type: 'strike', - children: ctx.parseInline(state.content.slice(state.pos + 2, endIdx)), + content: ctx.parseInline(state.content.slice(state.pos + 2, endIdx)), }, consumedChars: endIdx - state.pos + 2, }; diff --git a/package/parser/src/type.ts b/package/parser/src/type.ts index 88c18aa..0c545b7 100644 --- a/package/parser/src/type.ts +++ b/package/parser/src/type.ts @@ -32,14 +32,14 @@ export type NodeType = export interface ASTNode { type: NodeType; - content?: string; - children?: ASTNode[]; + value?: string; + content?: ASTNode[]; level?: number; lang?: string; url?: string; ordered?: boolean; start?: number; - headers?: ASTNode[]; + header?: ASTNode[]; name?: string; title?: ASTNode[]; isCompleted?: boolean; diff --git a/package/vue-renderer/src/render.ts b/package/vue-renderer/src/render.ts index 70782ea..8b8dbe0 100644 --- a/package/vue-renderer/src/render.ts +++ b/package/vue-renderer/src/render.ts @@ -14,25 +14,25 @@ export function renderToVue(nodes: ASTNode[]): (VNode | string)[] { ? node.level : null; return level === null - ? h('span', renderToVue(node.children || [])) - : h(`h${level}`, renderToVue(node.children || [])); + ? h('span', renderToVue(node.content || [])) + : h(`h${level}`, renderToVue(node.content || [])); } case 'paragraph': - return h('p', renderToVue(node.children || [])); + return h('p', renderToVue(node.content || [])); case 'text': - return node.content || ''; // Vue ๅคฉ็„ถ้˜ฒ XSS๏ผŒ็›ดๆŽฅ่ฟ”ๅ›žๅญ—็ฌฆไธฒ + return node.value || ''; // Vue ๅคฉ็„ถ้˜ฒ XSS๏ผŒ็›ดๆŽฅ่ฟ”ๅ›žๅญ—็ฌฆไธฒ case 'bold': - return h('strong', renderToVue(node.children || [])); + return h('strong', renderToVue(node.content || [])); case 'italic': - return h('em', renderToVue(node.children || [])); + return h('em', renderToVue(node.content || [])); case 'underline': - return h('u', renderToVue(node.children || [])); + return h('u', renderToVue(node.content || [])); case 'strike': - return h('del', renderToVue(node.children || [])); + return h('del', renderToVue(node.content || [])); case 'inline_code': - return h('code', node.content || ''); + return h('code', node.value || ''); case 'color_code': { - const color = String(node.content ?? ''); + const color = String(node.value ?? ''); if (!isSafeColorValue(color)) return color; return h('code', { class: 'ffm-color-code' }, [ h('span', { @@ -57,9 +57,9 @@ export function renderToVue(nodes: ASTNode[]): (VNode | string)[] { ? h( 'a', { href: url, target: '_blank', rel: 'noopener noreferrer' }, - renderToVue(node.children || []), + renderToVue(node.content || []), ) - : h('span', renderToVue(node.children || [])); + : h('span', renderToVue(node.content || [])); } case 'code_block': return h('div', { class: 'code-block-wrapper' }, [ @@ -70,7 +70,7 @@ export function renderToVue(nodes: ASTNode[]): (VNode | string)[] { h( 'code', { class: node.lang ? `language-${node.lang}` : '' }, - node.content || '', + node.value || '', ), ), ]); @@ -80,10 +80,10 @@ export function renderToVue(nodes: ASTNode[]): (VNode | string)[] { node.ordered && node.start && node.start !== 1 ? { start: node.start } : {}, - renderToVue(node.children || []), + renderToVue(node.content || []), ); case 'list_item': - return h('li', renderToVue(node.children || [])); + return h('li', renderToVue(node.content || [])); case 'table': return h('table', null, [ h( @@ -92,21 +92,21 @@ export function renderToVue(nodes: ASTNode[]): (VNode | string)[] { h( 'tr', null, - (node.headers ?? []).map((cell) => - h('th', renderToVue(cell.children || [])), + (node.header ?? []).map((cell) => + h('th', renderToVue(cell.content || [])), ), ), ), - node.children && node.children.length > 0 + node.content && node.content.length > 0 ? h( 'tbody', null, - node.children.map((row) => + node.content.map((row) => h( 'tr', null, - (row.children ?? []).map((cell) => - h('td', renderToVue(cell.children || [])), + (row.content ?? []).map((cell) => + h('td', renderToVue(cell.content || [])), ), ), ), @@ -116,14 +116,14 @@ export function renderToVue(nodes: ASTNode[]): (VNode | string)[] { case 'hr': return h('hr'); case 'blockquote': - return h('blockquote', renderToVue(node.children || [])); + return h('blockquote', renderToVue(node.content || [])); case 'hardbreak': return h('br'); case 'accordion': return h( 'div', { class: 'ffm-accordion' }, - renderToVue(node.children || []), + renderToVue(node.content || []), ); case 'accordion_item': return h('details', { name: node.name }, [ @@ -131,14 +131,14 @@ export function renderToVue(nodes: ASTNode[]): (VNode | string)[] { h( 'div', { class: 'accordion-content' }, - renderToVue(node.children || []), + renderToVue(node.content || []), ), ]); case 'chain': return h( 'div', { class: 'chain-container' }, - renderToVue(node.children || []), + renderToVue(node.content || []), ); case 'chain_item': { const statusClass = node.hasCheckbox @@ -152,7 +152,7 @@ export function renderToVue(nodes: ASTNode[]): (VNode | string)[] { node.title && node.title.length > 0 ? h('div', { class: 'chain-title' }, renderToVue(node.title)) : null, - h('div', { class: 'chain-body' }, renderToVue(node.children || [])), + h('div', { class: 'chain-body' }, renderToVue(node.content || [])), ]), ]); } @@ -161,17 +161,17 @@ export function renderToVue(nodes: ASTNode[]): (VNode | string)[] { h( 'div', { class: 'slide-container' }, - renderToVue(node.children || []), + renderToVue(node.content || []), ), ]); case 'slide_item': return h( 'div', { class: 'slide-item' }, - renderToVue(node.children || []), + renderToVue(node.content || []), ); default: - return h('span', renderToVue(node.children || [])); + return h('span', renderToVue(node.content || [])); } }); } From 67c6bff392adb3bfa80bbe9162fe512d953d50d8 Mon Sep 17 00:00:00 2001 From: Fuyeor Date: Tue, 22 Sep 2026 13:25:20 +0800 Subject: [PATCH 5/7] singularize marker array in parser --- package/parser/src/core/parser.ts | 8 ++--- package/parser/src/default.ts | 4 +-- package/parser/src/plugin/async-blocks.ts | 2 +- package/parser/src/plugin/latex.ts | 30 +++++++++---------- .../parser/src/rule/{blocks.ts => block.ts} | 16 +++++----- package/parser/src/rule/ffm.ts | 4 +-- .../parser/src/rule/{inlines.ts => inline.ts} | 16 +++++----- package/parser/src/type.ts | 6 ++-- 8 files changed, 43 insertions(+), 43 deletions(-) rename package/parser/src/rule/{blocks.ts => block.ts} (97%) rename package/parser/src/rule/{inlines.ts => inline.ts} (97%) diff --git a/package/parser/src/core/parser.ts b/package/parser/src/core/parser.ts index 004e986..e7d416b 100644 --- a/package/parser/src/core/parser.ts +++ b/package/parser/src/core/parser.ts @@ -1,6 +1,6 @@ // @ffm/parser/src/core/parser.ts -import { BlockState, InlineState } from './state'; import { linkify } from '@ffm/linkify'; +import { BlockState, InlineState } from './state'; import type { ASTNode, ASTTransform, @@ -58,7 +58,7 @@ export class MarkdownParser { // register block rule // By default, it inserts at the end, or before/after a specified rule. addBlockRule(rule: BlockRule): this { - for (const marker of rule.markers) { + for (const marker of rule.marker) { const list = this.#blockRuleMap.get(marker) || []; list.push(rule); this.#blockRuleMap.set(marker, list); @@ -68,8 +68,8 @@ export class MarkdownParser { // register inline rule addInlineRule(rule: InlineRule): this { - // register the rule under its markers for quick lookup during parsing - for (const marker of rule.markers) { + // register the rule under its marker for quick lookup during parsing + for (const marker of rule.marker) { const rulesForMarker = this.#inlineRuleMap.get(marker) || []; // ensure the rule is in the list for this marker rulesForMarker.unshift(rule); diff --git a/package/parser/src/default.ts b/package/parser/src/default.ts index 085b929..f258866 100644 --- a/package/parser/src/default.ts +++ b/package/parser/src/default.ts @@ -8,7 +8,7 @@ import { listRule, hrRule, blockquoteRule, -} from './rule/blocks'; +} from './rule/block'; import { hardBreakRule, inlineCodeRule, @@ -17,7 +17,7 @@ import { underlineRule, italicRule, strikeRule, -} from './rule/inlines'; +} from './rule/inline'; import { ffmBlockRule } from './rule/ffm'; import { latexPlugin } from './plugin/latex'; import { extensionsPlugin } from './plugin/extensions'; diff --git a/package/parser/src/plugin/async-blocks.ts b/package/parser/src/plugin/async-blocks.ts index ec79c44..6145159 100644 --- a/package/parser/src/plugin/async-blocks.ts +++ b/package/parser/src/plugin/async-blocks.ts @@ -10,7 +10,7 @@ export const SPECIAL_BLOCK_LANGUAGES = new Set(['mermaid', 'abc', 'smiles']); // Parse the FFM #[smiles = `...`] inline placeholder syntax. export const smilesInlineRule: InlineRule = { name: 'smiles_inline', - markers: ['#'], + marker: ['#'], parse(state: InlineState) { if (state.currentChar !== '#') return null; diff --git a/package/parser/src/plugin/latex.ts b/package/parser/src/plugin/latex.ts index 092cf98..4936f0a 100644 --- a/package/parser/src/plugin/latex.ts +++ b/package/parser/src/plugin/latex.ts @@ -2,8 +2,8 @@ import { BlockState, InlineState } from '#/core/state'; import type { BlockRule, InlineRule, MarkdownPlugin } from '#/type'; -const MATH_INLINE_MARKER = '$'; -const MATH_BLOCK_MARKER = '$$'; +const inlineMathMarker = '$'; +const blockMathMarker = '$$'; // Find an unescaped dollar delimiter without interpreting LaTeX escapes. const findMathDelimiter = ( @@ -29,15 +29,15 @@ const findMathDelimiter = ( // Parse inline $...$ formulas while leaving unmatched dollars as plain text. export const latexInlineRule: InlineRule = { name: 'math_inline', - markers: [MATH_INLINE_MARKER], + marker: [inlineMathMarker], parse(state: InlineState) { - if (state.currentChar !== MATH_INLINE_MARKER) return null; - if (state.content.startsWith(MATH_BLOCK_MARKER, state.pos)) return null; + if (state.currentChar !== inlineMathMarker) return null; + if (state.content.startsWith(blockMathMarker, state.pos)) return null; const endIndex = findMathDelimiter( state.content, - state.pos + MATH_INLINE_MARKER.length, - MATH_INLINE_MARKER, + state.pos + inlineMathMarker.length, + inlineMathMarker, ); if (endIndex === -1) return null; @@ -45,10 +45,10 @@ export const latexInlineRule: InlineRule = { node: { type: 'math_inline', value: state.content - .slice(state.pos + MATH_INLINE_MARKER.length, endIndex) + .slice(state.pos + inlineMathMarker.length, endIndex) .trim(), }, - consumedChars: endIndex - state.pos + MATH_INLINE_MARKER.length, + consumedChars: endIndex - state.pos + inlineMathMarker.length, }; }, }; @@ -56,30 +56,30 @@ export const latexInlineRule: InlineRule = { // Parse a $$...$$ formula as one block, including formulas spanning lines. export const latexBlockRule: BlockRule = { name: 'math_block', - markers: [MATH_INLINE_MARKER], + marker: [inlineMathMarker], parse(state: BlockState) { const firstLine = state.currentLine; if (!firstLine) return null; const trimmedLine = firstLine.trimStart(); - if (!trimmedLine.startsWith(MATH_BLOCK_MARKER)) return null; + if (!trimmedLine.startsWith(blockMathMarker)) return null; const contentLines: string[] = []; let consumedLines = 1; - let currentContent = trimmedLine.slice(MATH_BLOCK_MARKER.length); - let endIndex = findMathDelimiter(currentContent, 0, MATH_BLOCK_MARKER); + let currentContent = trimmedLine.slice(blockMathMarker.length); + let endIndex = findMathDelimiter(currentContent, 0, blockMathMarker); while (endIndex === -1) { contentLines.push(currentContent); if (state.lineIndex + consumedLines >= state.lineCount) return null; currentContent = state.lines[state.lineIndex + consumedLines]; consumedLines++; - endIndex = findMathDelimiter(currentContent, 0, MATH_BLOCK_MARKER); + endIndex = findMathDelimiter(currentContent, 0, blockMathMarker); } contentLines.push(currentContent.slice(0, endIndex)); const trailingContent = currentContent.slice( - endIndex + MATH_BLOCK_MARKER.length, + endIndex + blockMathMarker.length, ); if (trailingContent.trim() !== '') return null; diff --git a/package/parser/src/rule/blocks.ts b/package/parser/src/rule/block.ts similarity index 97% rename from package/parser/src/rule/blocks.ts rename to package/parser/src/rule/block.ts index 0c9a372..da7261c 100644 --- a/package/parser/src/rule/blocks.ts +++ b/package/parser/src/rule/block.ts @@ -1,4 +1,4 @@ -// @ffm/parser/src/rule/blocks.ts +// @ffm/parser/src/rule/block.ts import type { ASTNode, BlockRule } from '#/type'; import { BlockState } from '#/core/state'; @@ -18,7 +18,7 @@ const fencedBlockCache = new WeakMap< */ export const headingRule: BlockRule = { name: 'heading', - markers: ['#'], + marker: ['#'], parse(state: BlockState, ctx) { const line = state.currentLine; if (!line) return null; @@ -42,7 +42,7 @@ export const headingRule: BlockRule = { */ export const codeBlockRule: BlockRule = { name: 'code_block', - markers: ['`', '~'], + marker: ['`', '~'], parse(state: BlockState) { const block = extractFencedBlock(state); if (!block) return null; @@ -202,7 +202,7 @@ const createTableCell = ( */ export const tableRule: BlockRule = { name: 'table', - markers: ['|'], + marker: ['|'], parse(state: BlockState, ctx) { const line = state.currentLine; if (!line || !line.includes('|')) return null; @@ -270,7 +270,7 @@ export const tableRule: BlockRule = { */ export const hrRule: BlockRule = { name: 'hr', - markers: ['-', '*', '_'], + marker: ['-', '*', '_'], parse(state: BlockState) { const line = state.currentLine; if (!line) return null; @@ -291,7 +291,7 @@ export const hrRule: BlockRule = { */ export const blockquoteRule: BlockRule = { name: 'blockquote', - markers: ['>'], + marker: ['>'], parse(state: BlockState, ctx) { const line = state.currentLine; if (!line || !line.trimStart().startsWith('>')) return null; @@ -334,7 +334,7 @@ const LIST_INDENT_STEP = 2; */ export const listRule: BlockRule = { name: 'list', - markers: ['-', '*', '+', '1', '2', '3', '4', '5', '6', '7', '8', '9'], + marker: ['-', '*', '+', '1', '2', '3', '4', '5', '6', '7', '8', '9'], parse(state: BlockState, ctx) { const line = state.currentLine; if (!line) return null; @@ -364,7 +364,7 @@ export const listRule: BlockRule = { // detect subsequent lines belonging to this item // (lines with indentation deeper than the marker). const markerTotalWidth = baseIndent + marker.length + 1; - // Normalize nested list markers to two-space logical levels, tolerating odd legacy indentation. + // Normalize nested list marker to two-space logical levels, tolerating odd legacy indentation. const nestedListIndent = baseIndent + LIST_INDENT_STEP; while ( diff --git a/package/parser/src/rule/ffm.ts b/package/parser/src/rule/ffm.ts index 53a0b22..1a06ec5 100644 --- a/package/parser/src/rule/ffm.ts +++ b/package/parser/src/rule/ffm.ts @@ -1,6 +1,6 @@ // @ffm/parser/src/rule/ffm.ts import { BlockState } from '#/core/state'; -import { extractFencedBlock } from './blocks'; +import { extractFencedBlock } from './block'; import type { BlockRule, ASTNode } from '#/type'; const ffmKeyword = new Set(['quote', 'slide', 'chain', 'accordion']); @@ -18,7 +18,7 @@ const nodeTitleRegex = /^\s*\*\*(?:\[([ xX])\]\s*)?(.+?)\*\*\s*$/; export const ffmBlockRule: BlockRule = { name: 'ffm_blocks', // same as code block to leverage fenced block parsing - markers: ['`', '~'], + marker: ['`', '~'], parse(state: BlockState, ctx) { const block = extractFencedBlock(state); if (!block) return null; diff --git a/package/parser/src/rule/inlines.ts b/package/parser/src/rule/inline.ts similarity index 97% rename from package/parser/src/rule/inlines.ts rename to package/parser/src/rule/inline.ts index c9acf35..67385d1 100644 --- a/package/parser/src/rule/inlines.ts +++ b/package/parser/src/rule/inline.ts @@ -1,4 +1,4 @@ -// @ffm/parser/src/rule/inlines.ts +// @ffm/parser/src/rule/inline.ts import type { InlineRule } from '#/type'; import { InlineState } from '#/core/state'; import { isSafeColorValue } from '#/core/color'; @@ -9,7 +9,7 @@ import { isSafeLinkUrl } from '#/core/url'; */ export const hardBreakRule: InlineRule = { name: 'hardbreak', - markers: ['\\', ' '], + marker: ['\\', ' '], parse(state: InlineState) { if (state.currentChar === '\\' && state.content[state.pos + 1] === '\n') { return { @@ -26,7 +26,7 @@ export const hardBreakRule: InlineRule = { */ export const boldRule: InlineRule = { name: 'bold', - markers: ['*'], + marker: ['*'], parse(state: InlineState, ctx) { if (!state.content.startsWith('**', state.pos)) return null; @@ -53,7 +53,7 @@ export const boldRule: InlineRule = { */ export const italicRule: InlineRule = { name: 'italic', - markers: ['*'], + marker: ['*'], parse(state: InlineState, ctx) { const char = state.currentChar; // only triggers when it starts with * and @@ -82,7 +82,7 @@ export const italicRule: InlineRule = { */ export const inlineCodeRule: InlineRule = { name: 'inline_code', - markers: ['`'], + marker: ['`'], parse(state: InlineState) { if (state.currentChar !== '`') return null; @@ -144,7 +144,7 @@ export const inlineCodeRule: InlineRule = { */ export const linkRule: InlineRule = { name: 'link', - markers: ['['], + marker: ['['], parse(state: InlineState, ctx) { if (state.currentChar !== '[') return null; @@ -180,7 +180,7 @@ export const linkRule: InlineRule = { */ export const underlineRule: InlineRule = { name: 'underline', - markers: ['_'], + marker: ['_'], parse(state: InlineState, ctx) { if (!state.content.startsWith('__', state.pos)) return null; @@ -202,7 +202,7 @@ export const underlineRule: InlineRule = { */ export const strikeRule: InlineRule = { name: 'strike', - markers: ['-'], + marker: ['-'], parse(state: InlineState, ctx) { if (!state.content.startsWith('--', state.pos)) return null; diff --git a/package/parser/src/type.ts b/package/parser/src/type.ts index 0c545b7..34e8499 100644 --- a/package/parser/src/type.ts +++ b/package/parser/src/type.ts @@ -58,8 +58,8 @@ export interface ParserContext { // block parsing rule (such as header, code block, table) export interface BlockRule { name: string; - // optional markers to quickly identify potential matches (e.g., '#' for headings) - markers: string[]; + // optional marker to quickly identify potential matches (e.g., '#' for headings) + marker: string[]; // Returns the generated Node and the number of rows consumed if the match is successful; // returns null if the match fails. parse: ( @@ -71,7 +71,7 @@ export interface BlockRule { // inline parsing rule (such as bold, italics, links) export interface InlineRule { name: string; - markers: string[]; + marker: string[]; // Returns the generated Node and the number of chars consumed if the match is successful; // returns null if the match fails. parse: ( From cfa5880a30b09fff64b1e8e56b66866f826ff61f Mon Sep 17 00:00:00 2001 From: Fuyeor Date: Tue, 22 Sep 2026 09:18:16 +0000 Subject: [PATCH 6/7] align rust ffm-parser with new AST --- Cargo.toml | 2 +- crate/src/ast.rs | 22 ++--- crate/src/lib.rs | 65 +++++++-------- crate/src/parser.rs | 98 +++++++++++----------- crate/src/plain_text.rs | 36 ++++---- crate/src/render.rs | 68 +++++++-------- crate/src/{rules.rs => rule.rs} | 101 +++++++++++------------ extension/vscode/src/ffm-grammar.spec.ts | 92 --------------------- fixture/ffm.json | 4 +- 9 files changed, 196 insertions(+), 292 deletions(-) rename crate/src/{rules.rs => rule.rs} (91%) delete mode 100644 extension/vscode/src/ffm-grammar.spec.ts diff --git a/Cargo.toml b/Cargo.toml index 9926708..db32fd8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,4 +1,4 @@ # /Cargo.toml [workspace] -members = ["packages/markdown-parser-rust"] +members = ["crate"] resolver = "3" diff --git a/crate/src/ast.rs b/crate/src/ast.rs index b6cf84a..3d18350 100644 --- a/crate/src/ast.rs +++ b/crate/src/ast.rs @@ -128,10 +128,10 @@ impl Alignment { pub struct AstNode { /// The semantic node type. pub node_type: NodeType, - /// Literal text or code content, when present. - pub content: Option, + /// Literal text or code content value, when present. + pub value: Option, /// Child nodes in source order. - pub children: Vec, + pub content: Vec, /// ATX heading level. pub level: Option, /// Fenced code info string. @@ -143,7 +143,7 @@ pub struct AstNode { /// Ordered-list starting number. pub start: Option, /// Table header cells. - pub headers: Option>, + pub header: Option>, /// Accordion or chain group name. pub name: Option, /// Accordion or chain item title nodes. @@ -163,14 +163,14 @@ impl AstNode { pub fn new(node_type: NodeType) -> Self { Self { node_type, - content: None, - children: Vec::new(), + value: None, + content: Vec::new(), level: None, lang: None, url: None, ordered: None, start: None, - headers: None, + header: None, name: None, title: None, is_completed: None, @@ -181,16 +181,16 @@ impl AstNode { } /// Creates a text node with the supplied content. - pub fn text(content: impl Into) -> Self { + pub fn text(value: impl Into) -> Self { let mut node = Self::new(NodeType::Text); - node.content = Some(content.into()); + node.value = Some(value.into()); node } /// Creates a node with child nodes. - pub fn with_children(node_type: NodeType, children: Vec) -> Self { + pub fn with_content(node_type: NodeType, content: Vec) -> Self { let mut node = Self::new(node_type); - node.children = children; + node.content = content; node } } diff --git a/crate/src/lib.rs b/crate/src/lib.rs index 901b88e..e0b8b28 100644 --- a/crate/src/lib.rs +++ b/crate/src/lib.rs @@ -11,37 +11,36 @@ mod linkify; mod parser; mod plain_text; mod render; -mod rules; +mod rule; mod safety; mod state; pub use ast::{Alignment, AstNode, NodeType}; pub use linkify::{LinkMatch, linkify}; -pub use parser::{Linkifier, MarkdownParser, ParserContext, ParserError, ParserOptions}; +pub use parser::{Linkifier, MarkdownParser, ParserContext, ParserError, ParserOption}; pub use plain_text::to_plain_text; pub use render::{render, render_optional}; pub use safety::{is_safe_color_value, is_safe_link_url}; pub use state::{BlockState, InlineState}; /// Constructs a parser with the base Markdown rule set. -pub fn create_markdown_parser(options: ParserOptions) -> MarkdownParser { - MarkdownParser::create_standard(options) +pub fn create_common_parser(option: ParserOption) -> MarkdownParser { + MarkdownParser::create_common(option) } /// Constructs a parser with the base Markdown rules and FFM extensions. -pub fn create_fuyeor_markdown_parser(options: ParserOptions) -> MarkdownParser { - MarkdownParser::create_ffm(options) +pub fn create_ffm_parser(option: ParserOption) -> MarkdownParser { + MarkdownParser::create_ffm(option) } #[cfg(test)] -mod cross_language_fixtures { +mod shared_fixture { use serde::Deserialize; use serde_json::{Map, Value, json}; use super::{ - AstNode, MarkdownParser, ParserOptions, create_fuyeor_markdown_parser, - create_markdown_parser, is_safe_color_value, is_safe_link_url, linkify, render, - to_plain_text, + AstNode, MarkdownParser, ParserOption, create_common_parser, create_ffm_parser, + is_safe_color_value, is_safe_link_url, linkify, render, to_plain_text, }; #[derive(Deserialize)] @@ -59,7 +58,7 @@ mod cross_language_fixtures { assert: Vec, error: Option, no_throw: Option, - options: Option, + option: Option, } #[derive(Deserialize)] @@ -70,7 +69,7 @@ mod cross_language_fixtures { } #[derive(Deserialize)] - struct FixtureOptions { + struct FixtureOption { max_nesting_depth: Option, } @@ -107,7 +106,7 @@ mod cross_language_fixtures { // Load a shared JSON fixture from the repository root. fn read_fixture Deserialize<'de>>(name: &str) -> T { - let path = format!("{}/../../fixtures/{name}", env!("CARGO_MANIFEST_DIR")); + let path = format!("{}/../fixture/{name}", env!("CARGO_MANIFEST_DIR")); serde_json::from_str(&std::fs::read_to_string(path).expect("fixture must exist")) .expect("fixture must be valid JSON") } @@ -116,13 +115,13 @@ mod cross_language_fixtures { fn normalize_node(node: &AstNode) -> Value { let mut object = Map::new(); object.insert("type".into(), json!(node.node_type.as_str())); - if let Some(content) = &node.content { - object.insert("content".into(), json!(content)); + if let Some(value) = &node.value { + object.insert("value".into(), json!(value)); } - if !node.children.is_empty() { + if !node.content.is_empty() { object.insert( - "children".into(), - Value::Array(node.children.iter().map(normalize_node).collect()), + "content".into(), + Value::Array(node.content.iter().map(normalize_node).collect()), ); } if let Some(level) = node.level { @@ -140,10 +139,10 @@ mod cross_language_fixtures { if let Some(start) = node.start { object.insert("start".into(), json!(start)); } - if let Some(headers) = &node.headers { + if let Some(header) = &node.header { object.insert( - "headers".into(), - Value::Array(headers.iter().map(normalize_node).collect()), + "header".into(), + Value::Array(header.iter().map(normalize_node).collect()), ); } if let Some(name) = &node.name { @@ -179,29 +178,29 @@ mod cross_language_fixtures { }) } - // Translate fixture options into the Rust parser API. - fn parser_options(case: &MarkdownCase) -> ParserOptions { - ParserOptions { + // Translate fixture option into the Rust parser API. + fn parser_option(case: &MarkdownCase) -> ParserOption { + ParserOption { max_nesting_depth: case - .options + .option .as_ref() - .and_then(|options| options.max_nesting_depth) + .and_then(|option| option.max_nesting_depth) .unwrap_or(64), - ..ParserOptions::default() + ..ParserOption::default() } } // Execute one standard or FFM Markdown case against the Rust parser. fn execute_markdown_case(case: &MarkdownCase, ffm: bool) { - let options = parser_options(case); + let option = parser_option(case); if case.error.as_deref() == Some("invalid_nesting_depth") { - assert!(MarkdownParser::try_new(options).is_err()); + assert!(MarkdownParser::try_new(option).is_err()); return; } let parser = if ffm { - create_fuyeor_markdown_parser(options) + create_ffm_parser(option) } else { - create_markdown_parser(options) + create_common_parser(option) }; let ast = parser.parse(case.input.as_deref().unwrap_or("")); if let Some(expected_html) = &case.html { @@ -230,7 +229,7 @@ mod cross_language_fixtures { } #[test] - fn executes_shared_markdown_fixtures() { + fn test_markdown() { let standard = read_fixture::("markdown.json"); assert_eq!(standard.schema_version, 2); for case in &standard.cases { @@ -244,7 +243,7 @@ mod cross_language_fixtures { } #[test] - fn executes_shared_safety_and_linkify_fixtures() { + fn test_linkify() { let safety = read_fixture::("safety.json"); assert_eq!(safety.schema_version, 1); for case in safety.links { diff --git a/crate/src/parser.rs b/crate/src/parser.rs index dd49196..ec66940 100644 --- a/crate/src/parser.rs +++ b/crate/src/parser.rs @@ -4,7 +4,7 @@ use std::collections::HashMap; use crate::ast::{AstNode, NodeType}; use crate::linkify::{LinkMatch, linkify}; -use crate::rules::{BlockRule, FfmBlockRule, InlineRule}; +use crate::rule::{BlockRule, FuyeorBlockRule, InlineRule}; use crate::safety::is_safe_link_url; use crate::state::{BlockState, InlineState}; @@ -21,16 +21,16 @@ const PRELIGHT_BLOCK_RULES: &[&str] = &[ /// A linkifier callback used by the parser's inline text projection. pub type Linkifier = fn(&str) -> Vec; -/// Parser options matching the TypeScript parser constructor. +/// Parser option matching the TypeScript parser constructor. #[derive(Clone, Copy)] -pub struct ParserOptions { +pub struct ParserOption { /// Maximum recursive block/inline parsing depth. pub max_nesting_depth: usize, /// Callback used to discover links in plain text. pub linkifier: Linkifier, } -impl Default for ParserOptions { +impl Default for ParserOption { fn default() -> Self { Self { max_nesting_depth: 64, @@ -109,14 +109,14 @@ pub struct MarkdownParser { } impl MarkdownParser { - /// Creates a parser and fails fast if its options are invalid. - pub fn new(options: ParserOptions) -> Self { - Self::try_new(options).expect("invalid MarkdownParser options") + /// Creates a parser and fails fast if its option are invalid. + pub fn new(option: ParserOption) -> Self { + Self::try_new(option).expect("invalid MarkdownParser option") } /// Creates a parser without panicking on invalid configuration. - pub fn try_new(options: ParserOptions) -> Result { - if options.max_nesting_depth == 0 { + pub fn try_new(option: ParserOption) -> Result { + if option.max_nesting_depth == 0 { return Err(ParserError::InvalidMaxNestingDepth); } Ok(Self { @@ -124,8 +124,8 @@ impl MarkdownParser { inline_rules: Vec::new(), block_rule_map: HashMap::new(), inline_rule_map: HashMap::new(), - max_nesting_depth: options.max_nesting_depth, - linkifier: options.linkifier, + max_nesting_depth: option.max_nesting_depth, + linkifier: option.linkifier, }) } @@ -135,9 +135,9 @@ impl MarkdownParser { R: BlockRule + 'static, { let rule_index = self.block_rules.len(); - let markers = rule.markers(); + let marker = rule.marker(); self.block_rules.push(Box::new(rule)); - for marker in markers { + for marker in marker { self.block_rule_map .entry(*marker) .or_default() @@ -152,9 +152,9 @@ impl MarkdownParser { R: InlineRule + 'static, { let rule_index = self.inline_rules.len(); - let markers = rule.markers(); + let marker = rule.marker(); self.inline_rules.push(Box::new(rule)); - for marker in markers { + for marker in marker { self.inline_rule_map .entry(*marker) .or_default() @@ -176,41 +176,41 @@ impl MarkdownParser { } /// Parses the input with the standard rule set. - pub fn create_standard(options: ParserOptions) -> Self { - let mut parser = Self::new(options); - parser.add_block_rule(crate::rules::CodeBlockRule); - parser.add_block_rule(crate::rules::ListRule); - parser.add_block_rule(crate::rules::HeadingRule); - parser.add_block_rule(crate::rules::TableRule); - parser.add_block_rule(crate::rules::HrRule); - parser.add_block_rule(crate::rules::BlockquoteRule); - parser.add_inline_rule(crate::rules::HardBreakRule); - parser.add_inline_rule(crate::rules::InlineCodeRule); - parser.add_inline_rule(crate::rules::LinkRule); - parser.add_inline_rule(crate::rules::BoldRule); - parser.add_inline_rule(crate::rules::UnderlineRule); - parser.add_inline_rule(crate::rules::ItalicRule); - parser.add_inline_rule(crate::rules::StrikeRule); + pub fn create_common(option: ParserOption) -> Self { + let mut parser = Self::new(option); + parser.add_block_rule(crate::rule::CodeBlockRule); + parser.add_block_rule(crate::rule::ListRule); + parser.add_block_rule(crate::rule::HeadingRule); + parser.add_block_rule(crate::rule::TableRule); + parser.add_block_rule(crate::rule::HrRule); + parser.add_block_rule(crate::rule::BlockquoteRule); + parser.add_inline_rule(crate::rule::HardBreakRule); + parser.add_inline_rule(crate::rule::InlineCodeRule); + parser.add_inline_rule(crate::rule::LinkRule); + parser.add_inline_rule(crate::rule::BoldRule); + parser.add_inline_rule(crate::rule::UnderlineRule); + parser.add_inline_rule(crate::rule::ItalicRule); + parser.add_inline_rule(crate::rule::StrikeRule); parser } /// Parses the input with the standard rule set plus FFM fenced blocks. - pub fn create_ffm(options: ParserOptions) -> Self { - let mut parser = Self::new(options); - parser.add_block_rule(FfmBlockRule); - parser.add_block_rule(crate::rules::CodeBlockRule); - parser.add_block_rule(crate::rules::ListRule); - parser.add_block_rule(crate::rules::HeadingRule); - parser.add_block_rule(crate::rules::TableRule); - parser.add_block_rule(crate::rules::HrRule); - parser.add_block_rule(crate::rules::BlockquoteRule); - parser.add_inline_rule(crate::rules::HardBreakRule); - parser.add_inline_rule(crate::rules::InlineCodeRule); - parser.add_inline_rule(crate::rules::LinkRule); - parser.add_inline_rule(crate::rules::BoldRule); - parser.add_inline_rule(crate::rules::UnderlineRule); - parser.add_inline_rule(crate::rules::ItalicRule); - parser.add_inline_rule(crate::rules::StrikeRule); + pub fn create_ffm(option: ParserOption) -> Self { + let mut parser = Self::new(option); + parser.add_block_rule(FuyeorBlockRule); + parser.add_block_rule(crate::rule::CodeBlockRule); + parser.add_block_rule(crate::rule::ListRule); + parser.add_block_rule(crate::rule::HeadingRule); + parser.add_block_rule(crate::rule::TableRule); + parser.add_block_rule(crate::rule::HrRule); + parser.add_block_rule(crate::rule::BlockquoteRule); + parser.add_inline_rule(crate::rule::HardBreakRule); + parser.add_inline_rule(crate::rule::InlineCodeRule); + parser.add_inline_rule(crate::rule::LinkRule); + parser.add_inline_rule(crate::rule::BoldRule); + parser.add_inline_rule(crate::rule::UnderlineRule); + parser.add_inline_rule(crate::rule::ItalicRule); + parser.add_inline_rule(crate::rule::StrikeRule); parser } @@ -233,7 +233,7 @@ impl MarkdownParser { runtime: &RefCell, ) -> Vec { if depth > self.max_nesting_depth { - return vec![AstNode::with_children( + return vec![AstNode::with_content( NodeType::Paragraph, vec![AstNode::text(state.remaining_lines().join("\n"))], )]; @@ -316,7 +316,7 @@ impl MarkdownParser { if !paragraph_lines.is_empty() { let inline_content = paragraph_lines.join("\n"); - nodes.push(AstNode::with_children( + nodes.push(AstNode::with_content( NodeType::Paragraph, self.parse_inline_inner(&inline_content, depth, runtime), )); @@ -415,7 +415,7 @@ impl MarkdownParser { } let mut link = AstNode::new(NodeType::Link); link.url = Some(matched.url); - link.children = vec![AstNode::text(matched.text)]; + link.content = vec![AstNode::text(matched.text)]; nodes.push(link); last_index = matched.last_index; } diff --git a/crate/src/plain_text.rs b/crate/src/plain_text.rs index 4d8df76..56ed2de 100644 --- a/crate/src/plain_text.rs +++ b/crate/src/plain_text.rs @@ -27,8 +27,8 @@ fn append_block_nodes(nodes: &[AstNode], output: &mut String) { fn append_block_node(node: &AstNode, output: &mut String) { match &node.node_type { NodeType::Text | NodeType::InlineCode | NodeType::ColorCode => { - if let Some(content) = &node.content { - output.push_str(content); + if let Some(value) = &node.value { + output.push_str(value); } } NodeType::Heading @@ -37,10 +37,10 @@ fn append_block_node(node: &AstNode, output: &mut String) { | NodeType::Italic | NodeType::Underline | NodeType::Strike - | NodeType::Link => append_inline_nodes(&node.children, output), + | NodeType::Link => append_inline_nodes(&node.content, output), NodeType::CodeBlock => { - if let Some(content) = &node.content { - output.push_str(content); + if let Some(value) = &node.value { + output.push_str(value); } } NodeType::Blockquote @@ -49,43 +49,43 @@ fn append_block_node(node: &AstNode, output: &mut String) { | NodeType::Slide | NodeType::SlideItem | NodeType::Accordion - | NodeType::Chain => append_block_nodes(&node.children, output), + | NodeType::Chain => append_block_nodes(&node.content, output), NodeType::AccordionItem => { append_inline_nodes(node.title.as_deref().unwrap_or_default(), output); - if !output.is_empty() && !output.ends_with('\n') && !node.children.is_empty() { + if !output.is_empty() && !output.ends_with('\n') && !node.content.is_empty() { output.push('\n'); } - append_block_nodes(&node.children, output); + append_block_nodes(&node.content, output); } NodeType::ChainItem => { if let Some(title) = &node.title { append_inline_nodes(title, output); - if !output.is_empty() && !output.ends_with('\n') && !node.children.is_empty() { + if !output.is_empty() && !output.ends_with('\n') && !node.content.is_empty() { output.push('\n'); } } - append_block_nodes(&node.children, output); + append_block_nodes(&node.content, output); } NodeType::Table => { - if let Some(headers) = &node.headers { - append_table_cells(headers, output); + if let Some(header) = &node.header { + append_table_cells(header, output); } - if !node.children.is_empty() { + if !node.content.is_empty() { if !output.is_empty() { push_separator(output); } - append_block_nodes(&node.children, output); + append_block_nodes(&node.content, output); } } - NodeType::TableRow => append_table_cells(&node.children, output), - NodeType::TableCell => append_inline_nodes(&node.children, output), + NodeType::TableRow => append_table_cells(&node.content, output), + NodeType::TableCell => append_inline_nodes(&node.content, output), NodeType::Hardbreak => output.push('\n'), NodeType::Hr => {} - NodeType::Root | NodeType::Custom(_) => append_block_nodes(&node.children, output), + NodeType::Root | NodeType::Custom(_) => append_block_nodes(&node.content, output), } } -/// Appends inline children without inserting separators between formatting nodes. +/// Appends inline content without inserting separators between formatting nodes. fn append_inline_nodes(nodes: &[AstNode], output: &mut String) { for node in nodes { append_block_node(node, output); diff --git a/crate/src/render.rs b/crate/src/render.rs index 1f9f691..5d9e9b1 100644 --- a/crate/src/render.rs +++ b/crate/src/render.rs @@ -33,53 +33,53 @@ fn render_into(output: &mut String, nodes: &[AstNode]) { NodeType::Heading => { if let Some(level @ 1..=6) = node.level { let _ = write!(output, ""); - render_into(output, &node.children); + render_into(output, &node.content); let _ = writeln!(output, ""); } else { output.push_str(""); - render_into(output, &node.children); + render_into(output, &node.content); output.push_str(""); } } NodeType::Paragraph => { output.push_str("

    "); - render_into(output, &node.children); + render_into(output, &node.content); output.push_str("

    \n"); } NodeType::Text => { - if let Some(content) = &node.content { - output.push_str(&escape_html(content)); + if let Some(value) = &node.value { + output.push_str(&escape_html(value)); } } NodeType::Bold => { output.push_str(""); - render_into(output, &node.children); + render_into(output, &node.content); output.push_str(""); } NodeType::Italic => { output.push_str(""); - render_into(output, &node.children); + render_into(output, &node.content); output.push_str(""); } NodeType::Underline => { output.push_str(""); - render_into(output, &node.children); + render_into(output, &node.content); output.push_str(""); } NodeType::Strike => { output.push_str(""); - render_into(output, &node.children); + render_into(output, &node.content); output.push_str(""); } NodeType::InlineCode => { output.push_str(""); - if let Some(content) = &node.content { - output.push_str(&escape_html(content)); + if let Some(value) = &node.value { + output.push_str(&escape_html(value)); } output.push_str(""); } NodeType::ColorCode => { - let color = node.content.as_deref().unwrap_or_default(); + let color = node.value.as_deref().unwrap_or_default(); if !is_safe_color_value(color) { output.push_str(&escape_html(color)); } else { @@ -95,10 +95,10 @@ fn render_into(output: &mut String, nodes: &[AstNode]) { if is_safe_link_url(url) { let escaped = escape_html(url); let _ = write!(output, ""); - render_into(output, &node.children); + render_into(output, &node.content); output.push_str(""); } else { - render_into(output, &node.children); + render_into(output, &node.content); } } NodeType::CodeBlock => { @@ -115,8 +115,8 @@ fn render_into(output: &mut String, nodes: &[AstNode]) { let _ = write!(output, " class=\"language-{}\"", escape_html(lang)); } output.push('>'); - if let Some(content) = &node.content { - output.push_str(&escape_html(content)); + if let Some(value) = &node.value { + output.push_str(&escape_html(value)); } output.push_str("\n\n"); } @@ -134,32 +134,32 @@ fn render_into(output: &mut String, nodes: &[AstNode]) { String::new() }; let _ = writeln!(output, "<{tag}{start}>"); - render_into(output, &node.children); + render_into(output, &node.content); let _ = writeln!(output, ""); } NodeType::ListItem => { output.push_str("
  • "); - render_into(output, &node.children); + render_into(output, &node.content); output.push_str("
  • \n"); } NodeType::Table => { output.push_str("
    \n"); - if let Some(headers) = &node.headers { + if let Some(header) = &node.header { output.push_str("\n\n"); - for cell in headers { + for cell in header { let _ = write!(output, "", table_alignment(cell)); - render_into(output, &cell.children); + render_into(output, &cell.content); output.push_str("\n"); } output.push_str("\n\n"); } - if !node.children.is_empty() { + if !node.content.is_empty() { output.push_str("\n"); - for row in &node.children { + for row in &node.content { output.push_str("\n"); - for cell in &row.children { + for cell in &row.content { let _ = write!(output, "", table_alignment(cell)); - render_into(output, &cell.children); + render_into(output, &cell.content); output.push_str("\n"); } output.push_str("\n"); @@ -171,12 +171,12 @@ fn render_into(output: &mut String, nodes: &[AstNode]) { NodeType::Hr => output.push_str("
    \n"), NodeType::Blockquote => { output.push_str("
    \n"); - render_into(output, &node.children); + render_into(output, &node.content); output.push_str("
    \n"); } NodeType::Accordion => { output.push_str("
    "); - render_into(output, &node.children); + render_into(output, &node.content); output.push_str("
    "); } NodeType::AccordionItem => { @@ -184,12 +184,12 @@ fn render_into(output: &mut String, nodes: &[AstNode]) { let _ = write!(output, "
    "); render_optional_into(output, node.title.as_deref()); output.push_str("
    "); - render_into(output, &node.children); + render_into(output, &node.content); output.push_str("
    "); } NodeType::Chain => { output.push_str("
    "); - render_into(output, &node.children); + render_into(output, &node.content); output.push_str("
    "); } NodeType::ChainItem => { @@ -210,32 +210,32 @@ fn render_into(output: &mut String, nodes: &[AstNode]) { output, "
    {title}
    " ); - render_into(output, &node.children); + render_into(output, &node.content); output.push_str("
    "); } NodeType::Slide => { output.push_str( "
    ", ); - render_into(output, &node.children); + render_into(output, &node.content); output.push_str("
    "); } NodeType::SlideItem => { output.push_str("
    "); - render_into(output, &node.children); + render_into(output, &node.content); output.push_str("
    "); } NodeType::Hardbreak => output.push_str("
    \n"), NodeType::Root | NodeType::TableRow | NodeType::TableCell | NodeType::Custom(_) => { output.push_str(""); - render_into(output, &node.children); + render_into(output, &node.content); output.push_str(""); } } } } -/// Renders optional title children without allocating when absent. +/// Renders optional title content without allocating when absent. fn render_optional_into(output: &mut String, nodes: Option<&[AstNode]>) { if let Some(nodes) = nodes { render_into(output, nodes); diff --git a/crate/src/rules.rs b/crate/src/rule.rs similarity index 91% rename from crate/src/rules.rs rename to crate/src/rule.rs index 049751b..2d01402 100644 --- a/crate/src/rules.rs +++ b/crate/src/rule.rs @@ -1,4 +1,4 @@ -// src/rules.rs +// src/rule.rs use crate::ast::{Alignment, AstNode, NodeType}; use crate::parser::ParserContext; use crate::safety::{is_safe_color_value, is_safe_link_url}; @@ -19,14 +19,14 @@ pub struct InlineMatch { /// Extensible block-rule interface matching the TypeScript parser contract. pub trait BlockRule { fn name(&self) -> &'static str; - fn markers(&self) -> &'static [char]; + fn marker(&self) -> &'static [char]; fn parse(&self, state: &BlockState, ctx: &ParserContext<'_>) -> Option; } /// Extensible inline-rule interface matching the TypeScript parser contract. pub trait InlineRule { fn name(&self) -> &'static str; - fn markers(&self) -> &'static [char]; + fn marker(&self) -> &'static [char]; fn parse(&self, state: &mut InlineState, ctx: &ParserContext<'_>) -> Option; } @@ -125,7 +125,7 @@ impl BlockRule for HeadingRule { "heading" } - fn markers(&self) -> &'static [char] { + fn marker(&self) -> &'static [char] { &['#'] } @@ -156,7 +156,7 @@ impl BlockRule for HeadingRule { let mut node = AstNode::new(NodeType::Heading); node.level = Some(level as u8); - node.children = if text.is_empty() { + node.content = if text.is_empty() { Vec::new() } else { ctx.parse_inline(&text) @@ -176,7 +176,7 @@ impl BlockRule for CodeBlockRule { "code_block" } - fn markers(&self) -> &'static [char] { + fn marker(&self) -> &'static [char] { &['`', '~'] } @@ -184,7 +184,7 @@ impl BlockRule for CodeBlockRule { let block = extract_fenced_block(state)?; let mut node = AstNode::new(NodeType::CodeBlock); node.lang = Some(block.lang); - node.content = Some(block.content); + node.value = Some(block.content); Some(BlockMatch { node, consumed_lines: block.consumed_lines, @@ -297,7 +297,7 @@ fn create_table_cell( alignment: Option, ctx: &ParserContext<'_>, ) -> AstNode { - let mut node = AstNode::with_children(NodeType::TableCell, ctx.parse_inline(content)); + let mut node = AstNode::with_content(NodeType::TableCell, ctx.parse_inline(content)); node.align = alignment; node } @@ -310,7 +310,7 @@ impl BlockRule for TableRule { "table" } - fn markers(&self) -> &'static [char] { + fn marker(&self) -> &'static [char] { &['|'] } @@ -326,33 +326,33 @@ impl BlockRule for TableRule { if alignments.is_none() { let next = state.lines.get(state.line_index + 1)?; alignments = parse_table_alignments(next); - let parsed_headers = extract_table_cells(line); - if alignments.as_ref()?.len() != parsed_headers.len() { + let parsed_header = extract_table_cells(line); + if alignments.as_ref()?.len() != parsed_header.len() { return None; } - header_cells = Some(parsed_headers); + header_cells = Some(parsed_header); consumed_lines = 2; } let alignments = alignments?; let column_count = alignments.len(); - let headers = header_cells.map(|cells| normalize_table_cells(cells, column_count)); + let header = header_cells.map(|cells| normalize_table_cells(cells, column_count)); let mut rows = Vec::new(); while let Some(row_line) = state.lines.get(state.line_index + consumed_lines) && row_line.contains('|') { let cells = normalize_table_cells(extract_table_cells(row_line), column_count); - let row_children = cells + let row_content = cells .iter() .enumerate() .map(|(index, cell)| create_table_cell(cell, alignments[index], ctx)) .collect(); - rows.push(AstNode::with_children(NodeType::TableRow, row_children)); + rows.push(AstNode::with_content(NodeType::TableRow, row_content)); consumed_lines += 1; } - let mut node = AstNode::with_children(NodeType::Table, rows); - node.headers = headers.map(|cells| { + let mut node = AstNode::with_content(NodeType::Table, rows); + node.header = header.map(|cells| { cells .iter() .enumerate() @@ -374,7 +374,7 @@ impl BlockRule for HrRule { "hr" } - fn markers(&self) -> &'static [char] { + fn marker(&self) -> &'static [char] { &['-', '*', '_'] } @@ -409,7 +409,7 @@ impl BlockRule for BlockquoteRule { "blockquote" } - fn markers(&self) -> &'static [char] { + fn marker(&self) -> &'static [char] { &['>'] } @@ -431,7 +431,7 @@ impl BlockRule for BlockquoteRule { consumed_lines += 1; } let mut node = AstNode::new(NodeType::Blockquote); - node.children = ctx.parse_blocks(&content_lines.join("\n")); + node.content = ctx.parse_blocks(&content_lines.join("\n")); Some(BlockMatch { node, consumed_lines, @@ -439,7 +439,7 @@ impl BlockRule for BlockquoteRule { } } -const LIST_MARKERS: &[char] = &['-', '*', '+', '1', '2', '3', '4', '5', '6', '7', '8', '9']; +const LIST_MARKER: &[char] = &['-', '*', '+', '1', '2', '3', '4', '5', '6', '7', '8', '9']; /// Parses a list-item prefix and returns indentation, marker and content. fn parse_list_item(line: &str) -> Option<(usize, String, String)> { @@ -479,8 +479,8 @@ impl BlockRule for ListRule { "list" } - fn markers(&self) -> &'static [char] { - LIST_MARKERS + fn marker(&self) -> &'static [char] { + LIST_MARKER } fn parse(&self, state: &BlockState, ctx: &ParserContext<'_>) -> Option { @@ -553,13 +553,13 @@ impl BlockRule for ListRule { } let mut item = AstNode::new(NodeType::ListItem); - item.children = ctx.parse_blocks(&item_lines.join("\n")); + item.content = ctx.parse_blocks(&item_lines.join("\n")); items.push(item); consumed_lines += item_consumed_lines; let _ = item_marker; } - let mut node = AstNode::with_children(NodeType::List, items); + let mut node = AstNode::with_content(NodeType::List, items); node.ordered = Some(ordered); node.start = start_number; Some(BlockMatch { @@ -577,7 +577,7 @@ impl InlineRule for HardBreakRule { "hardbreak" } - fn markers(&self) -> &'static [char] { + fn marker(&self) -> &'static [char] { &['\\', ' '] } @@ -601,7 +601,7 @@ impl InlineRule for InlineCodeRule { "inline_code" } - fn markers(&self) -> &'static [char] { + fn marker(&self) -> &'static [char] { &['`'] } @@ -648,7 +648,7 @@ impl InlineRule for InlineCodeRule { NodeType::InlineCode }; let mut node = AstNode::new(node_type); - node.content = Some(raw); + node.value = Some(raw); Some(InlineMatch { node, consumed_bytes: end + marker_len - state.pos, @@ -664,7 +664,7 @@ impl InlineRule for LinkRule { "link" } - fn markers(&self) -> &'static [char] { + fn marker(&self) -> &'static [char] { &['['] } @@ -684,7 +684,7 @@ impl InlineRule for LinkRule { } let mut node = AstNode::new(NodeType::Link); node.url = Some(url); - node.children = ctx.parse_inline(&inner); + node.content = ctx.parse_inline(&inner); Some(InlineMatch { node, consumed_bytes: url_end + 1 - state.pos, @@ -700,7 +700,7 @@ impl InlineRule for UnderlineRule { "underline" } - fn markers(&self) -> &'static [char] { + fn marker(&self) -> &'static [char] { &['_'] } @@ -710,7 +710,7 @@ impl InlineRule for UnderlineRule { } let end = state.find_next_token("__", state.pos + 2)?; let mut node = AstNode::new(NodeType::Underline); - node.children = ctx.parse_inline(&state.content[state.pos + 2..end]); + node.content = ctx.parse_inline(&state.content[state.pos + 2..end]); Some(InlineMatch { node, consumed_bytes: end + 2 - state.pos, @@ -726,7 +726,7 @@ impl InlineRule for StrikeRule { "strike" } - fn markers(&self) -> &'static [char] { + fn marker(&self) -> &'static [char] { &['-'] } @@ -736,7 +736,7 @@ impl InlineRule for StrikeRule { } let end = state.find_next_token("--", state.pos + 2)?; let mut node = AstNode::new(NodeType::Strike); - node.children = ctx.parse_inline(&state.content[state.pos + 2..end]); + node.content = ctx.parse_inline(&state.content[state.pos + 2..end]); Some(InlineMatch { node, consumed_bytes: end + 2 - state.pos, @@ -752,7 +752,7 @@ impl InlineRule for BoldRule { "bold" } - fn markers(&self) -> &'static [char] { + fn marker(&self) -> &'static [char] { &['*'] } @@ -762,7 +762,7 @@ impl InlineRule for BoldRule { } let end = state.find_next_token("**", state.pos + 2)?; let mut node = AstNode::new(NodeType::Bold); - node.children = ctx.parse_inline(&state.content[state.pos + 2..end]); + node.content = ctx.parse_inline(&state.content[state.pos + 2..end]); Some(InlineMatch { node, consumed_bytes: end + 2 - state.pos, @@ -778,7 +778,7 @@ impl InlineRule for ItalicRule { "italic" } - fn markers(&self) -> &'static [char] { + fn marker(&self) -> &'static [char] { &['*'] } @@ -791,7 +791,7 @@ impl InlineRule for ItalicRule { return None; } let mut node = AstNode::new(NodeType::Italic); - node.children = ctx.parse_inline(&state.content[state.pos + 1..end]); + node.content = ctx.parse_inline(&state.content[state.pos + 1..end]); Some(InlineMatch { node, consumed_bytes: end + 1 - state.pos, @@ -802,14 +802,14 @@ impl InlineRule for ItalicRule { const FFM_KEYWORDS: &[&str] = &["quote", "slide", "chain", "accordion"]; /// Parses FFM fenced blocks before generic code blocks. -pub struct FfmBlockRule; +pub struct FuyeorBlockRule; -impl BlockRule for FfmBlockRule { +impl BlockRule for FuyeorBlockRule { fn name(&self) -> &'static str { "ffm_blocks" } - fn markers(&self) -> &'static [char] { + fn marker(&self) -> &'static [char] { &['`', '~'] } @@ -822,20 +822,17 @@ impl BlockRule for FfmBlockRule { let node = match block_type { "quote" => { - AstNode::with_children(NodeType::Blockquote, ctx.parse_blocks(&block.content)) + AstNode::with_content(NodeType::Blockquote, ctx.parse_blocks(&block.content)) } "slide" => { let slides = split_slide_contents(&block.content) .into_iter() .filter(|content| !content.trim().is_empty()) .map(|content| { - AstNode::with_children( - NodeType::SlideItem, - ctx.parse_blocks(content.trim()), - ) + AstNode::with_content(NodeType::SlideItem, ctx.parse_blocks(content.trim())) }) .collect(); - AstNode::with_children(NodeType::Slide, slides) + AstNode::with_content(NodeType::Slide, slides) } "accordion" | "chain" => parse_foldable_block(block_type, &block.content, ctx), _ => unreachable!("FFM keywords are exhaustive"), @@ -910,7 +907,7 @@ fn parse_foldable_block(block_type: &str, content: &str, ctx: &ParserContext<'_> for line in content.split('\n') { if let Some((checkbox, title)) = parse_ffm_title(line) { if let Some(mut previous) = current_item.take() { - previous.children = ctx.parse_blocks(current_lines.join("\n").trim()); + previous.content = ctx.parse_blocks(current_lines.join("\n").trim()); items.push(previous); } else if !current_lines.is_empty() && !current_lines.join("").trim().is_empty() { preamble_lines = std::mem::take(&mut current_lines); @@ -936,24 +933,24 @@ fn parse_foldable_block(block_type: &str, content: &str, ctx: &ParserContext<'_> } if let Some(mut last) = current_item { - last.children = ctx.parse_blocks(current_lines.join("\n").trim()); + last.content = ctx.parse_blocks(current_lines.join("\n").trim()); items.push(last); } else if !current_lines.is_empty() && !current_lines.join("").trim().is_empty() { preamble_lines = current_lines; } - let mut children = if preamble_lines.is_empty() { + let mut content = if preamble_lines.is_empty() { Vec::new() } else { ctx.parse_blocks(preamble_lines.join("\n").trim()) }; - children.extend(items); + content.extend(items); let node_type = if block_type == "accordion" { NodeType::Accordion } else { NodeType::Chain }; - let mut node = AstNode::with_children(node_type, children); + let mut node = AstNode::with_content(node_type, content); node.name = accordion_name; node } diff --git a/extension/vscode/src/ffm-grammar.spec.ts b/extension/vscode/src/ffm-grammar.spec.ts deleted file mode 100644 index f22375a..0000000 --- a/extension/vscode/src/ffm-grammar.spec.ts +++ /dev/null @@ -1,92 +0,0 @@ -// extension/vscode/src/ffm-grammar.spec.ts -import { readFileSync } from 'node:fs'; -import { resolve } from 'node:path'; - -const packageDirectory = resolve(import.meta.dirname, '..'); -const readJson = (relativePath: string) => - JSON.parse( - readFileSync(resolve(packageDirectory, relativePath), 'utf8'), - ) as Record; - -describe('FFM VS Code extension source', () => { - it('declares a valid FFM language contribution', () => { - const manifest = readJson('package.json'); - const contributes = manifest.contributes as Record; - const languages = contributes.languages as Array>; - const language = languages.find((entry) => entry.id === 'ffm'); - - expect(language).toBeDefined(); - expect(language?.extensions).toEqual(['.ffm', '.fmd']); - expect(language?.configuration).toBe('./language-configuration.json'); - }); - - it('points the grammar contribution to the FFM TextMate grammar', () => { - const manifest = readJson('package.json'); - const contributes = manifest.contributes as Record; - const grammars = contributes.grammars as Array>; - - expect(grammars).toContainEqual({ - language: 'ffm', - scopeName: 'text.html.markdown.ffm', - path: './syntaxes/ffm.tmLanguage.json', - }); - }); - - it('covers the syntax families defined by the FFM tutorials', () => { - const grammar = readJson('syntaxes/ffm.tmLanguage.json'); - const repository = grammar.repository as Record; - const expectedRules = [ - 'heading', - 'bold_italic', - 'bold', - 'italic', - 'underline', - 'strikethrough', - 'list_marker', - 'link', - 'image', - 'blockquote', - 'fenced_code_block', - 'special_fenced_blocks', - 'math_block', - 'inline_math', - 'table_row', - ]; - - for (const ruleName of expectedRules) - expect(repository[ruleName]).toBeDefined(); - }); - - it('configures .ffm documents as wrapped natural text without ambiguous-character highlights', () => { - const manifest = readJson('package.json'); - const configurationDefaults = manifest.configurationDefaults as Record< - string, - unknown - >; - const ffmDefaults = configurationDefaults['[ffm]'] as Record< - string, - unknown - >; - - expect(ffmDefaults).toEqual({ - 'editor.wordWrap': 'on', - 'editor.wrappingStrategy': 'advanced', - 'editor.unicodeHighlight.ambiguousCharacters': false, - }); - }); - - it('recognizes FFM-only fenced block keywords and task titles', () => { - const grammar = readJson('syntaxes/ffm.tmLanguage.json'); - const repository = grammar.repository as Record; - const specialBlocks = repository.special_fenced_blocks as Record< - string, - unknown - >; - const title = repository.special_block_title as Record; - - expect(specialBlocks.begin).toContain( - 'quote|slide|chain|accordion|mermaid|smiles|abc', - ); - expect(title.match).toContain('\\[[ xX]\\]'); - }); -}); diff --git a/fixture/ffm.json b/fixture/ffm.json index 836351e..60a544e 100644 --- a/fixture/ffm.json +++ b/fixture/ffm.json @@ -125,14 +125,14 @@ }, { "input": ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> value", - "options": { + "option": { "max_nesting_depth": 8 }, "no_throw": true, "plain_text": ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> value" }, { - "options": { + "option": { "max_nesting_depth": 0 }, "error": "invalid_nesting_depth" From 276f4bbab311d62832b3e8488003bc14a77f22e3 Mon Sep 17 00:00:00 2001 From: Fuyeor Date: Tue, 22 Sep 2026 09:39:26 +0000 Subject: [PATCH 7/7] fix test in @ffm/parser --- fixture/markdown.json | 5 ----- package/parser/src/index.spec.ts | 32 +++++++++++++++----------------- package/parser/src/index.ts | 4 ++-- 3 files changed, 17 insertions(+), 24 deletions(-) diff --git a/fixture/markdown.json b/fixture/markdown.json index f59fb4d..c987556 100644 --- a/fixture/markdown.json +++ b/fixture/markdown.json @@ -7,11 +7,6 @@ "html": "

    Hello World

    ", "plain_text": "Hello World" }, - { - "input": "```ts\nconst a = 1;\n```", - "html": "
    ts
    const a = 1;\n
    ", - "plain_text": "const a = 1;" - }, { "input": "visit www.fuyeor.com or [click here](https://fuyeor.com)", "html": "

    visit www.fuyeor.com or click here

    ", diff --git a/package/parser/src/index.spec.ts b/package/parser/src/index.spec.ts index a8d73f1..0d72632 100644 --- a/package/parser/src/index.spec.ts +++ b/package/parser/src/index.spec.ts @@ -22,7 +22,7 @@ type MarkdownCase = { assert?: MarkdownAssertion[]; error?: 'invalid_nesting_depth'; no_throw?: boolean; - options?: { max_nesting_depth?: number }; + option?: { max_nesting_depth?: number }; }; type MarkdownFixtureFile = { @@ -47,8 +47,8 @@ const loadJson = (name: string): T => ), ) as T; -const standardFixtures = loadJson('markdown.json'); -const ffmFixtures = loadJson('ffm.json'); +const cfmFixture = loadJson('markdown.json'); +const ffmFixture = loadJson('ffm.json'); const safetyFixtures = loadJson('safety.json'); // Read a portable JSON Pointer path from a TypeScript AST value. @@ -69,24 +69,24 @@ function readJsonPointer(root: unknown, pointer: string): unknown { return value; } -// Map the wire-level snake_case options to the TypeScript API. -function parserOptions(fixture: MarkdownCase) { - return fixture.options === undefined +// Map the wire-level snake_case option to the TypeScript API. +function parserOption(fixture: MarkdownCase) { + return fixture.option === undefined ? undefined - : { maxNestingDepth: fixture.options.max_nesting_depth }; + : { maxNestingDepth: fixture.option.max_nesting_depth }; } // Execute one canonical Markdown case against a selected parser dialect. function executeMarkdownCase(fixture: MarkdownCase, ffm: boolean): void { - const options = parserOptions(fixture); + const option = parserOption(fixture); if (fixture.error === 'invalid_nesting_depth') { - expect(() => new MarkdownParser(options)).toThrow(RangeError); + expect(() => new MarkdownParser(option)).toThrow(RangeError); return; } const parser = ffm - ? createFuyeorMarkdownParser(options) - : createMarkdownParser(options); + ? createFuyeorMarkdownParser(option) + : createMarkdownParser(option); const ast = parser(fixture.input ?? ''); if (fixture.html !== undefined) expect(render(ast).trim()).toBe(fixture.html); if (fixture.plain_text !== undefined) @@ -120,8 +120,8 @@ function executeMarkdownSuite( }); } -executeMarkdownSuite('standard Markdown fixtures', standardFixtures, false); -executeMarkdownSuite('FFM fixtures', ffmFixtures, true); +executeMarkdownSuite('cfm fixture', cfmFixture, false); +executeMarkdownSuite('ffm fixture', ffmFixture, true); // Verify the public AST shape uses the modern scalar and structural field names. describe('AST field names', () => { @@ -139,9 +139,7 @@ describe('AST field names', () => { }, ]); - const table = createMarkdownParser()( - '\n| A | B |\n|---|---|\n| C | D |', - ); + const table = createMarkdownParser()('\n| A | B |\n|---|---|\n| C | D |'); expect(table[0]).toMatchObject({ type: 'table', header: [ @@ -161,7 +159,7 @@ describe('AST field names', () => { }); }); -describe('safety fixtures', () => { +describe('safety fixture', () => { expect(safetyFixtures.schema_version).toBe(1); for (const fixture of safetyFixtures.links) it(`link: ${fixture.input}`, () => diff --git a/package/parser/src/index.ts b/package/parser/src/index.ts index bfe848b..dc0c346 100644 --- a/package/parser/src/index.ts +++ b/package/parser/src/index.ts @@ -11,7 +11,7 @@ export { listRule, hrRule, blockquoteRule, -} from './rule/blocks'; +} from './rule/block'; export { hardBreakRule, inlineCodeRule, @@ -19,7 +19,7 @@ export { linkRule, underlineRule, strikeRule, -} from './rule/inlines'; +} from './rule/inline'; export { latexPlugin, latexInlineRule, latexBlockRule } from './plugin/latex'; export { twemojiPlugin,