├── .github ├── FUNDING.yml └── workflows │ ├── publish.yaml │ └── ci.yaml ├── test ├── unit │ ├── fixtures │ │ ├── empty-file │ │ │ ├── input.typ │ │ │ └── output.json │ │ ├── Link │ │ │ ├── input.typ │ │ │ └── output.json │ │ ├── Header │ │ │ ├── input.typ │ │ │ └── output.json │ │ ├── List │ │ │ ├── input.typ │ │ │ └── output.json │ │ ├── Strong │ │ │ ├── input.typ │ │ │ └── output.json │ │ ├── Emphasis │ │ │ ├── input.typ │ │ │ └── output.json │ │ ├── list-with-paragraph-between │ │ │ ├── input.typ │ │ │ └── output.json │ │ ├── start-with-single-newline │ │ │ ├── input.typ │ │ │ └── output.json │ │ ├── Break │ │ │ ├── input.typ │ │ │ └── output.json │ │ ├── nested-list │ │ │ ├── input.typ │ │ │ └── output.json │ │ ├── paragraph-with-list-between │ │ │ ├── input.typ │ │ │ └── output.json │ │ ├── CodeBlock │ │ │ ├── input.typ │ │ │ └── output.json │ │ ├── Equation │ │ │ ├── input.typ │ │ │ └── output.json │ │ ├── Comment │ │ │ ├── input.typ │ │ │ └── output.json │ │ └── Code │ │ │ ├── input.typ │ │ │ └── output.json │ ├── example.typ │ ├── parsing.test.ts │ ├── update-fixtures.ts │ ├── typstToTextlintAst.test.ts │ └── rawTypstAstString.txt └── integration │ ├── fixtures │ └── smoke │ │ ├── textlint-rule-period-in-list-item │ │ └── main.typ │ │ └── textlint-rule-preset-ja-technical-writing │ │ └── main.typ │ └── linting.test.ts ├── .tool-versions ├── .gitignore ├── src ├── index.ts ├── typstProcessor.ts └── typstToTextlintAst.ts ├── .editorconfig ├── tsconfig.json ├── biome.json ├── LICENSE ├── package.json └── README.md /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: [3w36zj6] 2 | -------------------------------------------------------------------------------- /test/unit/fixtures/empty-file/input.typ: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.tool-versions: -------------------------------------------------------------------------------- 1 | bun 1.2.13 2 | nodejs 20.17.0 3 | -------------------------------------------------------------------------------- /test/unit/fixtures/Link/input.typ: -------------------------------------------------------------------------------- 1 | https://example.com 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | lib/ 3 | tsconfig.tsbuildinfo 4 | -------------------------------------------------------------------------------- /test/unit/fixtures/Header/input.typ: -------------------------------------------------------------------------------- 1 | = header 1 2 | 3 | == header 2 4 | 5 | === header 3 6 | -------------------------------------------------------------------------------- /test/unit/fixtures/List/input.typ: -------------------------------------------------------------------------------- 1 | - a 2 | - b 3 | - c 4 | 5 | 1. x 6 | 2. y 7 | 3. z 8 | -------------------------------------------------------------------------------- /test/unit/fixtures/Strong/input.typ: -------------------------------------------------------------------------------- 1 | *strong* 2 | 3 | * a b c * 4 | \ 5 | This is *strong*. 6 | -------------------------------------------------------------------------------- /test/unit/fixtures/Emphasis/input.typ: -------------------------------------------------------------------------------- 1 | _emphasis_ 2 | 3 | _ a b c _ 4 | \ 5 | This is _emphasized_. 6 | -------------------------------------------------------------------------------- /test/unit/fixtures/list-with-paragraph-between/input.typ: -------------------------------------------------------------------------------- 1 | - a 2 | - b 3 | 4 | paragraph. 5 | 6 | - c 7 | - d 8 | -------------------------------------------------------------------------------- /test/unit/fixtures/start-with-single-newline/input.typ: -------------------------------------------------------------------------------- 1 | 2 | This is test that starts with a single newline. 3 | -------------------------------------------------------------------------------- /test/unit/fixtures/Break/input.typ: -------------------------------------------------------------------------------- 1 | *Date:* 26.12.2022 \ 2 | *Topic:* Infrastructure Test \ 3 | *Severity:* High \ 4 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import { TypstProcessor } from "./typstProcessor"; 2 | 3 | export default { 4 | Processor: TypstProcessor, 5 | }; 6 | -------------------------------------------------------------------------------- /test/unit/fixtures/nested-list/input.typ: -------------------------------------------------------------------------------- 1 | - a 2 | - b 3 | - ba 4 | - bb 5 | - bba 6 | - bbb 7 | - bbc 8 | - bc 9 | - c 10 | -------------------------------------------------------------------------------- /test/unit/fixtures/paragraph-with-list-between/input.typ: -------------------------------------------------------------------------------- 1 | paragraph1. 2 | 3 | - a 4 | - b 5 | 6 | paragraph2. 7 | 8 | - c 9 | - d 10 | 11 | paragraph3. 12 | -------------------------------------------------------------------------------- /test/unit/fixtures/CodeBlock/input.typ: -------------------------------------------------------------------------------- 1 | ```rust 2 | fn main() { 3 | println!("Hello World!"); 4 | } 5 | ``` 6 | 7 | ``` 8 | language is undefined. 9 | ``` 10 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*.{js,ts,json}] 4 | indent_style = tab 5 | indent_size = 2 6 | charset = utf-8 7 | trim_trailing_whitespace = true 8 | insert_final_newline = true 9 | end_of_line = lf 10 | -------------------------------------------------------------------------------- /test/unit/fixtures/Equation/input.typ: -------------------------------------------------------------------------------- 1 | Let $a$, $b$, and $c$ be the side 2 | lengths of right-angled triangle. 3 | Then, we know that: 4 | $ a^2 + b^2 = c^2 $ 5 | 6 | Prove by induction: 7 | $ sum_(k=1)^n k = (n(n+1)) / 2 $ 8 | -------------------------------------------------------------------------------- /test/unit/fixtures/Comment/input.typ: -------------------------------------------------------------------------------- 1 | // This is a comment. 2 | // This is another comment. 3 | 4 | This is a /* comment */. 5 | 6 | Our study design is as follows: 7 | /* Somebody write this up: 8 | - 1000 participants. 9 | - 2x2 data design. */ 10 | -------------------------------------------------------------------------------- /test/unit/fixtures/empty-file/output.json: -------------------------------------------------------------------------------- 1 | { 2 | "children": [], 3 | "loc": { 4 | "end": { 5 | "column": 0, 6 | "line": 1 7 | }, 8 | "start": { 9 | "column": 0, 10 | "line": 1 11 | } 12 | }, 13 | "range": [0, 0], 14 | "raw": "", 15 | "type": "Document" 16 | } 17 | -------------------------------------------------------------------------------- /test/unit/fixtures/Code/input.typ: -------------------------------------------------------------------------------- 1 | `inline code` 2 | 3 | aaa `inline code` bbb 4 | 5 | What is ```rust fn main()``` in Rust 6 | would be ```c int main()``` in C. 7 | 8 | This has ``` `backticks` ``` in it 9 | (but the spaces are trimmed). And 10 | ``` here``` the leading space is 11 | also trimmed. 12 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "lib": ["ESNext"], 4 | "module": "esnext", 5 | "target": "esnext", 6 | "moduleResolution": "bundler", 7 | "moduleDetection": "force", 8 | "allowImportingTsExtensions": true, 9 | "noEmit": true, 10 | "composite": true, 11 | "strict": true, 12 | "downlevelIteration": true, 13 | "skipLibCheck": true, 14 | "jsx": "react-jsx", 15 | "allowSyntheticDefaultImports": true, 16 | "forceConsistentCasingInFileNames": true, 17 | "allowJs": true, 18 | "types": [ 19 | "bun-types" // add Bun global 20 | ] 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /biome.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://biomejs.dev/schemas/1.9.2/schema.json", 3 | "vcs": { 4 | "enabled": true, 5 | "clientKind": "git", 6 | "useIgnoreFile": true 7 | }, 8 | "files": { 9 | "ignoreUnknown": false, 10 | "ignore": [] 11 | }, 12 | "formatter": { 13 | "enabled": true, 14 | "indentStyle": "tab" 15 | }, 16 | "organizeImports": { 17 | "enabled": true 18 | }, 19 | "linter": { 20 | "enabled": true, 21 | "rules": { 22 | "recommended": true 23 | } 24 | }, 25 | "javascript": { 26 | "formatter": { 27 | "quoteStyle": "double" 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /.github/workflows/publish.yaml: -------------------------------------------------------------------------------- 1 | name: Publish package to npmjs 2 | on: 3 | release: 4 | types: [published] 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v4 10 | - uses: actions/setup-node@v4 11 | with: 12 | node-version-file: ".tool-versions" 13 | registry-url: "https://registry.npmjs.org" 14 | - uses: oven-sh/setup-bun@v2 15 | with: 16 | bun-version-file: ".tool-versions" 17 | - run: bun install --frozen-lockfile 18 | - run: npm run build 19 | - run: npm publish 20 | env: 21 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} 22 | -------------------------------------------------------------------------------- /test/unit/example.typ: -------------------------------------------------------------------------------- 1 | #set page(width: 10cm, height: auto) 2 | #set heading(numbering: "1.") 3 | 4 | = Fibonacci sequence 5 | The Fibonacci sequence is defined through the 6 | recurrence relation $F_n = F_(n-1) + F_(n-2)$. 7 | It can also be expressed in _closed form:_ 8 | 9 | $ F_n = round(1 / sqrt(5) phi.alt^n), quad 10 | phi.alt = (1 + sqrt(5)) / 2 $ 11 | 12 | #let count = 8 13 | #let nums = range(1, count + 1) 14 | #let fib(n) = ( 15 | if n <= 2 { 1 } 16 | else { fib(n - 1) + fib(n - 2) } 17 | ) 18 | 19 | The first #count numbers of the sequence are: 20 | 21 | #align(center, table( 22 | columns: count, 23 | ..nums.map(n => $F_#n$), 24 | ..nums.map(n => str(fib(n))), 25 | )) 26 | 27 | // https://github.com/typst/typst/blob/main/README.md 28 | -------------------------------------------------------------------------------- /test/integration/fixtures/smoke/textlint-rule-period-in-list-item/main.typ: -------------------------------------------------------------------------------- 1 | = Bullet List 2 | 3 | == Normal list 4 | 5 | - Text 6 | - Math. 7 | - Layout 8 | 9 | == Multiple lines 10 | 11 | - This list item spans multiple 12 | lines because it is indented. 13 | 14 | == Function call 15 | 16 | #list( 17 | [Foundations], 18 | [Calculate], 19 | [Construct], 20 | [Data Loading.], 21 | ) 22 | 23 | = Numbered List 24 | 25 | == Automatically numbered 26 | 27 | + Preparations. 28 | + Analysis 29 | + Conclusions 30 | 31 | == Manually numbered 32 | 33 | 2. What is the first step? 34 | 5. I am confused. 35 | + Moving on 36 | 37 | == Multiple lines 38 | 39 | + This enum item has multiple 40 | lines because the next line 41 | is indented. 42 | 43 | == Function call 44 | 45 | #enum[First.][Second] 46 | -------------------------------------------------------------------------------- /test/unit/parsing.test.ts: -------------------------------------------------------------------------------- 1 | import fs from "node:fs"; 2 | import path from "node:path"; 3 | // parse all fixture and should has 4 | import { test } from "@textlint/ast-tester"; 5 | import { describe, expect, it } from "vitest"; 6 | import { convertTypstSourceToTextlintAstObject } from "../../src/typstToTextlintAst"; 7 | 8 | describe("parsing", () => { 9 | const fixtureDir = path.join(__dirname, "fixtures"); 10 | for (const filePath of fs.readdirSync(fixtureDir)) { 11 | const dirName = path.basename(filePath); 12 | it(`${dirName} match AST`, async () => { 13 | const input = fs.readFileSync( 14 | path.join(fixtureDir, filePath, "input.typ"), 15 | "utf-8", 16 | ); 17 | const AST = await convertTypstSourceToTextlintAstObject(input); 18 | test(AST); 19 | const output = JSON.parse( 20 | fs.readFileSync( 21 | path.join(fixtureDir, filePath, "output.json"), 22 | "utf-8", 23 | ), 24 | ); 25 | expect(AST).toEqual(output); 26 | }); 27 | } 28 | }); 29 | -------------------------------------------------------------------------------- /src/typstProcessor.ts: -------------------------------------------------------------------------------- 1 | import type { TxtDocumentNode } from "@textlint/ast-node-types"; 2 | import type { TextlintPluginOptions } from "@textlint/types"; 3 | import { convertTypstSourceToTextlintAstObject } from "./typstToTextlintAst"; 4 | 5 | export class TypstProcessor { 6 | config: TextlintPluginOptions; 7 | extensions: Array; 8 | constructor(config = {}) { 9 | this.config = config; 10 | this.extensions = this.config.extensions ? this.config.extensions : []; 11 | } 12 | 13 | availableExtensions() { 14 | return [".typ"].concat(this.extensions); 15 | } 16 | 17 | processor(_ext: string) { 18 | return { 19 | async preProcess( 20 | text: string, 21 | _filePath?: string, 22 | ): Promise { 23 | return await convertTypstSourceToTextlintAstObject(text); 24 | }, 25 | // biome-ignore lint/suspicious/noExplicitAny: Allowing 'any' type for messages as the exact structure is not known. 26 | postProcess(messages: any[], filePath?: string) { 27 | return { 28 | messages, 29 | filePath: filePath ? filePath : "", 30 | }; 31 | }, 32 | }; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 3w36zj6 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /test/unit/fixtures/start-with-single-newline/output.json: -------------------------------------------------------------------------------- 1 | { 2 | "children": [ 3 | { 4 | "loc": { 5 | "end": { 6 | "column": 0, 7 | "line": 2 8 | }, 9 | "start": { 10 | "column": 0, 11 | "line": 1 12 | } 13 | }, 14 | "range": [0, 1], 15 | "raw": "\n", 16 | "type": "Break", 17 | "value": "\n" 18 | }, 19 | { 20 | "children": [ 21 | { 22 | "loc": { 23 | "end": { 24 | "column": 47, 25 | "line": 2 26 | }, 27 | "start": { 28 | "column": 0, 29 | "line": 2 30 | } 31 | }, 32 | "range": [1, 48], 33 | "raw": "This is test that starts with a single newline.", 34 | "type": "Str", 35 | "value": "This is test that starts with a single newline." 36 | } 37 | ], 38 | "loc": { 39 | "end": { 40 | "column": 47, 41 | "line": 2 42 | }, 43 | "start": { 44 | "column": 0, 45 | "line": 2 46 | } 47 | }, 48 | "range": [1, 48], 49 | "raw": "This is test that starts with a single newline.", 50 | "type": "Paragraph" 51 | } 52 | ], 53 | "loc": { 54 | "end": { 55 | "column": 47, 56 | "line": 2 57 | }, 58 | "start": { 59 | "column": 0, 60 | "line": 1 61 | } 62 | }, 63 | "range": [0, 48], 64 | "raw": "\nThis is test that starts with a single newline.", 65 | "type": "Document" 66 | } 67 | -------------------------------------------------------------------------------- /test/unit/fixtures/Link/output.json: -------------------------------------------------------------------------------- 1 | { 2 | "children": [ 3 | { 4 | "children": [ 5 | { 6 | "children": [ 7 | { 8 | "loc": { 9 | "end": { 10 | "column": 19, 11 | "line": 1 12 | }, 13 | "start": { 14 | "column": 0, 15 | "line": 1 16 | } 17 | }, 18 | "range": [0, 19], 19 | "raw": "https://example.com", 20 | "type": "Str", 21 | "value": "https://example.com" 22 | } 23 | ], 24 | "loc": { 25 | "end": { 26 | "column": 19, 27 | "line": 1 28 | }, 29 | "start": { 30 | "column": 0, 31 | "line": 1 32 | } 33 | }, 34 | "range": [0, 19], 35 | "raw": "https://example.com", 36 | "title": null, 37 | "type": "Link", 38 | "url": "https://example.com" 39 | } 40 | ], 41 | "loc": { 42 | "end": { 43 | "column": 19, 44 | "line": 1 45 | }, 46 | "start": { 47 | "column": 0, 48 | "line": 1 49 | } 50 | }, 51 | "range": [0, 19], 52 | "raw": "https://example.com", 53 | "type": "Paragraph" 54 | } 55 | ], 56 | "loc": { 57 | "end": { 58 | "column": 19, 59 | "line": 1 60 | }, 61 | "start": { 62 | "column": 0, 63 | "line": 1 64 | } 65 | }, 66 | "range": [0, 19], 67 | "raw": "https://example.com", 68 | "type": "Document" 69 | } 70 | -------------------------------------------------------------------------------- /test/unit/fixtures/CodeBlock/output.json: -------------------------------------------------------------------------------- 1 | { 2 | "children": [ 3 | { 4 | "lang": "rust", 5 | "loc": { 6 | "end": { 7 | "column": 3, 8 | "line": 5 9 | }, 10 | "start": { 11 | "column": 0, 12 | "line": 1 13 | } 14 | }, 15 | "range": [0, 55], 16 | "raw": "```rust\nfn main() {\n println!(\"Hello World!\");\n}\n```", 17 | "type": "CodeBlock", 18 | "value": "fn main() {\n println!(\"Hello World!\");\n}" 19 | }, 20 | { 21 | "loc": { 22 | "end": { 23 | "column": 0, 24 | "line": 7 25 | }, 26 | "start": { 27 | "column": 3, 28 | "line": 5 29 | } 30 | }, 31 | "range": [55, 57], 32 | "raw": "\n\n", 33 | "type": "Break", 34 | "value": "\n\n" 35 | }, 36 | { 37 | "lang": null, 38 | "loc": { 39 | "end": { 40 | "column": 3, 41 | "line": 9 42 | }, 43 | "start": { 44 | "column": 0, 45 | "line": 7 46 | } 47 | }, 48 | "range": [57, 87], 49 | "raw": "```\nlanguage is undefined.\n```", 50 | "type": "CodeBlock", 51 | "value": "language is undefined." 52 | } 53 | ], 54 | "loc": { 55 | "end": { 56 | "column": 3, 57 | "line": 9 58 | }, 59 | "start": { 60 | "column": 0, 61 | "line": 1 62 | } 63 | }, 64 | "range": [0, 87], 65 | "raw": "```rust\nfn main() {\n println!(\"Hello World!\");\n}\n```\n\n```\nlanguage is undefined.\n```", 66 | "type": "Document" 67 | } 68 | -------------------------------------------------------------------------------- /test/unit/update-fixtures.ts: -------------------------------------------------------------------------------- 1 | import fs from "node:fs"; 2 | import path from "node:path"; 3 | import { test } from "@textlint/ast-tester"; 4 | import { convertTypstSourceToTextlintAstObject } from "../../src/typstToTextlintAst"; 5 | 6 | /** 7 | * Sort JSON object keys recursively for consistent output 8 | * @param obj The object to sort 9 | * @returns The object with sorted keys 10 | */ 11 | 12 | // biome-ignore lint/suspicious/noExplicitAny: JSON structure is not known. 13 | const sortJsonKeys = (obj: any): any => { 14 | if (obj === null || typeof obj !== "object") { 15 | return obj; 16 | } 17 | 18 | if (Array.isArray(obj)) { 19 | return obj.map(sortJsonKeys); 20 | } 21 | 22 | // biome-ignore lint/suspicious/noExplicitAny: JSON structure is not known. 23 | const sorted: any = {}; 24 | for (const key of Object.keys(obj).sort()) { 25 | sorted[key] = sortJsonKeys(obj[key]); 26 | } 27 | 28 | return sorted; 29 | }; 30 | 31 | const fixtureDir = path.join(__dirname, "fixtures"); 32 | for (const filePath of fs.readdirSync(fixtureDir)) { 33 | const dirName = path.basename(filePath); 34 | const input = fs.readFileSync( 35 | path.join(fixtureDir, filePath, "input.typ"), 36 | "utf-8", 37 | ); 38 | const AST = sortJsonKeys(await convertTypstSourceToTextlintAstObject(input)); 39 | test(AST); 40 | fs.writeFileSync( 41 | path.join(fixtureDir, filePath, "output.json"), 42 | JSON.stringify(AST, null, "\t"), 43 | ); 44 | } 45 | -------------------------------------------------------------------------------- /.github/workflows/ci.yaml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | branches: 9 | - main 10 | workflow_dispatch: 11 | 12 | jobs: 13 | biome: 14 | runs-on: ubuntu-24.04 15 | steps: 16 | - name: Checkout your repository using git 17 | uses: actions/checkout@v4 18 | - name: Setup toolchain 19 | uses: jdx/mise-action@v2 20 | - name: Install dependencies 21 | run: bun install --frozen-lockfile 22 | - name: Check using Biome 23 | run: bun run ci 24 | 25 | tsc: 26 | runs-on: ubuntu-24.04 27 | steps: 28 | - name: Checkout your repository using git 29 | uses: actions/checkout@v4 30 | - name: Setup toolchain 31 | uses: jdx/mise-action@v2 32 | - name: Install dependencies 33 | run: bun install --frozen-lockfile 34 | - name: Check using tsc 35 | run: bun run tsc 36 | 37 | test: 38 | runs-on: ubuntu-24.04 39 | steps: 40 | - name: Checkout your repository using git 41 | uses: actions/checkout@v4 42 | - name: Setup toolchain 43 | uses: jdx/mise-action@v2 44 | - name: Install dependencies 45 | run: bun install --frozen-lockfile 46 | - name: Test 47 | run: bun run test 48 | 49 | build: 50 | runs-on: ubuntu-24.04 51 | steps: 52 | - name: Checkout your repository using git 53 | uses: actions/checkout@v4 54 | - name: Setup toolchain 55 | uses: jdx/mise-action@v2 56 | - name: Install dependencies 57 | run: bun install --frozen-lockfile 58 | - name: Build 59 | run: bun run build 60 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "textlint-plugin-typst", 3 | "version": "1.3.0", 4 | "description": "textlint plugin to lint Typst", 5 | "keywords": ["textlint", "textlintplugin", "plugin", "lint", "typst"], 6 | "repository": "https://github.com/3w36zj6/textlint-plugin-typst", 7 | "funding": { 8 | "type": "github", 9 | "url": "https://github.com/sponsors/3w36zj6" 10 | }, 11 | "license": "MIT", 12 | "author": "3w36zj6", 13 | "main": "lib/index.js", 14 | "files": ["lib"], 15 | "scripts": { 16 | "build": "esbuild --bundle src/index.ts --outfile=lib/index.js --platform=node --external:@myriaddreamin/typst-ts-web-compiler --minify", 17 | "clean": "rm -frv lib", 18 | "format:write": "biome format --write .", 19 | "format": "biome format .", 20 | "lint": "biome lint .", 21 | "lint:apply": "biome lint --apply .", 22 | "check": "biome check .", 23 | "check:write": "biome check --write .", 24 | "ci": "biome ci .", 25 | "test": "vitest", 26 | "update-fixtures": "bun ./test/update-fixtures.ts && biome check --write ./test/fixtures" 27 | }, 28 | "dependencies": { 29 | "@myriaddreamin/typst-ts-web-compiler": "0.6.0" 30 | }, 31 | "devDependencies": { 32 | "@biomejs/biome": "^1.9.2", 33 | "@myriaddreamin/typst.ts": "0.6.0", 34 | "@textlint/ast-node-types": "^14.0.4", 35 | "@textlint/ast-tester": "^14.0.4", 36 | "@textlint/types": "^14.0.4", 37 | "@types/node": "^20.12.2", 38 | "bun-types": "latest", 39 | "esbuild": "^0.20.2", 40 | "textlint-rule-period-in-list-item": "^1.0.1", 41 | "textlint-rule-preset-ja-technical-writing": "^12.0.2", 42 | "textlint-scripts": "^14.0.4", 43 | "textlint-tester": "^14.0.4", 44 | "typescript": "^5.4.3", 45 | "vitest": "^1.4.0", 46 | "yaml": "^2.5.1" 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /test/integration/fixtures/smoke/textlint-rule-preset-ja-technical-writing/main.typ: -------------------------------------------------------------------------------- 1 | = textlint for Typst 2 | 3 | == sentence-length 4 | 5 | あいうえおあいうえおあいうえおあいうえおあいうえおあいうえおあいうえおあいうえおあいうえおあいうえおあいうえおあいうえおあいうえおあいうえおあいうえおあいうえおあいうえおあいうえおあいうえおあいうえおあいうえお。 6 | 7 | == max-comma 8 | 9 | 組版処理システムの例としては、Tex (LaTeX), SATySFi, Typst, Twight, SILEなどがあります。 10 | 11 | == max-ten 12 | 13 | これは、これは、これは、これは、これはだめ。 14 | 15 | == max-kanji-continuous-len 16 | 17 | 情報処理学会は、電気学会、照明学会、応用物理学会、映像情報メディア学会および電子情報通信学会とともに電気系6学会の1つに数えられる存在です。 18 | 19 | == arabic-kanji-numbers 20 | 21 | Fibonacci数列の十番目の項は55です。 22 | 23 | 1時的なファイルは`/tmp`に保存されます。 24 | 25 | == no-mix-dearu-desumasu 26 | 27 | 今日はいい天気ですね。今日はいい天気である。 28 | 29 | == no-mixed-period 30 | 31 | これは問題ないです。 32 | 33 | 「これはセリフ」 34 | 35 | english only 36 | 37 | これは句点がありません 38 | 39 | == no-double-negative-ja 40 | 41 | それが事件の発端だったといえなくもない。 42 | 43 | 確かにそういった懸念はない事はない。 44 | 45 | == no-dropping-the-ra 46 | 47 | お刺身を食べれない。 48 | 49 | == no-doubled-conjunctive-particle-ga 50 | 51 | 今日は早朝から出発したが、定刻には間に合わなかったが、無事会場に到着した。 52 | 53 | == no-doubled-conjunction 54 | 55 | かな漢字変換により漢字が多用される傾向がある。しかし漢字の多用が読みにくさをもたらす側面は否定できない。しかし、平仮名が多い文は間延びした印象を与える恐れもある。 56 | 57 | == no-doubled-joshi 58 | 59 | 私は彼は好きだ。 60 | 61 | == no-nfd 62 | 63 | ホ゜ケット エンシ゛ン。 64 | 65 | == no-exclamation-question-mark 66 | 67 | 技術文書では、感嘆符、疑問符は基本的には使用しないでください! 68 | 69 | == no-hankaku-kana 70 | 71 | 半角カタカナを使用しないでください。 72 | 73 | == no-weak-phrase 74 | 75 | この表現には問題があるかもしれないです。 76 | 77 | == no-successive-word 78 | 79 | これは問題ない文章です。 80 | 81 | これはは問題ある文章です。 82 | 83 | これは問題あるある文章です。 84 | 85 | == no-abusage 86 | 87 | ウインドウ幅が可変すると、レイアウトが崩れる。 88 | 89 | 今朝起きた事件に法律を適応する。 90 | 91 | == no-redundant-expression 92 | 93 | これは省略することが可能です。 94 | 95 | == ja-unnatural-alphabet 96 | 97 | リイr−ス。 98 | 99 | == no-unmatched-pair 100 | 101 | これは(秘密)です。 102 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # textlint-plugin-typst 2 | 3 | [![NPM Version](https://img.shields.io/npm/v/textlint-plugin-typst)](https://www.npmjs.com/package/textlint-plugin-typst?activeTab=versions) 4 | [![NPM Downloads](https://img.shields.io/npm/d18m/textlint-plugin-typst)](https://www.npmjs.com/package/textlint-plugin-typst) 5 | [![NPM License](https://img.shields.io/npm/l/textlint-plugin-typst)](https://github.com/3w36zj6/textlint-plugin-typst/blob/HEAD/LICENSE) 6 | [![CI](https://github.com/3w36zj6/textlint-plugin-typst/actions/workflows/ci.yaml/badge.svg?branch=main&event=push)](https://github.com/3w36zj6/textlint-plugin-typst/actions/workflows/ci.yaml) 7 | 8 | [textlint](https://github.com/textlint/textlint) plugin to lint [Typst](https://typst.app/) 9 | 10 | ## Installation 11 | 12 | ```sh 13 | # npm 14 | npm install textlint-plugin-typst 15 | 16 | # Yarn 17 | yarn add textlint-plugin-typst 18 | 19 | # pnpm 20 | pnpm add textlint-plugin-typst 21 | 22 | # Bun 23 | bun add textlint-plugin-typst 24 | ``` 25 | 26 | ## Usage 27 | 28 | ```json 29 | { 30 | "plugins": { 31 | "typst": true 32 | } 33 | } 34 | ``` 35 | 36 | ## Options 37 | 38 | - `extensions`: `string[]` 39 | - Additional file extensions for Typst 40 | 41 | ## Syntax support 42 | 43 | This plugin supports the syntax of Typst [v0.13.1](https://github.com/typst/typst/releases/tag/v0.13.1). 44 | 45 | Legend for syntax support: 46 | 47 | - ✅: Supported 48 | - 🚫: Not in progress 49 | - ⌛️: In progress 50 | - ⚠️: Partially supported (with some caveats) 51 | 52 | | Typst | textlint | Markup | Function | 53 | | --- | --- | --- | --- | 54 | | Paragraph break | Paragraph | ✅ | 🚫 | 55 | | Strong emphasis | Strong | ✅ | 🚫 | 56 | | Emphasis | Emphasis | ✅ | 🚫 | 57 | | Raw text | Code / CodeBlock | ✅ | 🚫 | 58 | | Link | Link | ✅ | 🚫 | 59 | | Label | | 🚫 | 🚫 | 60 | | Reference | | 🚫 | 🚫 | 61 | | Heading | Header | ✅ | 🚫 | 62 | | Bullet list | List / ListItem | ✅ | 🚫 | 63 | | Numbered list | List / ListItem | ✅ | 🚫 | 64 | | Term list | | 🚫 | 🚫 | 65 | | Math | Code / CodeBlock | ✅ | 🚫 | 66 | | Line break | Break | ✅ | 🚫 | 67 | | Smart quote | | 🚫 | 🚫 | 68 | | Symbol shorthand | | 🚫 | 🚫 | 69 | | Code expression | | 🚫 | 🚫 | 70 | | Character escape | | 🚫 | 🚫 | 71 | | Comment | Comment | ✅ | 🚫 | 72 | 73 | ## Examples 74 | 75 | ### textlint-filter-rule-comments 76 | 77 | Example of how to use [textlint-filter-rule-comments](https://www.npmjs.com/package/textlint-filter-rule-comments) is shown below. 78 | 79 | ```typst 80 | This is error text. 81 | 82 | /* textlint-disable */ 83 | 84 | This is ignored text by rule. 85 | Disables all rules between comments 86 | 87 | /* textlint-enable */ 88 | 89 | This is error text. 90 | ``` 91 | 92 | Also, you can use single-line comments. 93 | 94 | ```typst 95 | This is error text. 96 | 97 | // textlint-disable 98 | 99 | This is ignored text by rule. 100 | Disables all rules between comments 101 | 102 | // textlint-enable 103 | 104 | This is error text. 105 | ``` 106 | 107 | ## Contributing 108 | 109 | This project is still under development, so please feel free to contribute! 110 | 111 | 1. Fork it! 112 | 2. Create your feature branch: `git checkout -b my-new-feature` 113 | 3. Commit your changes: `git commit -am 'Add some feature'` 114 | 4. Push to the branch: `git push origin my-new-feature` 115 | 5. Submit a pull request :D 116 | 117 | ## Maintainers 118 | 119 | - [@3w36zj6](https://github.com/3w36zj6) 120 | 121 | ## License 122 | 123 | [MIT License](LICENSE) 124 | -------------------------------------------------------------------------------- /test/unit/typstToTextlintAst.test.ts: -------------------------------------------------------------------------------- 1 | import fs from "node:fs"; 2 | import path from "node:path"; 3 | import * as ASTTester from "@textlint/ast-tester"; 4 | import { describe, expect, it } from "vitest"; 5 | import { 6 | convertRawTypstAstObjectToTextlintAstObject, 7 | convertRawTypstAstStringToObject, 8 | convertTypstSourceToTextlintAstObject, 9 | extractRawSourceByLocation, 10 | getRawTypstAstString, 11 | paragraphizeTextlintAstObject, 12 | } from "../../src/typstToTextlintAst"; 13 | 14 | const typstSource = fs.readFileSync( 15 | path.join(__dirname, "example.typ"), 16 | "utf-8", 17 | ); 18 | 19 | const rawTypstAstString = fs 20 | .readFileSync(path.join(__dirname, "rawTypstAstString.txt"), "utf-8") 21 | .replace(/\n$/, ""); 22 | 23 | const rawTypstAstObject = JSON.parse( 24 | fs.readFileSync(path.join(__dirname, "rawTypstAstObject.json"), "utf-8"), 25 | ); 26 | 27 | const textlintAstObject = JSON.parse( 28 | fs.readFileSync(path.join(__dirname, "textlintAstObject.json"), "utf-8"), 29 | ); 30 | 31 | const paragraphizedTextlintAstObject = JSON.parse( 32 | fs.readFileSync( 33 | path.join(__dirname, "paragraphizedTextlintAstObject.json"), 34 | "utf-8", 35 | ), 36 | ); 37 | 38 | describe("getRawTypstAstString", () => { 39 | it("should return raw typst ast string", async () => { 40 | const actualRawTypstAstString = await getRawTypstAstString(typstSource); 41 | expect(actualRawTypstAstString).toStrictEqual(rawTypstAstString); 42 | }); 43 | }); 44 | 45 | describe("convertRawTypstAstStringToObject", () => { 46 | it("should convert raw Typst AST to raw Typst AST object", async () => { 47 | const actualRawTypstAstObject = 48 | await convertRawTypstAstStringToObject(rawTypstAstString); 49 | expect(actualRawTypstAstObject).toStrictEqual(rawTypstAstObject); 50 | }); 51 | }); 52 | 53 | describe("extractRawSourceByLocation", () => { 54 | it("should extract substring from a single line", async () => { 55 | const location = { 56 | start: { line: 1, column: 3 }, 57 | end: { line: 1, column: 8 }, 58 | }; 59 | const actualRawSource = await extractRawSourceByLocation( 60 | typstSource, 61 | location, 62 | ); 63 | expect(actualRawSource).toStrictEqual("t pag"); 64 | }); 65 | 66 | it("should extract substring across multiple lines", async () => { 67 | const location = { 68 | start: { line: 1, column: 2 }, 69 | end: { line: 2, column: 12 }, 70 | }; 71 | const actualRawSource = await extractRawSourceByLocation( 72 | typstSource, 73 | location, 74 | ); 75 | expect(actualRawSource).toStrictEqual(`et page(width: 10cm, height: auto) 76 | #set heading`); 77 | }); 78 | }); 79 | 80 | describe("convertRawTypstAstObjectToTextlintAstObject", () => { 81 | it("should convert raw Typst AST object to textlint AST object", async () => { 82 | const actualTextlintAstObject = 83 | await convertRawTypstAstObjectToTextlintAstObject( 84 | rawTypstAstObject, 85 | typstSource, 86 | ); 87 | expect(actualTextlintAstObject).toStrictEqual(textlintAstObject); 88 | ASTTester.test(actualTextlintAstObject); 89 | }); 90 | }); 91 | 92 | describe("paragraphizeTextlintAstObject", () => { 93 | it("should convert textlint AST object to paragraphized textlint AST object", async () => { 94 | const actualParagraphizedTextlintAstObject = 95 | await paragraphizeTextlintAstObject(textlintAstObject); 96 | expect(actualParagraphizedTextlintAstObject).toStrictEqual( 97 | paragraphizedTextlintAstObject, 98 | ); 99 | ASTTester.test(actualParagraphizedTextlintAstObject); 100 | }); 101 | }); 102 | 103 | describe("convertTypstSourceToParagraphizedTextlintAstObject", () => { 104 | it("should convert Typst source to textlint AST object", async () => { 105 | const actualTextlintAstObject = 106 | await convertTypstSourceToTextlintAstObject(typstSource); 107 | expect(actualTextlintAstObject).toStrictEqual( 108 | paragraphizedTextlintAstObject, 109 | ); 110 | ASTTester.test(actualTextlintAstObject); 111 | }); 112 | }); 113 | -------------------------------------------------------------------------------- /test/unit/fixtures/Header/output.json: -------------------------------------------------------------------------------- 1 | { 2 | "children": [ 3 | { 4 | "children": [ 5 | { 6 | "loc": { 7 | "end": { 8 | "column": 1, 9 | "line": 1 10 | }, 11 | "start": { 12 | "column": 0, 13 | "line": 1 14 | } 15 | }, 16 | "range": [0, 1], 17 | "raw": "=", 18 | "type": "Marked::HeadingMarker", 19 | "value": "=" 20 | }, 21 | { 22 | "loc": { 23 | "end": { 24 | "column": 2, 25 | "line": 1 26 | }, 27 | "start": { 28 | "column": 1, 29 | "line": 1 30 | } 31 | }, 32 | "range": [1, 2], 33 | "raw": " ", 34 | "type": "Str", 35 | "value": " " 36 | }, 37 | { 38 | "children": [ 39 | { 40 | "loc": { 41 | "end": { 42 | "column": 10, 43 | "line": 1 44 | }, 45 | "start": { 46 | "column": 2, 47 | "line": 1 48 | } 49 | }, 50 | "range": [2, 10], 51 | "raw": "header 1", 52 | "type": "Str", 53 | "value": "header 1" 54 | } 55 | ], 56 | "loc": { 57 | "end": { 58 | "column": 10, 59 | "line": 1 60 | }, 61 | "start": { 62 | "column": 2, 63 | "line": 1 64 | } 65 | }, 66 | "range": [2, 10], 67 | "raw": "header 1", 68 | "type": "Marked::Markup" 69 | } 70 | ], 71 | "loc": { 72 | "end": { 73 | "column": 10, 74 | "line": 1 75 | }, 76 | "start": { 77 | "column": 0, 78 | "line": 1 79 | } 80 | }, 81 | "range": [0, 10], 82 | "raw": "= header 1", 83 | "type": "Header" 84 | }, 85 | { 86 | "loc": { 87 | "end": { 88 | "column": 0, 89 | "line": 3 90 | }, 91 | "start": { 92 | "column": 10, 93 | "line": 1 94 | } 95 | }, 96 | "range": [10, 12], 97 | "raw": "\n\n", 98 | "type": "Break", 99 | "value": "\n\n" 100 | }, 101 | { 102 | "children": [ 103 | { 104 | "loc": { 105 | "end": { 106 | "column": 2, 107 | "line": 3 108 | }, 109 | "start": { 110 | "column": 0, 111 | "line": 3 112 | } 113 | }, 114 | "range": [12, 14], 115 | "raw": "==", 116 | "type": "Marked::HeadingMarker", 117 | "value": "==" 118 | }, 119 | { 120 | "loc": { 121 | "end": { 122 | "column": 3, 123 | "line": 3 124 | }, 125 | "start": { 126 | "column": 2, 127 | "line": 3 128 | } 129 | }, 130 | "range": [14, 15], 131 | "raw": " ", 132 | "type": "Str", 133 | "value": " " 134 | }, 135 | { 136 | "children": [ 137 | { 138 | "loc": { 139 | "end": { 140 | "column": 11, 141 | "line": 3 142 | }, 143 | "start": { 144 | "column": 3, 145 | "line": 3 146 | } 147 | }, 148 | "range": [15, 23], 149 | "raw": "header 2", 150 | "type": "Str", 151 | "value": "header 2" 152 | } 153 | ], 154 | "loc": { 155 | "end": { 156 | "column": 11, 157 | "line": 3 158 | }, 159 | "start": { 160 | "column": 3, 161 | "line": 3 162 | } 163 | }, 164 | "range": [15, 23], 165 | "raw": "header 2", 166 | "type": "Marked::Markup" 167 | } 168 | ], 169 | "loc": { 170 | "end": { 171 | "column": 11, 172 | "line": 3 173 | }, 174 | "start": { 175 | "column": 0, 176 | "line": 3 177 | } 178 | }, 179 | "range": [12, 23], 180 | "raw": "== header 2", 181 | "type": "Header" 182 | }, 183 | { 184 | "loc": { 185 | "end": { 186 | "column": 0, 187 | "line": 5 188 | }, 189 | "start": { 190 | "column": 11, 191 | "line": 3 192 | } 193 | }, 194 | "range": [23, 25], 195 | "raw": "\n\n", 196 | "type": "Break", 197 | "value": "\n\n" 198 | }, 199 | { 200 | "children": [ 201 | { 202 | "loc": { 203 | "end": { 204 | "column": 3, 205 | "line": 5 206 | }, 207 | "start": { 208 | "column": 0, 209 | "line": 5 210 | } 211 | }, 212 | "range": [25, 28], 213 | "raw": "===", 214 | "type": "Marked::HeadingMarker", 215 | "value": "===" 216 | }, 217 | { 218 | "loc": { 219 | "end": { 220 | "column": 4, 221 | "line": 5 222 | }, 223 | "start": { 224 | "column": 3, 225 | "line": 5 226 | } 227 | }, 228 | "range": [28, 29], 229 | "raw": " ", 230 | "type": "Str", 231 | "value": " " 232 | }, 233 | { 234 | "children": [ 235 | { 236 | "loc": { 237 | "end": { 238 | "column": 12, 239 | "line": 5 240 | }, 241 | "start": { 242 | "column": 4, 243 | "line": 5 244 | } 245 | }, 246 | "range": [29, 37], 247 | "raw": "header 3", 248 | "type": "Str", 249 | "value": "header 3" 250 | } 251 | ], 252 | "loc": { 253 | "end": { 254 | "column": 12, 255 | "line": 5 256 | }, 257 | "start": { 258 | "column": 4, 259 | "line": 5 260 | } 261 | }, 262 | "range": [29, 37], 263 | "raw": "header 3", 264 | "type": "Marked::Markup" 265 | } 266 | ], 267 | "loc": { 268 | "end": { 269 | "column": 12, 270 | "line": 5 271 | }, 272 | "start": { 273 | "column": 0, 274 | "line": 5 275 | } 276 | }, 277 | "range": [25, 37], 278 | "raw": "=== header 3", 279 | "type": "Header" 280 | } 281 | ], 282 | "loc": { 283 | "end": { 284 | "column": 12, 285 | "line": 5 286 | }, 287 | "start": { 288 | "column": 0, 289 | "line": 1 290 | } 291 | }, 292 | "range": [0, 37], 293 | "raw": "= header 1\n\n== header 2\n\n=== header 3", 294 | "type": "Document" 295 | } 296 | -------------------------------------------------------------------------------- /test/unit/fixtures/Comment/output.json: -------------------------------------------------------------------------------- 1 | { 2 | "children": [ 3 | { 4 | "children": [ 5 | { 6 | "loc": { 7 | "end": { 8 | "column": 21, 9 | "line": 1 10 | }, 11 | "start": { 12 | "column": 0, 13 | "line": 1 14 | } 15 | }, 16 | "range": [0, 21], 17 | "raw": "// This is a comment.", 18 | "type": "Comment", 19 | "value": "This is a comment." 20 | }, 21 | { 22 | "loc": { 23 | "end": { 24 | "column": 0, 25 | "line": 2 26 | }, 27 | "start": { 28 | "column": 21, 29 | "line": 1 30 | } 31 | }, 32 | "range": [21, 22], 33 | "raw": "\n", 34 | "type": "Str", 35 | "value": "\n" 36 | }, 37 | { 38 | "loc": { 39 | "end": { 40 | "column": 27, 41 | "line": 2 42 | }, 43 | "start": { 44 | "column": 0, 45 | "line": 2 46 | } 47 | }, 48 | "range": [22, 49], 49 | "raw": "// This is another comment.", 50 | "type": "Comment", 51 | "value": "This is another comment." 52 | } 53 | ], 54 | "loc": { 55 | "end": { 56 | "column": 27, 57 | "line": 2 58 | }, 59 | "start": { 60 | "column": 0, 61 | "line": 1 62 | } 63 | }, 64 | "range": [0, 49], 65 | "raw": "// This is a comment.\n// This is another comment.", 66 | "type": "Paragraph" 67 | }, 68 | { 69 | "loc": { 70 | "end": { 71 | "column": 0, 72 | "line": 4 73 | }, 74 | "start": { 75 | "column": 27, 76 | "line": 2 77 | } 78 | }, 79 | "range": [49, 51], 80 | "raw": "\n\n", 81 | "type": "Break", 82 | "value": "\n\n" 83 | }, 84 | { 85 | "children": [ 86 | { 87 | "loc": { 88 | "end": { 89 | "column": 9, 90 | "line": 4 91 | }, 92 | "start": { 93 | "column": 0, 94 | "line": 4 95 | } 96 | }, 97 | "range": [51, 60], 98 | "raw": "This is a", 99 | "type": "Str", 100 | "value": "This is a" 101 | }, 102 | { 103 | "loc": { 104 | "end": { 105 | "column": 10, 106 | "line": 4 107 | }, 108 | "start": { 109 | "column": 9, 110 | "line": 4 111 | } 112 | }, 113 | "range": [60, 61], 114 | "raw": " ", 115 | "type": "Str", 116 | "value": " " 117 | }, 118 | { 119 | "loc": { 120 | "end": { 121 | "column": 23, 122 | "line": 4 123 | }, 124 | "start": { 125 | "column": 10, 126 | "line": 4 127 | } 128 | }, 129 | "range": [61, 74], 130 | "raw": "/* comment */", 131 | "type": "Comment", 132 | "value": "comment" 133 | }, 134 | { 135 | "loc": { 136 | "end": { 137 | "column": 24, 138 | "line": 4 139 | }, 140 | "start": { 141 | "column": 23, 142 | "line": 4 143 | } 144 | }, 145 | "range": [74, 75], 146 | "raw": ".", 147 | "type": "Str", 148 | "value": "." 149 | } 150 | ], 151 | "loc": { 152 | "end": { 153 | "column": 24, 154 | "line": 4 155 | }, 156 | "start": { 157 | "column": 0, 158 | "line": 4 159 | } 160 | }, 161 | "range": [51, 75], 162 | "raw": "This is a /* comment */.", 163 | "type": "Paragraph" 164 | }, 165 | { 166 | "loc": { 167 | "end": { 168 | "column": 0, 169 | "line": 6 170 | }, 171 | "start": { 172 | "column": 24, 173 | "line": 4 174 | } 175 | }, 176 | "range": [75, 77], 177 | "raw": "\n\n", 178 | "type": "Break", 179 | "value": "\n\n" 180 | }, 181 | { 182 | "children": [ 183 | { 184 | "loc": { 185 | "end": { 186 | "column": 30, 187 | "line": 6 188 | }, 189 | "start": { 190 | "column": 0, 191 | "line": 6 192 | } 193 | }, 194 | "range": [77, 107], 195 | "raw": "Our study design is as follows", 196 | "type": "Str", 197 | "value": "Our study design is as follows" 198 | }, 199 | { 200 | "loc": { 201 | "end": { 202 | "column": 31, 203 | "line": 6 204 | }, 205 | "start": { 206 | "column": 30, 207 | "line": 6 208 | } 209 | }, 210 | "range": [107, 108], 211 | "raw": ":", 212 | "type": "Str", 213 | "value": ":" 214 | }, 215 | { 216 | "loc": { 217 | "end": { 218 | "column": 0, 219 | "line": 7 220 | }, 221 | "start": { 222 | "column": 31, 223 | "line": 6 224 | } 225 | }, 226 | "range": [108, 109], 227 | "raw": "\n", 228 | "type": "Str", 229 | "value": "\n" 230 | }, 231 | { 232 | "loc": { 233 | "end": { 234 | "column": 24, 235 | "line": 9 236 | }, 237 | "start": { 238 | "column": 0, 239 | "line": 7 240 | } 241 | }, 242 | "range": [109, 184], 243 | "raw": "/* Somebody write this up:\n - 1000 participants.\n - 2x2 data design. */", 244 | "type": "Comment", 245 | "value": "Somebody write this up:\n - 1000 participants.\n - 2x2 data design." 246 | } 247 | ], 248 | "loc": { 249 | "end": { 250 | "column": 24, 251 | "line": 9 252 | }, 253 | "start": { 254 | "column": 0, 255 | "line": 6 256 | } 257 | }, 258 | "range": [77, 184], 259 | "raw": "Our study design is as follows:\n/* Somebody write this up:\n - 1000 participants.\n - 2x2 data design. */", 260 | "type": "Paragraph" 261 | } 262 | ], 263 | "loc": { 264 | "end": { 265 | "column": 24, 266 | "line": 9 267 | }, 268 | "start": { 269 | "column": 0, 270 | "line": 1 271 | } 272 | }, 273 | "range": [0, 184], 274 | "raw": "// This is a comment.\n// This is another comment.\n\nThis is a /* comment */.\n\nOur study design is as follows:\n/* Somebody write this up:\n - 1000 participants.\n - 2x2 data design. */", 275 | "type": "Document" 276 | } 277 | -------------------------------------------------------------------------------- /test/unit/fixtures/list-with-paragraph-between/output.json: -------------------------------------------------------------------------------- 1 | { 2 | "children": [ 3 | { 4 | "children": [ 5 | { 6 | "checked": null, 7 | "children": [ 8 | { 9 | "children": [ 10 | { 11 | "loc": { 12 | "end": { 13 | "column": 3, 14 | "line": 1 15 | }, 16 | "start": { 17 | "column": 2, 18 | "line": 1 19 | } 20 | }, 21 | "range": [2, 3], 22 | "raw": "a", 23 | "type": "Str", 24 | "value": "a" 25 | } 26 | ], 27 | "loc": { 28 | "end": { 29 | "column": 3, 30 | "line": 1 31 | }, 32 | "start": { 33 | "column": 2, 34 | "line": 1 35 | } 36 | }, 37 | "range": [2, 3], 38 | "raw": "a", 39 | "type": "Paragraph" 40 | } 41 | ], 42 | "loc": { 43 | "end": { 44 | "column": 3, 45 | "line": 1 46 | }, 47 | "start": { 48 | "column": 0, 49 | "line": 1 50 | } 51 | }, 52 | "range": [0, 3], 53 | "raw": "- a", 54 | "spread": false, 55 | "type": "ListItem" 56 | }, 57 | { 58 | "checked": null, 59 | "children": [ 60 | { 61 | "children": [ 62 | { 63 | "loc": { 64 | "end": { 65 | "column": 3, 66 | "line": 2 67 | }, 68 | "start": { 69 | "column": 2, 70 | "line": 2 71 | } 72 | }, 73 | "range": [6, 7], 74 | "raw": "b", 75 | "type": "Str", 76 | "value": "b" 77 | } 78 | ], 79 | "loc": { 80 | "end": { 81 | "column": 3, 82 | "line": 2 83 | }, 84 | "start": { 85 | "column": 2, 86 | "line": 2 87 | } 88 | }, 89 | "range": [6, 7], 90 | "raw": "b", 91 | "type": "Paragraph" 92 | } 93 | ], 94 | "loc": { 95 | "end": { 96 | "column": 3, 97 | "line": 2 98 | }, 99 | "start": { 100 | "column": 0, 101 | "line": 2 102 | } 103 | }, 104 | "range": [4, 7], 105 | "raw": "- b", 106 | "spread": false, 107 | "type": "ListItem" 108 | } 109 | ], 110 | "loc": { 111 | "end": { 112 | "column": 3, 113 | "line": 2 114 | }, 115 | "start": { 116 | "column": 0, 117 | "line": 1 118 | } 119 | }, 120 | "ordered": false, 121 | "range": [0, 7], 122 | "raw": "- a\n- b", 123 | "spread": false, 124 | "start": null, 125 | "type": "List" 126 | }, 127 | { 128 | "loc": { 129 | "end": { 130 | "column": 0, 131 | "line": 4 132 | }, 133 | "start": { 134 | "column": 3, 135 | "line": 2 136 | } 137 | }, 138 | "range": [7, 9], 139 | "raw": "\n\n", 140 | "type": "Break", 141 | "value": "\n\n" 142 | }, 143 | { 144 | "children": [ 145 | { 146 | "loc": { 147 | "end": { 148 | "column": 10, 149 | "line": 4 150 | }, 151 | "start": { 152 | "column": 0, 153 | "line": 4 154 | } 155 | }, 156 | "range": [9, 19], 157 | "raw": "paragraph.", 158 | "type": "Str", 159 | "value": "paragraph." 160 | } 161 | ], 162 | "loc": { 163 | "end": { 164 | "column": 10, 165 | "line": 4 166 | }, 167 | "start": { 168 | "column": 0, 169 | "line": 4 170 | } 171 | }, 172 | "range": [9, 19], 173 | "raw": "paragraph.", 174 | "type": "Paragraph" 175 | }, 176 | { 177 | "loc": { 178 | "end": { 179 | "column": 0, 180 | "line": 6 181 | }, 182 | "start": { 183 | "column": 10, 184 | "line": 4 185 | } 186 | }, 187 | "range": [19, 21], 188 | "raw": "\n\n", 189 | "type": "Break", 190 | "value": "\n\n" 191 | }, 192 | { 193 | "children": [ 194 | { 195 | "checked": null, 196 | "children": [ 197 | { 198 | "children": [ 199 | { 200 | "loc": { 201 | "end": { 202 | "column": 3, 203 | "line": 6 204 | }, 205 | "start": { 206 | "column": 2, 207 | "line": 6 208 | } 209 | }, 210 | "range": [23, 24], 211 | "raw": "c", 212 | "type": "Str", 213 | "value": "c" 214 | } 215 | ], 216 | "loc": { 217 | "end": { 218 | "column": 3, 219 | "line": 6 220 | }, 221 | "start": { 222 | "column": 2, 223 | "line": 6 224 | } 225 | }, 226 | "range": [23, 24], 227 | "raw": "c", 228 | "type": "Paragraph" 229 | } 230 | ], 231 | "loc": { 232 | "end": { 233 | "column": 3, 234 | "line": 6 235 | }, 236 | "start": { 237 | "column": 0, 238 | "line": 6 239 | } 240 | }, 241 | "range": [21, 24], 242 | "raw": "- c", 243 | "spread": false, 244 | "type": "ListItem" 245 | }, 246 | { 247 | "checked": null, 248 | "children": [ 249 | { 250 | "children": [ 251 | { 252 | "loc": { 253 | "end": { 254 | "column": 3, 255 | "line": 7 256 | }, 257 | "start": { 258 | "column": 2, 259 | "line": 7 260 | } 261 | }, 262 | "range": [27, 28], 263 | "raw": "d", 264 | "type": "Str", 265 | "value": "d" 266 | } 267 | ], 268 | "loc": { 269 | "end": { 270 | "column": 3, 271 | "line": 7 272 | }, 273 | "start": { 274 | "column": 2, 275 | "line": 7 276 | } 277 | }, 278 | "range": [27, 28], 279 | "raw": "d", 280 | "type": "Paragraph" 281 | } 282 | ], 283 | "loc": { 284 | "end": { 285 | "column": 3, 286 | "line": 7 287 | }, 288 | "start": { 289 | "column": 0, 290 | "line": 7 291 | } 292 | }, 293 | "range": [25, 28], 294 | "raw": "- d", 295 | "spread": false, 296 | "type": "ListItem" 297 | } 298 | ], 299 | "loc": { 300 | "end": { 301 | "column": 3, 302 | "line": 7 303 | }, 304 | "start": { 305 | "column": 0, 306 | "line": 6 307 | } 308 | }, 309 | "ordered": false, 310 | "range": [21, 28], 311 | "raw": "- c\n- d", 312 | "spread": false, 313 | "start": null, 314 | "type": "List" 315 | } 316 | ], 317 | "loc": { 318 | "end": { 319 | "column": 3, 320 | "line": 7 321 | }, 322 | "start": { 323 | "column": 0, 324 | "line": 1 325 | } 326 | }, 327 | "range": [0, 28], 328 | "raw": "- a\n- b\n\nparagraph.\n\n- c\n- d", 329 | "type": "Document" 330 | } 331 | -------------------------------------------------------------------------------- /test/unit/fixtures/Strong/output.json: -------------------------------------------------------------------------------- 1 | { 2 | "children": [ 3 | { 4 | "children": [ 5 | { 6 | "children": [ 7 | { 8 | "loc": { 9 | "end": { 10 | "column": 1, 11 | "line": 1 12 | }, 13 | "start": { 14 | "column": 0, 15 | "line": 1 16 | } 17 | }, 18 | "range": [0, 1], 19 | "raw": "*", 20 | "type": "Marked::Star", 21 | "value": "*" 22 | }, 23 | { 24 | "loc": { 25 | "end": { 26 | "column": 7, 27 | "line": 1 28 | }, 29 | "start": { 30 | "column": 1, 31 | "line": 1 32 | } 33 | }, 34 | "range": [1, 7], 35 | "raw": "strong", 36 | "type": "Str", 37 | "value": "strong" 38 | }, 39 | { 40 | "loc": { 41 | "end": { 42 | "column": 8, 43 | "line": 1 44 | }, 45 | "start": { 46 | "column": 7, 47 | "line": 1 48 | } 49 | }, 50 | "range": [7, 8], 51 | "raw": "*", 52 | "type": "Marked::Star", 53 | "value": "*" 54 | } 55 | ], 56 | "loc": { 57 | "end": { 58 | "column": 8, 59 | "line": 1 60 | }, 61 | "start": { 62 | "column": 0, 63 | "line": 1 64 | } 65 | }, 66 | "range": [0, 8], 67 | "raw": "*strong*", 68 | "type": "Strong" 69 | } 70 | ], 71 | "loc": { 72 | "end": { 73 | "column": 8, 74 | "line": 1 75 | }, 76 | "start": { 77 | "column": 0, 78 | "line": 1 79 | } 80 | }, 81 | "range": [0, 8], 82 | "raw": "*strong*", 83 | "type": "Paragraph" 84 | }, 85 | { 86 | "loc": { 87 | "end": { 88 | "column": 0, 89 | "line": 3 90 | }, 91 | "start": { 92 | "column": 8, 93 | "line": 1 94 | } 95 | }, 96 | "range": [8, 10], 97 | "raw": "\n\n", 98 | "type": "Break", 99 | "value": "\n\n" 100 | }, 101 | { 102 | "children": [ 103 | { 104 | "children": [ 105 | { 106 | "loc": { 107 | "end": { 108 | "column": 1, 109 | "line": 3 110 | }, 111 | "start": { 112 | "column": 0, 113 | "line": 3 114 | } 115 | }, 116 | "range": [10, 11], 117 | "raw": "*", 118 | "type": "Marked::Star", 119 | "value": "*" 120 | }, 121 | { 122 | "loc": { 123 | "end": { 124 | "column": 8, 125 | "line": 3 126 | }, 127 | "start": { 128 | "column": 1, 129 | "line": 3 130 | } 131 | }, 132 | "range": [11, 18], 133 | "raw": " a b c ", 134 | "type": "Str", 135 | "value": "a b c" 136 | }, 137 | { 138 | "loc": { 139 | "end": { 140 | "column": 9, 141 | "line": 3 142 | }, 143 | "start": { 144 | "column": 8, 145 | "line": 3 146 | } 147 | }, 148 | "range": [18, 19], 149 | "raw": "*", 150 | "type": "Marked::Star", 151 | "value": "*" 152 | } 153 | ], 154 | "loc": { 155 | "end": { 156 | "column": 9, 157 | "line": 3 158 | }, 159 | "start": { 160 | "column": 0, 161 | "line": 3 162 | } 163 | }, 164 | "range": [10, 19], 165 | "raw": "* a b c *", 166 | "type": "Strong" 167 | }, 168 | { 169 | "loc": { 170 | "end": { 171 | "column": 0, 172 | "line": 4 173 | }, 174 | "start": { 175 | "column": 9, 176 | "line": 3 177 | } 178 | }, 179 | "range": [19, 20], 180 | "raw": "\n", 181 | "type": "Str", 182 | "value": "\n" 183 | } 184 | ], 185 | "loc": { 186 | "end": { 187 | "column": 0, 188 | "line": 4 189 | }, 190 | "start": { 191 | "column": 0, 192 | "line": 3 193 | } 194 | }, 195 | "range": [10, 20], 196 | "raw": "* a b c *\n", 197 | "type": "Paragraph" 198 | }, 199 | { 200 | "loc": { 201 | "end": { 202 | "column": 1, 203 | "line": 4 204 | }, 205 | "start": { 206 | "column": 0, 207 | "line": 4 208 | } 209 | }, 210 | "range": [20, 21], 211 | "raw": "\\", 212 | "type": "Break", 213 | "value": "\\" 214 | }, 215 | { 216 | "children": [ 217 | { 218 | "loc": { 219 | "end": { 220 | "column": 7, 221 | "line": 5 222 | }, 223 | "start": { 224 | "column": 0, 225 | "line": 5 226 | } 227 | }, 228 | "range": [22, 29], 229 | "raw": "This is", 230 | "type": "Str", 231 | "value": "This is" 232 | }, 233 | { 234 | "loc": { 235 | "end": { 236 | "column": 8, 237 | "line": 5 238 | }, 239 | "start": { 240 | "column": 7, 241 | "line": 5 242 | } 243 | }, 244 | "range": [29, 30], 245 | "raw": " ", 246 | "type": "Str", 247 | "value": " " 248 | }, 249 | { 250 | "children": [ 251 | { 252 | "loc": { 253 | "end": { 254 | "column": 9, 255 | "line": 5 256 | }, 257 | "start": { 258 | "column": 8, 259 | "line": 5 260 | } 261 | }, 262 | "range": [30, 31], 263 | "raw": "*", 264 | "type": "Marked::Star", 265 | "value": "*" 266 | }, 267 | { 268 | "loc": { 269 | "end": { 270 | "column": 15, 271 | "line": 5 272 | }, 273 | "start": { 274 | "column": 9, 275 | "line": 5 276 | } 277 | }, 278 | "range": [31, 37], 279 | "raw": "strong", 280 | "type": "Str", 281 | "value": "strong" 282 | }, 283 | { 284 | "loc": { 285 | "end": { 286 | "column": 16, 287 | "line": 5 288 | }, 289 | "start": { 290 | "column": 15, 291 | "line": 5 292 | } 293 | }, 294 | "range": [37, 38], 295 | "raw": "*", 296 | "type": "Marked::Star", 297 | "value": "*" 298 | } 299 | ], 300 | "loc": { 301 | "end": { 302 | "column": 16, 303 | "line": 5 304 | }, 305 | "start": { 306 | "column": 8, 307 | "line": 5 308 | } 309 | }, 310 | "range": [30, 38], 311 | "raw": "*strong*", 312 | "type": "Strong" 313 | }, 314 | { 315 | "loc": { 316 | "end": { 317 | "column": 17, 318 | "line": 5 319 | }, 320 | "start": { 321 | "column": 16, 322 | "line": 5 323 | } 324 | }, 325 | "range": [38, 39], 326 | "raw": ".", 327 | "type": "Str", 328 | "value": "." 329 | } 330 | ], 331 | "loc": { 332 | "end": { 333 | "column": 17, 334 | "line": 5 335 | }, 336 | "start": { 337 | "column": 0, 338 | "line": 5 339 | } 340 | }, 341 | "range": [22, 39], 342 | "raw": "This is *strong*.", 343 | "type": "Paragraph" 344 | } 345 | ], 346 | "loc": { 347 | "end": { 348 | "column": 17, 349 | "line": 5 350 | }, 351 | "start": { 352 | "column": 0, 353 | "line": 1 354 | } 355 | }, 356 | "range": [0, 39], 357 | "raw": "*strong*\n\n* a b c *\n\\\nThis is *strong*.", 358 | "type": "Document" 359 | } 360 | -------------------------------------------------------------------------------- /test/unit/fixtures/Emphasis/output.json: -------------------------------------------------------------------------------- 1 | { 2 | "children": [ 3 | { 4 | "children": [ 5 | { 6 | "children": [ 7 | { 8 | "loc": { 9 | "end": { 10 | "column": 1, 11 | "line": 1 12 | }, 13 | "start": { 14 | "column": 0, 15 | "line": 1 16 | } 17 | }, 18 | "range": [0, 1], 19 | "raw": "_", 20 | "type": "Marked::Underscore", 21 | "value": "_" 22 | }, 23 | { 24 | "loc": { 25 | "end": { 26 | "column": 9, 27 | "line": 1 28 | }, 29 | "start": { 30 | "column": 1, 31 | "line": 1 32 | } 33 | }, 34 | "range": [1, 9], 35 | "raw": "emphasis", 36 | "type": "Str", 37 | "value": "emphasis" 38 | }, 39 | { 40 | "loc": { 41 | "end": { 42 | "column": 10, 43 | "line": 1 44 | }, 45 | "start": { 46 | "column": 9, 47 | "line": 1 48 | } 49 | }, 50 | "range": [9, 10], 51 | "raw": "_", 52 | "type": "Marked::Underscore", 53 | "value": "_" 54 | } 55 | ], 56 | "loc": { 57 | "end": { 58 | "column": 10, 59 | "line": 1 60 | }, 61 | "start": { 62 | "column": 0, 63 | "line": 1 64 | } 65 | }, 66 | "range": [0, 10], 67 | "raw": "_emphasis_", 68 | "type": "Emphasis" 69 | } 70 | ], 71 | "loc": { 72 | "end": { 73 | "column": 10, 74 | "line": 1 75 | }, 76 | "start": { 77 | "column": 0, 78 | "line": 1 79 | } 80 | }, 81 | "range": [0, 10], 82 | "raw": "_emphasis_", 83 | "type": "Paragraph" 84 | }, 85 | { 86 | "loc": { 87 | "end": { 88 | "column": 0, 89 | "line": 3 90 | }, 91 | "start": { 92 | "column": 10, 93 | "line": 1 94 | } 95 | }, 96 | "range": [10, 12], 97 | "raw": "\n\n", 98 | "type": "Break", 99 | "value": "\n\n" 100 | }, 101 | { 102 | "children": [ 103 | { 104 | "children": [ 105 | { 106 | "loc": { 107 | "end": { 108 | "column": 1, 109 | "line": 3 110 | }, 111 | "start": { 112 | "column": 0, 113 | "line": 3 114 | } 115 | }, 116 | "range": [12, 13], 117 | "raw": "_", 118 | "type": "Marked::Underscore", 119 | "value": "_" 120 | }, 121 | { 122 | "loc": { 123 | "end": { 124 | "column": 8, 125 | "line": 3 126 | }, 127 | "start": { 128 | "column": 1, 129 | "line": 3 130 | } 131 | }, 132 | "range": [13, 20], 133 | "raw": " a b c ", 134 | "type": "Str", 135 | "value": "a b c" 136 | }, 137 | { 138 | "loc": { 139 | "end": { 140 | "column": 9, 141 | "line": 3 142 | }, 143 | "start": { 144 | "column": 8, 145 | "line": 3 146 | } 147 | }, 148 | "range": [20, 21], 149 | "raw": "_", 150 | "type": "Marked::Underscore", 151 | "value": "_" 152 | } 153 | ], 154 | "loc": { 155 | "end": { 156 | "column": 9, 157 | "line": 3 158 | }, 159 | "start": { 160 | "column": 0, 161 | "line": 3 162 | } 163 | }, 164 | "range": [12, 21], 165 | "raw": "_ a b c _", 166 | "type": "Emphasis" 167 | }, 168 | { 169 | "loc": { 170 | "end": { 171 | "column": 0, 172 | "line": 4 173 | }, 174 | "start": { 175 | "column": 9, 176 | "line": 3 177 | } 178 | }, 179 | "range": [21, 22], 180 | "raw": "\n", 181 | "type": "Str", 182 | "value": "\n" 183 | } 184 | ], 185 | "loc": { 186 | "end": { 187 | "column": 0, 188 | "line": 4 189 | }, 190 | "start": { 191 | "column": 0, 192 | "line": 3 193 | } 194 | }, 195 | "range": [12, 22], 196 | "raw": "_ a b c _\n", 197 | "type": "Paragraph" 198 | }, 199 | { 200 | "loc": { 201 | "end": { 202 | "column": 1, 203 | "line": 4 204 | }, 205 | "start": { 206 | "column": 0, 207 | "line": 4 208 | } 209 | }, 210 | "range": [22, 23], 211 | "raw": "\\", 212 | "type": "Break", 213 | "value": "\\" 214 | }, 215 | { 216 | "children": [ 217 | { 218 | "loc": { 219 | "end": { 220 | "column": 7, 221 | "line": 5 222 | }, 223 | "start": { 224 | "column": 0, 225 | "line": 5 226 | } 227 | }, 228 | "range": [24, 31], 229 | "raw": "This is", 230 | "type": "Str", 231 | "value": "This is" 232 | }, 233 | { 234 | "loc": { 235 | "end": { 236 | "column": 8, 237 | "line": 5 238 | }, 239 | "start": { 240 | "column": 7, 241 | "line": 5 242 | } 243 | }, 244 | "range": [31, 32], 245 | "raw": " ", 246 | "type": "Str", 247 | "value": " " 248 | }, 249 | { 250 | "children": [ 251 | { 252 | "loc": { 253 | "end": { 254 | "column": 9, 255 | "line": 5 256 | }, 257 | "start": { 258 | "column": 8, 259 | "line": 5 260 | } 261 | }, 262 | "range": [32, 33], 263 | "raw": "_", 264 | "type": "Marked::Underscore", 265 | "value": "_" 266 | }, 267 | { 268 | "loc": { 269 | "end": { 270 | "column": 19, 271 | "line": 5 272 | }, 273 | "start": { 274 | "column": 9, 275 | "line": 5 276 | } 277 | }, 278 | "range": [33, 43], 279 | "raw": "emphasized", 280 | "type": "Str", 281 | "value": "emphasized" 282 | }, 283 | { 284 | "loc": { 285 | "end": { 286 | "column": 20, 287 | "line": 5 288 | }, 289 | "start": { 290 | "column": 19, 291 | "line": 5 292 | } 293 | }, 294 | "range": [43, 44], 295 | "raw": "_", 296 | "type": "Marked::Underscore", 297 | "value": "_" 298 | } 299 | ], 300 | "loc": { 301 | "end": { 302 | "column": 20, 303 | "line": 5 304 | }, 305 | "start": { 306 | "column": 8, 307 | "line": 5 308 | } 309 | }, 310 | "range": [32, 44], 311 | "raw": "_emphasized_", 312 | "type": "Emphasis" 313 | }, 314 | { 315 | "loc": { 316 | "end": { 317 | "column": 21, 318 | "line": 5 319 | }, 320 | "start": { 321 | "column": 20, 322 | "line": 5 323 | } 324 | }, 325 | "range": [44, 45], 326 | "raw": ".", 327 | "type": "Str", 328 | "value": "." 329 | } 330 | ], 331 | "loc": { 332 | "end": { 333 | "column": 21, 334 | "line": 5 335 | }, 336 | "start": { 337 | "column": 0, 338 | "line": 5 339 | } 340 | }, 341 | "range": [24, 45], 342 | "raw": "This is _emphasized_.", 343 | "type": "Paragraph" 344 | } 345 | ], 346 | "loc": { 347 | "end": { 348 | "column": 21, 349 | "line": 5 350 | }, 351 | "start": { 352 | "column": 0, 353 | "line": 1 354 | } 355 | }, 356 | "range": [0, 45], 357 | "raw": "_emphasis_\n\n_ a b c _\n\\\nThis is _emphasized_.", 358 | "type": "Document" 359 | } 360 | -------------------------------------------------------------------------------- /test/integration/linting.test.ts: -------------------------------------------------------------------------------- 1 | import fs from "node:fs"; 2 | import path from "node:path"; 3 | import { 4 | TextlintKernel, 5 | type TextlintKernelRule, 6 | type TextlintResult, 7 | } from "@textlint/kernel"; 8 | // @ts-expect-error 9 | import textlintRulePeriodInListItem from "textlint-rule-period-in-list-item"; 10 | import { 11 | rules as textlintRulePresetJaTechnicalWritingRules, 12 | rulesConfig as textlintRulePresetJaTechnicalWritingRulesConfig, 13 | // @ts-expect-error 14 | } from "textlint-rule-preset-ja-technical-writing"; 15 | 16 | import { beforeAll, describe, expect, it } from "vitest"; 17 | 18 | import typstPlugin from "../../src"; 19 | 20 | describe("linting", () => { 21 | describe("smoke tests", () => { 22 | describe("textlint-rule-preset-ja-technical-writing", () => { 23 | let textlintResult: TextlintResult; 24 | 25 | beforeAll(async () => { 26 | const kernel = new TextlintKernel(); 27 | textlintResult = await kernel.lintText( 28 | fs.readFileSync( 29 | path.join( 30 | __dirname, 31 | "./fixtures/smoke/textlint-rule-preset-ja-technical-writing/main.typ", 32 | ), 33 | "utf-8", 34 | ), 35 | { 36 | ext: ".typ", 37 | plugins: [ 38 | { 39 | pluginId: "typst", 40 | plugin: typstPlugin, 41 | }, 42 | ], 43 | rules: [ 44 | // Set each rule in the preset individually 45 | ...Object.entries(textlintRulePresetJaTechnicalWritingRules).map( 46 | ([id, rule]) => 47 | ({ 48 | ruleId: `ja-technical-writing/${id}`, 49 | rule: rule, 50 | options: 51 | textlintRulePresetJaTechnicalWritingRulesConfig[id] || 52 | true, 53 | }) as TextlintKernelRule, 54 | ), 55 | ], 56 | }, 57 | ); 58 | }); 59 | 60 | // Rule test configurations 61 | const ruleTests = [ 62 | { 63 | ruleId: "sentence-length", 64 | expectedMessage: "exceeds the maximum sentence length", 65 | }, 66 | { 67 | ruleId: "max-ten", 68 | expectedMessage: '一つの文で"、"を4つ以上使用', 69 | }, 70 | { 71 | ruleId: "max-kanji-continuous-len", 72 | expectedMessage: "漢字が7つ以上連続", 73 | }, 74 | { 75 | ruleId: "no-mix-dearu-desumasu", 76 | expectedMessage: '"ですます"調 でなければなりません', 77 | }, 78 | { 79 | ruleId: "ja-no-mixed-period", 80 | expectedMessage: '文末が"。"で終わっていません', 81 | }, 82 | { 83 | ruleId: "no-doubled-joshi", 84 | expectedMessage: "一文に二回以上利用されている助詞", 85 | }, 86 | { 87 | ruleId: "no-dropping-the-ra", 88 | expectedMessage: "ら抜き言葉を使用", 89 | }, 90 | { 91 | ruleId: "no-doubled-conjunctive-particle-ga", 92 | expectedMessage: '逆接の接続助詞 "が" が二回以上使われています', 93 | }, 94 | { 95 | ruleId: "no-doubled-conjunction", 96 | expectedMessage: "同じ接続詞(しかし)が連続", 97 | }, 98 | { 99 | ruleId: "no-exclamation-question-mark", 100 | expectedMessage: 'Disallow to use "!"', 101 | }, 102 | { 103 | ruleId: "no-hankaku-kana", 104 | expectedMessage: "Disallow to use 半角カタカナ", 105 | }, 106 | { 107 | ruleId: "ja-no-weak-phrase", 108 | expectedMessage: '弱い表現: "かも" が使われています', 109 | }, 110 | { 111 | ruleId: "ja-no-successive-word", 112 | expectedMessage: "が連続して2回使われています", 113 | }, 114 | { 115 | ruleId: "ja-no-redundant-expression", 116 | expectedMessage: 'することが可能です"は冗長な表現', 117 | }, 118 | { 119 | ruleId: "ja-unnatural-alphabet", 120 | expectedMessage: "不自然なアルファベット", 121 | }, 122 | { 123 | ruleId: "no-unmatched-pair", 124 | expectedMessage: "Cannot find a pairing character for (", 125 | }, 126 | ]; 127 | 128 | // Special cases with multiple expected messages 129 | const multiMessageTests = [ 130 | { 131 | ruleId: "arabic-kanji-numbers", 132 | expectedMessages: ["十番目 => 10番目", "1時的 => 一時的"], 133 | }, 134 | { 135 | ruleId: "no-double-negative-ja", 136 | expectedMessages: [ 137 | "二重否定: 〜なくもない", 138 | "二重否定: 〜ないことはない", 139 | ], 140 | }, 141 | { 142 | ruleId: "no-nfd", 143 | expectedMessages: ['ホ゜" => "ポ"', 'シ゛" => "ジ"'], 144 | }, 145 | { 146 | ruleId: "ja-no-abusage", 147 | expectedMessages: ["可変する", "適用"], 148 | }, 149 | ]; 150 | 151 | const getViolations = (ruleId: string) => { 152 | return textlintResult.messages.filter( 153 | (message) => message.ruleId === `ja-technical-writing/${ruleId}`, 154 | ); 155 | }; 156 | 157 | // Single message tests 158 | for (const { ruleId, expectedMessage } of ruleTests) { 159 | it(`should detect ${ruleId} violations`, () => { 160 | const violations = getViolations(ruleId); 161 | expect(violations.length).toBeGreaterThan(0); 162 | expect(violations[0].message).toContain(expectedMessage); 163 | }); 164 | } 165 | 166 | // Multi-message tests 167 | for (const { ruleId, expectedMessages } of multiMessageTests) { 168 | it(`should detect ${ruleId} violations`, () => { 169 | const violations = getViolations(ruleId); 170 | expect(violations.length).toBeGreaterThan(0); 171 | 172 | for (const expectedMessage of expectedMessages) { 173 | expect( 174 | violations.some((v) => v.message.includes(expectedMessage)), 175 | ).toBe(true); 176 | } 177 | }); 178 | } 179 | }); 180 | describe("textlint-rule-period-in-list-item", () => { 181 | let textlintResult: TextlintResult; 182 | 183 | beforeAll(async () => { 184 | const kernel = new TextlintKernel(); 185 | textlintResult = await kernel.lintText( 186 | fs.readFileSync( 187 | path.join( 188 | __dirname, 189 | "./fixtures/smoke/textlint-rule-period-in-list-item/main.typ", 190 | ), 191 | "utf-8", 192 | ), 193 | { 194 | ext: ".typ", 195 | plugins: [ 196 | { 197 | pluginId: "typst", 198 | plugin: typstPlugin, 199 | }, 200 | ], 201 | rules: [ 202 | { 203 | ruleId: "period-in-list-item", 204 | rule: textlintRulePeriodInListItem, 205 | }, 206 | ], 207 | }, 208 | ); 209 | }); 210 | const getViolations = () => { 211 | return textlintResult.messages.filter( 212 | (message) => message.ruleId === "period-in-list-item", 213 | ); 214 | }; 215 | 216 | it("should detect period in single line bullet list items", () => { 217 | const violations = getViolations(); 218 | 219 | const singleLineBulletViolation = violations.find( 220 | (v) => v.loc.start.line === 6, 221 | ); 222 | expect(singleLineBulletViolation).toBeDefined(); 223 | expect(singleLineBulletViolation?.message).toBe( 224 | 'Should remove period mark(".") at end of list item.', 225 | ); 226 | }); 227 | 228 | it("should detect period in multiple lines bullet list items", () => { 229 | const violations = getViolations(); 230 | 231 | const multipleLinesBulletViolation = violations.find( 232 | (v) => v.loc.start.line === 12, 233 | ); 234 | expect(multipleLinesBulletViolation).toBeDefined(); 235 | expect(multipleLinesBulletViolation?.message).toBe( 236 | 'Should remove period mark(".") at end of list item.', 237 | ); 238 | }); 239 | 240 | it("should detect period in single line numbered list items", () => { 241 | const violations = getViolations(); 242 | 243 | const singleLineNumberedViolation = violations.find( 244 | (v) => v.loc.start.line === 27, 245 | ); 246 | expect(singleLineNumberedViolation).toBeDefined(); 247 | expect(singleLineNumberedViolation?.message).toBe( 248 | 'Should remove period mark(".") at end of list item.', 249 | ); 250 | }); 251 | 252 | it("should detect period in multiple lines numbered list items", () => { 253 | const violations = getViolations(); 254 | 255 | const multipleLineNumberedViolation = violations.find( 256 | (v) => v.loc.start.line === 41, 257 | ); 258 | expect(multipleLineNumberedViolation).toBeDefined(); 259 | expect(multipleLineNumberedViolation?.message).toBe( 260 | 'Should remove period mark(".") at end of list item.', 261 | ); 262 | }); 263 | }); 264 | }); 265 | }); 266 | -------------------------------------------------------------------------------- /test/unit/fixtures/List/output.json: -------------------------------------------------------------------------------- 1 | { 2 | "children": [ 3 | { 4 | "children": [ 5 | { 6 | "checked": null, 7 | "children": [ 8 | { 9 | "children": [ 10 | { 11 | "loc": { 12 | "end": { 13 | "column": 3, 14 | "line": 1 15 | }, 16 | "start": { 17 | "column": 2, 18 | "line": 1 19 | } 20 | }, 21 | "range": [2, 3], 22 | "raw": "a", 23 | "type": "Str", 24 | "value": "a" 25 | } 26 | ], 27 | "loc": { 28 | "end": { 29 | "column": 3, 30 | "line": 1 31 | }, 32 | "start": { 33 | "column": 2, 34 | "line": 1 35 | } 36 | }, 37 | "range": [2, 3], 38 | "raw": "a", 39 | "type": "Paragraph" 40 | } 41 | ], 42 | "loc": { 43 | "end": { 44 | "column": 3, 45 | "line": 1 46 | }, 47 | "start": { 48 | "column": 0, 49 | "line": 1 50 | } 51 | }, 52 | "range": [0, 3], 53 | "raw": "- a", 54 | "spread": false, 55 | "type": "ListItem" 56 | }, 57 | { 58 | "checked": null, 59 | "children": [ 60 | { 61 | "children": [ 62 | { 63 | "loc": { 64 | "end": { 65 | "column": 3, 66 | "line": 2 67 | }, 68 | "start": { 69 | "column": 2, 70 | "line": 2 71 | } 72 | }, 73 | "range": [6, 7], 74 | "raw": "b", 75 | "type": "Str", 76 | "value": "b" 77 | } 78 | ], 79 | "loc": { 80 | "end": { 81 | "column": 3, 82 | "line": 2 83 | }, 84 | "start": { 85 | "column": 2, 86 | "line": 2 87 | } 88 | }, 89 | "range": [6, 7], 90 | "raw": "b", 91 | "type": "Paragraph" 92 | } 93 | ], 94 | "loc": { 95 | "end": { 96 | "column": 3, 97 | "line": 2 98 | }, 99 | "start": { 100 | "column": 0, 101 | "line": 2 102 | } 103 | }, 104 | "range": [4, 7], 105 | "raw": "- b", 106 | "spread": false, 107 | "type": "ListItem" 108 | }, 109 | { 110 | "checked": null, 111 | "children": [ 112 | { 113 | "children": [ 114 | { 115 | "loc": { 116 | "end": { 117 | "column": 3, 118 | "line": 3 119 | }, 120 | "start": { 121 | "column": 2, 122 | "line": 3 123 | } 124 | }, 125 | "range": [10, 11], 126 | "raw": "c", 127 | "type": "Str", 128 | "value": "c" 129 | } 130 | ], 131 | "loc": { 132 | "end": { 133 | "column": 3, 134 | "line": 3 135 | }, 136 | "start": { 137 | "column": 2, 138 | "line": 3 139 | } 140 | }, 141 | "range": [10, 11], 142 | "raw": "c", 143 | "type": "Paragraph" 144 | } 145 | ], 146 | "loc": { 147 | "end": { 148 | "column": 3, 149 | "line": 3 150 | }, 151 | "start": { 152 | "column": 0, 153 | "line": 3 154 | } 155 | }, 156 | "range": [8, 11], 157 | "raw": "- c", 158 | "spread": false, 159 | "type": "ListItem" 160 | } 161 | ], 162 | "loc": { 163 | "end": { 164 | "column": 3, 165 | "line": 3 166 | }, 167 | "start": { 168 | "column": 0, 169 | "line": 1 170 | } 171 | }, 172 | "ordered": false, 173 | "range": [0, 11], 174 | "raw": "- a\n- b\n- c", 175 | "spread": false, 176 | "start": null, 177 | "type": "List" 178 | }, 179 | { 180 | "loc": { 181 | "end": { 182 | "column": 0, 183 | "line": 5 184 | }, 185 | "start": { 186 | "column": 3, 187 | "line": 3 188 | } 189 | }, 190 | "range": [11, 13], 191 | "raw": "\n\n", 192 | "type": "Break", 193 | "value": "\n\n" 194 | }, 195 | { 196 | "children": [ 197 | { 198 | "checked": null, 199 | "children": [ 200 | { 201 | "children": [ 202 | { 203 | "loc": { 204 | "end": { 205 | "column": 4, 206 | "line": 5 207 | }, 208 | "start": { 209 | "column": 3, 210 | "line": 5 211 | } 212 | }, 213 | "range": [16, 17], 214 | "raw": "x", 215 | "type": "Str", 216 | "value": "x" 217 | } 218 | ], 219 | "loc": { 220 | "end": { 221 | "column": 4, 222 | "line": 5 223 | }, 224 | "start": { 225 | "column": 3, 226 | "line": 5 227 | } 228 | }, 229 | "range": [16, 17], 230 | "raw": "x", 231 | "type": "Paragraph" 232 | } 233 | ], 234 | "loc": { 235 | "end": { 236 | "column": 4, 237 | "line": 5 238 | }, 239 | "start": { 240 | "column": 0, 241 | "line": 5 242 | } 243 | }, 244 | "range": [13, 17], 245 | "raw": "1. x", 246 | "spread": false, 247 | "type": "ListItem" 248 | }, 249 | { 250 | "checked": null, 251 | "children": [ 252 | { 253 | "children": [ 254 | { 255 | "loc": { 256 | "end": { 257 | "column": 4, 258 | "line": 6 259 | }, 260 | "start": { 261 | "column": 3, 262 | "line": 6 263 | } 264 | }, 265 | "range": [21, 22], 266 | "raw": "y", 267 | "type": "Str", 268 | "value": "y" 269 | } 270 | ], 271 | "loc": { 272 | "end": { 273 | "column": 4, 274 | "line": 6 275 | }, 276 | "start": { 277 | "column": 3, 278 | "line": 6 279 | } 280 | }, 281 | "range": [21, 22], 282 | "raw": "y", 283 | "type": "Paragraph" 284 | } 285 | ], 286 | "loc": { 287 | "end": { 288 | "column": 4, 289 | "line": 6 290 | }, 291 | "start": { 292 | "column": 0, 293 | "line": 6 294 | } 295 | }, 296 | "range": [18, 22], 297 | "raw": "2. y", 298 | "spread": false, 299 | "type": "ListItem" 300 | }, 301 | { 302 | "checked": null, 303 | "children": [ 304 | { 305 | "children": [ 306 | { 307 | "loc": { 308 | "end": { 309 | "column": 4, 310 | "line": 7 311 | }, 312 | "start": { 313 | "column": 3, 314 | "line": 7 315 | } 316 | }, 317 | "range": [26, 27], 318 | "raw": "z", 319 | "type": "Str", 320 | "value": "z" 321 | } 322 | ], 323 | "loc": { 324 | "end": { 325 | "column": 4, 326 | "line": 7 327 | }, 328 | "start": { 329 | "column": 3, 330 | "line": 7 331 | } 332 | }, 333 | "range": [26, 27], 334 | "raw": "z", 335 | "type": "Paragraph" 336 | } 337 | ], 338 | "loc": { 339 | "end": { 340 | "column": 4, 341 | "line": 7 342 | }, 343 | "start": { 344 | "column": 0, 345 | "line": 7 346 | } 347 | }, 348 | "range": [23, 27], 349 | "raw": "3. z", 350 | "spread": false, 351 | "type": "ListItem" 352 | } 353 | ], 354 | "loc": { 355 | "end": { 356 | "column": 4, 357 | "line": 7 358 | }, 359 | "start": { 360 | "column": 0, 361 | "line": 5 362 | } 363 | }, 364 | "ordered": true, 365 | "range": [13, 27], 366 | "raw": "1. x\n2. y\n3. z", 367 | "spread": false, 368 | "start": 1, 369 | "type": "List" 370 | } 371 | ], 372 | "loc": { 373 | "end": { 374 | "column": 4, 375 | "line": 7 376 | }, 377 | "start": { 378 | "column": 0, 379 | "line": 1 380 | } 381 | }, 382 | "range": [0, 27], 383 | "raw": "- a\n- b\n- c\n\n1. x\n2. y\n3. z", 384 | "type": "Document" 385 | } 386 | -------------------------------------------------------------------------------- /test/unit/fixtures/Equation/output.json: -------------------------------------------------------------------------------- 1 | { 2 | "children": [ 3 | { 4 | "children": [ 5 | { 6 | "loc": { 7 | "end": { 8 | "column": 3, 9 | "line": 1 10 | }, 11 | "start": { 12 | "column": 0, 13 | "line": 1 14 | } 15 | }, 16 | "range": [0, 3], 17 | "raw": "Let", 18 | "type": "Str", 19 | "value": "Let" 20 | }, 21 | { 22 | "loc": { 23 | "end": { 24 | "column": 4, 25 | "line": 1 26 | }, 27 | "start": { 28 | "column": 3, 29 | "line": 1 30 | } 31 | }, 32 | "range": [3, 4], 33 | "raw": " ", 34 | "type": "Str", 35 | "value": " " 36 | }, 37 | { 38 | "loc": { 39 | "end": { 40 | "column": 7, 41 | "line": 1 42 | }, 43 | "start": { 44 | "column": 4, 45 | "line": 1 46 | } 47 | }, 48 | "range": [4, 7], 49 | "raw": "$a$", 50 | "type": "Code", 51 | "value": "a" 52 | }, 53 | { 54 | "loc": { 55 | "end": { 56 | "column": 8, 57 | "line": 1 58 | }, 59 | "start": { 60 | "column": 7, 61 | "line": 1 62 | } 63 | }, 64 | "range": [7, 8], 65 | "raw": ",", 66 | "type": "Str", 67 | "value": "," 68 | }, 69 | { 70 | "loc": { 71 | "end": { 72 | "column": 9, 73 | "line": 1 74 | }, 75 | "start": { 76 | "column": 8, 77 | "line": 1 78 | } 79 | }, 80 | "range": [8, 9], 81 | "raw": " ", 82 | "type": "Str", 83 | "value": " " 84 | }, 85 | { 86 | "loc": { 87 | "end": { 88 | "column": 12, 89 | "line": 1 90 | }, 91 | "start": { 92 | "column": 9, 93 | "line": 1 94 | } 95 | }, 96 | "range": [9, 12], 97 | "raw": "$b$", 98 | "type": "Code", 99 | "value": "b" 100 | }, 101 | { 102 | "loc": { 103 | "end": { 104 | "column": 17, 105 | "line": 1 106 | }, 107 | "start": { 108 | "column": 12, 109 | "line": 1 110 | } 111 | }, 112 | "range": [12, 17], 113 | "raw": ", and", 114 | "type": "Str", 115 | "value": ", and" 116 | }, 117 | { 118 | "loc": { 119 | "end": { 120 | "column": 18, 121 | "line": 1 122 | }, 123 | "start": { 124 | "column": 17, 125 | "line": 1 126 | } 127 | }, 128 | "range": [17, 18], 129 | "raw": " ", 130 | "type": "Str", 131 | "value": " " 132 | }, 133 | { 134 | "loc": { 135 | "end": { 136 | "column": 21, 137 | "line": 1 138 | }, 139 | "start": { 140 | "column": 18, 141 | "line": 1 142 | } 143 | }, 144 | "range": [18, 21], 145 | "raw": "$c$", 146 | "type": "Code", 147 | "value": "c" 148 | }, 149 | { 150 | "loc": { 151 | "end": { 152 | "column": 22, 153 | "line": 1 154 | }, 155 | "start": { 156 | "column": 21, 157 | "line": 1 158 | } 159 | }, 160 | "range": [21, 22], 161 | "raw": " ", 162 | "type": "Str", 163 | "value": " " 164 | }, 165 | { 166 | "loc": { 167 | "end": { 168 | "column": 33, 169 | "line": 1 170 | }, 171 | "start": { 172 | "column": 22, 173 | "line": 1 174 | } 175 | }, 176 | "range": [22, 33], 177 | "raw": "be the side", 178 | "type": "Str", 179 | "value": "be the side" 180 | }, 181 | { 182 | "loc": { 183 | "end": { 184 | "column": 0, 185 | "line": 2 186 | }, 187 | "start": { 188 | "column": 33, 189 | "line": 1 190 | } 191 | }, 192 | "range": [33, 34], 193 | "raw": "\n", 194 | "type": "Str", 195 | "value": "\n" 196 | }, 197 | { 198 | "loc": { 199 | "end": { 200 | "column": 33, 201 | "line": 2 202 | }, 203 | "start": { 204 | "column": 0, 205 | "line": 2 206 | } 207 | }, 208 | "range": [34, 67], 209 | "raw": "lengths of right-angled triangle.", 210 | "type": "Str", 211 | "value": "lengths of right-angled triangle." 212 | }, 213 | { 214 | "loc": { 215 | "end": { 216 | "column": 0, 217 | "line": 3 218 | }, 219 | "start": { 220 | "column": 33, 221 | "line": 2 222 | } 223 | }, 224 | "range": [67, 68], 225 | "raw": "\n", 226 | "type": "Str", 227 | "value": "\n" 228 | }, 229 | { 230 | "loc": { 231 | "end": { 232 | "column": 18, 233 | "line": 3 234 | }, 235 | "start": { 236 | "column": 0, 237 | "line": 3 238 | } 239 | }, 240 | "range": [68, 86], 241 | "raw": "Then, we know that", 242 | "type": "Str", 243 | "value": "Then, we know that" 244 | }, 245 | { 246 | "loc": { 247 | "end": { 248 | "column": 19, 249 | "line": 3 250 | }, 251 | "start": { 252 | "column": 18, 253 | "line": 3 254 | } 255 | }, 256 | "range": [86, 87], 257 | "raw": ":", 258 | "type": "Str", 259 | "value": ":" 260 | }, 261 | { 262 | "loc": { 263 | "end": { 264 | "column": 0, 265 | "line": 4 266 | }, 267 | "start": { 268 | "column": 19, 269 | "line": 3 270 | } 271 | }, 272 | "range": [87, 88], 273 | "raw": "\n", 274 | "type": "Str", 275 | "value": "\n" 276 | } 277 | ], 278 | "loc": { 279 | "end": { 280 | "column": 0, 281 | "line": 4 282 | }, 283 | "start": { 284 | "column": 0, 285 | "line": 1 286 | } 287 | }, 288 | "range": [0, 88], 289 | "raw": "Let $a$, $b$, and $c$ be the side\nlengths of right-angled triangle.\nThen, we know that:\n", 290 | "type": "Paragraph" 291 | }, 292 | { 293 | "loc": { 294 | "end": { 295 | "column": 19, 296 | "line": 4 297 | }, 298 | "start": { 299 | "column": 0, 300 | "line": 4 301 | } 302 | }, 303 | "range": [88, 107], 304 | "raw": "$ a^2 + b^2 = c^2 $", 305 | "type": "CodeBlock", 306 | "value": "a^2 + b^2 = c^2" 307 | }, 308 | { 309 | "loc": { 310 | "end": { 311 | "column": 0, 312 | "line": 6 313 | }, 314 | "start": { 315 | "column": 19, 316 | "line": 4 317 | } 318 | }, 319 | "range": [107, 109], 320 | "raw": "\n\n", 321 | "type": "Break", 322 | "value": "\n\n" 323 | }, 324 | { 325 | "children": [ 326 | { 327 | "loc": { 328 | "end": { 329 | "column": 18, 330 | "line": 6 331 | }, 332 | "start": { 333 | "column": 0, 334 | "line": 6 335 | } 336 | }, 337 | "range": [109, 127], 338 | "raw": "Prove by induction", 339 | "type": "Str", 340 | "value": "Prove by induction" 341 | }, 342 | { 343 | "loc": { 344 | "end": { 345 | "column": 19, 346 | "line": 6 347 | }, 348 | "start": { 349 | "column": 18, 350 | "line": 6 351 | } 352 | }, 353 | "range": [127, 128], 354 | "raw": ":", 355 | "type": "Str", 356 | "value": ":" 357 | }, 358 | { 359 | "loc": { 360 | "end": { 361 | "column": 0, 362 | "line": 7 363 | }, 364 | "start": { 365 | "column": 19, 366 | "line": 6 367 | } 368 | }, 369 | "range": [128, 129], 370 | "raw": "\n", 371 | "type": "Str", 372 | "value": "\n" 373 | } 374 | ], 375 | "loc": { 376 | "end": { 377 | "column": 0, 378 | "line": 7 379 | }, 380 | "start": { 381 | "column": 0, 382 | "line": 6 383 | } 384 | }, 385 | "range": [109, 129], 386 | "raw": "Prove by induction:\n", 387 | "type": "Paragraph" 388 | }, 389 | { 390 | "loc": { 391 | "end": { 392 | "column": 32, 393 | "line": 7 394 | }, 395 | "start": { 396 | "column": 0, 397 | "line": 7 398 | } 399 | }, 400 | "range": [129, 161], 401 | "raw": "$ sum_(k=1)^n k = (n(n+1)) / 2 $", 402 | "type": "CodeBlock", 403 | "value": "sum_(k=1)^n k = (n(n+1)) / 2" 404 | } 405 | ], 406 | "loc": { 407 | "end": { 408 | "column": 32, 409 | "line": 7 410 | }, 411 | "start": { 412 | "column": 0, 413 | "line": 1 414 | } 415 | }, 416 | "range": [0, 161], 417 | "raw": "Let $a$, $b$, and $c$ be the side\nlengths of right-angled triangle.\nThen, we know that:\n$ a^2 + b^2 = c^2 $\n\nProve by induction:\n$ sum_(k=1)^n k = (n(n+1)) / 2 $", 418 | "type": "Document" 419 | } 420 | -------------------------------------------------------------------------------- /test/unit/fixtures/paragraph-with-list-between/output.json: -------------------------------------------------------------------------------- 1 | { 2 | "children": [ 3 | { 4 | "children": [ 5 | { 6 | "loc": { 7 | "end": { 8 | "column": 11, 9 | "line": 1 10 | }, 11 | "start": { 12 | "column": 0, 13 | "line": 1 14 | } 15 | }, 16 | "range": [0, 11], 17 | "raw": "paragraph1.", 18 | "type": "Str", 19 | "value": "paragraph1." 20 | } 21 | ], 22 | "loc": { 23 | "end": { 24 | "column": 11, 25 | "line": 1 26 | }, 27 | "start": { 28 | "column": 0, 29 | "line": 1 30 | } 31 | }, 32 | "range": [0, 11], 33 | "raw": "paragraph1.", 34 | "type": "Paragraph" 35 | }, 36 | { 37 | "loc": { 38 | "end": { 39 | "column": 0, 40 | "line": 3 41 | }, 42 | "start": { 43 | "column": 11, 44 | "line": 1 45 | } 46 | }, 47 | "range": [11, 13], 48 | "raw": "\n\n", 49 | "type": "Break", 50 | "value": "\n\n" 51 | }, 52 | { 53 | "children": [ 54 | { 55 | "checked": null, 56 | "children": [ 57 | { 58 | "children": [ 59 | { 60 | "loc": { 61 | "end": { 62 | "column": 3, 63 | "line": 3 64 | }, 65 | "start": { 66 | "column": 2, 67 | "line": 3 68 | } 69 | }, 70 | "range": [15, 16], 71 | "raw": "a", 72 | "type": "Str", 73 | "value": "a" 74 | } 75 | ], 76 | "loc": { 77 | "end": { 78 | "column": 3, 79 | "line": 3 80 | }, 81 | "start": { 82 | "column": 2, 83 | "line": 3 84 | } 85 | }, 86 | "range": [15, 16], 87 | "raw": "a", 88 | "type": "Paragraph" 89 | } 90 | ], 91 | "loc": { 92 | "end": { 93 | "column": 3, 94 | "line": 3 95 | }, 96 | "start": { 97 | "column": 0, 98 | "line": 3 99 | } 100 | }, 101 | "range": [13, 16], 102 | "raw": "- a", 103 | "spread": false, 104 | "type": "ListItem" 105 | }, 106 | { 107 | "checked": null, 108 | "children": [ 109 | { 110 | "children": [ 111 | { 112 | "loc": { 113 | "end": { 114 | "column": 3, 115 | "line": 4 116 | }, 117 | "start": { 118 | "column": 2, 119 | "line": 4 120 | } 121 | }, 122 | "range": [19, 20], 123 | "raw": "b", 124 | "type": "Str", 125 | "value": "b" 126 | } 127 | ], 128 | "loc": { 129 | "end": { 130 | "column": 3, 131 | "line": 4 132 | }, 133 | "start": { 134 | "column": 2, 135 | "line": 4 136 | } 137 | }, 138 | "range": [19, 20], 139 | "raw": "b", 140 | "type": "Paragraph" 141 | } 142 | ], 143 | "loc": { 144 | "end": { 145 | "column": 3, 146 | "line": 4 147 | }, 148 | "start": { 149 | "column": 0, 150 | "line": 4 151 | } 152 | }, 153 | "range": [17, 20], 154 | "raw": "- b", 155 | "spread": false, 156 | "type": "ListItem" 157 | } 158 | ], 159 | "loc": { 160 | "end": { 161 | "column": 3, 162 | "line": 4 163 | }, 164 | "start": { 165 | "column": 0, 166 | "line": 3 167 | } 168 | }, 169 | "ordered": false, 170 | "range": [13, 20], 171 | "raw": "- a\n- b", 172 | "spread": false, 173 | "start": null, 174 | "type": "List" 175 | }, 176 | { 177 | "loc": { 178 | "end": { 179 | "column": 0, 180 | "line": 6 181 | }, 182 | "start": { 183 | "column": 3, 184 | "line": 4 185 | } 186 | }, 187 | "range": [20, 22], 188 | "raw": "\n\n", 189 | "type": "Break", 190 | "value": "\n\n" 191 | }, 192 | { 193 | "children": [ 194 | { 195 | "loc": { 196 | "end": { 197 | "column": 11, 198 | "line": 6 199 | }, 200 | "start": { 201 | "column": 0, 202 | "line": 6 203 | } 204 | }, 205 | "range": [22, 33], 206 | "raw": "paragraph2.", 207 | "type": "Str", 208 | "value": "paragraph2." 209 | } 210 | ], 211 | "loc": { 212 | "end": { 213 | "column": 11, 214 | "line": 6 215 | }, 216 | "start": { 217 | "column": 0, 218 | "line": 6 219 | } 220 | }, 221 | "range": [22, 33], 222 | "raw": "paragraph2.", 223 | "type": "Paragraph" 224 | }, 225 | { 226 | "loc": { 227 | "end": { 228 | "column": 0, 229 | "line": 8 230 | }, 231 | "start": { 232 | "column": 11, 233 | "line": 6 234 | } 235 | }, 236 | "range": [33, 35], 237 | "raw": "\n\n", 238 | "type": "Break", 239 | "value": "\n\n" 240 | }, 241 | { 242 | "children": [ 243 | { 244 | "checked": null, 245 | "children": [ 246 | { 247 | "children": [ 248 | { 249 | "loc": { 250 | "end": { 251 | "column": 3, 252 | "line": 8 253 | }, 254 | "start": { 255 | "column": 2, 256 | "line": 8 257 | } 258 | }, 259 | "range": [37, 38], 260 | "raw": "c", 261 | "type": "Str", 262 | "value": "c" 263 | } 264 | ], 265 | "loc": { 266 | "end": { 267 | "column": 3, 268 | "line": 8 269 | }, 270 | "start": { 271 | "column": 2, 272 | "line": 8 273 | } 274 | }, 275 | "range": [37, 38], 276 | "raw": "c", 277 | "type": "Paragraph" 278 | } 279 | ], 280 | "loc": { 281 | "end": { 282 | "column": 3, 283 | "line": 8 284 | }, 285 | "start": { 286 | "column": 0, 287 | "line": 8 288 | } 289 | }, 290 | "range": [35, 38], 291 | "raw": "- c", 292 | "spread": false, 293 | "type": "ListItem" 294 | }, 295 | { 296 | "checked": null, 297 | "children": [ 298 | { 299 | "children": [ 300 | { 301 | "loc": { 302 | "end": { 303 | "column": 3, 304 | "line": 9 305 | }, 306 | "start": { 307 | "column": 2, 308 | "line": 9 309 | } 310 | }, 311 | "range": [41, 42], 312 | "raw": "d", 313 | "type": "Str", 314 | "value": "d" 315 | } 316 | ], 317 | "loc": { 318 | "end": { 319 | "column": 3, 320 | "line": 9 321 | }, 322 | "start": { 323 | "column": 2, 324 | "line": 9 325 | } 326 | }, 327 | "range": [41, 42], 328 | "raw": "d", 329 | "type": "Paragraph" 330 | } 331 | ], 332 | "loc": { 333 | "end": { 334 | "column": 3, 335 | "line": 9 336 | }, 337 | "start": { 338 | "column": 0, 339 | "line": 9 340 | } 341 | }, 342 | "range": [39, 42], 343 | "raw": "- d", 344 | "spread": false, 345 | "type": "ListItem" 346 | } 347 | ], 348 | "loc": { 349 | "end": { 350 | "column": 3, 351 | "line": 9 352 | }, 353 | "start": { 354 | "column": 0, 355 | "line": 8 356 | } 357 | }, 358 | "ordered": false, 359 | "range": [35, 42], 360 | "raw": "- c\n- d", 361 | "spread": false, 362 | "start": null, 363 | "type": "List" 364 | }, 365 | { 366 | "loc": { 367 | "end": { 368 | "column": 0, 369 | "line": 11 370 | }, 371 | "start": { 372 | "column": 3, 373 | "line": 9 374 | } 375 | }, 376 | "range": [42, 44], 377 | "raw": "\n\n", 378 | "type": "Break", 379 | "value": "\n\n" 380 | }, 381 | { 382 | "children": [ 383 | { 384 | "loc": { 385 | "end": { 386 | "column": 11, 387 | "line": 11 388 | }, 389 | "start": { 390 | "column": 0, 391 | "line": 11 392 | } 393 | }, 394 | "range": [44, 55], 395 | "raw": "paragraph3.", 396 | "type": "Str", 397 | "value": "paragraph3." 398 | } 399 | ], 400 | "loc": { 401 | "end": { 402 | "column": 11, 403 | "line": 11 404 | }, 405 | "start": { 406 | "column": 0, 407 | "line": 11 408 | } 409 | }, 410 | "range": [44, 55], 411 | "raw": "paragraph3.", 412 | "type": "Paragraph" 413 | } 414 | ], 415 | "loc": { 416 | "end": { 417 | "column": 11, 418 | "line": 11 419 | }, 420 | "start": { 421 | "column": 0, 422 | "line": 1 423 | } 424 | }, 425 | "range": [0, 55], 426 | "raw": "paragraph1.\n\n- a\n- b\n\nparagraph2.\n\n- c\n- d\n\nparagraph3.", 427 | "type": "Document" 428 | } 429 | -------------------------------------------------------------------------------- /test/unit/fixtures/Break/output.json: -------------------------------------------------------------------------------- 1 | { 2 | "children": [ 3 | { 4 | "children": [ 5 | { 6 | "children": [ 7 | { 8 | "loc": { 9 | "end": { 10 | "column": 1, 11 | "line": 1 12 | }, 13 | "start": { 14 | "column": 0, 15 | "line": 1 16 | } 17 | }, 18 | "range": [0, 1], 19 | "raw": "*", 20 | "type": "Marked::Star", 21 | "value": "*" 22 | }, 23 | { 24 | "loc": { 25 | "end": { 26 | "column": 6, 27 | "line": 1 28 | }, 29 | "start": { 30 | "column": 1, 31 | "line": 1 32 | } 33 | }, 34 | "range": [1, 6], 35 | "raw": "Date:", 36 | "type": "Str", 37 | "value": "Date" 38 | }, 39 | { 40 | "loc": { 41 | "end": { 42 | "column": 7, 43 | "line": 1 44 | }, 45 | "start": { 46 | "column": 6, 47 | "line": 1 48 | } 49 | }, 50 | "range": [6, 7], 51 | "raw": "*", 52 | "type": "Marked::Star", 53 | "value": "*" 54 | } 55 | ], 56 | "loc": { 57 | "end": { 58 | "column": 7, 59 | "line": 1 60 | }, 61 | "start": { 62 | "column": 0, 63 | "line": 1 64 | } 65 | }, 66 | "range": [0, 7], 67 | "raw": "*Date:*", 68 | "type": "Strong" 69 | }, 70 | { 71 | "loc": { 72 | "end": { 73 | "column": 8, 74 | "line": 1 75 | }, 76 | "start": { 77 | "column": 7, 78 | "line": 1 79 | } 80 | }, 81 | "range": [7, 8], 82 | "raw": " ", 83 | "type": "Str", 84 | "value": " " 85 | }, 86 | { 87 | "loc": { 88 | "end": { 89 | "column": 18, 90 | "line": 1 91 | }, 92 | "start": { 93 | "column": 8, 94 | "line": 1 95 | } 96 | }, 97 | "range": [8, 18], 98 | "raw": "26.12.2022", 99 | "type": "Str", 100 | "value": "26.12.2022" 101 | }, 102 | { 103 | "loc": { 104 | "end": { 105 | "column": 19, 106 | "line": 1 107 | }, 108 | "start": { 109 | "column": 18, 110 | "line": 1 111 | } 112 | }, 113 | "range": [18, 19], 114 | "raw": " ", 115 | "type": "Str", 116 | "value": " " 117 | } 118 | ], 119 | "loc": { 120 | "end": { 121 | "column": 19, 122 | "line": 1 123 | }, 124 | "start": { 125 | "column": 0, 126 | "line": 1 127 | } 128 | }, 129 | "range": [0, 19], 130 | "raw": "*Date:* 26.12.2022 ", 131 | "type": "Paragraph" 132 | }, 133 | { 134 | "loc": { 135 | "end": { 136 | "column": 20, 137 | "line": 1 138 | }, 139 | "start": { 140 | "column": 19, 141 | "line": 1 142 | } 143 | }, 144 | "range": [19, 20], 145 | "raw": "\\", 146 | "type": "Break", 147 | "value": "\\" 148 | }, 149 | { 150 | "children": [ 151 | { 152 | "children": [ 153 | { 154 | "loc": { 155 | "end": { 156 | "column": 1, 157 | "line": 2 158 | }, 159 | "start": { 160 | "column": 0, 161 | "line": 2 162 | } 163 | }, 164 | "range": [21, 22], 165 | "raw": "*", 166 | "type": "Marked::Star", 167 | "value": "*" 168 | }, 169 | { 170 | "loc": { 171 | "end": { 172 | "column": 7, 173 | "line": 2 174 | }, 175 | "start": { 176 | "column": 1, 177 | "line": 2 178 | } 179 | }, 180 | "range": [22, 28], 181 | "raw": "Topic:", 182 | "type": "Str", 183 | "value": "Topic" 184 | }, 185 | { 186 | "loc": { 187 | "end": { 188 | "column": 8, 189 | "line": 2 190 | }, 191 | "start": { 192 | "column": 7, 193 | "line": 2 194 | } 195 | }, 196 | "range": [28, 29], 197 | "raw": "*", 198 | "type": "Marked::Star", 199 | "value": "*" 200 | } 201 | ], 202 | "loc": { 203 | "end": { 204 | "column": 8, 205 | "line": 2 206 | }, 207 | "start": { 208 | "column": 0, 209 | "line": 2 210 | } 211 | }, 212 | "range": [21, 29], 213 | "raw": "*Topic:*", 214 | "type": "Strong" 215 | }, 216 | { 217 | "loc": { 218 | "end": { 219 | "column": 9, 220 | "line": 2 221 | }, 222 | "start": { 223 | "column": 8, 224 | "line": 2 225 | } 226 | }, 227 | "range": [29, 30], 228 | "raw": " ", 229 | "type": "Str", 230 | "value": " " 231 | }, 232 | { 233 | "loc": { 234 | "end": { 235 | "column": 28, 236 | "line": 2 237 | }, 238 | "start": { 239 | "column": 9, 240 | "line": 2 241 | } 242 | }, 243 | "range": [30, 49], 244 | "raw": "Infrastructure Test", 245 | "type": "Str", 246 | "value": "Infrastructure Test" 247 | }, 248 | { 249 | "loc": { 250 | "end": { 251 | "column": 29, 252 | "line": 2 253 | }, 254 | "start": { 255 | "column": 28, 256 | "line": 2 257 | } 258 | }, 259 | "range": [49, 50], 260 | "raw": " ", 261 | "type": "Str", 262 | "value": " " 263 | } 264 | ], 265 | "loc": { 266 | "end": { 267 | "column": 29, 268 | "line": 2 269 | }, 270 | "start": { 271 | "column": 0, 272 | "line": 2 273 | } 274 | }, 275 | "range": [21, 50], 276 | "raw": "*Topic:* Infrastructure Test ", 277 | "type": "Paragraph" 278 | }, 279 | { 280 | "loc": { 281 | "end": { 282 | "column": 30, 283 | "line": 2 284 | }, 285 | "start": { 286 | "column": 29, 287 | "line": 2 288 | } 289 | }, 290 | "range": [50, 51], 291 | "raw": "\\", 292 | "type": "Break", 293 | "value": "\\" 294 | }, 295 | { 296 | "children": [ 297 | { 298 | "children": [ 299 | { 300 | "loc": { 301 | "end": { 302 | "column": 1, 303 | "line": 3 304 | }, 305 | "start": { 306 | "column": 0, 307 | "line": 3 308 | } 309 | }, 310 | "range": [52, 53], 311 | "raw": "*", 312 | "type": "Marked::Star", 313 | "value": "*" 314 | }, 315 | { 316 | "loc": { 317 | "end": { 318 | "column": 10, 319 | "line": 3 320 | }, 321 | "start": { 322 | "column": 1, 323 | "line": 3 324 | } 325 | }, 326 | "range": [53, 62], 327 | "raw": "Severity:", 328 | "type": "Str", 329 | "value": "Severity" 330 | }, 331 | { 332 | "loc": { 333 | "end": { 334 | "column": 11, 335 | "line": 3 336 | }, 337 | "start": { 338 | "column": 10, 339 | "line": 3 340 | } 341 | }, 342 | "range": [62, 63], 343 | "raw": "*", 344 | "type": "Marked::Star", 345 | "value": "*" 346 | } 347 | ], 348 | "loc": { 349 | "end": { 350 | "column": 11, 351 | "line": 3 352 | }, 353 | "start": { 354 | "column": 0, 355 | "line": 3 356 | } 357 | }, 358 | "range": [52, 63], 359 | "raw": "*Severity:*", 360 | "type": "Strong" 361 | }, 362 | { 363 | "loc": { 364 | "end": { 365 | "column": 12, 366 | "line": 3 367 | }, 368 | "start": { 369 | "column": 11, 370 | "line": 3 371 | } 372 | }, 373 | "range": [63, 64], 374 | "raw": " ", 375 | "type": "Str", 376 | "value": " " 377 | }, 378 | { 379 | "loc": { 380 | "end": { 381 | "column": 16, 382 | "line": 3 383 | }, 384 | "start": { 385 | "column": 12, 386 | "line": 3 387 | } 388 | }, 389 | "range": [64, 68], 390 | "raw": "High", 391 | "type": "Str", 392 | "value": "High" 393 | }, 394 | { 395 | "loc": { 396 | "end": { 397 | "column": 17, 398 | "line": 3 399 | }, 400 | "start": { 401 | "column": 16, 402 | "line": 3 403 | } 404 | }, 405 | "range": [68, 69], 406 | "raw": " ", 407 | "type": "Str", 408 | "value": " " 409 | } 410 | ], 411 | "loc": { 412 | "end": { 413 | "column": 17, 414 | "line": 3 415 | }, 416 | "start": { 417 | "column": 0, 418 | "line": 3 419 | } 420 | }, 421 | "range": [52, 69], 422 | "raw": "*Severity:* High ", 423 | "type": "Paragraph" 424 | }, 425 | { 426 | "loc": { 427 | "end": { 428 | "column": 18, 429 | "line": 3 430 | }, 431 | "start": { 432 | "column": 17, 433 | "line": 3 434 | } 435 | }, 436 | "range": [69, 70], 437 | "raw": "\\", 438 | "type": "Break", 439 | "value": "\\" 440 | } 441 | ], 442 | "loc": { 443 | "end": { 444 | "column": 18, 445 | "line": 3 446 | }, 447 | "start": { 448 | "column": 0, 449 | "line": 1 450 | } 451 | }, 452 | "range": [0, 70], 453 | "raw": "*Date:* 26.12.2022 \\\n*Topic:* Infrastructure Test \\\n*Severity:* High \\", 454 | "type": "Document" 455 | } 456 | -------------------------------------------------------------------------------- /test/unit/fixtures/Code/output.json: -------------------------------------------------------------------------------- 1 | { 2 | "children": [ 3 | { 4 | "children": [ 5 | { 6 | "loc": { 7 | "end": { 8 | "column": 13, 9 | "line": 1 10 | }, 11 | "start": { 12 | "column": 0, 13 | "line": 1 14 | } 15 | }, 16 | "range": [0, 13], 17 | "raw": "`inline code`", 18 | "type": "Code", 19 | "value": "inline code" 20 | } 21 | ], 22 | "loc": { 23 | "end": { 24 | "column": 13, 25 | "line": 1 26 | }, 27 | "start": { 28 | "column": 0, 29 | "line": 1 30 | } 31 | }, 32 | "range": [0, 13], 33 | "raw": "`inline code`", 34 | "type": "Paragraph" 35 | }, 36 | { 37 | "loc": { 38 | "end": { 39 | "column": 0, 40 | "line": 3 41 | }, 42 | "start": { 43 | "column": 13, 44 | "line": 1 45 | } 46 | }, 47 | "range": [13, 15], 48 | "raw": "\n\n", 49 | "type": "Break", 50 | "value": "\n\n" 51 | }, 52 | { 53 | "children": [ 54 | { 55 | "loc": { 56 | "end": { 57 | "column": 3, 58 | "line": 3 59 | }, 60 | "start": { 61 | "column": 0, 62 | "line": 3 63 | } 64 | }, 65 | "range": [15, 18], 66 | "raw": "aaa", 67 | "type": "Str", 68 | "value": "aaa" 69 | }, 70 | { 71 | "loc": { 72 | "end": { 73 | "column": 4, 74 | "line": 3 75 | }, 76 | "start": { 77 | "column": 3, 78 | "line": 3 79 | } 80 | }, 81 | "range": [18, 19], 82 | "raw": " ", 83 | "type": "Str", 84 | "value": " " 85 | }, 86 | { 87 | "loc": { 88 | "end": { 89 | "column": 17, 90 | "line": 3 91 | }, 92 | "start": { 93 | "column": 4, 94 | "line": 3 95 | } 96 | }, 97 | "range": [19, 32], 98 | "raw": "`inline code`", 99 | "type": "Code", 100 | "value": "inline code" 101 | }, 102 | { 103 | "loc": { 104 | "end": { 105 | "column": 18, 106 | "line": 3 107 | }, 108 | "start": { 109 | "column": 17, 110 | "line": 3 111 | } 112 | }, 113 | "range": [32, 33], 114 | "raw": " ", 115 | "type": "Str", 116 | "value": " " 117 | }, 118 | { 119 | "loc": { 120 | "end": { 121 | "column": 21, 122 | "line": 3 123 | }, 124 | "start": { 125 | "column": 18, 126 | "line": 3 127 | } 128 | }, 129 | "range": [33, 36], 130 | "raw": "bbb", 131 | "type": "Str", 132 | "value": "bbb" 133 | } 134 | ], 135 | "loc": { 136 | "end": { 137 | "column": 21, 138 | "line": 3 139 | }, 140 | "start": { 141 | "column": 0, 142 | "line": 3 143 | } 144 | }, 145 | "range": [15, 36], 146 | "raw": "aaa `inline code` bbb", 147 | "type": "Paragraph" 148 | }, 149 | { 150 | "loc": { 151 | "end": { 152 | "column": 0, 153 | "line": 5 154 | }, 155 | "start": { 156 | "column": 21, 157 | "line": 3 158 | } 159 | }, 160 | "range": [36, 38], 161 | "raw": "\n\n", 162 | "type": "Break", 163 | "value": "\n\n" 164 | }, 165 | { 166 | "children": [ 167 | { 168 | "loc": { 169 | "end": { 170 | "column": 7, 171 | "line": 5 172 | }, 173 | "start": { 174 | "column": 0, 175 | "line": 5 176 | } 177 | }, 178 | "range": [38, 45], 179 | "raw": "What is", 180 | "type": "Str", 181 | "value": "What is" 182 | }, 183 | { 184 | "loc": { 185 | "end": { 186 | "column": 8, 187 | "line": 5 188 | }, 189 | "start": { 190 | "column": 7, 191 | "line": 5 192 | } 193 | }, 194 | "range": [45, 46], 195 | "raw": " ", 196 | "type": "Str", 197 | "value": " " 198 | }, 199 | { 200 | "loc": { 201 | "end": { 202 | "column": 28, 203 | "line": 5 204 | }, 205 | "start": { 206 | "column": 8, 207 | "line": 5 208 | } 209 | }, 210 | "range": [46, 66], 211 | "raw": "```rust fn main()```", 212 | "type": "Code", 213 | "value": "fn main()" 214 | }, 215 | { 216 | "loc": { 217 | "end": { 218 | "column": 29, 219 | "line": 5 220 | }, 221 | "start": { 222 | "column": 28, 223 | "line": 5 224 | } 225 | }, 226 | "range": [66, 67], 227 | "raw": " ", 228 | "type": "Str", 229 | "value": " " 230 | }, 231 | { 232 | "loc": { 233 | "end": { 234 | "column": 36, 235 | "line": 5 236 | }, 237 | "start": { 238 | "column": 29, 239 | "line": 5 240 | } 241 | }, 242 | "range": [67, 74], 243 | "raw": "in Rust", 244 | "type": "Str", 245 | "value": "in Rust" 246 | }, 247 | { 248 | "loc": { 249 | "end": { 250 | "column": 0, 251 | "line": 6 252 | }, 253 | "start": { 254 | "column": 36, 255 | "line": 5 256 | } 257 | }, 258 | "range": [74, 75], 259 | "raw": "\n", 260 | "type": "Str", 261 | "value": "\n" 262 | }, 263 | { 264 | "loc": { 265 | "end": { 266 | "column": 8, 267 | "line": 6 268 | }, 269 | "start": { 270 | "column": 0, 271 | "line": 6 272 | } 273 | }, 274 | "range": [75, 83], 275 | "raw": "would be", 276 | "type": "Str", 277 | "value": "would be" 278 | }, 279 | { 280 | "loc": { 281 | "end": { 282 | "column": 9, 283 | "line": 6 284 | }, 285 | "start": { 286 | "column": 8, 287 | "line": 6 288 | } 289 | }, 290 | "range": [83, 84], 291 | "raw": " ", 292 | "type": "Str", 293 | "value": " " 294 | }, 295 | { 296 | "loc": { 297 | "end": { 298 | "column": 27, 299 | "line": 6 300 | }, 301 | "start": { 302 | "column": 9, 303 | "line": 6 304 | } 305 | }, 306 | "range": [84, 102], 307 | "raw": "```c int main()```", 308 | "type": "Code", 309 | "value": "int main()" 310 | }, 311 | { 312 | "loc": { 313 | "end": { 314 | "column": 28, 315 | "line": 6 316 | }, 317 | "start": { 318 | "column": 27, 319 | "line": 6 320 | } 321 | }, 322 | "range": [102, 103], 323 | "raw": " ", 324 | "type": "Str", 325 | "value": " " 326 | }, 327 | { 328 | "loc": { 329 | "end": { 330 | "column": 33, 331 | "line": 6 332 | }, 333 | "start": { 334 | "column": 28, 335 | "line": 6 336 | } 337 | }, 338 | "range": [103, 108], 339 | "raw": "in C.", 340 | "type": "Str", 341 | "value": "in C." 342 | } 343 | ], 344 | "loc": { 345 | "end": { 346 | "column": 33, 347 | "line": 6 348 | }, 349 | "start": { 350 | "column": 0, 351 | "line": 5 352 | } 353 | }, 354 | "range": [38, 108], 355 | "raw": "What is ```rust fn main()``` in Rust\nwould be ```c int main()``` in C.", 356 | "type": "Paragraph" 357 | }, 358 | { 359 | "loc": { 360 | "end": { 361 | "column": 0, 362 | "line": 8 363 | }, 364 | "start": { 365 | "column": 33, 366 | "line": 6 367 | } 368 | }, 369 | "range": [108, 110], 370 | "raw": "\n\n", 371 | "type": "Break", 372 | "value": "\n\n" 373 | }, 374 | { 375 | "children": [ 376 | { 377 | "loc": { 378 | "end": { 379 | "column": 8, 380 | "line": 8 381 | }, 382 | "start": { 383 | "column": 0, 384 | "line": 8 385 | } 386 | }, 387 | "range": [110, 118], 388 | "raw": "This has", 389 | "type": "Str", 390 | "value": "This has" 391 | }, 392 | { 393 | "loc": { 394 | "end": { 395 | "column": 9, 396 | "line": 8 397 | }, 398 | "start": { 399 | "column": 8, 400 | "line": 8 401 | } 402 | }, 403 | "range": [118, 119], 404 | "raw": " ", 405 | "type": "Str", 406 | "value": " " 407 | }, 408 | { 409 | "loc": { 410 | "end": { 411 | "column": 28, 412 | "line": 8 413 | }, 414 | "start": { 415 | "column": 9, 416 | "line": 8 417 | } 418 | }, 419 | "range": [119, 138], 420 | "raw": "``` `backticks` ```", 421 | "type": "Code", 422 | "value": "`backticks`" 423 | }, 424 | { 425 | "loc": { 426 | "end": { 427 | "column": 29, 428 | "line": 8 429 | }, 430 | "start": { 431 | "column": 28, 432 | "line": 8 433 | } 434 | }, 435 | "range": [138, 139], 436 | "raw": " ", 437 | "type": "Str", 438 | "value": " " 439 | }, 440 | { 441 | "loc": { 442 | "end": { 443 | "column": 34, 444 | "line": 8 445 | }, 446 | "start": { 447 | "column": 29, 448 | "line": 8 449 | } 450 | }, 451 | "range": [139, 144], 452 | "raw": "in it", 453 | "type": "Str", 454 | "value": "in it" 455 | }, 456 | { 457 | "loc": { 458 | "end": { 459 | "column": 0, 460 | "line": 9 461 | }, 462 | "start": { 463 | "column": 34, 464 | "line": 8 465 | } 466 | }, 467 | "range": [144, 145], 468 | "raw": "\n", 469 | "type": "Str", 470 | "value": "\n" 471 | }, 472 | { 473 | "loc": { 474 | "end": { 475 | "column": 33, 476 | "line": 9 477 | }, 478 | "start": { 479 | "column": 0, 480 | "line": 9 481 | } 482 | }, 483 | "range": [145, 178], 484 | "raw": "(but the spaces are trimmed). And", 485 | "type": "Str", 486 | "value": "(but the spaces are trimmed). And" 487 | }, 488 | { 489 | "loc": { 490 | "end": { 491 | "column": 0, 492 | "line": 10 493 | }, 494 | "start": { 495 | "column": 33, 496 | "line": 9 497 | } 498 | }, 499 | "range": [178, 179], 500 | "raw": "\n", 501 | "type": "Str", 502 | "value": "\n" 503 | }, 504 | { 505 | "loc": { 506 | "end": { 507 | "column": 11, 508 | "line": 10 509 | }, 510 | "start": { 511 | "column": 0, 512 | "line": 10 513 | } 514 | }, 515 | "range": [179, 190], 516 | "raw": "``` here```", 517 | "type": "Code", 518 | "value": "here" 519 | }, 520 | { 521 | "loc": { 522 | "end": { 523 | "column": 12, 524 | "line": 10 525 | }, 526 | "start": { 527 | "column": 11, 528 | "line": 10 529 | } 530 | }, 531 | "range": [190, 191], 532 | "raw": " ", 533 | "type": "Str", 534 | "value": " " 535 | }, 536 | { 537 | "loc": { 538 | "end": { 539 | "column": 32, 540 | "line": 10 541 | }, 542 | "start": { 543 | "column": 12, 544 | "line": 10 545 | } 546 | }, 547 | "range": [191, 211], 548 | "raw": "the leading space is", 549 | "type": "Str", 550 | "value": "the leading space is" 551 | }, 552 | { 553 | "loc": { 554 | "end": { 555 | "column": 0, 556 | "line": 11 557 | }, 558 | "start": { 559 | "column": 32, 560 | "line": 10 561 | } 562 | }, 563 | "range": [211, 212], 564 | "raw": "\n", 565 | "type": "Str", 566 | "value": "\n" 567 | }, 568 | { 569 | "loc": { 570 | "end": { 571 | "column": 13, 572 | "line": 11 573 | }, 574 | "start": { 575 | "column": 0, 576 | "line": 11 577 | } 578 | }, 579 | "range": [212, 225], 580 | "raw": "also trimmed.", 581 | "type": "Str", 582 | "value": "also trimmed." 583 | } 584 | ], 585 | "loc": { 586 | "end": { 587 | "column": 13, 588 | "line": 11 589 | }, 590 | "start": { 591 | "column": 0, 592 | "line": 8 593 | } 594 | }, 595 | "range": [110, 225], 596 | "raw": "This has ``` `backticks` ``` in it\n(but the spaces are trimmed). And\n``` here``` the leading space is\nalso trimmed.", 597 | "type": "Paragraph" 598 | } 599 | ], 600 | "loc": { 601 | "end": { 602 | "column": 13, 603 | "line": 11 604 | }, 605 | "start": { 606 | "column": 0, 607 | "line": 1 608 | } 609 | }, 610 | "range": [0, 225], 611 | "raw": "`inline code`\n\naaa `inline code` bbb\n\nWhat is ```rust fn main()``` in Rust\nwould be ```c int main()``` in C.\n\nThis has ``` `backticks` ``` in it\n(but the spaces are trimmed). And\n``` here``` the leading space is\nalso trimmed.", 612 | "type": "Document" 613 | } 614 | -------------------------------------------------------------------------------- /test/unit/fixtures/nested-list/output.json: -------------------------------------------------------------------------------- 1 | { 2 | "children": [ 3 | { 4 | "children": [ 5 | { 6 | "checked": null, 7 | "children": [ 8 | { 9 | "children": [ 10 | { 11 | "loc": { 12 | "end": { 13 | "column": 3, 14 | "line": 1 15 | }, 16 | "start": { 17 | "column": 2, 18 | "line": 1 19 | } 20 | }, 21 | "range": [2, 3], 22 | "raw": "a", 23 | "type": "Str", 24 | "value": "a" 25 | } 26 | ], 27 | "loc": { 28 | "end": { 29 | "column": 3, 30 | "line": 1 31 | }, 32 | "start": { 33 | "column": 2, 34 | "line": 1 35 | } 36 | }, 37 | "range": [2, 3], 38 | "raw": "a", 39 | "type": "Paragraph" 40 | } 41 | ], 42 | "loc": { 43 | "end": { 44 | "column": 3, 45 | "line": 1 46 | }, 47 | "start": { 48 | "column": 0, 49 | "line": 1 50 | } 51 | }, 52 | "range": [0, 3], 53 | "raw": "- a", 54 | "spread": false, 55 | "type": "ListItem" 56 | }, 57 | { 58 | "checked": null, 59 | "children": [ 60 | { 61 | "children": [ 62 | { 63 | "loc": { 64 | "end": { 65 | "column": 3, 66 | "line": 2 67 | }, 68 | "start": { 69 | "column": 2, 70 | "line": 2 71 | } 72 | }, 73 | "range": [6, 7], 74 | "raw": "b", 75 | "type": "Str", 76 | "value": "b" 77 | } 78 | ], 79 | "loc": { 80 | "end": { 81 | "column": 3, 82 | "line": 2 83 | }, 84 | "start": { 85 | "column": 2, 86 | "line": 2 87 | } 88 | }, 89 | "range": [6, 7], 90 | "raw": "b", 91 | "type": "Paragraph" 92 | }, 93 | { 94 | "children": [ 95 | { 96 | "checked": null, 97 | "children": [ 98 | { 99 | "children": [ 100 | { 101 | "loc": { 102 | "end": { 103 | "column": 6, 104 | "line": 3 105 | }, 106 | "start": { 107 | "column": 4, 108 | "line": 3 109 | } 110 | }, 111 | "range": [12, 14], 112 | "raw": "ba", 113 | "type": "Str", 114 | "value": "ba" 115 | } 116 | ], 117 | "loc": { 118 | "end": { 119 | "column": 6, 120 | "line": 3 121 | }, 122 | "start": { 123 | "column": 4, 124 | "line": 3 125 | } 126 | }, 127 | "range": [12, 14], 128 | "raw": "ba", 129 | "type": "Paragraph" 130 | }, 131 | { 132 | "children": [ 133 | { 134 | "checked": null, 135 | "children": [ 136 | { 137 | "children": [ 138 | { 139 | "loc": { 140 | "end": { 141 | "column": 8, 142 | "line": 4 143 | }, 144 | "start": { 145 | "column": 6, 146 | "line": 4 147 | } 148 | }, 149 | "range": [21, 23], 150 | "raw": "bb", 151 | "type": "Str", 152 | "value": "bb" 153 | } 154 | ], 155 | "loc": { 156 | "end": { 157 | "column": 8, 158 | "line": 4 159 | }, 160 | "start": { 161 | "column": 6, 162 | "line": 4 163 | } 164 | }, 165 | "range": [21, 23], 166 | "raw": "bb", 167 | "type": "Paragraph" 168 | }, 169 | { 170 | "children": [ 171 | { 172 | "checked": null, 173 | "children": [ 174 | { 175 | "children": [ 176 | { 177 | "loc": { 178 | "end": { 179 | "column": 11, 180 | "line": 5 181 | }, 182 | "start": { 183 | "column": 8, 184 | "line": 5 185 | } 186 | }, 187 | "range": [32, 35], 188 | "raw": "bba", 189 | "type": "Str", 190 | "value": "bba" 191 | } 192 | ], 193 | "loc": { 194 | "end": { 195 | "column": 11, 196 | "line": 5 197 | }, 198 | "start": { 199 | "column": 8, 200 | "line": 5 201 | } 202 | }, 203 | "range": [32, 35], 204 | "raw": "bba", 205 | "type": "Paragraph" 206 | } 207 | ], 208 | "loc": { 209 | "end": { 210 | "column": 11, 211 | "line": 5 212 | }, 213 | "start": { 214 | "column": 6, 215 | "line": 5 216 | } 217 | }, 218 | "range": [30, 35], 219 | "raw": "- bba", 220 | "spread": false, 221 | "type": "ListItem" 222 | }, 223 | { 224 | "checked": null, 225 | "children": [ 226 | { 227 | "children": [ 228 | { 229 | "loc": { 230 | "end": { 231 | "column": 11, 232 | "line": 6 233 | }, 234 | "start": { 235 | "column": 8, 236 | "line": 6 237 | } 238 | }, 239 | "range": [44, 47], 240 | "raw": "bbb", 241 | "type": "Str", 242 | "value": "bbb" 243 | } 244 | ], 245 | "loc": { 246 | "end": { 247 | "column": 11, 248 | "line": 6 249 | }, 250 | "start": { 251 | "column": 8, 252 | "line": 6 253 | } 254 | }, 255 | "range": [44, 47], 256 | "raw": "bbb", 257 | "type": "Paragraph" 258 | } 259 | ], 260 | "loc": { 261 | "end": { 262 | "column": 11, 263 | "line": 6 264 | }, 265 | "start": { 266 | "column": 6, 267 | "line": 6 268 | } 269 | }, 270 | "range": [42, 47], 271 | "raw": "- bbb", 272 | "spread": false, 273 | "type": "ListItem" 274 | }, 275 | { 276 | "checked": null, 277 | "children": [ 278 | { 279 | "children": [ 280 | { 281 | "loc": { 282 | "end": { 283 | "column": 11, 284 | "line": 7 285 | }, 286 | "start": { 287 | "column": 8, 288 | "line": 7 289 | } 290 | }, 291 | "range": [56, 59], 292 | "raw": "bbc", 293 | "type": "Str", 294 | "value": "bbc" 295 | } 296 | ], 297 | "loc": { 298 | "end": { 299 | "column": 11, 300 | "line": 7 301 | }, 302 | "start": { 303 | "column": 8, 304 | "line": 7 305 | } 306 | }, 307 | "range": [56, 59], 308 | "raw": "bbc", 309 | "type": "Paragraph" 310 | } 311 | ], 312 | "loc": { 313 | "end": { 314 | "column": 11, 315 | "line": 7 316 | }, 317 | "start": { 318 | "column": 6, 319 | "line": 7 320 | } 321 | }, 322 | "range": [54, 59], 323 | "raw": "- bbc", 324 | "spread": false, 325 | "type": "ListItem" 326 | } 327 | ], 328 | "loc": { 329 | "end": { 330 | "column": 11, 331 | "line": 7 332 | }, 333 | "start": { 334 | "column": 6, 335 | "line": 5 336 | } 337 | }, 338 | "ordered": false, 339 | "range": [30, 59], 340 | "raw": "- bba\n- bbb\n- bbc", 341 | "spread": false, 342 | "start": null, 343 | "type": "List" 344 | } 345 | ], 346 | "loc": { 347 | "end": { 348 | "column": 11, 349 | "line": 7 350 | }, 351 | "start": { 352 | "column": 4, 353 | "line": 4 354 | } 355 | }, 356 | "range": [19, 59], 357 | "raw": "- bb\n - bba\n - bbb\n - bbc", 358 | "spread": false, 359 | "type": "ListItem" 360 | }, 361 | { 362 | "checked": null, 363 | "children": [ 364 | { 365 | "children": [ 366 | { 367 | "loc": { 368 | "end": { 369 | "column": 8, 370 | "line": 8 371 | }, 372 | "start": { 373 | "column": 6, 374 | "line": 8 375 | } 376 | }, 377 | "range": [66, 68], 378 | "raw": "bc", 379 | "type": "Str", 380 | "value": "bc" 381 | } 382 | ], 383 | "loc": { 384 | "end": { 385 | "column": 8, 386 | "line": 8 387 | }, 388 | "start": { 389 | "column": 6, 390 | "line": 8 391 | } 392 | }, 393 | "range": [66, 68], 394 | "raw": "bc", 395 | "type": "Paragraph" 396 | } 397 | ], 398 | "loc": { 399 | "end": { 400 | "column": 8, 401 | "line": 8 402 | }, 403 | "start": { 404 | "column": 4, 405 | "line": 8 406 | } 407 | }, 408 | "range": [64, 68], 409 | "raw": "- bc", 410 | "spread": false, 411 | "type": "ListItem" 412 | } 413 | ], 414 | "loc": { 415 | "end": { 416 | "column": 8, 417 | "line": 8 418 | }, 419 | "start": { 420 | "column": 4, 421 | "line": 4 422 | } 423 | }, 424 | "ordered": false, 425 | "range": [19, 68], 426 | "raw": "- bb\n - bba\n - bbb\n - bbc\n- bc", 427 | "spread": false, 428 | "start": null, 429 | "type": "List" 430 | } 431 | ], 432 | "loc": { 433 | "end": { 434 | "column": 8, 435 | "line": 8 436 | }, 437 | "start": { 438 | "column": 2, 439 | "line": 3 440 | } 441 | }, 442 | "range": [10, 68], 443 | "raw": "- ba\n - bb\n - bba\n - bbb\n - bbc\n - bc", 444 | "spread": false, 445 | "type": "ListItem" 446 | } 447 | ], 448 | "loc": { 449 | "end": { 450 | "column": 8, 451 | "line": 8 452 | }, 453 | "start": { 454 | "column": 2, 455 | "line": 3 456 | } 457 | }, 458 | "ordered": false, 459 | "range": [10, 68], 460 | "raw": "- ba\n - bb\n - bba\n - bbb\n - bbc\n - bc", 461 | "spread": false, 462 | "start": null, 463 | "type": "List" 464 | } 465 | ], 466 | "loc": { 467 | "end": { 468 | "column": 8, 469 | "line": 8 470 | }, 471 | "start": { 472 | "column": 0, 473 | "line": 2 474 | } 475 | }, 476 | "range": [4, 68], 477 | "raw": "- b\n - ba\n - bb\n - bba\n - bbb\n - bbc\n - bc", 478 | "spread": false, 479 | "type": "ListItem" 480 | }, 481 | { 482 | "checked": null, 483 | "children": [ 484 | { 485 | "children": [ 486 | { 487 | "loc": { 488 | "end": { 489 | "column": 3, 490 | "line": 9 491 | }, 492 | "start": { 493 | "column": 2, 494 | "line": 9 495 | } 496 | }, 497 | "range": [71, 72], 498 | "raw": "c", 499 | "type": "Str", 500 | "value": "c" 501 | } 502 | ], 503 | "loc": { 504 | "end": { 505 | "column": 3, 506 | "line": 9 507 | }, 508 | "start": { 509 | "column": 2, 510 | "line": 9 511 | } 512 | }, 513 | "range": [71, 72], 514 | "raw": "c", 515 | "type": "Paragraph" 516 | } 517 | ], 518 | "loc": { 519 | "end": { 520 | "column": 3, 521 | "line": 9 522 | }, 523 | "start": { 524 | "column": 0, 525 | "line": 9 526 | } 527 | }, 528 | "range": [69, 72], 529 | "raw": "- c", 530 | "spread": false, 531 | "type": "ListItem" 532 | } 533 | ], 534 | "loc": { 535 | "end": { 536 | "column": 3, 537 | "line": 9 538 | }, 539 | "start": { 540 | "column": 0, 541 | "line": 1 542 | } 543 | }, 544 | "ordered": false, 545 | "range": [0, 72], 546 | "raw": "- a\n- b\n - ba\n - bb\n - bba\n - bbb\n - bbc\n - bc\n- c", 547 | "spread": false, 548 | "start": null, 549 | "type": "List" 550 | } 551 | ], 552 | "loc": { 553 | "end": { 554 | "column": 3, 555 | "line": 9 556 | }, 557 | "start": { 558 | "column": 0, 559 | "line": 1 560 | } 561 | }, 562 | "range": [0, 72], 563 | "raw": "- a\n- b\n - ba\n - bb\n - bba\n - bbb\n - bbc\n - bc\n- c", 564 | "type": "Document" 565 | } 566 | -------------------------------------------------------------------------------- /test/unit/rawTypstAstString.txt: -------------------------------------------------------------------------------- 1 | --- 2 | path: main.typ 3 | ast: 4 | s: Marked::Markup 5 | c: 6 | - s: Kw::Hash <1:0~1:1> 7 | - s: Marked::SetRule <1:1~1:36> 8 | c: 9 | - s: Kw::Set <1:1~1:4> 10 | - s: Fn::(Ident: "page") <1:5~1:9> 11 | - s: Marked::Args <1:9~1:36> 12 | c: 13 | - s: Punc::LeftParen <1:9~1:10> 14 | - s: Marked::Named <1:10~1:21> 15 | c: 16 | - s: Marked::(Ident: "width") <1:10~1:15> 17 | - s: Punc::Colon <1:15~1:16> 18 | - s: Num(Numeric, 10Cm) <1:17~1:21> 19 | - s: Punc::Comma <1:21~1:22> 20 | - s: Marked::Named <1:23~1:35> 21 | c: 22 | - s: Marked::(Ident: "height") <1:23~1:29> 23 | - s: Punc::Colon <1:29~1:30> 24 | - s: Kw::Auto <1:31~1:35> 25 | - s: Punc::RightParen <1:35~1:36> 26 | - s: Kw::Hash <2:0~2:1> 27 | - s: Marked::SetRule <2:1~2:29> 28 | c: 29 | - s: Kw::Set <2:1~2:4> 30 | - s: Fn::(Ident: "heading") <2:5~2:12> 31 | - s: Marked::Args <2:12~2:29> 32 | c: 33 | - s: Punc::LeftParen <2:12~2:13> 34 | - s: Marked::Named <2:13~2:28> 35 | c: 36 | - s: Marked::(Ident: "numbering") <2:13~2:22> 37 | - s: Punc::Colon <2:22~2:23> 38 | - s: Str("1.") <2:24~2:28> 39 | - s: Punc::RightParen <2:28~2:29> 40 | - s: Marked::Parbreak <2:29~4:0> 41 | - s: Marked::Heading <4:0~4:20> 42 | c: 43 | - s: Marked::HeadingMarker <4:0~4:1> 44 | - s: Marked::Markup <4:2~4:20> 45 | c: 46 | - s: Marked::Text <4:2~4:20> 47 | - s: Marked::Text <5:0~5:45> 48 | - s: Marked::Text <6:0~6:19> 49 | - s: Marked::Equation <6:20~6:45> 50 | c: 51 | - s: Marked::Dollar <6:20~6:21> 52 | - s: Marked::Math <6:21~6:44> 53 | c: 54 | - s: Marked::MathAttach <6:21~6:24> 55 | c: 56 | - s: Marked::MathText <6:21~6:22> 57 | - s: Marked::Underscore <6:22~6:23> 58 | - s: Marked::MathText <6:23~6:24> 59 | - s: Marked::MathText <6:25~6:26> 60 | - s: Marked::MathAttach <6:27~6:34> 61 | c: 62 | - s: Marked::MathText <6:27~6:28> 63 | - s: Marked::Underscore <6:28~6:29> 64 | - s: Marked::Math <6:29~6:34> 65 | c: 66 | - s: Punc::LeftParen <6:29~6:30> 67 | - s: Marked::Math <6:30~6:33> 68 | c: 69 | - s: Marked::MathText <6:30~6:31> 70 | - s: Escape::MathShorthand <6:31~6:32> 71 | - s: Marked::MathText <6:32~6:33> 72 | - s: Punc::RightParen <6:33~6:34> 73 | - s: Marked::MathText <6:35~6:36> 74 | - s: Marked::MathAttach <6:37~6:44> 75 | c: 76 | - s: Marked::MathText <6:37~6:38> 77 | - s: Marked::Underscore <6:38~6:39> 78 | - s: Marked::Math <6:39~6:44> 79 | c: 80 | - s: Punc::LeftParen <6:39~6:40> 81 | - s: Marked::Math <6:40~6:43> 82 | c: 83 | - s: Marked::MathText <6:40~6:41> 84 | - s: Escape::MathShorthand <6:41~6:42> 85 | - s: Marked::MathText <6:42~6:43> 86 | - s: Punc::RightParen <6:43~6:44> 87 | - s: Marked::Dollar <6:44~6:45> 88 | - s: Marked::Text <6:45~6:46> 89 | - s: Marked::Text <7:0~7:27> 90 | - s: Marked::Emph <7:28~7:42> 91 | c: 92 | - s: Marked::Underscore <7:28~7:29> 93 | - s: Marked::Markup <7:29~7:41> 94 | c: 95 | - s: Marked::Text <7:29~7:40> 96 | - s: Marked::Text <7:40~7:41> 97 | - s: Marked::Underscore <7:41~7:42> 98 | - s: Marked::Parbreak <7:42~9:0> 99 | - s: Marked::Equation <9:0~10:31> 100 | c: 101 | - s: Marked::Dollar <9:0~9:1> 102 | - s: Marked::Math <9:2~10:29> 103 | c: 104 | - s: Marked::MathAttach <9:2~9:5> 105 | c: 106 | - s: Marked::MathText <9:2~9:3> 107 | - s: Marked::Underscore <9:3~9:4> 108 | - s: Marked::MathText <9:4~9:5> 109 | - s: Marked::MathText <9:6~9:7> 110 | - s: Marked::FuncCall <9:8~9:36> 111 | c: 112 | - s: Fn::(MathIdent: "round") <9:8~9:13> 113 | - s: Marked::Args <9:13~9:36> 114 | c: 115 | - s: Punc::LeftParen <9:13~9:14> 116 | - s: Marked::Math <9:14~9:35> 117 | c: 118 | - s: Marked::MathFrac <9:14~9:25> 119 | c: 120 | - s: Marked::MathText <9:14~9:15> 121 | - s: Marked::Slash <9:16~9:17> 122 | - s: Marked::FuncCall <9:18~9:25> 123 | c: 124 | - s: Fn::(MathIdent: "sqrt") <9:18~9:22> 125 | - s: Marked::Args <9:22~9:25> 126 | c: 127 | - s: Punc::LeftParen <9:22~9:23> 128 | - s: Marked::MathText <9:23~9:24> 129 | - s: Punc::RightParen <9:24~9:25> 130 | - s: Marked::MathAttach <9:26~9:35> 131 | c: 132 | - s: Marked::FieldAccess <9:26~9:33> 133 | c: 134 | - s: Var::(MathIdent: "phi") <9:26~9:29> 135 | - s: Punc::Dot <9:29~9:30> 136 | - s: Var::(Ident: "alt") <9:30~9:33> 137 | - s: Marked::Hat <9:33~9:34> 138 | - s: Marked::MathText <9:34~9:35> 139 | - s: Punc::RightParen <9:35~9:36> 140 | - s: Marked::MathText <9:36~9:37> 141 | - s: Var::(MathIdent: "quad") <9:38~9:42> 142 | - s: Marked::FieldAccess <10:2~10:9> 143 | c: 144 | - s: Var::(MathIdent: "phi") <10:2~10:5> 145 | - s: Punc::Dot <10:5~10:6> 146 | - s: Var::(Ident: "alt") <10:6~10:9> 147 | - s: Marked::MathText <10:10~10:11> 148 | - s: Marked::MathFrac <10:12~10:29> 149 | c: 150 | - s: Marked::Math <10:12~10:25> 151 | c: 152 | - s: Punc::LeftParen <10:12~10:13> 153 | - s: Marked::Math <10:13~10:24> 154 | c: 155 | - s: Marked::MathText <10:13~10:14> 156 | - s: Marked::MathText <10:15~10:16> 157 | - s: Marked::FuncCall <10:17~10:24> 158 | c: 159 | - s: Fn::(MathIdent: "sqrt") <10:17~10:21> 160 | - s: Marked::Args <10:21~10:24> 161 | c: 162 | - s: Punc::LeftParen <10:21~10:22> 163 | - s: Marked::MathText <10:22~10:23> 164 | - s: Punc::RightParen <10:23~10:24> 165 | - s: Punc::RightParen <10:24~10:25> 166 | - s: Marked::Slash <10:26~10:27> 167 | - s: Marked::MathText <10:28~10:29> 168 | - s: Marked::Dollar <10:30~10:31> 169 | - s: Marked::Parbreak <10:31~12:0> 170 | - s: Kw::Hash <12:0~12:1> 171 | - s: Marked::LetBinding <12:1~12:14> 172 | c: 173 | - s: Kw::Let <12:1~12:4> 174 | - s: Marked::(Ident: "count") <12:5~12:10> 175 | - s: Op::Eq <12:11~12:12> 176 | - s: Num(Int, 8) <12:13~12:14> 177 | - s: Kw::Hash <13:0~13:1> 178 | - s: Marked::LetBinding <13:1~13:31> 179 | c: 180 | - s: Kw::Let <13:1~13:4> 181 | - s: Marked::(Ident: "nums") <13:5~13:9> 182 | - s: Op::Eq <13:10~13:11> 183 | - s: Marked::FuncCall <13:12~13:31> 184 | c: 185 | - s: Fn::(Ident: "range") <13:12~13:17> 186 | - s: Marked::Args <13:17~13:31> 187 | c: 188 | - s: Punc::LeftParen <13:17~13:18> 189 | - s: Num(Int, 1) <13:18~13:19> 190 | - s: Punc::Comma <13:19~13:20> 191 | - s: Marked::Binary <13:21~13:30> 192 | c: 193 | - s: Marked::(Ident: "count") <13:21~13:26> 194 | - s: Op::Plus <13:27~13:28> 195 | - s: Num(Int, 1) <13:29~13:30> 196 | - s: Punc::RightParen <13:30~13:31> 197 | - s: Kw::Hash <14:0~14:1> 198 | - s: Marked::LetBinding <14:1~17:1> 199 | c: 200 | - s: Kw::Let <14:1~14:4> 201 | - s: Marked::Closure <14:5~17:1> 202 | c: 203 | - s: Fn::(Ident: "fib") <14:5~14:8> 204 | - s: Marked::Params <14:8~14:11> 205 | c: 206 | - s: Punc::LeftParen <14:8~14:9> 207 | - s: Marked::(Ident: "n") <14:9~14:10> 208 | - s: Punc::RightParen <14:10~14:11> 209 | - s: Op::Eq <14:12~14:13> 210 | - s: Marked::Parenthesized <14:14~17:1> 211 | c: 212 | - s: Punc::LeftParen <14:14~14:15> 213 | - s: Marked::Conditional <15:2~16:34> 214 | c: 215 | - s: Kw::If <15:2~15:4> 216 | - s: Marked::Binary <15:5~15:11> 217 | c: 218 | - s: Marked::(Ident: "n") <15:5~15:6> 219 | - s: Op::LtEq <15:7~15:9> 220 | - s: Num(Int, 2) <15:10~15:11> 221 | - s: Marked::CodeBlock <15:12~15:17> 222 | c: 223 | - s: Punc::LeftBrace <15:12~15:13> 224 | - s: Marked::Code <15:14~15:15> 225 | c: 226 | - s: Num(Int, 1) <15:14~15:15> 227 | - s: Punc::RightBrace <15:16~15:17> 228 | - s: Kw::Else <16:2~16:6> 229 | - s: Marked::CodeBlock <16:7~16:34> 230 | c: 231 | - s: Punc::LeftBrace <16:7~16:8> 232 | - s: Marked::Code <16:9~16:32> 233 | c: 234 | - s: Marked::Binary <16:9~16:32> 235 | c: 236 | - s: Marked::FuncCall <16:9~16:19> 237 | c: 238 | - s: Fn::(Ident: "fib") <16:9~16:12> 239 | - s: Marked::Args <16:12~16:19> 240 | c: 241 | - s: Punc::LeftParen <16:12~16:13> 242 | - s: Marked::Binary <16:13~16:18> 243 | c: 244 | - s: Marked::(Ident: "n") <16:13~16:14> 245 | - s: Op::Minus <16:15~16:16> 246 | - s: Num(Int, 1) <16:17~16:18> 247 | - s: Punc::RightParen <16:18~16:19> 248 | - s: Op::Plus <16:20~16:21> 249 | - s: Marked::FuncCall <16:22~16:32> 250 | c: 251 | - s: Fn::(Ident: "fib") <16:22~16:25> 252 | - s: Marked::Args <16:25~16:32> 253 | c: 254 | - s: Punc::LeftParen <16:25~16:26> 255 | - s: Marked::Binary <16:26~16:31> 256 | c: 257 | - s: Marked::(Ident: "n") <16:26~16:27> 258 | - s: Op::Minus <16:28~16:29> 259 | - s: Num(Int, 2) <16:30~16:31> 260 | - s: Punc::RightParen <16:31~16:32> 261 | - s: Punc::RightBrace <16:33~16:34> 262 | - s: Punc::RightParen <17:0~17:1> 263 | - s: Marked::Parbreak <17:1~19:0> 264 | - s: Marked::Text <19:0~19:9> 265 | - s: Var::(Hash: "#") <19:10~19:11> 266 | - s: Var::(Ident: "count") <19:11~19:16> 267 | - s: Marked::Text <19:17~19:44> 268 | - s: Marked::Text <19:44~19:45> 269 | - s: Marked::Parbreak <19:45~21:0> 270 | - s: Fn::(Hash: "#") <21:0~21:1> 271 | - s: Marked::FuncCall <21:1~25:2> 272 | c: 273 | - s: Fn::(Ident: "align") <21:1~21:6> 274 | - s: Marked::Args <21:6~25:2> 275 | c: 276 | - s: Punc::LeftParen <21:6~21:7> 277 | - s: Marked::(Ident: "center") <21:7~21:13> 278 | - s: Punc::Comma <21:13~21:14> 279 | - s: Marked::FuncCall <21:15~25:1> 280 | c: 281 | - s: Fn::(Ident: "table") <21:15~21:20> 282 | - s: Marked::Args <21:20~25:1> 283 | c: 284 | - s: Punc::LeftParen <21:20~21:21> 285 | - s: Marked::Named <22:2~22:16> 286 | c: 287 | - s: Marked::(Ident: "columns") <22:2~22:9> 288 | - s: Punc::Colon <22:9~22:10> 289 | - s: Marked::(Ident: "count") <22:11~22:16> 290 | - s: Punc::Comma <22:16~22:17> 291 | - s: Marked::Spread <23:2~23:25> 292 | c: 293 | - s: Op::Dots <23:2~23:4> 294 | - s: Marked::FuncCall <23:4~23:25> 295 | c: 296 | - s: Marked::FieldAccess <23:4~23:12> 297 | c: 298 | - s: Marked::(Ident: "nums") <23:4~23:8> 299 | - s: Punc::Dot <23:8~23:9> 300 | - s: Fn::(Ident: "map") <23:9~23:12> 301 | - s: Marked::Args <23:12~23:25> 302 | c: 303 | - s: Punc::LeftParen <23:12~23:13> 304 | - s: Marked::Closure <23:13~23:24> 305 | c: 306 | - s: Marked::Params <23:13~23:14> 307 | c: 308 | - s: Marked::(Ident: "n") <23:13~23:14> 309 | - s: Op::Arrow <23:15~23:17> 310 | - s: Marked::Equation <23:18~23:24> 311 | c: 312 | - s: Marked::Dollar <23:18~23:19> 313 | - s: Marked::Math <23:19~23:23> 314 | c: 315 | - s: Marked::MathAttach <23:19~23:23> 316 | c: 317 | - s: Marked::MathText <23:19~23:20> 318 | - s: Marked::Underscore <23:20~23:21> 319 | - s: Var::(Hash: "#") <23:21~23:22> 320 | - s: Var::(Ident: "n") <23:22~23:23> 321 | - s: Marked::Dollar <23:23~23:24> 322 | - s: Punc::RightParen <23:24~23:25> 323 | - s: Punc::Comma <23:25~23:26> 324 | - s: Marked::Spread <24:2~24:30> 325 | c: 326 | - s: Op::Dots <24:2~24:4> 327 | - s: Marked::FuncCall <24:4~24:30> 328 | c: 329 | - s: Marked::FieldAccess <24:4~24:12> 330 | c: 331 | - s: Marked::(Ident: "nums") <24:4~24:8> 332 | - s: Punc::Dot <24:8~24:9> 333 | - s: Fn::(Ident: "map") <24:9~24:12> 334 | - s: Marked::Args <24:12~24:30> 335 | c: 336 | - s: Punc::LeftParen <24:12~24:13> 337 | - s: Marked::Closure <24:13~24:29> 338 | c: 339 | - s: Marked::Params <24:13~24:14> 340 | c: 341 | - s: Marked::(Ident: "n") <24:13~24:14> 342 | - s: Op::Arrow <24:15~24:17> 343 | - s: Marked::FuncCall <24:18~24:29> 344 | c: 345 | - s: Fn::(Ident: "str") <24:18~24:21> 346 | - s: Marked::Args <24:21~24:29> 347 | c: 348 | - s: Punc::LeftParen <24:21~24:22> 349 | - s: Marked::FuncCall <24:22~24:28> 350 | c: 351 | - s: Fn::(Ident: "fib") <24:22~24:25> 352 | - s: Marked::Args <24:25~24:28> 353 | c: 354 | - s: Punc::LeftParen <24:25~24:26> 355 | - s: Marked::(Ident: "n") <24:26~24:27> 356 | - s: Punc::RightParen <24:27~24:28> 357 | - s: Punc::RightParen <24:28~24:29> 358 | - s: Punc::RightParen <24:29~24:30> 359 | - s: Punc::Comma <24:30~24:31> 360 | - s: Punc::RightParen <25:0~25:1> 361 | - s: Punc::RightParen <25:1~25:2> 362 | - s: Marked::Parbreak <25:2~27:0> 363 | - s: Ct::LineComment <27:0~27:53> 364 | -------------------------------------------------------------------------------- /src/typstToTextlintAst.ts: -------------------------------------------------------------------------------- 1 | import { createTypstCompiler } from "@myriaddreamin/typst.ts/dist/esm/compiler.mjs"; 2 | import { 3 | ASTNodeTypes, 4 | type TxtCodeBlockNode, 5 | type TxtDocumentNode, 6 | type TxtLinkNode, 7 | type TxtListItemNode, 8 | type TxtListNode, 9 | type TxtNode, 10 | type TxtNodePosition, 11 | type TxtParentNode, 12 | type TxtStrNode, 13 | type TxtTextNode, 14 | } from "@textlint/ast-node-types"; 15 | import type { Content } from "@textlint/ast-node-types/lib/src/NodeType"; 16 | import { parse } from "yaml"; 17 | 18 | /** 19 | * Get the raw Typst AST string from a source string. 20 | * @param source The source string. 21 | * @returns The raw Typst AST string. 22 | */ 23 | export const getRawTypstAstString = async (source: string) => { 24 | const compiler = createTypstCompiler(); 25 | await compiler.init(); 26 | compiler.addSource("/main.typ", source); 27 | 28 | const rawTypstAstString = await compiler.getAst("/main.typ"); 29 | 30 | return rawTypstAstString; 31 | }; 32 | 33 | /** 34 | * Convert a raw Typst AST string to a Typst AST object. 35 | * @param rawTypstAstString The raw Typst AST string. 36 | * @returns The Typst AST object. 37 | */ 38 | export const convertRawTypstAstStringToObject = (rawTypstAstString: string) => { 39 | const removeFirstLine = (input: string): string => { 40 | const lines = input.split("\n"); 41 | lines.shift(); 42 | return lines.join("\n"); 43 | }; 44 | 45 | const escapeYamlValues = (yamlString: string): string => { 46 | return yamlString 47 | .split("\n") 48 | .reduce((acc, line) => { 49 | // NOTE: If the line does not match the pattern, it is considered a continuation of the previous value. 50 | if (!/^\s*(path:|ast:|- s: |s: |c:)/.test(line)) { 51 | if (acc.length > 0) { 52 | acc[acc.length - 1] = `${acc[acc.length - 1].slice( 53 | 0, 54 | -1, 55 | )}\\n${line}"`; 56 | } 57 | return acc; 58 | } 59 | const [key, ...rest] = line.split(":"); 60 | if (rest[0] === "") { 61 | acc.push(line); 62 | return acc; 63 | } 64 | const value = rest.join(":").trim(); 65 | acc.push(`${key}: "${value}"`); 66 | return acc; 67 | }, []) 68 | .join("\n"); 69 | }; 70 | 71 | const escapedRawTypstAstYamlString = escapeYamlValues( 72 | removeFirstLine(rawTypstAstString), 73 | ); 74 | 75 | const parsed = parse(escapedRawTypstAstYamlString); 76 | 77 | // Handle empty file case 78 | if (parsed.ast.c === null) { 79 | parsed.ast.c = []; 80 | } 81 | 82 | return parsed.ast as TypstAstNode; 83 | }; 84 | 85 | interface TypstAstNode { 86 | s: string; 87 | c?: TypstAstNode[]; 88 | } 89 | 90 | // Flexible AST node type for conversion process 91 | // This allows gradual transformation from Typst AST to textlint AST 92 | interface AstNode { 93 | // Typst AST node properties (present during conversion, removed afterward) 94 | s?: string; 95 | c?: AstNode[]; 96 | 97 | // textlint AST node properties (from @textlint/ast-node-types) 98 | // During conversion, type can be either Typst type (e.g., "Marked::Raw") or textlint type 99 | type: TxtNode["type"] | string; 100 | raw: TxtNode["raw"]; 101 | range: TxtNode["range"]; 102 | loc: TxtNode["loc"]; 103 | 104 | value?: TxtTextNode["value"]; // For text nodes, code nodes, etc. 105 | children?: Content[]; // For parent nodes (using Content[] during conversion) 106 | 107 | // List-specific properties (from TxtListNode) 108 | ordered?: TxtListNode["ordered"]; 109 | start?: TxtListNode["start"]; 110 | spread?: TxtListNode["spread"]; 111 | 112 | // ListItem-specific properties (from TxtListItemNode) 113 | checked?: TxtListItemNode["checked"]; 114 | 115 | // Link-specific properties (from TxtLinkNode) 116 | title?: TxtLinkNode["title"]; 117 | url?: TxtLinkNode["url"]; 118 | 119 | // CodeBlock-specific properties (from TxtCodeBlockNode) 120 | lang?: TxtCodeBlockNode["lang"]; 121 | } 122 | 123 | // Type guard functions 124 | const hasChildren = ( 125 | node: AstNode, 126 | ): node is AstNode & { children: Content[] } => { 127 | return node.children !== undefined && Array.isArray(node.children); 128 | }; 129 | 130 | const hasValue = ( 131 | node: AstNode, 132 | ): node is AstNode & { value: TxtTextNode["value"] } => { 133 | return node.value !== undefined && typeof node.value === "string"; 134 | }; 135 | 136 | const hasSProperty = (node: AstNode): node is AstNode & { s: string } => { 137 | return node.s !== undefined && typeof node.s === "string"; 138 | }; 139 | 140 | const hasCProperty = (node: AstNode): node is AstNode & { c: AstNode[] } => { 141 | return node.c !== undefined && Array.isArray(node.c); 142 | }; 143 | 144 | const isAstNode = (value: unknown): value is AstNode => { 145 | if (typeof value !== "object" || value === null) { 146 | return false; 147 | } 148 | const maybeNode = value as Record; 149 | return ( 150 | typeof maybeNode.type === "string" && 151 | typeof maybeNode.raw === "string" && 152 | Array.isArray(maybeNode.range) && 153 | maybeNode.range.length === 2 && 154 | typeof maybeNode.loc === "object" && 155 | maybeNode.loc !== null 156 | ); 157 | }; 158 | 159 | const extractFirstTextValue = (node: AstNode): string | undefined => { 160 | if (hasValue(node)) { 161 | return node.value; 162 | } 163 | if (!hasChildren(node)) { 164 | return undefined; 165 | } 166 | for (const child of node.children) { 167 | if (isAstNode(child)) { 168 | const value = extractFirstTextValue(child); 169 | if (value !== undefined) { 170 | return value; 171 | } 172 | } 173 | } 174 | return undefined; 175 | }; 176 | 177 | const getAstChild = (node: AstNode, index: number): AstNode | undefined => { 178 | if (!hasChildren(node)) { 179 | return undefined; 180 | } 181 | const child = node.children[index]; 182 | if (!isAstNode(child)) { 183 | return undefined; 184 | } 185 | return child; 186 | }; 187 | 188 | // Type guard to check if a node has a Typst type (during conversion) 189 | const isTypstType = (type: string, pattern: RegExp): boolean => { 190 | return pattern.test(type); 191 | }; 192 | 193 | type TxtNodeLineLocation = TxtNode["loc"]; 194 | 195 | /** 196 | * Extract the raw source code from the specified location. 197 | * @param typstSource The raw Typst source code. 198 | * @param location The location specifying the start and end positions. 199 | * @returns The extracted source code. 200 | */ 201 | export const extractRawSourceByLocation = ( 202 | typstSource: string, 203 | location: TxtNodeLineLocation, 204 | ): string => { 205 | const { start, end } = location; 206 | const lines = typstSource.split("\n"); 207 | 208 | // NOTE: Line numbers are 1-based, but array indexes are 0-based. 209 | const targetLines = lines.slice(start.line - 1, end.line); 210 | 211 | const targetLinesFirst = targetLines[0].slice(start.column); 212 | const targetLinesMiddle = targetLines.slice(1, -1); 213 | const targetLinesLast = targetLines[targetLines.length - 1].slice( 214 | 0, 215 | end.column, 216 | ); 217 | let result: string; 218 | if (start.line === end.line) { 219 | result = targetLinesFirst.slice(0, end.column - start.column); 220 | } else { 221 | result = targetLinesFirst; 222 | if (targetLinesMiddle.length > 0) { 223 | result += `\n${targetLinesMiddle.join("\n")}`; 224 | } 225 | result += `\n${targetLinesLast}`; 226 | } 227 | 228 | return result; 229 | }; 230 | 231 | /** 232 | * Convert a raw Typst AST object to a textlint AST object. 233 | * @param rawTypstAstObject The raw Typst AST object. 234 | * @returns The textlint AST object. 235 | **/ 236 | export const convertRawTypstAstObjectToTextlintAstObject = ( 237 | rawTypstAstObject: TypstAstNode, 238 | typstSource: string, 239 | ) => { 240 | // Copy from rawTypstAstObject to textlintAstObject 241 | const textlintAstObject: AstNode = JSON.parse( 242 | JSON.stringify(rawTypstAstObject), 243 | ); 244 | 245 | const parsePosition = (position: string): TxtNodePosition => { 246 | const [line, column] = position.split(":").map(Number); 247 | return { 248 | line, 249 | column, 250 | }; 251 | }; 252 | 253 | const extractNodeType = (s: string): string => { 254 | const match = s.match( 255 | /(?:([^<]+)<\/span>|([^<]+))/, 256 | ); 257 | if (!match) throw new Error("Invalid format"); 258 | return match[1] || match[2]; 259 | }; 260 | 261 | const extractLocation = (s: string, c?: AstNode[]): TxtNodeLineLocation => { 262 | const match = s.match(/<(\d+:\d+)~(\d+:\d+)>/); 263 | if (!match) { 264 | if (c !== undefined) { 265 | // If root node 266 | 267 | if (c.length === 0) { 268 | return { 269 | start: { line: 1, column: 0 }, 270 | end: { line: 1, column: 0 }, 271 | }; 272 | } 273 | 274 | const firstChild = c[0]; 275 | const lastChild = c[c.length - 1]; 276 | if (!hasSProperty(firstChild) || !hasSProperty(lastChild)) { 277 | throw new Error( 278 | "Cannot extract location: AST node is missing position information", 279 | ); 280 | } 281 | const rootChildrenStartLocation = extractLocation( 282 | firstChild.s, 283 | firstChild.c, 284 | ); 285 | const rootChildrenEndLocation = extractLocation( 286 | lastChild.s, 287 | lastChild.c, 288 | ); 289 | return { 290 | start: rootChildrenStartLocation.start, 291 | end: rootChildrenEndLocation.end, 292 | }; 293 | } 294 | throw new Error("Invalid format"); 295 | } 296 | 297 | const startLocation = parsePosition(match[1]); 298 | const endLocation = parsePosition(match[2]); 299 | 300 | return { 301 | start: startLocation, 302 | end: endLocation, 303 | }; 304 | }; 305 | 306 | const calculateOffsets = (node: AstNode, currentOffset = 0): number => { 307 | const calculateOffsetFromLocation = ( 308 | location: TxtNodeLineLocation, 309 | ): number => { 310 | const lines = typstSource.split("\n"); 311 | let offset = 0; 312 | for (let i = 0; i < location.start.line - 1; i++) { 313 | offset += lines[i].length + 1; // +1 for newline 314 | } 315 | offset += location.start.column; 316 | return offset; 317 | }; 318 | if (!hasSProperty(node)) { 319 | throw new Error( 320 | "Cannot calculate offsets: AST node is missing position information", 321 | ); 322 | } 323 | const location = extractLocation(node.s, node.c); 324 | const nodeRawText = extractRawSourceByLocation(typstSource, location); 325 | const nodeLength = nodeRawText.length; 326 | const startOffset = calculateOffsetFromLocation(location); 327 | 328 | if (node.c) { 329 | // If TxtParentNode 330 | let childOffset = startOffset; 331 | const whitespaceNodes: AstNode[] = []; 332 | const softBreakNodes: AstNode[] = []; 333 | for ( 334 | let nodeChildIndex = 0; 335 | nodeChildIndex < node.c.length; 336 | nodeChildIndex++ 337 | ) { 338 | const child = node.c[nodeChildIndex]; 339 | childOffset = calculateOffsets(child, childOffset); 340 | 341 | // Check between child nodes 342 | if (nodeChildIndex < node.c.length - 1) { 343 | const nextChild = node.c[nodeChildIndex + 1]; 344 | if (!hasSProperty(nextChild)) { 345 | throw new Error( 346 | "Cannot calculate child positions: AST node is missing position information", 347 | ); 348 | } 349 | 350 | const currentEndLine = child.loc.end.line; 351 | const currentEndColumn = child.loc.end.column; 352 | const nextStartLine = extractLocation(nextChild.s, nextChild.c).start 353 | .line; 354 | const nextStartColumn = extractLocation(nextChild.s, nextChild.c) 355 | .start.column; 356 | 357 | // whitespace 358 | if ( 359 | currentEndLine === nextStartLine && 360 | currentEndColumn !== nextStartColumn 361 | ) { 362 | const whitespaceNode: AstNode = { 363 | type: "Str", 364 | raw: " ".repeat(nextStartColumn - currentEndColumn), 365 | value: " ".repeat(nextStartColumn - currentEndColumn), 366 | range: [childOffset, childOffset + 1], 367 | loc: { 368 | start: { line: currentEndLine, column: currentEndColumn }, 369 | end: { line: nextStartLine, column: nextStartColumn }, 370 | }, 371 | }; 372 | whitespaceNodes.push(whitespaceNode); 373 | childOffset += 1; 374 | } 375 | 376 | // soft breaks 377 | if ( 378 | currentEndLine !== nextStartLine && 379 | child.type !== ASTNodeTypes.Break && 380 | nextChild.type !== ASTNodeTypes.Break 381 | ) { 382 | const breakNode: AstNode = { 383 | type: "Str", 384 | raw: "\n", 385 | value: "\n", 386 | range: [childOffset, childOffset + 1], 387 | loc: { 388 | start: { line: currentEndLine, column: currentEndColumn }, 389 | end: { line: nextStartLine, column: nextStartColumn }, 390 | }, 391 | }; 392 | softBreakNodes.push(breakNode); 393 | childOffset += 1; 394 | } 395 | } 396 | } 397 | 398 | node.c = [...node.c, ...softBreakNodes, ...whitespaceNodes].sort( 399 | (a, b) => a.range[0] - b.range[0], 400 | ); 401 | node.children = node.c as Content[]; 402 | } else { 403 | // If TxtTextNode 404 | node.value = extractRawSourceByLocation(typstSource, location); 405 | } 406 | 407 | const endOffset = startOffset + nodeLength; 408 | 409 | node.raw = extractRawSourceByLocation(typstSource, location); 410 | node.range = [startOffset, endOffset]; 411 | node.loc = location; 412 | if (!hasSProperty(node)) { 413 | throw new Error( 414 | "Cannot determine node type: AST node is missing type information", 415 | ); 416 | } 417 | node.type = extractNodeType(node.s); 418 | if (/^Marked::Heading$/.test(node.type)) { 419 | node.type = ASTNodeTypes.Header; 420 | } 421 | if (/^Marked::Text/.test(node.type)) { 422 | node.type = ASTNodeTypes.Str; 423 | } 424 | if (/^Marked::Parbreak/.test(node.type)) { 425 | node.type = ASTNodeTypes.Break; 426 | } 427 | if (/^Escape::Linebreak/.test(node.type)) { 428 | node.type = ASTNodeTypes.Break; 429 | } 430 | if (/^Marked::(ListItem|EnumItem)$/.test(node.type)) { 431 | node.type = ASTNodeTypes.ListItem; 432 | node.spread = false; 433 | node.checked = null; 434 | 435 | if (hasChildren(node)) { 436 | const originalRange = node.range; 437 | const originalLoc = node.loc; 438 | const originalRaw = node.raw; 439 | 440 | const contentChildren = node.children.filter( 441 | (child) => 442 | !["Marked::ListMarker", "Marked::EnumMarker"].includes(child.type), 443 | ); 444 | 445 | const flattenedContent: Content[] = []; 446 | for (const child of contentChildren) { 447 | if ( 448 | typeof child.type === "string" && 449 | isTypstType(child.type, /^Marked::Markup$/) && 450 | isAstNode(child) && 451 | hasChildren(child) 452 | ) { 453 | flattenedContent.push(...child.children); 454 | } else { 455 | flattenedContent.push(child); 456 | } 457 | } 458 | const textContent: Content[] = []; 459 | const nestedListItems: Content[] = []; 460 | 461 | for (const child of flattenedContent) { 462 | if (child.type === ASTNodeTypes.ListItem) { 463 | nestedListItems.push(child); 464 | } else { 465 | textContent.push(child); 466 | } 467 | } 468 | 469 | const processedChildren: Content[] = []; 470 | 471 | if (textContent.length > 0) { 472 | const validTextContent = textContent.filter( 473 | (child) => 474 | !(child.type === ASTNodeTypes.Str && child.raw?.trim() === ""), 475 | ); 476 | 477 | if (validTextContent.length > 0) { 478 | const firstChild = validTextContent[0]; 479 | const lastChild = validTextContent[validTextContent.length - 1]; 480 | 481 | processedChildren.push({ 482 | type: ASTNodeTypes.Paragraph, 483 | children: validTextContent, 484 | loc: { 485 | start: firstChild.loc.start, 486 | end: lastChild.loc.end, 487 | }, 488 | range: [firstChild.range[0], lastChild.range[1]], 489 | raw: validTextContent.map((c) => c.raw).join(""), 490 | } as Content); 491 | } 492 | } 493 | 494 | if (nestedListItems.length > 0) { 495 | const isOrdered = nestedListItems.some((item) => 496 | /^\d+\./.test(item.raw?.trim() || ""), 497 | ); 498 | 499 | const firstNestedItem = nestedListItems[0]; 500 | const lastNestedItem = nestedListItems[nestedListItems.length - 1]; 501 | 502 | processedChildren.push({ 503 | type: ASTNodeTypes.List, 504 | ordered: isOrdered, 505 | start: isOrdered ? 1 : null, 506 | spread: false, 507 | children: nestedListItems, 508 | loc: { 509 | start: firstNestedItem.loc.start, 510 | end: lastNestedItem.loc.end, 511 | }, 512 | range: [firstNestedItem.range[0], lastNestedItem.range[1]], 513 | raw: nestedListItems.map((item) => item.raw).join("\n"), 514 | } as Content); 515 | } 516 | 517 | for (const child of processedChildren) { 518 | if ( 519 | child.type === ASTNodeTypes.Paragraph && 520 | isAstNode(child) && 521 | hasChildren(child) && 522 | child.children.length > 0 523 | ) { 524 | const firstStr = child.children[0]; 525 | const lastStr = child.children[child.children.length - 1]; 526 | 527 | const actualStart = calculateOffsetFromLocation(firstStr.loc); 528 | const actualEnd = calculateOffsetFromLocation({ 529 | start: lastStr.loc.end, 530 | end: lastStr.loc.end, 531 | }); 532 | 533 | child.range = [actualStart, actualEnd]; 534 | 535 | for (const strChild of child.children) { 536 | if (strChild.type === ASTNodeTypes.Str) { 537 | const strStart = calculateOffsetFromLocation(strChild.loc); 538 | const strEnd = calculateOffsetFromLocation({ 539 | start: strChild.loc.end, 540 | end: strChild.loc.end, 541 | }); 542 | strChild.range = [strStart, strEnd]; 543 | } 544 | } 545 | } else if ( 546 | child.type === ASTNodeTypes.List && 547 | isAstNode(child) && 548 | hasChildren(child) && 549 | child.children.length > 0 550 | ) { 551 | const firstListItem = child.children[0]; 552 | const lastListItem = child.children[child.children.length - 1]; 553 | if (firstListItem && lastListItem) { 554 | child.range = [firstListItem.range[0], lastListItem.range[1]]; 555 | } 556 | } 557 | } 558 | 559 | node.children = processedChildren; 560 | if (processedChildren.length > 0) { 561 | const firstChild = processedChildren[0]; 562 | const lastChild = processedChildren[processedChildren.length - 1]; 563 | 564 | const markerStart = calculateOffsetFromLocation(originalLoc); 565 | const contentEnd = lastChild.range[1]; 566 | 567 | node.range = [markerStart, contentEnd]; 568 | node.loc = { 569 | start: originalLoc.start, 570 | end: lastChild.loc.end, 571 | }; 572 | 573 | node.raw = extractRawSourceByLocation(typstSource, node.loc); 574 | } else { 575 | const nodeStart = calculateOffsetFromLocation(originalLoc); 576 | const nodeEnd = nodeStart + originalRaw.length; 577 | node.range = [nodeStart, nodeEnd]; 578 | node.loc = originalLoc; 579 | node.raw = originalRaw; 580 | } 581 | 582 | Reflect.deleteProperty(node, "s"); 583 | Reflect.deleteProperty(node, "c"); 584 | 585 | return node.range[1]; 586 | } 587 | } 588 | if (node.type === "Marked::Raw") { 589 | if (node.loc.start.line === node.loc.end.line) { 590 | // If Code 591 | node.type = ASTNodeTypes.Code; 592 | if (/^```([\s\S]*?)```$/.test(node.raw)) { 593 | const codeBlockPattern = /^```(\w+)?\s([\s\S]*?)\s*```$/; 594 | const match = node.raw.match(codeBlockPattern); 595 | node.value = match ? match[2].trim() : ""; 596 | } else { 597 | node.value = node.raw.replace(/`([\s\S]*?)`/, "$1"); 598 | } 599 | } else { 600 | // If CodeBlock 601 | node.type = ASTNodeTypes.CodeBlock; 602 | node.value = node.raw.replace(/```(?:\w*)\n([\s\S]*?)\n```/, "$1"); 603 | 604 | if (hasChildren(node) && node.children.length > 1) { 605 | const langNode = node.children[1]; 606 | if ( 607 | isAstNode(langNode) && 608 | isTypstType(langNode.type, /^Marked::RawLang$/) && 609 | hasValue(langNode) 610 | ) { 611 | node.lang = langNode.value ?? null; 612 | } else { 613 | node.lang = null; 614 | } 615 | } else { 616 | node.lang = null; 617 | } 618 | } 619 | // biome-ignore lint/performance/noDelete: Convert TxtParentNode to TxtTextNode 620 | delete node.children; 621 | } 622 | if (node.type === "Marked::Equation") { 623 | const value = node.raw 624 | .replace(/^[\s\n]*\$[\s\n]*/, "") 625 | .replace(/[\s\n]*\$[\s\n]*$/, ""); 626 | 627 | const lineText = typstSource.split("\n")[node.loc.start.line - 1]; 628 | const isCodeBlock = 629 | lineText.trim().startsWith("$") && 630 | lineText.trim().endsWith("$") && 631 | lineText.trim().length > 2 && 632 | lineText 633 | .trim() 634 | .replace(/^\$|\$$/g, "") 635 | .trim().length > 0 && 636 | lineText 637 | .trim() 638 | .replace(/^\$|\$$/g, "") 639 | .trim().length === value.length; 640 | 641 | if (isCodeBlock) { 642 | node.type = ASTNodeTypes.CodeBlock; 643 | node.value = value; 644 | } else if (node.loc.start.line === node.loc.end.line) { 645 | node.type = ASTNodeTypes.Code; 646 | node.value = value; 647 | } else { 648 | node.type = ASTNodeTypes.CodeBlock; 649 | node.value = value; 650 | } 651 | // biome-ignore lint/performance/noDelete: Marked::Equation node have children property but textlint AST object does not. 652 | delete node.children; 653 | } 654 | if (node.type === "Marked::Link") { 655 | node.type = ASTNodeTypes.Link; 656 | node.title = null; 657 | node.url = node.value ?? ""; 658 | const linkTextNode: TxtStrNode = { 659 | type: ASTNodeTypes.Str, 660 | value: node.raw, 661 | loc: node.loc, 662 | range: node.range, 663 | raw: node.raw, 664 | }; 665 | node.children = [linkTextNode]; 666 | // biome-ignore lint/performance/noDelete: Marked::Link node have value property but textlint AST object does not. 667 | delete node.value; 668 | } 669 | if (node.type === "Marked::Strong") { 670 | node.type = ASTNodeTypes.Strong; 671 | const childNode = getAstChild(node, 1); 672 | if (childNode) { 673 | childNode.type = ASTNodeTypes.Str; 674 | const value = extractFirstTextValue(childNode); 675 | childNode.value = value ?? ""; 676 | if (hasChildren(childNode)) { 677 | Reflect.deleteProperty(childNode, "children"); 678 | } 679 | } 680 | } 681 | if (node.type === "Marked::Emph") { 682 | node.type = ASTNodeTypes.Emphasis; 683 | const childNode = getAstChild(node, 1); 684 | if (childNode) { 685 | childNode.type = ASTNodeTypes.Str; 686 | const value = extractFirstTextValue(childNode); 687 | childNode.value = value ?? ""; 688 | if (hasChildren(childNode)) { 689 | Reflect.deleteProperty(childNode, "children"); 690 | } 691 | } 692 | } 693 | if (node.type === "Ct::LineComment") { 694 | node.type = ASTNodeTypes.Comment; 695 | node.value = node.raw.replace(/^\/\/\s*/, ""); 696 | } 697 | if (node.type === "Ct::BlockComment") { 698 | node.type = ASTNodeTypes.Comment; 699 | node.value = node.raw.replace(/^\/\*\s*/, "").replace(/\s*\*\/$/, ""); 700 | } 701 | 702 | Reflect.deleteProperty(node, "s"); 703 | Reflect.deleteProperty(node, "c"); 704 | 705 | return endOffset; 706 | }; 707 | 708 | // If the source code starts with a single newline, add a Break node before the first node. 709 | if (hasCProperty(textlintAstObject) && textlintAstObject.c.length > 0) { 710 | const firstChild = textlintAstObject.c[0]; 711 | if (hasSProperty(firstChild)) { 712 | const rootChildrenStartLocation = extractLocation( 713 | firstChild.s, 714 | firstChild.c, 715 | ); 716 | if (rootChildrenStartLocation.start.line === 2) { 717 | const breakNode: AstNode = { 718 | s: "Marked::Parbreak <1:0~2:0>", 719 | type: "Marked::Parbreak", 720 | raw: "", 721 | range: [0, 0], 722 | loc: { start: { line: 1, column: 0 }, end: { line: 1, column: 0 } }, 723 | }; 724 | textlintAstObject.c.unshift(breakNode); 725 | } 726 | } 727 | } 728 | 729 | if (hasSProperty(textlintAstObject)) { 730 | calculateOffsets(textlintAstObject); 731 | } 732 | 733 | // Root node is always `Document` node 734 | textlintAstObject.type = ASTNodeTypes.Document; 735 | 736 | return textlintAstObject as TxtDocumentNode; 737 | }; 738 | 739 | /** 740 | * Paragraphize a textlint AST object. 741 | * @param rootNode The textlint AST object. 742 | * @returns The paragraphized textlint AST object. 743 | */ 744 | export const paragraphizeTextlintAstObject = ( 745 | rootNode: TxtDocumentNode, 746 | ): TxtDocumentNode => { 747 | const children: Content[] = []; 748 | let i = 0; 749 | 750 | while (i < rootNode.children.length) { 751 | const node = rootNode.children[i]; 752 | 753 | // Collect consecutive ListItems into a single List node. 754 | if (node.type === ASTNodeTypes.ListItem) { 755 | const listItems: Content[] = [node]; 756 | i++; 757 | 758 | // Determine if this is an ordered list by checking the original node type. 759 | // Look at the raw content to determine if it's ordered. 760 | const isOrdered = /^\d+\./.test(node.raw?.trim() || ""); 761 | 762 | // Collect consecutive ListItems including those separated by line breaks. 763 | while (i < rootNode.children.length) { 764 | const currentNode = rootNode.children[i]; 765 | 766 | if (currentNode.type === ASTNodeTypes.ListItem) { 767 | // Check if the current item matches the list type (ordered/unordered). 768 | const currentIsOrdered = /^\d+\./.test(currentNode.raw?.trim() || ""); 769 | if (currentIsOrdered === isOrdered) { 770 | listItems.push(currentNode); 771 | i++; 772 | continue; 773 | } 774 | // Different list type, break here. 775 | break; 776 | } 777 | 778 | // Skip line breaks between ListItems. 779 | if (currentNode.type === ASTNodeTypes.Str && currentNode.raw === "\n") { 780 | if ( 781 | i + 1 < rootNode.children.length && 782 | rootNode.children[i + 1].type === ASTNodeTypes.ListItem 783 | ) { 784 | const nextIsOrdered = /^\d+\./.test( 785 | rootNode.children[i + 1].raw?.trim() || "", 786 | ); 787 | if (nextIsOrdered === isOrdered) { 788 | i++; 789 | continue; 790 | } 791 | } 792 | } 793 | 794 | // Skip line-break-only Paragraphs between ListItems. 795 | if ( 796 | currentNode.type === ASTNodeTypes.Paragraph && 797 | currentNode.children.length === 1 && 798 | currentNode.children[0].type === ASTNodeTypes.Str && 799 | currentNode.children[0].raw === "\n" 800 | ) { 801 | if ( 802 | i + 1 < rootNode.children.length && 803 | rootNode.children[i + 1].type === ASTNodeTypes.ListItem 804 | ) { 805 | const nextIsOrdered = /^\d+\./.test( 806 | rootNode.children[i + 1].raw?.trim() || "", 807 | ); 808 | if (nextIsOrdered === isOrdered) { 809 | i++; 810 | continue; 811 | } 812 | } 813 | } 814 | 815 | break; 816 | } 817 | 818 | // Create List node from collected ListItems. 819 | const firstItem = listItems[0]; 820 | const lastItem = listItems[listItems.length - 1]; 821 | 822 | children.push({ 823 | type: ASTNodeTypes.List, 824 | ordered: isOrdered, 825 | start: isOrdered ? 1 : null, 826 | spread: false, 827 | children: [...listItems], 828 | loc: { 829 | start: firstItem.loc.start, 830 | end: lastItem.loc.end, 831 | }, 832 | range: [firstItem.range[0], lastItem.range[1]], 833 | raw: listItems.map((item) => item.raw).join("\n"), 834 | } as Content); 835 | } 836 | // Skip line-break-only Paragraphs. 837 | else if ( 838 | node.type === ASTNodeTypes.Paragraph && 839 | node.children.length === 1 && 840 | node.children[0].type === ASTNodeTypes.Str && 841 | node.children[0].raw === "\n" 842 | ) { 843 | i++; 844 | } else { 845 | const paragraph: Content[] = []; 846 | 847 | // Add standalone nodes directly without wrapping in Paragraph. 848 | if ( 849 | node.type === ASTNodeTypes.Header || 850 | node.type === ASTNodeTypes.CodeBlock || 851 | node.type === ASTNodeTypes.Break 852 | ) { 853 | children.push(node); 854 | i++; 855 | } 856 | // Group other nodes into paragraphs, but handle EnumItems specially. 857 | else { 858 | // Check if this paragraph contains EnumItems that should be converted to a List. 859 | if ( 860 | node.type === ASTNodeTypes.Paragraph && 861 | isAstNode(node) && 862 | hasChildren(node) 863 | ) { 864 | const enumItems = node.children.filter( 865 | (child) => 866 | isAstNode(child) && isTypstType(child.type, /^Marked::EnumItem$/), 867 | ); 868 | if (enumItems.length > 0) { 869 | // Convert EnumItems to ListItems. 870 | const listItems = enumItems.map((enumItem) => { 871 | const enumItemNode = enumItem as AstNode; 872 | // Remove enum marker and empty Str nodes. 873 | const contentChildren: Content[] = hasChildren(enumItemNode) 874 | ? enumItemNode.children.filter((child) => { 875 | if (!isAstNode(child)) { 876 | return false; 877 | } 878 | return ( 879 | !isTypstType(child.type, /^Marked::EnumMarker$/) && 880 | !( 881 | child.type === ASTNodeTypes.Str && 882 | child.raw?.trim() === "" 883 | ) 884 | ); 885 | }) 886 | : []; 887 | // Use the children of Marked::Markup nodes if they exist. 888 | const actualContent: AstNode[] = []; 889 | for (const child of contentChildren) { 890 | if ( 891 | typeof child.type === "string" && 892 | isTypstType(child.type, /^Marked::Markup$/) && 893 | isAstNode(child) && 894 | hasChildren(child) 895 | ) { 896 | actualContent.push(...child.children.filter(isAstNode)); 897 | } else if (isAstNode(child)) { 898 | actualContent.push(child); 899 | } 900 | } 901 | 902 | const firstContentChild = actualContent[0]; 903 | const lastContentChild = actualContent[actualContent.length - 1]; 904 | 905 | const paragraphNode: AstNode = { 906 | type: ASTNodeTypes.Paragraph, 907 | children: actualContent as Content[], 908 | loc: { 909 | start: 910 | firstContentChild?.loc?.start || enumItemNode.loc.start, 911 | end: lastContentChild?.loc?.end || enumItemNode.loc.end, 912 | }, 913 | range: [ 914 | firstContentChild?.range?.[0] || enumItemNode.range[0], 915 | lastContentChild?.range?.[1] || enumItemNode.range[1], 916 | ], 917 | raw: actualContent.map((childNode) => childNode.raw).join(""), 918 | }; 919 | 920 | const listItemNode: Content = { 921 | type: ASTNodeTypes.ListItem, 922 | spread: false, 923 | checked: null, 924 | children: 925 | actualContent.length > 0 ? [paragraphNode as Content] : [], 926 | loc: enumItemNode.loc, 927 | range: enumItemNode.range, 928 | raw: enumItemNode.raw, 929 | } as Content; 930 | 931 | return listItemNode; 932 | }); 933 | 934 | const firstItem = listItems[0]; 935 | const lastItem = listItems[listItems.length - 1]; 936 | 937 | const listNode: Content = { 938 | type: ASTNodeTypes.List, 939 | ordered: true, 940 | start: 1, 941 | spread: false, 942 | children: listItems, 943 | loc: { 944 | start: firstItem.loc.start, 945 | end: lastItem.loc.end, 946 | }, 947 | range: [firstItem.range[0], lastItem.range[1]], 948 | raw: listItems.map((item) => item.raw).join("\n"), 949 | } as Content; 950 | 951 | children.push(listNode); 952 | 953 | i++; 954 | continue; 955 | } 956 | } 957 | 958 | paragraph.push(node); 959 | i++; 960 | 961 | // Collect consecutive nodes for paragraph grouping. 962 | while (i < rootNode.children.length) { 963 | const currentNode = rootNode.children[i]; 964 | 965 | if ( 966 | currentNode.type === ASTNodeTypes.Header || 967 | currentNode.type === ASTNodeTypes.CodeBlock || 968 | currentNode.type === ASTNodeTypes.Break || 969 | currentNode.type === ASTNodeTypes.ListItem 970 | ) { 971 | break; 972 | } 973 | 974 | paragraph.push(currentNode); 975 | i++; 976 | } 977 | 978 | if (paragraph.length > 0) { 979 | const headNode = paragraph[0]; 980 | const lastNode = paragraph[paragraph.length - 1]; 981 | 982 | // Special handling for hash symbols. 983 | if ( 984 | ["Kw::Hash", "Fn::(Hash: "#")"].includes(headNode.type) 985 | ) { 986 | children.push(...paragraph); 987 | } else { 988 | children.push({ 989 | loc: { 990 | start: headNode.loc.start, 991 | end: lastNode.loc.end, 992 | }, 993 | range: [headNode.range[0], lastNode.range[1]], 994 | raw: paragraph.map((node) => node.raw).join(""), 995 | type: ASTNodeTypes.Paragraph, 996 | children: paragraph, 997 | } as Content); 998 | } 999 | } 1000 | } 1001 | } 1002 | } 1003 | 1004 | return { ...rootNode, children }; 1005 | }; 1006 | 1007 | /** 1008 | * Convert a Typst source code to a textlint AST object. 1009 | * @param typstSource The Typst source code. 1010 | * @returns The textlint AST object. 1011 | */ 1012 | export const convertTypstSourceToTextlintAstObject = async ( 1013 | typstSource: string, 1014 | ) => { 1015 | const rawTypstAstString = await getRawTypstAstString(typstSource); 1016 | const rawTypstAstObject = convertRawTypstAstStringToObject(rawTypstAstString); 1017 | const textlintAstObject = convertRawTypstAstObjectToTextlintAstObject( 1018 | rawTypstAstObject, 1019 | typstSource, 1020 | ); 1021 | const paragraphizedTextlintAstObject = 1022 | paragraphizeTextlintAstObject(textlintAstObject); 1023 | return paragraphizedTextlintAstObject as TxtDocumentNode; 1024 | }; 1025 | --------------------------------------------------------------------------------