The response has been limited to 50k tokens of the smallest files in the repo. You can remove this limitation by removing the max tokens filter.
├── .editorconfig
├── .eslintrc.yml
├── .gitattributes
├── .github
    ├── FUNDING.yml
    ├── ISSUE_TEMPLATE
    │   ├── bug_report.md
    │   ├── development-question.md
    │   ├── feature_request.md
    │   └── question.md
    ├── dependabot.yml
    └── workflows
    │   └── ci.yml
├── .gitignore
├── .ndocrc
├── CHANGELOG.md
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── benchmark
    ├── benchmark.mjs
    ├── implementations
    │   ├── commonmark-reference
    │   │   └── index.mjs
    │   ├── current-commonmark
    │   │   └── index.mjs
    │   ├── current
    │   │   └── index.mjs
    │   ├── markdown-it-2.2.1-commonmark
    │   │   └── index.mjs
    │   └── marked
    │   │   └── index.mjs
    ├── profile.mjs
    └── samples
    │   ├── README.md
    │   ├── block-bq-flat.md
    │   ├── block-bq-nested.md
    │   ├── block-code.md
    │   ├── block-fences.md
    │   ├── block-heading.md
    │   ├── block-hr.md
    │   ├── block-html.md
    │   ├── block-lheading.md
    │   ├── block-list-flat.md
    │   ├── block-list-nested.md
    │   ├── block-ref-flat.md
    │   ├── block-ref-list.md
    │   ├── block-ref-nested.md
    │   ├── block-tables.md
    │   ├── inline-autolink.md
    │   ├── inline-backticks.md
    │   ├── inline-em-flat.md
    │   ├── inline-em-nested.md
    │   ├── inline-em-worst.md
    │   ├── inline-entity.md
    │   ├── inline-escape.md
    │   ├── inline-html.md
    │   ├── inline-links-flat.md
    │   ├── inline-links-nested.md
    │   ├── inline-newlines.md
    │   ├── lorem1.txt
    │   └── rawtabs.md
├── bin
    └── markdown-it.mjs
├── docs
    ├── 4.0_migration.md
    ├── 5.0_migration.md
    ├── README.md
    ├── architecture.md
    ├── development.md
    ├── examples
    │   └── renderer_rules.md
    └── security.md
├── index.mjs
├── lib
    ├── common
    │   ├── html_blocks.mjs
    │   ├── html_re.mjs
    │   └── utils.mjs
    ├── helpers
    │   ├── index.mjs
    │   ├── parse_link_destination.mjs
    │   ├── parse_link_label.mjs
    │   └── parse_link_title.mjs
    ├── index.mjs
    ├── parser_block.mjs
    ├── parser_core.mjs
    ├── parser_inline.mjs
    ├── presets
    │   ├── commonmark.mjs
    │   ├── default.mjs
    │   └── zero.mjs
    ├── renderer.mjs
    ├── ruler.mjs
    ├── rules_block
    │   ├── blockquote.mjs
    │   ├── code.mjs
    │   ├── fence.mjs
    │   ├── heading.mjs
    │   ├── hr.mjs
    │   ├── html_block.mjs
    │   ├── lheading.mjs
    │   ├── list.mjs
    │   ├── paragraph.mjs
    │   ├── reference.mjs
    │   ├── state_block.mjs
    │   └── table.mjs
    ├── rules_core
    │   ├── block.mjs
    │   ├── inline.mjs
    │   ├── linkify.mjs
    │   ├── normalize.mjs
    │   ├── replacements.mjs
    │   ├── smartquotes.mjs
    │   ├── state_core.mjs
    │   └── text_join.mjs
    ├── rules_inline
    │   ├── autolink.mjs
    │   ├── backticks.mjs
    │   ├── balance_pairs.mjs
    │   ├── emphasis.mjs
    │   ├── entity.mjs
    │   ├── escape.mjs
    │   ├── fragments_join.mjs
    │   ├── html_inline.mjs
    │   ├── image.mjs
    │   ├── link.mjs
    │   ├── linkify.mjs
    │   ├── newline.mjs
    │   ├── state_inline.mjs
    │   ├── strikethrough.mjs
    │   └── text.mjs
    └── token.mjs
├── package.json
├── support
    ├── api_header.md
    ├── build_demo.mjs
    ├── build_doc.mjs
    ├── demo_template
    │   ├── README.md
    │   ├── index.css
    │   ├── index.html
    │   ├── index.mjs
    │   ├── rollup.config.mjs
    │   └── sample.md
    ├── rollup.config.mjs
    └── specsplit.mjs
└── test
    ├── cjs.js
    ├── commonmark.mjs
    ├── fixtures
        ├── commonmark
        │   ├── bad.txt
        │   ├── good.txt
        │   └── spec.txt
        └── markdown-it
        │   ├── commonmark_extras.txt
        │   ├── fatal.txt
        │   ├── linkify.txt
        │   ├── normalize.txt
        │   ├── proto.txt
        │   ├── smartquotes.txt
        │   ├── strikethrough.txt
        │   ├── tables.txt
        │   ├── typographer.txt
        │   └── xss.txt
    ├── markdown-it.mjs
    ├── misc.mjs
    ├── pathological.json
    ├── pathological.mjs
    ├── pathological_worker.js
    ├── ruler.mjs
    ├── token.mjs
    └── utils.mjs


/.editorconfig:
--------------------------------------------------------------------------------
 1 | root = true
 2 | 
 3 | [*]
 4 | charset = utf-8
 5 | end_of_line = lf
 6 | trim_trailing_whitespace = true
 7 | insert_final_newline = true
 8 | 
 9 | [{.,}*.{js{,*},y{a,}ml}]
10 | indent_style = space
11 | indent_size = 2
12 | 
13 | [*.{md,txt}]
14 | indent_style = space
15 | indent_size = 4
16 | trim_trailing_whitespace = false
17 | 
18 | [Makefile]
19 | indent_style = tab
20 | 


--------------------------------------------------------------------------------
/.eslintrc.yml:
--------------------------------------------------------------------------------
 1 | extends: standard
 2 | 
 3 | overrides:
 4 |   -
 5 |     files: [ '*.mjs' ]
 6 |     rules:
 7 |       no-restricted-globals: [ 2, require, __dirname ]
 8 |   -
 9 |     files: [ 'test/**' ]
10 |     env: { mocha: true }
11 |   -
12 |     files: [ 'lib/**' ]
13 |     parserOptions: { ecmaVersion: 2015 }
14 | 
15 | ignorePatterns:
16 |   - demo/
17 |   - dist/
18 |   - benchmark/extra/
19 | 
20 | rules:
21 |   camelcase: 0
22 |   no-multi-spaces: 0
23 | 


--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Enforce Unix newlines
2 | * text=auto eol=lf
3 | 


--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | github: markdown-it
2 | open_collective: markdown-it
3 | tidelift: "npm/markdown-it"
4 | 


--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
 1 | ---
 2 | name: Bug report
 3 | about: Create a report to help us improve
 4 | title: ''
 5 | labels: ''
 6 | assignees: ''
 7 | 
 8 | ---
 9 | 
10 | <!--
11 | 
12 | Please note, this package is about IMPLEMENTATION of CommonMark https://commonmark.org/, not about markdown itself. We stay aside of markup discussions. Prior to report a bug, make sure it's about this package, not generic thing.
13 | 
14 | **Before you post**
15 | 
16 | 1. https://spec.commonmark.org/ - make sure you've read CommonMark spec.
17 | 2. https://spec.commonmark.org/dingus/ - if you think you found parse error, check it in reference implementation first.
18 | 
19 | **In your report** 
20 | 
21 | It will be very helpful, if you can provide permalinks with online samples and explain the difference:
22 | 
23 | - https://markdown-it.github.io/ - online demo of `markdown-it`.
24 | - https://spec.commonmark.org/dingus/ - online demo of reference CommonMark's implementation.
25 | 
26 | If you wish to provide code sample - make sure it is as small as possible and can be executed.
27 | 
28 | -->
29 | 


--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/development-question.md:
--------------------------------------------------------------------------------
 1 | ---
 2 | name: Development question
 3 | about: ''
 4 | title: ''
 5 | labels: ''
 6 | assignees: ''
 7 | 
 8 | ---
 9 | 
10 | <!--
11 | 
12 | Note, we have some time constrains, but we always try to help developers, who write plugins. So:
13 | 
14 | - Please, avoid generic programming questions.
15 | - Avoid questions about markdown. Use CommonMark resources for that https://commonmark.org/.
16 | - If you have issue with plugin - report it to plugin's repo/author.
17 | - Make sure you are familiar with dev docs https://github.com/markdown-it/markdown-it/tree/master/docs, and tried to do something.
18 | - Code samples are welcome.
19 | 
20 | Also, you may find useful this links (may be someone already solved your problem):
21 | 
22 | - https://github.com/markdown-it - list of "officially" provided plugins.
23 | - https://www.npmjs.com/search?q=keywords:markdown-it-plugin - community-written plugins.
24 | 
25 | -->
26 | 


--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
 1 | ---
 2 | name: Feature request
 3 | about: Suggest an idea for this project
 4 | title: ''
 5 | labels: ''
 6 | assignees: ''
 7 | 
 8 | ---
 9 | 
10 | <!--
11 | 
12 | Please note, this package is highly extendable. Prior to request new feature, make sure it can not be implemented via plugins.
13 | 
14 | You may also find useful this links:
15 | 
16 | - https://github.com/markdown-it - list of "officially" provided plugins.
17 | - https://www.npmjs.com/search?q=keywords:markdown-it-plugin - community-written plugins.
18 | - https://github.com/markdown-it/markdown-it/tree/master/docs - docs for plugin developers.
19 | 
20 | -->
21 | 


--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/question.md:
--------------------------------------------------------------------------------
 1 | ---
 2 | name: Question
 3 | about: For developpers
 4 | title: ''
 5 | labels: ''
 6 | assignees: ''
 7 | 
 8 | ---
 9 | 
10 | <!--
11 | 
12 | Note, we have some time constraints, but we always try to help developers, who write plugins. So:
13 | 
14 | - Please, avoid generic programming questions.
15 | - Avoid questions about markdown. Use CommonMark resources for that https://commonmark.org/.
16 | - If you have issue with plugin - report it to plugin's repo/author.
17 | - Make sure you are familiar with dev docs https://github.com/markdown-it/markdown-it/tree/master/docs, and tried to do something.
18 | - Code samples are welcome.
19 | 
20 | Also, you may find useful this links (may be someone already solved your problem):
21 | 
22 | - https://github.com/markdown-it - list of "officially" provided plugins.
23 | - https://www.npmjs.com/search?q=keywords:markdown-it-plugin - community-written plugins.
24 | 
25 | -->
26 | 


--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
 1 | version: 2
 2 | updates:
 3 |   - package-ecosystem: github-actions
 4 |     directory: /
 5 |     schedule:
 6 |       interval: daily
 7 | 
 8 |   - package-ecosystem: npm
 9 |     directory: /
10 |     schedule:
11 |       interval: daily
12 |     allow:
13 |       - dependency-type: production
14 | 


--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
 1 | name: CI
 2 | 
 3 | on:
 4 |   push:
 5 |   pull_request:
 6 |   schedule:
 7 |     - cron: '0 0 * * 3'
 8 | 
 9 | jobs:
10 |   test:
11 | 
12 |     runs-on: ubuntu-latest
13 | 
14 |     strategy:
15 |       matrix:
16 |         node-version: [ '18' ]
17 | 
18 |     steps:
19 |     - uses: actions/checkout@v4
20 | 
21 |     - name: Use Node.js ${{ matrix.node-version }}
22 |       uses: actions/setup-node@v4
23 |       with:
24 |         node-version: ${{ matrix.node-version }}
25 | 
26 |     - run: npm install
27 | 
28 |     - name: Test
29 |       run: npm test
30 | 
31 |     - name: Upload coverage report to coveralls.io
32 |       uses: coverallsapp/github-action@master
33 |       with:
34 |         github-token: ${{ secrets.GITHUB_TOKEN }}
35 | 


--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | benchmark/extra/
2 | node_modules/
3 | coverage/
4 | demo/
5 | apidoc/
6 | dist/
7 | *.log
8 | 


--------------------------------------------------------------------------------
/.ndocrc:
--------------------------------------------------------------------------------
 1 | #
 2 | # Common nodeca config
 3 | ################################################################################
 4 | 
 5 | --index         "./support/api_header.md"
 6 | --package       "./package.json"
 7 | --gh-ribbon     "https://github.com/{package.repository}"
 8 | --output        "apidoc"
 9 | --render        "html"
10 | --link-format   "https://github.com/{package.repository}/blob/master/{file}#L{line}"
11 | --broken-links  "show"
12 | 
13 | 
14 | #
15 | # Paths with sources
16 | ################################################################################
17 | 
18 | lib
19 | 
20 | 
21 | #
22 | # Project specific configuration
23 | ################################################################################
24 | 
25 | --show-all
26 | 


--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
 1 | ### If you commit changes:
 2 | 
 3 | 1. Make sure all tests pass.
 4 | 2. Run `./benchmark/benchmark.mjs`, make sure that performance not degraded.
 5 | 3. DON'T include auto-generated browser files to commit.
 6 | 
 7 | ### Other things:
 8 | 
 9 | 1. Prefer [gitter](https://gitter.im/markdown-it/markdown-it) for short "questions".
10 |    Keep issues for bug reports, suggestions and so on.
11 | 2. Make sure to read [dev info](https://github.com/markdown-it/markdown-it/tree/master/docs)
12 |    prior to ask about plugins development.
13 | 3. __Provide examples with [demo](https://markdown-it.github.io/) when possible.__
14 | 4. Issues of "question" type are closed after several days of inactivity,
15 |    if not qualified as bug report, enhancement etc (see 1).
16 | 


--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
 1 | Copyright (c) 2014 Vitaly Puzrin, Alex Kocharin.
 2 | 
 3 | Permission is hereby granted, free of charge, to any person
 4 | obtaining a copy of this software and associated documentation
 5 | files (the "Software"), to deal in the Software without
 6 | restriction, including without limitation the rights to use,
 7 | copy, modify, merge, publish, distribute, sublicense, and/or sell
 8 | copies of the Software, and to permit persons to whom the
 9 | Software is furnished to do so, subject to the following
10 | conditions:
11 | 
12 | The above copyright notice and this permission notice shall be
13 | included in all copies or substantial portions of the Software.
14 | 
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
17 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
19 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
20 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22 | OTHER DEALINGS IN THE SOFTWARE.
23 | 


--------------------------------------------------------------------------------
/benchmark/benchmark.mjs:
--------------------------------------------------------------------------------
 1 | #!/usr/bin/env node
 2 | /* eslint no-console:0 */
 3 | 
 4 | import fs from 'node:fs'
 5 | import util from 'node:util'
 6 | import Benchmark from 'benchmark'
 7 | import ansi from 'ansi'
 8 | const cursor    = ansi(process.stdout)
 9 | 
10 | const IMPLS = []
11 | 
12 | for (const name of fs.readdirSync(new URL('./implementations', import.meta.url)).sort()) {
13 |   const filepath = new URL(`./implementations/${name}/index.mjs`, import.meta.url)
14 |   const code = (await import(filepath))
15 | 
16 |   IMPLS.push({ name, code })
17 | }
18 | 
19 | const SAMPLES = []
20 | 
21 | fs.readdirSync(new URL('./samples', import.meta.url)).sort().forEach(sample => {
22 |   const filepath = new URL(`./samples/${sample}`, import.meta.url)
23 | 
24 |   const content = {}
25 | 
26 |   content.string = fs.readFileSync(filepath, 'utf8')
27 | 
28 |   const title = `(${content.string.length} bytes)`
29 | 
30 |   function onComplete () { cursor.write('\n') }
31 | 
32 |   const suite = new Benchmark.Suite(
33 |     title,
34 |     {
35 |       onStart: () => { console.log('\nSample: %s %s', sample, title) },
36 |       onComplete
37 |     }
38 |   )
39 | 
40 |   IMPLS.forEach(function (impl) {
41 |     suite.add(
42 |       impl.name,
43 |       {
44 |         onCycle: function onCycle (event) {
45 |           cursor.horizontalAbsolute()
46 |           cursor.eraseLine()
47 |           cursor.write(' > ' + event.target)
48 |         },
49 |         onComplete,
50 |         fn: function () { impl.code.run(content.string) }
51 |       }
52 |     )
53 |   })
54 | 
55 |   SAMPLES.push({ name: sample.split('.')[0], title, content, suite })
56 | })
57 | 
58 | function select (patterns) {
59 |   const result = []
60 | 
61 |   if (!(patterns instanceof Array)) {
62 |     patterns = [patterns]
63 |   }
64 | 
65 |   function checkName (name) {
66 |     return patterns.length === 0 || patterns.some(function (regexp) {
67 |       return regexp.test(name)
68 |     })
69 |   }
70 | 
71 |   SAMPLES.forEach(function (sample) {
72 |     if (checkName(sample.name)) {
73 |       result.push(sample)
74 |     }
75 |   })
76 | 
77 |   return result
78 | }
79 | 
80 | function run (files) {
81 |   const selected = select(files)
82 | 
83 |   if (selected.length > 0) {
84 |     console.log('Selected samples: (%d of %d)', selected.length, SAMPLES.length)
85 |     selected.forEach(function (sample) {
86 |       console.log(' > %s', sample.name)
87 |     })
88 |   } else {
89 |     console.log('There isn\'t any sample matches any of these patterns: %s', util.inspect(files))
90 |   }
91 | 
92 |   selected.forEach(function (sample) {
93 |     sample.suite.run()
94 |   })
95 | }
96 | 
97 | run(process.argv.slice(2).map(source => new RegExp(source, 'i')))
98 | 


--------------------------------------------------------------------------------
/benchmark/implementations/commonmark-reference/index.mjs:
--------------------------------------------------------------------------------
 1 | import { createRequire } from 'node:module'
 2 | 
 3 | const commonmark = createRequire(import.meta.url)('../../extra/lib/node_modules/commonmark')
 4 | 
 5 | const parser = new commonmark.Parser()
 6 | const renderer = new commonmark.HtmlRenderer()
 7 | 
 8 | export function run (data) {
 9 |   return renderer.render(parser.parse(data))
10 | }
11 | 


--------------------------------------------------------------------------------
/benchmark/implementations/current-commonmark/index.mjs:
--------------------------------------------------------------------------------
 1 | import markdownit from '../../../index.mjs'
 2 | 
 3 | const md = markdownit('commonmark')
 4 | 
 5 | // Replace normalizers to more primitive, for more "honest" compare.
 6 | // Default ones can cause 1.5x slowdown.
 7 | const encode = md.utils.lib.mdurl.encode
 8 | 
 9 | md.normalizeLink     = function (url) { return encode(url) }
10 | md.normalizeLinkText = function (str) { return str }
11 | 
12 | export function run (data) {
13 |   return md.render(data)
14 | }
15 | 


--------------------------------------------------------------------------------
/benchmark/implementations/current/index.mjs:
--------------------------------------------------------------------------------
 1 | import markdownit from '../../../index.mjs'
 2 | 
 3 | const md = markdownit({
 4 |   html: true,
 5 |   linkify: true,
 6 |   typographer: true
 7 | })
 8 | 
 9 | export function run (data) {
10 |   return md.render(data)
11 | }
12 | 


--------------------------------------------------------------------------------
/benchmark/implementations/markdown-it-2.2.1-commonmark/index.mjs:
--------------------------------------------------------------------------------
 1 | import { createRequire } from 'node:module'
 2 | 
 3 | const markdownit = createRequire(import.meta.url)('../../extra/lib/node_modules/markdown-it')
 4 | 
 5 | const md = markdownit('commonmark')
 6 | 
 7 | export function run (data) {
 8 |   return md.render(data)
 9 | }
10 | 


--------------------------------------------------------------------------------
/benchmark/implementations/marked/index.mjs:
--------------------------------------------------------------------------------
1 | import { createRequire } from 'node:module'
2 | 
3 | const marked = createRequire(import.meta.url)('../../extra/lib/node_modules/marked')
4 | 
5 | export function run (data) {
6 |   return marked(data)
7 | }
8 | 


--------------------------------------------------------------------------------
/benchmark/profile.mjs:
--------------------------------------------------------------------------------
 1 | #!/usr/bin/env node
 2 | /* eslint no-console:0 */
 3 | 
 4 | import { readFileSync } from 'fs'
 5 | import markdownit from '../index.mjs'
 6 | 
 7 | const md = markdownit({
 8 |   html: true,
 9 |   linkify: false,
10 |   typographer: false
11 | })
12 | 
13 | const data = readFileSync(new URL('../test/fixtures/commonmark/spec.txt', import.meta.url), 'utf8')
14 | 
15 | for (let i = 0; i < 20; i++) {
16 |   md.render(data)
17 | }
18 | 


--------------------------------------------------------------------------------
/benchmark/samples/block-bq-flat.md:
--------------------------------------------------------------------------------
 1 | > the simple example of a blockquote 
 2 | > the simple example of a blockquote
 3 | > the simple example of a blockquote
 4 | > the simple example of a blockquote
 5 | ... continuation
 6 | ... continuation
 7 | ... continuation
 8 | ... continuation
 9 | 
10 | empty blockquote:
11 | 
12 | >
13 | >
14 | >
15 | >
16 | 
17 | 


--------------------------------------------------------------------------------
/benchmark/samples/block-bq-nested.md:
--------------------------------------------------------------------------------
 1 | >>>>>> deeply nested blockquote
 2 | >>>>> deeply nested blockquote
 3 | >>>> deeply nested blockquote
 4 | >>> deeply nested blockquote
 5 | >> deeply nested blockquote
 6 | > deeply nested blockquote
 7 | 
 8 | > deeply nested blockquote
 9 | >> deeply nested blockquote
10 | >>> deeply nested blockquote
11 | >>>> deeply nested blockquote
12 | >>>>> deeply nested blockquote
13 | >>>>>> deeply nested blockquote
14 | 


--------------------------------------------------------------------------------
/benchmark/samples/block-code.md:
--------------------------------------------------------------------------------
 1 | 
 2 |         an
 3 |         example
 4 | 
 5 |         of
 6 | 
 7 | 
 8 | 
 9 |         a code
10 |         block
11 | 
12 | 


--------------------------------------------------------------------------------
/benchmark/samples/block-fences.md:
--------------------------------------------------------------------------------
 1 | 
 2 | ``````````text
 3 | an
 4 | example
 5 | ```
 6 | of
 7 | 
 8 | 
 9 | a fenced
10 | ```
11 | code
12 | block
13 | ``````````
14 | 
15 | 


--------------------------------------------------------------------------------
/benchmark/samples/block-heading.md:
--------------------------------------------------------------------------------
 1 | # heading
 2 | ### heading
 3 | ##### heading
 4 | 
 5 | # heading #
 6 | ### heading ###
 7 | ##### heading \#\#\#\#\######
 8 | 
 9 | ############ not a heading
10 | 


--------------------------------------------------------------------------------
/benchmark/samples/block-hr.md:
--------------------------------------------------------------------------------
 1 | 
 2 |  * * * * *
 3 | 
 4 |  -  -  -  -  -
 5 | 
 6 |  ________
 7 | 
 8 | 
 9 |  ************************* text
10 | 
11 | 


--------------------------------------------------------------------------------
/benchmark/samples/block-html.md:
--------------------------------------------------------------------------------
 1 | <div class="this is an html block">
 2 | 
 3 | blah blah
 4 | 
 5 | </div>
 6 | 
 7 | <table>
 8 |   <tr>
 9 |     <td>
10 |       **test**
11 |     </td>
12 |   </tr>
13 | </table>
14 | 
15 | <table>
16 | 
17 |   <tr>
18 | 
19 |     <td>
20 | 
21 |       test
22 | 
23 |     </td>
24 | 
25 |   </tr>
26 | 
27 | </table>
28 | 
29 | <![CDATA[
30 |   [[[[[[[[[[[... *cdata section - this should not be parsed* ...]]]]]]]]]]]
31 | ]]>
32 | 
33 | 


--------------------------------------------------------------------------------
/benchmark/samples/block-lheading.md:
--------------------------------------------------------------------------------
1 | heading
2 | ---
3 | 
4 | heading
5 | ===================================
6 | 
7 | not a heading
8 | ----------------------------------- text
9 | 


--------------------------------------------------------------------------------
/benchmark/samples/block-list-flat.md:
--------------------------------------------------------------------------------
 1 |  - tidy
 2 |  - bullet
 3 |  - list
 4 | 
 5 | 
 6 |  - loose
 7 | 
 8 |  - bullet
 9 | 
10 |  - list
11 | 
12 | 
13 |  0. ordered
14 |  1. list
15 |  2. example
16 | 
17 | 
18 |  -
19 |  -
20 |  -
21 |  -
22 | 
23 | 
24 |  1.
25 |  2.
26 |  3.
27 | 
28 | 
29 |  -  an example
30 | of a list item
31 |        with a continuation
32 | 
33 |     this part is inside the list
34 | 
35 |    this part is just a paragraph  
36 | 
37 | 
38 |  1. test
39 |  -  test
40 |  1. test
41 |  -  test
42 | 
43 | 
44 | 111111111111111111111111111111111111111111. is this a valid bullet?
45 | 
46 |  - _________________________
47 | 
48 |  - this
49 |  - is
50 | 
51 |    a
52 | 
53 |    long
54 |  - loose
55 |  - list
56 | 
57 |  - with
58 |  - some
59 | 
60 |    tidy
61 | 
62 |  - list
63 |  - items
64 |  - in
65 | 
66 |  - between
67 |  - _________________________
68 | 


--------------------------------------------------------------------------------
/benchmark/samples/block-list-nested.md:
--------------------------------------------------------------------------------
 1 | 
 2 |  - this
 3 |    - is
 4 |      - a
 5 |        - deeply
 6 |          - nested
 7 |            - bullet
 8 |              - list
 9 |    
10 | 
11 |  1. this
12 |     2. is
13 |        3. a
14 |           4. deeply
15 |              5. nested
16 |                 6. unordered
17 |                    7. list
18 | 
19 | 
20 |  - 1
21 |   - 2
22 |    - 3
23 |     - 4
24 |      - 5
25 |       - 6
26 |        - 7
27 |       - 6
28 |      - 5
29 |     - 4
30 |    - 3
31 |   - 2
32 |  - 1
33 | 
34 | 
35 |  - - - - - - - - - deeply-nested one-element item
36 | 
37 | 


--------------------------------------------------------------------------------
/benchmark/samples/block-ref-flat.md:
--------------------------------------------------------------------------------
 1 | [1] [2] [3] [1] [2] [3]
 2 | 
 3 | [looooooooooooooooooooooooooooooooooooooooooooooooooong label]
 4 | 
 5 |  [1]: <http://something.example.com/foo/bar>
 6 |  [2]: http://something.example.com/foo/bar 'test'
 7 |  [3]:
 8 |  http://foo/bar
 9 |  [    looooooooooooooooooooooooooooooooooooooooooooooooooong   label    ]:
10 |  111
11 |  'test'
12 |  [[[[[[[[[[[[[[[[[[[[ this should not slow down anything ]]]]]]]]]]]]]]]]]]]]: q
13 |  (as long as it is not referenced anywhere)
14 | 
15 |  [[[[[[[[[[[[[[[[[[[[]: this is not a valid reference
16 | 


--------------------------------------------------------------------------------
/benchmark/samples/block-ref-list.md:
--------------------------------------------------------------------------------
 1 | [item 1]: <1>
 2 | [item 2]: <2>
 3 | [item 3]: <3>
 4 | [item 4]: <4>
 5 | [item 5]: <5>
 6 | [item 6]: <6>
 7 | [item 7]: <7>
 8 | [item 8]: <8>
 9 | [item 9]: <9>
10 | [item 10]: <10>
11 | [item 11]: <11>
12 | [item 12]: <12>
13 | [item 13]: <13>
14 | [item 14]: <14>
15 | [item 15]: <15>
16 | [item 16]: <16>
17 | [item 17]: <17>
18 | [item 18]: <18>
19 | [item 19]: <19>
20 | [item 20]: <20>
21 | [item 21]: <21>
22 | [item 22]: <22>
23 | [item 23]: <23>
24 | [item 24]: <24>
25 | [item 25]: <25>
26 | [item 26]: <26>
27 | [item 27]: <27>
28 | [item 28]: <28>
29 | [item 29]: <29>
30 | [item 30]: <30>
31 | [item 31]: <31>
32 | [item 32]: <32>
33 | [item 33]: <33>
34 | [item 34]: <34>
35 | [item 35]: <35>
36 | [item 36]: <36>
37 | [item 37]: <37>
38 | [item 38]: <38>
39 | [item 39]: <39>
40 | [item 40]: <40>
41 | [item 41]: <41>
42 | [item 42]: <42>
43 | [item 43]: <43>
44 | [item 44]: <44>
45 | [item 45]: <45>
46 | [item 46]: <46>
47 | [item 47]: <47>
48 | [item 48]: <48>
49 | [item 49]: <49>
50 | [item 50]: <50>
51 | 


--------------------------------------------------------------------------------
/benchmark/samples/block-ref-nested.md:
--------------------------------------------------------------------------------
 1 | [[[[[[[foo]]]]]]]
 2 | 
 3 | [[[[[[[foo]]]]]]]: bar
 4 | [[[[[[foo]]]]]]: bar
 5 | [[[[[foo]]]]]: bar
 6 | [[[[foo]]]]: bar
 7 | [[[foo]]]: bar
 8 | [[foo]]: bar
 9 | [foo]: bar
10 | 
11 | [*[*[*[*[foo]*]*]*]*]
12 | 
13 | [*[*[*[*[foo]*]*]*]*]: bar
14 | [*[*[*[foo]*]*]*]: bar
15 | [*[*[foo]*]*]: bar
16 | [*[foo]*]: bar
17 | [foo]: bar
18 | 


--------------------------------------------------------------------------------
/benchmark/samples/block-tables.md:
--------------------------------------------------------------------------------
 1 | | Heading 1 | Heading 2
 2 | | --------- | ---------
 3 | | Cell 1    | Cell 2
 4 | | Cell 3    | Cell 4
 5 | 
 6 | | Header 1 | Header 2 | Header 3 | Header 4 |
 7 | | :------: | -------: | :------- | -------- |
 8 | | Cell 1   | Cell 2   | Cell 3   | Cell 4   |
 9 | | Cell 5   | Cell 6   | Cell 7   | Cell 8   |
10 | 
11 |     Test code
12 | 
13 | Header 1 | Header 2
14 | -------- | --------
15 | Cell 1   | Cell 2
16 | Cell 3   | Cell 4
17 | 
18 | Header 1|Header 2|Header 3|Header 4
19 | :-------|:------:|-------:|--------
20 | Cell 1  |Cell 2  |Cell 3  |Cell 4
21 | *Cell 5*|Cell 6  |Cell 7  |Cell 8
22 | 


--------------------------------------------------------------------------------
/benchmark/samples/inline-autolink.md:
--------------------------------------------------------------------------------
 1 | closed (valid) autolinks:
 2 | 
 3 |  <ftp://1.2.3.4:21/path/foo>
 4 |  <http://foo.bar.baz?q=hello&id=22&boolean>
 5 |  <http://veeeeeeeeeeeeeeeeeeery.loooooooooooooooooooooooooooooooong.autolink/>
 6 |  <teeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeest@gmail.com>
 7 | 
 8 | these are not autolinks:
 9 | 
10 |  <ftp://1.2.3.4:21/path/foo
11 |  <http://foo.bar.baz?q=hello&id=22&boolean
12 |  <http://veeeeeeeeeeeeeeeeeeery.loooooooooooooooooooooooooooooooong.autolink
13 |  <teeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeest@gmail.com
14 |  < http://foo.bar.baz?q=hello&id=22&boolean >
15 | 


--------------------------------------------------------------------------------
/benchmark/samples/inline-backticks.md:
--------------------------------------------------------------------------------
1 | `lots`of`backticks`
2 | 
3 | ``i``wonder``how``this``will``be``parsed``
4 | 


--------------------------------------------------------------------------------
/benchmark/samples/inline-em-flat.md:
--------------------------------------------------------------------------------
1 | *this* *is* *your* *basic* *boring* *emphasis*
2 | 
3 | _this_ _is_ _your_ _basic_ _boring_ _emphasis_
4 | 
5 | **this** **is** **your** **basic** **boring** **emphasis**
6 | 


--------------------------------------------------------------------------------
/benchmark/samples/inline-em-nested.md:
--------------------------------------------------------------------------------
1 | *this *is *a *bunch* of* nested* emphases* 
2 | 
3 | __this __is __a __bunch__ of__ nested__ emphases__ 
4 | 
5 | ***this ***is ***a ***bunch*** of*** nested*** emphases*** 
6 | 


--------------------------------------------------------------------------------
/benchmark/samples/inline-em-worst.md:
--------------------------------------------------------------------------------
1 | *this *is *a *worst *case *for *em *backtracking
2 | 
3 | __this __is __a __worst __case __for __em __backtracking
4 | 
5 | ***this ***is ***a ***worst ***case ***for ***em ***backtracking
6 | 


--------------------------------------------------------------------------------
/benchmark/samples/inline-entity.md:
--------------------------------------------------------------------------------
 1 | entities:
 2 | 
 3 | &nbsp; &amp; &copy; &AElig; &Dcaron; &frac34; &HilbertSpace; &DifferentialD; &ClockwiseContourIntegral;
 4 | 
 5 | &#35; &#1234; &#992; &#98765432;
 6 | 
 7 | non-entities:
 8 | 
 9 | &18900987654321234567890; &1234567890098765432123456789009876543212345678987654;
10 | 
11 | &qwertyuioppoiuytrewqwer; &oiuytrewqwertyuioiuytrewqwertyuioytrewqwertyuiiuytri;
12 | 


--------------------------------------------------------------------------------
/benchmark/samples/inline-escape.md:
--------------------------------------------------------------------------------
 1 | 
 2 | \t\e\s\t\i\n\g \e\s\c\a\p\e \s\e\q\u\e\n\c\e\s
 3 | 
 4 | \!\\\"\#\$\%\&\'\(\)\*\+\,\.\/\:\;\<\=\>\?
 5 | 
 6 | \@ \[ \] \^ \_ \` \{ \| \} \~ \- \'
 7 | 
 8 | \
 9 | \\
10 | \\\
11 | \\\\
12 | \\\\\
13 | 
14 | \<this\> \<is\> \<not\> \<html\>
15 | 
16 | 


--------------------------------------------------------------------------------
/benchmark/samples/inline-html.md:
--------------------------------------------------------------------------------
 1 | Taking commonmark tests from the spec for benchmarking here:
 2 | 
 3 | <a><bab><c2c>
 4 | 
 5 | <a/><b2/>
 6 | 
 7 | <a  /><b2
 8 | data="foo" >
 9 | 
10 | <a foo="bar" bam = 'baz <em>"</em>'
11 | _boolean zoop:33=zoop:33 />
12 | 
13 | <33> <__>
14 | 
15 | <a h*#ref="hi">
16 | 
17 | <a href="hi'> <a href=hi'>
18 | 
19 | < a><
20 | foo><bar/ >
21 | 
22 | <a href='bar'title=title>
23 | 
24 | </a>
25 | </foo >
26 | 
27 | </a href="foo">
28 | 
29 | foo <!-- this is a
30 | comment - with hyphen -->
31 | 
32 | foo <!-- not a comment -- two hyphens -->
33 | 
34 | foo <?php echo $a; ?>
35 | 
36 | foo <!ELEMENT br EMPTY>
37 | 
38 | foo <![CDATA[>&<]]>
39 | 
40 | <a href="&ouml;">
41 | 
42 | <a href="\*">
43 | 
44 | <a href="\"">
45 | 


--------------------------------------------------------------------------------
/benchmark/samples/inline-links-flat.md:
--------------------------------------------------------------------------------
 1 | Valid links:
 2 | 
 3 |  [this is a link]()
 4 |  [this is a link](<http://something.example.com/foo/bar>)
 5 |  [this is a link](http://something.example.com/foo/bar 'test')
 6 |  ![this is an image]()
 7 |  ![this is an image](<http://something.example.com/foo/bar>)
 8 |  ![this is an image](http://something.example.com/foo/bar 'test')
 9 |  
10 |  [escape test](<\>\>\>\>\>\>\>\>\>\>\>\>\>\>> '\'\'\'\'\'\'\'\'\'\'\'\'\'\'')
11 |  [escape test \]\]\]\]\]\]\]\]\]\]\]\]\]\]\]\]](\)\)\)\)\)\)\)\)\)\)\)\)\)\))
12 | 
13 | Invalid links:
14 | 
15 |  [this is not a link
16 | 
17 |  [this is not a link](
18 | 
19 |  [this is not a link](http://something.example.com/foo/bar 'test'
20 |  
21 |  [this is not a link](((((((((((((((((((((((((((((((((((((((((((((((
22 |  
23 |  [this is not a link]((((((((((()))))))))) (((((((((()))))))))))
24 | 


--------------------------------------------------------------------------------
/benchmark/samples/inline-links-nested.md:
--------------------------------------------------------------------------------
 1 | Valid links:
 2 | 
 3 | [[[[[[[[](test)](test)](test)](test)](test)](test)](test)]
 4 | 
 5 | [ [[[[[[[[[[[[[[[[[[ [](test) ]]]]]]]]]]]]]]]]]] ](test)
 6 | 
 7 | Invalid links:
 8 | 
 9 | [[[[[[[[[
10 | 
11 | [ [ [ [ [ [ [ [ [ [ [ [ [ [ [ [ [ [ [ [ [ [ [ [ [ [ [ [ [ [ [ [ [ [ [ [ [ [
12 | 
13 | ![![![![![![![![![![![![![![![![![![![![![![![![![![![![![![![![![![![![![![
14 | 


--------------------------------------------------------------------------------
/benchmark/samples/inline-newlines.md:
--------------------------------------------------------------------------------
 1 | 
 2 | this\
 3 | should\
 4 | be\
 5 | separated\
 6 | by\
 7 | newlines
 8 | 
 9 | this  
10 | should  
11 | be  
12 | separated  
13 | by  
14 | newlines  
15 | too
16 | 
17 | this
18 | should
19 | not
20 | be
21 | separated
22 | by
23 | newlines
24 | 
25 | 


--------------------------------------------------------------------------------
/benchmark/samples/lorem1.txt:
--------------------------------------------------------------------------------
 1 | Lorem ipsum dolor sit amet, __consectetur__ adipiscing elit. Cras imperdiet nec erat ac condimentum. Nulla vel rutrum ligula. Sed hendrerit interdum orci a posuere. Vivamus ut velit aliquet, mollis purus eget, iaculis nisl. Proin posuere malesuada ante. Proin auctor orci eros, ac molestie lorem dictum nec. Vestibulum sit amet erat est. Morbi luctus sed elit ac luctus. Proin blandit, enim vitae egestas posuere, neque elit ultricies dui, vel mattis nibh enim ac lorem. Maecenas molestie nisl sit amet velit dictum lobortis. Aliquam erat volutpat.
 2 | 
 3 | Vivamus sagittis, diam in [vehicula](https://github.com/markdown-it/markdown-it) lobortis, sapien arcu mattis erat, vel aliquet sem urna et risus. Ut feugiat sapien vitae mi elementum laoreet. Suspendisse potenti. Aliquam erat nisl, aliquam pretium libero aliquet, sagittis eleifend nunc. In hac habitasse platea dictumst. Integer turpis augue, tincidunt dignissim mauris id, rhoncus dapibus purus. Maecenas et enim odio. Nullam massa metus, varius quis vehicula sed, pharetra mollis erat. In quis viverra velit. Vivamus placerat, est nec hendrerit varius, enim dui hendrerit magna, ut pulvinar nibh lorem vel lacus. Mauris a orci iaculis, hendrerit eros sed, gravida leo. In dictum mauris vel augue varius, ac ullamcorper nisl ornare. In eu posuere velit, ac fermentum arcu. Interdum et malesuada fames ac ante ipsum primis in faucibus. Nullam sed malesuada leo, at interdum elit.
 4 | 
 5 | Nullam ut tincidunt nunc. [Pellentesque][1] metus lacus, commodo eget justo ut, rutrum varius nunc. Sed non rhoncus risus. Morbi sodales gravida pulvinar. Duis malesuada, odio volutpat elementum vulputate, massa magna scelerisque ante, et accumsan tellus nunc in sem. Donec mattis arcu et velit aliquet, non sagittis justo vestibulum. Suspendisse volutpat felis lectus, nec consequat ipsum mattis id. Donec dapibus vehicula facilisis. In tincidunt mi nisi, nec faucibus tortor euismod nec. Suspendisse ante ligula, aliquet vitae libero eu, vulputate dapibus libero. Sed bibendum, sapien at posuere interdum, libero est sollicitudin magna, ac gravida tellus purus eu ipsum. Proin ut quam arcu.
 6 | 
 7 | Suspendisse potenti. Donec ante velit, ornare at augue quis, tristique laoreet sem. Etiam in ipsum elit. Nullam cursus dolor sit amet nulla feugiat tristique. Phasellus ac tellus tincidunt, imperdiet purus eget, ullamcorper ipsum. Cras eu tincidunt sem. Nullam sed dapibus magna. Lorem ipsum dolor sit amet, consectetur adipiscing elit. In id venenatis tortor. In consectetur sollicitudin pharetra. Etiam convallis nisi nunc, et aliquam turpis viverra sit amet. Maecenas faucibus sodales tortor. Suspendisse lobortis mi eu leo viverra volutpat. Pellentesque velit ante, vehicula sodales congue ut, elementum a urna. Cras tempor, ipsum eget luctus rhoncus, arcu ligula fermentum urna, vulputate pharetra enim enim non libero.
 8 | 
 9 | Proin diam quam, elementum in eleifend id, elementum et metus. Cras in justo consequat justo semper ultrices. Sed dignissim lectus a ante mollis, nec vulputate ante molestie. Proin in porta nunc. Etiam pulvinar turpis sed velit porttitor, vel adipiscing velit fringilla. Cras ac tellus vitae purus pharetra tincidunt. Sed cursus aliquet aliquet. Cras eleifend commodo malesuada. In turpis turpis, ullamcorper ut tincidunt a, ullamcorper a nunc. Etiam luctus tellus ac dapibus gravida. Ut nec lacus laoreet neque ullamcorper volutpat.
10 | 
11 | Nunc et leo erat. Aenean mattis ultrices lorem, eget adipiscing dolor ultricies eu. In hac habitasse platea dictumst. Vivamus cursus feugiat sapien quis aliquam. Mauris quam libero, porta vel volutpat ut, blandit a purus. Vivamus vestibulum dui vel tortor molestie, sit amet feugiat sem commodo. Nulla facilisi. Sed molestie arcu eget tellus vestibulum tristique.
12 | 
13 | [1]: https://github.com/markdown-it
14 | 


--------------------------------------------------------------------------------
/benchmark/samples/rawtabs.md:
--------------------------------------------------------------------------------
 1 | 
 2 | this is a test for tab expansion, be careful not to replace them with spaces
 3 | 
 4 | 1	4444
 5 | 22	333
 6 | 333	22
 7 | 4444	1
 8 | 
 9 | 
10 | 	tab-indented line
11 |     space-indented line
12 | 	tab-indented line
13 | 
14 | 
15 | a lot of                                                spaces in between here
16 | 
17 | a lot of												tabs in between here
18 | 
19 | 


--------------------------------------------------------------------------------
/bin/markdown-it.mjs:
--------------------------------------------------------------------------------
  1 | #!/usr/bin/env node
  2 | /* eslint no-console:0 */
  3 | 
  4 | import fs from 'node:fs'
  5 | import argparse from 'argparse'
  6 | import markdownit from '../index.mjs'
  7 | 
  8 | const cli = new argparse.ArgumentParser({
  9 |   prog: 'markdown-it',
 10 |   add_help: true
 11 | })
 12 | 
 13 | cli.add_argument('-v', '--version', {
 14 |   action: 'version',
 15 |   version: JSON.parse(fs.readFileSync(new URL('../package.json', import.meta.url))).version
 16 | })
 17 | 
 18 | cli.add_argument('--no-html', {
 19 |   help: 'Disable embedded HTML',
 20 |   action: 'store_true'
 21 | })
 22 | 
 23 | cli.add_argument('-l', '--linkify', {
 24 |   help: 'Autolink text',
 25 |   action: 'store_true'
 26 | })
 27 | 
 28 | cli.add_argument('-t', '--typographer', {
 29 |   help: 'Enable smartquotes and other typographic replacements',
 30 |   action: 'store_true'
 31 | })
 32 | 
 33 | cli.add_argument('--trace', {
 34 |   help: 'Show stack trace on error',
 35 |   action: 'store_true'
 36 | })
 37 | 
 38 | cli.add_argument('file', {
 39 |   help: 'File to read',
 40 |   nargs: '?',
 41 |   default: '-'
 42 | })
 43 | 
 44 | cli.add_argument('-o', '--output', {
 45 |   help: 'File to write',
 46 |   default: '-'
 47 | })
 48 | 
 49 | const options = cli.parse_args()
 50 | 
 51 | function readFile (filename, encoding, callback) {
 52 |   if (options.file === '-') {
 53 |     // read from stdin
 54 |     const chunks = []
 55 | 
 56 |     process.stdin.on('data', function (chunk) { chunks.push(chunk) })
 57 | 
 58 |     process.stdin.on('end', function () {
 59 |       return callback(null, Buffer.concat(chunks).toString(encoding))
 60 |     })
 61 |   } else {
 62 |     fs.readFile(filename, encoding, callback)
 63 |   }
 64 | }
 65 | 
 66 | readFile(options.file, 'utf8', function (err, input) {
 67 |   let output
 68 | 
 69 |   if (err) {
 70 |     if (err.code === 'ENOENT') {
 71 |       console.error('File not found: ' + options.file)
 72 |       process.exit(2)
 73 |     }
 74 | 
 75 |     console.error(
 76 |       (options.trace && err.stack) ||
 77 |       err.message ||
 78 |       String(err))
 79 | 
 80 |     process.exit(1)
 81 |   }
 82 | 
 83 |   const md = markdownit({
 84 |     html: !options.no_html,
 85 |     xhtmlOut: false,
 86 |     typographer: options.typographer,
 87 |     linkify: options.linkify
 88 |   })
 89 | 
 90 |   try {
 91 |     output = md.render(input)
 92 |   } catch (e) {
 93 |     console.error(
 94 |       (options.trace && e.stack) ||
 95 |       e.message ||
 96 |       String(e))
 97 | 
 98 |     process.exit(1)
 99 |   }
100 | 
101 |   if (options.output === '-') {
102 |     // write to stdout
103 |     process.stdout.write(output)
104 |   } else {
105 |     fs.writeFileSync(options.output, output)
106 |   }
107 | })
108 | 


--------------------------------------------------------------------------------
/docs/4.0_migration.md:
--------------------------------------------------------------------------------
 1 | Migration to v4
 2 | ===============
 3 | 
 4 | v4 has the same external API as v3, but significantly changed internals. Plugin
 5 | authors should update their packages.
 6 | 
 7 | ## For users
 8 | 
 9 | External API did not change.
10 | 
11 | - If you used `markdown-it` with plugins - make sure to update those.
12 | - If you modified renderer - see dev info below.
13 | - If you did not use plugins and renderer modification - no changes needed.
14 | 
15 | 
16 | ## For developers
17 | 
18 | ### Tokens and renderer
19 | 
20 | - [Tokens](https://github.com/markdown-it/markdown-it/blob/master/lib/token.js)
21 |   are now classes, and allow arbitrary attributes.
22 |   - new tokens are created with `token = state.push(type, tag, nesting)`.
23 |     See [this commit](https://github.com/markdown-it/markdown-it/commit/4aabd5592ea55fb43d6a215b316c89c6f6f1f7db) to understand
24 |     how to create tokens in new way. Also see changes in plugins from other
25 |     repos in this org.
26 | - [Renderer](https://github.com/markdown-it/markdown-it/blob/master/lib/renderer.js)
27 |   methods were unified. Number of custom renderer rules were significantly reduced.
28 |   Custom renderer functions need update due to tokens format change.
29 | 
30 | ### Other changes
31 | 
32 | - `.validateUrl()` -> moved to root class `.validateLink()`
33 | - added `.normalizeLink()` & `.normalizeLinkText()` to root class, and removed
34 |   `normalizeUrl()` from utils.
35 | - removed `replaceEntities()` in `utils`.
36 | 


--------------------------------------------------------------------------------
/docs/5.0_migration.md:
--------------------------------------------------------------------------------
 1 | Migration to v5
 2 | ===============
 3 | 
 4 | v5 has the same external API as v4, only internals were changed. Some external
 5 | plugins may need update (all plugins from `markdown-it` github organization are
 6 | up to date).
 7 | 
 8 | 
 9 | ## For users
10 | 
11 | External API did not change.
12 | 
13 | - If you use `markdown-it` with plugins, make sure to update them.
14 | 
15 | 
16 | ## For plugin developers
17 | 
18 | - added `stateBlock.sCount` to calculate indents instead of `stateBlock.tShift`, it only differs if tabs are present:
19 |   - `stateBlock.tShift` is used to calculate a number of *characters* (tab is 1 character)
20 |   - `stateBlock.sCount` is used to calculate the block *offset* (tab is 1-4 characters depending on position)
21 | - added `stateInline.ruler2` and `stateInline.delimiters` needed to parse emphasis-like markup better
22 |   - emphasis-like tags now can't be skipped with `stateInline.skipToken`, they treat a sequence of markers (e.g. `***`) as a token instead
23 |   - `stateInline.delimiters` is linked with `stateInline.tokens`, so truncating/splicing `stateInline.tokens` may break things
24 | 


--------------------------------------------------------------------------------
/docs/README.md:
--------------------------------------------------------------------------------
 1 | This folder contains useful info for plugin developers.
 2 | 
 3 | If you just use `markdown-it` in your app, see
 4 | [README](https://github.com/markdown-it/markdown-it#markdown-it) and
 5 | [API docs](https://markdown-it.github.io/markdown-it/).
 6 | 
 7 | __Content__:
 8 | 
 9 | - [Parser architecture & design principles](architecture.md)
10 | - [Some guidelines for plugin developers](development.md)
11 | 


--------------------------------------------------------------------------------
/docs/development.md:
--------------------------------------------------------------------------------
 1 | # Development recommendations
 2 | 
 3 | Before continuing, make sure you've read:
 4 | 
 5 | 1. [README](https://github.com/markdown-it/markdown-it#markdown-it)
 6 | 2. [API documentation](https://markdown-it.github.io/markdown-it/)
 7 | 3. [Architecture description](architecture.md)
 8 | 
 9 | 
10 | ## General considerations for plugins.
11 | 
12 | 1. Try to find the right place for your plugin rule:
13 |   - Will it conflict with existing markup (by priority)?
14 |     - If yes - you need to write an inline or block rule.
15 |     - If no - you can morph tokens within core chains.
16 |   - Remember that token morphing in core chains is always more simple than writing
17 |     block or inline rules, if you don't copy existing ones. However,
18 |     block and inline rules are usually faster.
19 |   - Sometimes, it's enough to only modify the renderer, for example, to add
20 |     header IDs or `target="_blank"` for the links.
21 |   - Plugins should not require the `markdown-it` package as dependency in `package.json`.
22 |     If you need access to internals, those are available via a parser instance,
23 |     passed on plugin load. See properties of main class and nested objects.
24 | 2. Search existing
25 |    [plugins](https://www.npmjs.org/browse/keyword/markdown-it-plugin)
26 |    or [rules](https://github.com/markdown-it/markdown-it/tree/master/lib),
27 |    doing something similar. It can be more simple to modify existing code,
28 |    instead of writing all from scratch.
29 | 3. If you did all steps above, but still has questions - ask in
30 |    [tracker](https://github.com/markdown-it/markdown-it/issues). But, please:
31 |    - Be specific. Generic questions like "how to do plugins" and
32 |      "how to learn programming" are not accepted.
33 |    - Don't ask us to break [CommonMark](http://commonmark.org/) specification.
34 |      Such things should be discussed first on [CommonMark forum](http://talk.commonmark.org/).
35 | 
36 | 
37 | ## Notes for NPM packages
38 | 
39 | To simplify search:
40 | 
41 | - add to `package.json` keywords `markdown-it` and `markdown-it-plugin` for plugins.
42 | - add keyword `markdown-it` for any other related packages.
43 | 
44 | 
45 | ## FAQ
46 | 
47 | 
48 | #### I need async rule, how to do it?
49 | 
50 | Sorry. You can't do it directly. All complex parsers are sync by nature. But you
51 | can use workarounds:
52 | 
53 | 1. On parse phase, replace content by random number and store it in `env`.
54 | 2. Do async processing over collected data.
55 | 3. Render content and replace those random numbers with text; or replace first, then render.
56 | 
57 | Alternatively, you can render HTML, then parse it to DOM, or
58 | [cheerio](https://github.com/cheeriojs/cheerio) AST, and apply transformations
59 | in a more convenient way.
60 | 
61 | 
62 | #### How to replace part of text token with link?
63 | 
64 | The right sequence is to split text to several tokens and add link tokens in between.
65 | The result will be: `text` + `link_open` + `text` + `link_close` + `text`.
66 | 
67 | See implementations of [linkify](https://github.com/markdown-it/markdown-it/blob/master/lib/rules_core/linkify.mjs) and [emoji](https://github.com/markdown-it/markdown-it-emoji/blob/master/lib/replace.mjs) - those do text token splits.
68 | 
69 | __Note.__ Don't try to replace text with HTML markup! That's not secure.
70 | 
71 | 
72 | #### Why my inline rule is not executed?
73 | 
74 | The inline parser skips pieces of texts to optimize speed. It stops only on [a small set of chars](https://github.com/markdown-it/markdown-it/blob/master/lib/rules_inline/text.mjs), which can be tokens. We did not made this list extensible for performance reasons too.
75 | 
76 | If you are absolutely sure that something important is missing there - create a
77 | ticket and we will consider adding it as a new charcode.
78 | 
79 | 
80 | #### Why do you reject some useful things?
81 | 
82 | We do a markdown parser. It should keep the "markdown spirit". Other things should
83 | be kept separate, in plugins, for example. We have no clear criteria, sorry.
84 | Probably, you will find [CommonMark forum](http://talk.commonmark.org/) a useful read to understand us better.
85 | 
86 | Of course, if you find the architecture of this parser interesting for another type
87 | of markup, you are welcome to reuse it in another project.
88 | 


--------------------------------------------------------------------------------
/docs/security.md:
--------------------------------------------------------------------------------
 1 | # Security
 2 | 
 3 | Many people don't understand that markdown format does not care much about
 4 | security. In many cases you have to pass output to sanitizers. `markdown-it`
 5 | provides 2 possible strategies to produce safe output:
 6 | 
 7 | 1. Don't enable HTML. Extend markup features with [plugins](https://www.npmjs.org/browse/keyword/markdown-it-plugin). We think it's the best choice and use it by default.
 8 |    - That's ok for 99% of user needs.
 9 |    - Output will be safe without sanitizer.
10 | 2. Enable HTML and use external sanitizer package.
11 | 
12 | Also by default `markdown-it` prohibits some kind of links, which could be used
13 | for XSS:
14 | 
15 | - `javascript:`, `vbscript:`
16 | - `file:`
17 | - `data:`, except some images (gif/png/jpeg/webp).
18 | 
19 | So, by default `markdown-it` should be safe. We care about it.
20 | 
21 | If you find a security problem - contact us via tracker or email. Such reports
22 | are fixed with top priority.
23 | 
24 | 
25 | ## Plugins
26 | 
27 | Usually, plugins operate with tokenized content, and that's enough to provide
28 | safe output.
29 | 
30 | But there is one non-evident case you should know - don't allow plugins to
31 | generate arbitrary element `id` and `name`. If those depend on user input -
32 | always add prefixes to avoid DOM clobbering. See [discussion](https://github.com/markdown-it/markdown-it/issues/28) for details.
33 | 
34 | So, if you decide to use plugins that add extended class syntax or
35 | autogenerating header anchors - be careful.
36 | 


--------------------------------------------------------------------------------
/index.mjs:
--------------------------------------------------------------------------------
1 | export { default } from './lib/index.mjs'
2 | 


--------------------------------------------------------------------------------
/lib/common/html_blocks.mjs:
--------------------------------------------------------------------------------
 1 | // List of valid html blocks names, according to commonmark spec
 2 | // https://spec.commonmark.org/0.30/#html-blocks
 3 | 
 4 | export default [
 5 |   'address',
 6 |   'article',
 7 |   'aside',
 8 |   'base',
 9 |   'basefont',
10 |   'blockquote',
11 |   'body',
12 |   'caption',
13 |   'center',
14 |   'col',
15 |   'colgroup',
16 |   'dd',
17 |   'details',
18 |   'dialog',
19 |   'dir',
20 |   'div',
21 |   'dl',
22 |   'dt',
23 |   'fieldset',
24 |   'figcaption',
25 |   'figure',
26 |   'footer',
27 |   'form',
28 |   'frame',
29 |   'frameset',
30 |   'h1',
31 |   'h2',
32 |   'h3',
33 |   'h4',
34 |   'h5',
35 |   'h6',
36 |   'head',
37 |   'header',
38 |   'hr',
39 |   'html',
40 |   'iframe',
41 |   'legend',
42 |   'li',
43 |   'link',
44 |   'main',
45 |   'menu',
46 |   'menuitem',
47 |   'nav',
48 |   'noframes',
49 |   'ol',
50 |   'optgroup',
51 |   'option',
52 |   'p',
53 |   'param',
54 |   'search',
55 |   'section',
56 |   'summary',
57 |   'table',
58 |   'tbody',
59 |   'td',
60 |   'tfoot',
61 |   'th',
62 |   'thead',
63 |   'title',
64 |   'tr',
65 |   'track',
66 |   'ul'
67 | ]
68 | 


--------------------------------------------------------------------------------
/lib/common/html_re.mjs:
--------------------------------------------------------------------------------
 1 | // Regexps to match html elements
 2 | 
 3 | const attr_name     = '[a-zA-Z_:][a-zA-Z0-9:._-]*'
 4 | 
 5 | const unquoted      = '[^"\'=<>`\\x00-\\x20]+'
 6 | const single_quoted = "'[^']*'"
 7 | const double_quoted = '"[^"]*"'
 8 | 
 9 | const attr_value  = '(?:' + unquoted + '|' + single_quoted + '|' + double_quoted + ')'
10 | 
11 | const attribute   = '(?:\\s+' + attr_name + '(?:\\s*=\\s*' + attr_value + ')?)'
12 | 
13 | const open_tag    = '<[A-Za-z][A-Za-z0-9\\-]*' + attribute + '*\\s*\\/?>'
14 | 
15 | const close_tag   = '<\\/[A-Za-z][A-Za-z0-9\\-]*\\s*>'
16 | const comment     = '<!---?>|<!--(?:[^-]|-[^-]|--[^>])*-->'
17 | const processing  = '<[?][\\s\\S]*?[?]>'
18 | const declaration = '<![A-Za-z][^>]*>'
19 | const cdata       = '<!\\[CDATA\\[[\\s\\S]*?\\]\\]>'
20 | 
21 | const HTML_TAG_RE = new RegExp('^(?:' + open_tag + '|' + close_tag + '|' + comment +
22 |                         '|' + processing + '|' + declaration + '|' + cdata + ')')
23 | const HTML_OPEN_CLOSE_TAG_RE = new RegExp('^(?:' + open_tag + '|' + close_tag + ')')
24 | 
25 | export { HTML_TAG_RE, HTML_OPEN_CLOSE_TAG_RE }
26 | 


--------------------------------------------------------------------------------
/lib/helpers/index.mjs:
--------------------------------------------------------------------------------
 1 | // Just a shortcut for bulk export
 2 | 
 3 | import parseLinkLabel from './parse_link_label.mjs'
 4 | import parseLinkDestination from './parse_link_destination.mjs'
 5 | import parseLinkTitle from './parse_link_title.mjs'
 6 | 
 7 | export {
 8 |   parseLinkLabel,
 9 |   parseLinkDestination,
10 |   parseLinkTitle
11 | }
12 | 


--------------------------------------------------------------------------------
/lib/helpers/parse_link_destination.mjs:
--------------------------------------------------------------------------------
 1 | // Parse link destination
 2 | //
 3 | 
 4 | import { unescapeAll } from '../common/utils.mjs'
 5 | 
 6 | export default function parseLinkDestination (str, start, max) {
 7 |   let code
 8 |   let pos = start
 9 | 
10 |   const result = {
11 |     ok: false,
12 |     pos: 0,
13 |     str: ''
14 |   }
15 | 
16 |   if (str.charCodeAt(pos) === 0x3C /* < */) {
17 |     pos++
18 |     while (pos < max) {
19 |       code = str.charCodeAt(pos)
20 |       if (code === 0x0A /* \n */) { return result }
21 |       if (code === 0x3C /* < */) { return result }
22 |       if (code === 0x3E /* > */) {
23 |         result.pos = pos + 1
24 |         result.str = unescapeAll(str.slice(start + 1, pos))
25 |         result.ok = true
26 |         return result
27 |       }
28 |       if (code === 0x5C /* \ */ && pos + 1 < max) {
29 |         pos += 2
30 |         continue
31 |       }
32 | 
33 |       pos++
34 |     }
35 | 
36 |     // no closing '>'
37 |     return result
38 |   }
39 | 
40 |   // this should be ... } else { ... branch
41 | 
42 |   let level = 0
43 |   while (pos < max) {
44 |     code = str.charCodeAt(pos)
45 | 
46 |     if (code === 0x20) { break }
47 | 
48 |     // ascii control characters
49 |     if (code < 0x20 || code === 0x7F) { break }
50 | 
51 |     if (code === 0x5C /* \ */ && pos + 1 < max) {
52 |       if (str.charCodeAt(pos + 1) === 0x20) { break }
53 |       pos += 2
54 |       continue
55 |     }
56 | 
57 |     if (code === 0x28 /* ( */) {
58 |       level++
59 |       if (level > 32) { return result }
60 |     }
61 | 
62 |     if (code === 0x29 /* ) */) {
63 |       if (level === 0) { break }
64 |       level--
65 |     }
66 | 
67 |     pos++
68 |   }
69 | 
70 |   if (start === pos) { return result }
71 |   if (level !== 0) { return result }
72 | 
73 |   result.str = unescapeAll(str.slice(start, pos))
74 |   result.pos = pos
75 |   result.ok = true
76 |   return result
77 | }
78 | 


--------------------------------------------------------------------------------
/lib/helpers/parse_link_label.mjs:
--------------------------------------------------------------------------------
 1 | // Parse link label
 2 | //
 3 | // this function assumes that first character ("[") already matches;
 4 | // returns the end of the label
 5 | //
 6 | 
 7 | export default function parseLinkLabel (state, start, disableNested) {
 8 |   let level, found, marker, prevPos
 9 | 
10 |   const max = state.posMax
11 |   const oldPos = state.pos
12 | 
13 |   state.pos = start + 1
14 |   level = 1
15 | 
16 |   while (state.pos < max) {
17 |     marker = state.src.charCodeAt(state.pos)
18 |     if (marker === 0x5D /* ] */) {
19 |       level--
20 |       if (level === 0) {
21 |         found = true
22 |         break
23 |       }
24 |     }
25 | 
26 |     prevPos = state.pos
27 |     state.md.inline.skipToken(state)
28 |     if (marker === 0x5B /* [ */) {
29 |       if (prevPos === state.pos - 1) {
30 |         // increase level if we find text `[`, which is not a part of any token
31 |         level++
32 |       } else if (disableNested) {
33 |         state.pos = oldPos
34 |         return -1
35 |       }
36 |     }
37 |   }
38 | 
39 |   let labelEnd = -1
40 | 
41 |   if (found) {
42 |     labelEnd = state.pos
43 |   }
44 | 
45 |   // restore old state
46 |   state.pos = oldPos
47 | 
48 |   return labelEnd
49 | }
50 | 


--------------------------------------------------------------------------------
/lib/helpers/parse_link_title.mjs:
--------------------------------------------------------------------------------
 1 | // Parse link title
 2 | //
 3 | 
 4 | import { unescapeAll } from '../common/utils.mjs'
 5 | 
 6 | // Parse link title within `str` in [start, max] range,
 7 | // or continue previous parsing if `prev_state` is defined (equal to result of last execution).
 8 | //
 9 | export default function parseLinkTitle (str, start, max, prev_state) {
10 |   let code
11 |   let pos = start
12 | 
13 |   const state = {
14 |     // if `true`, this is a valid link title
15 |     ok: false,
16 |     // if `true`, this link can be continued on the next line
17 |     can_continue: false,
18 |     // if `ok`, it's the position of the first character after the closing marker
19 |     pos: 0,
20 |     // if `ok`, it's the unescaped title
21 |     str: '',
22 |     // expected closing marker character code
23 |     marker: 0
24 |   }
25 | 
26 |   if (prev_state) {
27 |     // this is a continuation of a previous parseLinkTitle call on the next line,
28 |     // used in reference links only
29 |     state.str = prev_state.str
30 |     state.marker = prev_state.marker
31 |   } else {
32 |     if (pos >= max) { return state }
33 | 
34 |     let marker = str.charCodeAt(pos)
35 |     if (marker !== 0x22 /* " */ && marker !== 0x27 /* ' */ && marker !== 0x28 /* ( */) { return state }
36 | 
37 |     start++
38 |     pos++
39 | 
40 |     // if opening marker is "(", switch it to closing marker ")"
41 |     if (marker === 0x28) { marker = 0x29 }
42 | 
43 |     state.marker = marker
44 |   }
45 | 
46 |   while (pos < max) {
47 |     code = str.charCodeAt(pos)
48 |     if (code === state.marker) {
49 |       state.pos = pos + 1
50 |       state.str += unescapeAll(str.slice(start, pos))
51 |       state.ok = true
52 |       return state
53 |     } else if (code === 0x28 /* ( */ && state.marker === 0x29 /* ) */) {
54 |       return state
55 |     } else if (code === 0x5C /* \ */ && pos + 1 < max) {
56 |       pos++
57 |     }
58 | 
59 |     pos++
60 |   }
61 | 
62 |   // no closing marker found, but this link title may continue on the next line (for references)
63 |   state.can_continue = true
64 |   state.str += unescapeAll(str.slice(start, pos))
65 |   return state
66 | }
67 | 


--------------------------------------------------------------------------------
/lib/parser_block.mjs:
--------------------------------------------------------------------------------
  1 | /** internal
  2 |  * class ParserBlock
  3 |  *
  4 |  * Block-level tokenizer.
  5 |  **/
  6 | 
  7 | import Ruler from './ruler.mjs'
  8 | import StateBlock from './rules_block/state_block.mjs'
  9 | 
 10 | import r_table from './rules_block/table.mjs'
 11 | import r_code from './rules_block/code.mjs'
 12 | import r_fence from './rules_block/fence.mjs'
 13 | import r_blockquote from './rules_block/blockquote.mjs'
 14 | import r_hr from './rules_block/hr.mjs'
 15 | import r_list from './rules_block/list.mjs'
 16 | import r_reference from './rules_block/reference.mjs'
 17 | import r_html_block from './rules_block/html_block.mjs'
 18 | import r_heading from './rules_block/heading.mjs'
 19 | import r_lheading from './rules_block/lheading.mjs'
 20 | import r_paragraph from './rules_block/paragraph.mjs'
 21 | 
 22 | const _rules = [
 23 |   // First 2 params - rule name & source. Secondary array - list of rules,
 24 |   // which can be terminated by this one.
 25 |   ['table',      r_table,      ['paragraph', 'reference']],
 26 |   ['code',       r_code],
 27 |   ['fence',      r_fence,      ['paragraph', 'reference', 'blockquote', 'list']],
 28 |   ['blockquote', r_blockquote, ['paragraph', 'reference', 'blockquote', 'list']],
 29 |   ['hr',         r_hr,         ['paragraph', 'reference', 'blockquote', 'list']],
 30 |   ['list',       r_list,       ['paragraph', 'reference', 'blockquote']],
 31 |   ['reference',  r_reference],
 32 |   ['html_block', r_html_block, ['paragraph', 'reference', 'blockquote']],
 33 |   ['heading',    r_heading,    ['paragraph', 'reference', 'blockquote']],
 34 |   ['lheading',   r_lheading],
 35 |   ['paragraph',  r_paragraph]
 36 | ]
 37 | 
 38 | /**
 39 |  * new ParserBlock()
 40 |  **/
 41 | function ParserBlock () {
 42 |   /**
 43 |    * ParserBlock#ruler -> Ruler
 44 |    *
 45 |    * [[Ruler]] instance. Keep configuration of block rules.
 46 |    **/
 47 |   this.ruler = new Ruler()
 48 | 
 49 |   for (let i = 0; i < _rules.length; i++) {
 50 |     this.ruler.push(_rules[i][0], _rules[i][1], { alt: (_rules[i][2] || []).slice() })
 51 |   }
 52 | }
 53 | 
 54 | // Generate tokens for input range
 55 | //
 56 | ParserBlock.prototype.tokenize = function (state, startLine, endLine) {
 57 |   const rules = this.ruler.getRules('')
 58 |   const len = rules.length
 59 |   const maxNesting = state.md.options.maxNesting
 60 |   let line = startLine
 61 |   let hasEmptyLines = false
 62 | 
 63 |   while (line < endLine) {
 64 |     state.line = line = state.skipEmptyLines(line)
 65 |     if (line >= endLine) { break }
 66 | 
 67 |     // Termination condition for nested calls.
 68 |     // Nested calls currently used for blockquotes & lists
 69 |     if (state.sCount[line] < state.blkIndent) { break }
 70 | 
 71 |     // If nesting level exceeded - skip tail to the end. That's not ordinary
 72 |     // situation and we should not care about content.
 73 |     if (state.level >= maxNesting) {
 74 |       state.line = endLine
 75 |       break
 76 |     }
 77 | 
 78 |     // Try all possible rules.
 79 |     // On success, rule should:
 80 |     //
 81 |     // - update `state.line`
 82 |     // - update `state.tokens`
 83 |     // - return true
 84 |     const prevLine = state.line
 85 |     let ok = false
 86 | 
 87 |     for (let i = 0; i < len; i++) {
 88 |       ok = rules[i](state, line, endLine, false)
 89 |       if (ok) {
 90 |         if (prevLine >= state.line) {
 91 |           throw new Error("block rule didn't increment state.line")
 92 |         }
 93 |         break
 94 |       }
 95 |     }
 96 | 
 97 |     // this can only happen if user disables paragraph rule
 98 |     if (!ok) throw new Error('none of the block rules matched')
 99 | 
100 |     // set state.tight if we had an empty line before current tag
101 |     // i.e. latest empty line should not count
102 |     state.tight = !hasEmptyLines
103 | 
104 |     // paragraph might "eat" one newline after it in nested lists
105 |     if (state.isEmpty(state.line - 1)) {
106 |       hasEmptyLines = true
107 |     }
108 | 
109 |     line = state.line
110 | 
111 |     if (line < endLine && state.isEmpty(line)) {
112 |       hasEmptyLines = true
113 |       line++
114 |       state.line = line
115 |     }
116 |   }
117 | }
118 | 
119 | /**
120 |  * ParserBlock.parse(str, md, env, outTokens)
121 |  *
122 |  * Process input string and push block tokens into `outTokens`
123 |  **/
124 | ParserBlock.prototype.parse = function (src, md, env, outTokens) {
125 |   if (!src) { return }
126 | 
127 |   const state = new this.State(src, md, env, outTokens)
128 | 
129 |   this.tokenize(state, state.line, state.lineMax)
130 | }
131 | 
132 | ParserBlock.prototype.State = StateBlock
133 | 
134 | export default ParserBlock
135 | 


--------------------------------------------------------------------------------
/lib/parser_core.mjs:
--------------------------------------------------------------------------------
 1 | /** internal
 2 |  * class Core
 3 |  *
 4 |  * Top-level rules executor. Glues block/inline parsers and does intermediate
 5 |  * transformations.
 6 |  **/
 7 | 
 8 | import Ruler from './ruler.mjs'
 9 | import StateCore from './rules_core/state_core.mjs'
10 | 
11 | import r_normalize from './rules_core/normalize.mjs'
12 | import r_block from './rules_core/block.mjs'
13 | import r_inline from './rules_core/inline.mjs'
14 | import r_linkify from './rules_core/linkify.mjs'
15 | import r_replacements from './rules_core/replacements.mjs'
16 | import r_smartquotes from './rules_core/smartquotes.mjs'
17 | import r_text_join from './rules_core/text_join.mjs'
18 | 
19 | const _rules = [
20 |   ['normalize',      r_normalize],
21 |   ['block',          r_block],
22 |   ['inline',         r_inline],
23 |   ['linkify',        r_linkify],
24 |   ['replacements',   r_replacements],
25 |   ['smartquotes',    r_smartquotes],
26 |   // `text_join` finds `text_special` tokens (for escape sequences)
27 |   // and joins them with the rest of the text
28 |   ['text_join',      r_text_join]
29 | ]
30 | 
31 | /**
32 |  * new Core()
33 |  **/
34 | function Core () {
35 |   /**
36 |    * Core#ruler -> Ruler
37 |    *
38 |    * [[Ruler]] instance. Keep configuration of core rules.
39 |    **/
40 |   this.ruler = new Ruler()
41 | 
42 |   for (let i = 0; i < _rules.length; i++) {
43 |     this.ruler.push(_rules[i][0], _rules[i][1])
44 |   }
45 | }
46 | 
47 | /**
48 |  * Core.process(state)
49 |  *
50 |  * Executes core chain rules.
51 |  **/
52 | Core.prototype.process = function (state) {
53 |   const rules = this.ruler.getRules('')
54 | 
55 |   for (let i = 0, l = rules.length; i < l; i++) {
56 |     rules[i](state)
57 |   }
58 | }
59 | 
60 | Core.prototype.State = StateCore
61 | 
62 | export default Core
63 | 


--------------------------------------------------------------------------------
/lib/parser_inline.mjs:
--------------------------------------------------------------------------------
  1 | /** internal
  2 |  * class ParserInline
  3 |  *
  4 |  * Tokenizes paragraph content.
  5 |  **/
  6 | 
  7 | import Ruler from './ruler.mjs'
  8 | import StateInline from './rules_inline/state_inline.mjs'
  9 | 
 10 | import r_text from './rules_inline/text.mjs'
 11 | import r_linkify from './rules_inline/linkify.mjs'
 12 | import r_newline from './rules_inline/newline.mjs'
 13 | import r_escape from './rules_inline/escape.mjs'
 14 | import r_backticks from './rules_inline/backticks.mjs'
 15 | import r_strikethrough from './rules_inline/strikethrough.mjs'
 16 | import r_emphasis from './rules_inline/emphasis.mjs'
 17 | import r_link from './rules_inline/link.mjs'
 18 | import r_image from './rules_inline/image.mjs'
 19 | import r_autolink from './rules_inline/autolink.mjs'
 20 | import r_html_inline from './rules_inline/html_inline.mjs'
 21 | import r_entity from './rules_inline/entity.mjs'
 22 | 
 23 | import r_balance_pairs from './rules_inline/balance_pairs.mjs'
 24 | import r_fragments_join from './rules_inline/fragments_join.mjs'
 25 | 
 26 | // Parser rules
 27 | 
 28 | const _rules = [
 29 |   ['text',            r_text],
 30 |   ['linkify',         r_linkify],
 31 |   ['newline',         r_newline],
 32 |   ['escape',          r_escape],
 33 |   ['backticks',       r_backticks],
 34 |   ['strikethrough',   r_strikethrough.tokenize],
 35 |   ['emphasis',        r_emphasis.tokenize],
 36 |   ['link',            r_link],
 37 |   ['image',           r_image],
 38 |   ['autolink',        r_autolink],
 39 |   ['html_inline',     r_html_inline],
 40 |   ['entity',          r_entity]
 41 | ]
 42 | 
 43 | // `rule2` ruleset was created specifically for emphasis/strikethrough
 44 | // post-processing and may be changed in the future.
 45 | //
 46 | // Don't use this for anything except pairs (plugins working with `balance_pairs`).
 47 | //
 48 | const _rules2 = [
 49 |   ['balance_pairs',   r_balance_pairs],
 50 |   ['strikethrough',   r_strikethrough.postProcess],
 51 |   ['emphasis',        r_emphasis.postProcess],
 52 |   // rules for pairs separate '**' into its own text tokens, which may be left unused,
 53 |   // rule below merges unused segments back with the rest of the text
 54 |   ['fragments_join',  r_fragments_join]
 55 | ]
 56 | 
 57 | /**
 58 |  * new ParserInline()
 59 |  **/
 60 | function ParserInline () {
 61 |   /**
 62 |    * ParserInline#ruler -> Ruler
 63 |    *
 64 |    * [[Ruler]] instance. Keep configuration of inline rules.
 65 |    **/
 66 |   this.ruler = new Ruler()
 67 | 
 68 |   for (let i = 0; i < _rules.length; i++) {
 69 |     this.ruler.push(_rules[i][0], _rules[i][1])
 70 |   }
 71 | 
 72 |   /**
 73 |    * ParserInline#ruler2 -> Ruler
 74 |    *
 75 |    * [[Ruler]] instance. Second ruler used for post-processing
 76 |    * (e.g. in emphasis-like rules).
 77 |    **/
 78 |   this.ruler2 = new Ruler()
 79 | 
 80 |   for (let i = 0; i < _rules2.length; i++) {
 81 |     this.ruler2.push(_rules2[i][0], _rules2[i][1])
 82 |   }
 83 | }
 84 | 
 85 | // Skip single token by running all rules in validation mode;
 86 | // returns `true` if any rule reported success
 87 | //
 88 | ParserInline.prototype.skipToken = function (state) {
 89 |   const pos = state.pos
 90 |   const rules = this.ruler.getRules('')
 91 |   const len = rules.length
 92 |   const maxNesting = state.md.options.maxNesting
 93 |   const cache = state.cache
 94 | 
 95 |   if (typeof cache[pos] !== 'undefined') {
 96 |     state.pos = cache[pos]
 97 |     return
 98 |   }
 99 | 
100 |   let ok = false
101 | 
102 |   if (state.level < maxNesting) {
103 |     for (let i = 0; i < len; i++) {
104 |       // Increment state.level and decrement it later to limit recursion.
105 |       // It's harmless to do here, because no tokens are created. But ideally,
106 |       // we'd need a separate private state variable for this purpose.
107 |       //
108 |       state.level++
109 |       ok = rules[i](state, true)
110 |       state.level--
111 | 
112 |       if (ok) {
113 |         if (pos >= state.pos) { throw new Error("inline rule didn't increment state.pos") }
114 |         break
115 |       }
116 |     }
117 |   } else {
118 |     // Too much nesting, just skip until the end of the paragraph.
119 |     //
120 |     // NOTE: this will cause links to behave incorrectly in the following case,
121 |     //       when an amount of `[` is exactly equal to `maxNesting + 1`:
122 |     //
123 |     //       [[[[[[[[[[[[[[[[[[[[[foo]()
124 |     //
125 |     // TODO: remove this workaround when CM standard will allow nested links
126 |     //       (we can replace it by preventing links from being parsed in
127 |     //       validation mode)
128 |     //
129 |     state.pos = state.posMax
130 |   }
131 | 
132 |   if (!ok) { state.pos++ }
133 |   cache[pos] = state.pos
134 | }
135 | 
136 | // Generate tokens for input range
137 | //
138 | ParserInline.prototype.tokenize = function (state) {
139 |   const rules = this.ruler.getRules('')
140 |   const len = rules.length
141 |   const end = state.posMax
142 |   const maxNesting = state.md.options.maxNesting
143 | 
144 |   while (state.pos < end) {
145 |     // Try all possible rules.
146 |     // On success, rule should:
147 |     //
148 |     // - update `state.pos`
149 |     // - update `state.tokens`
150 |     // - return true
151 |     const prevPos = state.pos
152 |     let ok = false
153 | 
154 |     if (state.level < maxNesting) {
155 |       for (let i = 0; i < len; i++) {
156 |         ok = rules[i](state, false)
157 |         if (ok) {
158 |           if (prevPos >= state.pos) { throw new Error("inline rule didn't increment state.pos") }
159 |           break
160 |         }
161 |       }
162 |     }
163 | 
164 |     if (ok) {
165 |       if (state.pos >= end) { break }
166 |       continue
167 |     }
168 | 
169 |     state.pending += state.src[state.pos++]
170 |   }
171 | 
172 |   if (state.pending) {
173 |     state.pushPending()
174 |   }
175 | }
176 | 
177 | /**
178 |  * ParserInline.parse(str, md, env, outTokens)
179 |  *
180 |  * Process input string and push inline tokens into `outTokens`
181 |  **/
182 | ParserInline.prototype.parse = function (str, md, env, outTokens) {
183 |   const state = new this.State(str, md, env, outTokens)
184 | 
185 |   this.tokenize(state)
186 | 
187 |   const rules = this.ruler2.getRules('')
188 |   const len = rules.length
189 | 
190 |   for (let i = 0; i < len; i++) {
191 |     rules[i](state)
192 |   }
193 | }
194 | 
195 | ParserInline.prototype.State = StateInline
196 | 
197 | export default ParserInline
198 | 


--------------------------------------------------------------------------------
/lib/presets/commonmark.mjs:
--------------------------------------------------------------------------------
 1 | // Commonmark default options
 2 | 
 3 | export default {
 4 |   options: {
 5 |     // Enable HTML tags in source
 6 |     html: true,
 7 | 
 8 |     // Use '/' to close single tags (<br />)
 9 |     xhtmlOut: true,
10 | 
11 |     // Convert '\n' in paragraphs into <br>
12 |     breaks: false,
13 | 
14 |     // CSS language prefix for fenced blocks
15 |     langPrefix: 'language-',
16 | 
17 |     // autoconvert URL-like texts to links
18 |     linkify: false,
19 | 
20 |     // Enable some language-neutral replacements + quotes beautification
21 |     typographer: false,
22 | 
23 |     // Double + single quotes replacement pairs, when typographer enabled,
24 |     // and smartquotes on. Could be either a String or an Array.
25 |     //
26 |     // For example, you can use '«»„“' for Russian, '„“‚‘' for German,
27 |     // and ['«\xA0', '\xA0»', '‹\xA0', '\xA0›'] for French (including nbsp).
28 |     quotes: '\u201c\u201d\u2018\u2019', /* “”‘’ */
29 | 
30 |     // Highlighter function. Should return escaped HTML,
31 |     // or '' if the source string is not changed and should be escaped externaly.
32 |     // If result starts with <pre... internal wrapper is skipped.
33 |     //
34 |     // function (/*str, lang*/) { return ''; }
35 |     //
36 |     highlight: null,
37 | 
38 |     // Internal protection, recursion limit
39 |     maxNesting: 20
40 |   },
41 | 
42 |   components: {
43 | 
44 |     core: {
45 |       rules: [
46 |         'normalize',
47 |         'block',
48 |         'inline',
49 |         'text_join'
50 |       ]
51 |     },
52 | 
53 |     block: {
54 |       rules: [
55 |         'blockquote',
56 |         'code',
57 |         'fence',
58 |         'heading',
59 |         'hr',
60 |         'html_block',
61 |         'lheading',
62 |         'list',
63 |         'reference',
64 |         'paragraph'
65 |       ]
66 |     },
67 | 
68 |     inline: {
69 |       rules: [
70 |         'autolink',
71 |         'backticks',
72 |         'emphasis',
73 |         'entity',
74 |         'escape',
75 |         'html_inline',
76 |         'image',
77 |         'link',
78 |         'newline',
79 |         'text'
80 |       ],
81 |       rules2: [
82 |         'balance_pairs',
83 |         'emphasis',
84 |         'fragments_join'
85 |       ]
86 |     }
87 |   }
88 | }
89 | 


--------------------------------------------------------------------------------
/lib/presets/default.mjs:
--------------------------------------------------------------------------------
 1 | // markdown-it default options
 2 | 
 3 | export default {
 4 |   options: {
 5 |     // Enable HTML tags in source
 6 |     html: false,
 7 | 
 8 |     // Use '/' to close single tags (<br />)
 9 |     xhtmlOut: false,
10 | 
11 |     // Convert '\n' in paragraphs into <br>
12 |     breaks: false,
13 | 
14 |     // CSS language prefix for fenced blocks
15 |     langPrefix: 'language-',
16 | 
17 |     // autoconvert URL-like texts to links
18 |     linkify: false,
19 | 
20 |     // Enable some language-neutral replacements + quotes beautification
21 |     typographer: false,
22 | 
23 |     // Double + single quotes replacement pairs, when typographer enabled,
24 |     // and smartquotes on. Could be either a String or an Array.
25 |     //
26 |     // For example, you can use '«»„“' for Russian, '„“‚‘' for German,
27 |     // and ['«\xA0', '\xA0»', '‹\xA0', '\xA0›'] for French (including nbsp).
28 |     quotes: '\u201c\u201d\u2018\u2019', /* “”‘’ */
29 | 
30 |     // Highlighter function. Should return escaped HTML,
31 |     // or '' if the source string is not changed and should be escaped externaly.
32 |     // If result starts with <pre... internal wrapper is skipped.
33 |     //
34 |     // function (/*str, lang*/) { return ''; }
35 |     //
36 |     highlight: null,
37 | 
38 |     // Internal protection, recursion limit
39 |     maxNesting: 100
40 |   },
41 | 
42 |   components: {
43 |     core: {},
44 |     block: {},
45 |     inline: {}
46 |   }
47 | }
48 | 


--------------------------------------------------------------------------------
/lib/presets/zero.mjs:
--------------------------------------------------------------------------------
 1 | // "Zero" preset, with nothing enabled. Useful for manual configuring of simple
 2 | // modes. For example, to parse bold/italic only.
 3 | 
 4 | export default {
 5 |   options: {
 6 |     // Enable HTML tags in source
 7 |     html: false,
 8 | 
 9 |     // Use '/' to close single tags (<br />)
10 |     xhtmlOut: false,
11 | 
12 |     // Convert '\n' in paragraphs into <br>
13 |     breaks: false,
14 | 
15 |     // CSS language prefix for fenced blocks
16 |     langPrefix: 'language-',
17 | 
18 |     // autoconvert URL-like texts to links
19 |     linkify: false,
20 | 
21 |     // Enable some language-neutral replacements + quotes beautification
22 |     typographer: false,
23 | 
24 |     // Double + single quotes replacement pairs, when typographer enabled,
25 |     // and smartquotes on. Could be either a String or an Array.
26 |     //
27 |     // For example, you can use '«»„“' for Russian, '„“‚‘' for German,
28 |     // and ['«\xA0', '\xA0»', '‹\xA0', '\xA0›'] for French (including nbsp).
29 |     quotes: '\u201c\u201d\u2018\u2019', /* “”‘’ */
30 | 
31 |     // Highlighter function. Should return escaped HTML,
32 |     // or '' if the source string is not changed and should be escaped externaly.
33 |     // If result starts with <pre... internal wrapper is skipped.
34 |     //
35 |     // function (/*str, lang*/) { return ''; }
36 |     //
37 |     highlight: null,
38 | 
39 |     // Internal protection, recursion limit
40 |     maxNesting: 20
41 |   },
42 | 
43 |   components: {
44 | 
45 |     core: {
46 |       rules: [
47 |         'normalize',
48 |         'block',
49 |         'inline',
50 |         'text_join'
51 |       ]
52 |     },
53 | 
54 |     block: {
55 |       rules: [
56 |         'paragraph'
57 |       ]
58 |     },
59 | 
60 |     inline: {
61 |       rules: [
62 |         'text'
63 |       ],
64 |       rules2: [
65 |         'balance_pairs',
66 |         'fragments_join'
67 |       ]
68 |     }
69 |   }
70 | }
71 | 


--------------------------------------------------------------------------------
/lib/rules_block/code.mjs:
--------------------------------------------------------------------------------
 1 | // Code block (4 spaces padded)
 2 | 
 3 | export default function code (state, startLine, endLine/*, silent */) {
 4 |   if (state.sCount[startLine] - state.blkIndent < 4) { return false }
 5 | 
 6 |   let nextLine = startLine + 1
 7 |   let last = nextLine
 8 | 
 9 |   while (nextLine < endLine) {
10 |     if (state.isEmpty(nextLine)) {
11 |       nextLine++
12 |       continue
13 |     }
14 | 
15 |     if (state.sCount[nextLine] - state.blkIndent >= 4) {
16 |       nextLine++
17 |       last = nextLine
18 |       continue
19 |     }
20 |     break
21 |   }
22 | 
23 |   state.line = last
24 | 
25 |   const token   = state.push('code_block', 'code', 0)
26 |   token.content = state.getLines(startLine, last, 4 + state.blkIndent, false) + '\n'
27 |   token.map     = [startLine, state.line]
28 | 
29 |   return true
30 | }
31 | 


--------------------------------------------------------------------------------
/lib/rules_block/fence.mjs:
--------------------------------------------------------------------------------
 1 | // fences (``` lang, ~~~ lang)
 2 | 
 3 | export default function fence (state, startLine, endLine, silent) {
 4 |   let pos = state.bMarks[startLine] + state.tShift[startLine]
 5 |   let max = state.eMarks[startLine]
 6 | 
 7 |   // if it's indented more than 3 spaces, it should be a code block
 8 |   if (state.sCount[startLine] - state.blkIndent >= 4) { return false }
 9 | 
10 |   if (pos + 3 > max) { return false }
11 | 
12 |   const marker = state.src.charCodeAt(pos)
13 | 
14 |   if (marker !== 0x7E/* ~ */ && marker !== 0x60 /* ` */) {
15 |     return false
16 |   }
17 | 
18 |   // scan marker length
19 |   let mem = pos
20 |   pos = state.skipChars(pos, marker)
21 | 
22 |   let len = pos - mem
23 | 
24 |   if (len < 3) { return false }
25 | 
26 |   const markup = state.src.slice(mem, pos)
27 |   const params = state.src.slice(pos, max)
28 | 
29 |   if (marker === 0x60 /* ` */) {
30 |     if (params.indexOf(String.fromCharCode(marker)) >= 0) {
31 |       return false
32 |     }
33 |   }
34 | 
35 |   // Since start is found, we can report success here in validation mode
36 |   if (silent) { return true }
37 | 
38 |   // search end of block
39 |   let nextLine = startLine
40 |   let haveEndMarker = false
41 | 
42 |   for (;;) {
43 |     nextLine++
44 |     if (nextLine >= endLine) {
45 |       // unclosed block should be autoclosed by end of document.
46 |       // also block seems to be autoclosed by end of parent
47 |       break
48 |     }
49 | 
50 |     pos = mem = state.bMarks[nextLine] + state.tShift[nextLine]
51 |     max = state.eMarks[nextLine]
52 | 
53 |     if (pos < max && state.sCount[nextLine] < state.blkIndent) {
54 |       // non-empty line with negative indent should stop the list:
55 |       // - ```
56 |       //  test
57 |       break
58 |     }
59 | 
60 |     if (state.src.charCodeAt(pos) !== marker) { continue }
61 | 
62 |     if (state.sCount[nextLine] - state.blkIndent >= 4) {
63 |       // closing fence should be indented less than 4 spaces
64 |       continue
65 |     }
66 | 
67 |     pos = state.skipChars(pos, marker)
68 | 
69 |     // closing code fence must be at least as long as the opening one
70 |     if (pos - mem < len) { continue }
71 | 
72 |     // make sure tail has spaces only
73 |     pos = state.skipSpaces(pos)
74 | 
75 |     if (pos < max) { continue }
76 | 
77 |     haveEndMarker = true
78 |     // found!
79 |     break
80 |   }
81 | 
82 |   // If a fence has heading spaces, they should be removed from its inner block
83 |   len = state.sCount[startLine]
84 | 
85 |   state.line = nextLine + (haveEndMarker ? 1 : 0)
86 | 
87 |   const token   = state.push('fence', 'code', 0)
88 |   token.info    = params
89 |   token.content = state.getLines(startLine + 1, nextLine, len, true)
90 |   token.markup  = markup
91 |   token.map     = [startLine, state.line]
92 | 
93 |   return true
94 | }
95 | 


--------------------------------------------------------------------------------
/lib/rules_block/heading.mjs:
--------------------------------------------------------------------------------
 1 | // heading (#, ##, ...)
 2 | 
 3 | import { isSpace } from '../common/utils.mjs'
 4 | 
 5 | export default function heading (state, startLine, endLine, silent) {
 6 |   let pos = state.bMarks[startLine] + state.tShift[startLine]
 7 |   let max = state.eMarks[startLine]
 8 | 
 9 |   // if it's indented more than 3 spaces, it should be a code block
10 |   if (state.sCount[startLine] - state.blkIndent >= 4) { return false }
11 | 
12 |   let ch  = state.src.charCodeAt(pos)
13 | 
14 |   if (ch !== 0x23/* # */ || pos >= max) { return false }
15 | 
16 |   // count heading level
17 |   let level = 1
18 |   ch = state.src.charCodeAt(++pos)
19 |   while (ch === 0x23/* # */ && pos < max && level <= 6) {
20 |     level++
21 |     ch = state.src.charCodeAt(++pos)
22 |   }
23 | 
24 |   if (level > 6 || (pos < max && !isSpace(ch))) { return false }
25 | 
26 |   if (silent) { return true }
27 | 
28 |   // Let's cut tails like '    ###  ' from the end of string
29 | 
30 |   max = state.skipSpacesBack(max, pos)
31 |   const tmp = state.skipCharsBack(max, 0x23, pos) // #
32 |   if (tmp > pos && isSpace(state.src.charCodeAt(tmp - 1))) {
33 |     max = tmp
34 |   }
35 | 
36 |   state.line = startLine + 1
37 | 
38 |   const token_o  = state.push('heading_open', 'h' + String(level), 1)
39 |   token_o.markup = '########'.slice(0, level)
40 |   token_o.map    = [startLine, state.line]
41 | 
42 |   const token_i    = state.push('inline', '', 0)
43 |   token_i.content  = state.src.slice(pos, max).trim()
44 |   token_i.map      = [startLine, state.line]
45 |   token_i.children = []
46 | 
47 |   const token_c  = state.push('heading_close', 'h' + String(level), -1)
48 |   token_c.markup = '########'.slice(0, level)
49 | 
50 |   return true
51 | }
52 | 


--------------------------------------------------------------------------------
/lib/rules_block/hr.mjs:
--------------------------------------------------------------------------------
 1 | // Horizontal rule
 2 | 
 3 | import { isSpace } from '../common/utils.mjs'
 4 | 
 5 | export default function hr (state, startLine, endLine, silent) {
 6 |   const max = state.eMarks[startLine]
 7 |   // if it's indented more than 3 spaces, it should be a code block
 8 |   if (state.sCount[startLine] - state.blkIndent >= 4) { return false }
 9 | 
10 |   let pos = state.bMarks[startLine] + state.tShift[startLine]
11 |   const marker = state.src.charCodeAt(pos++)
12 | 
13 |   // Check hr marker
14 |   if (marker !== 0x2A/* * */ &&
15 |       marker !== 0x2D/* - */ &&
16 |       marker !== 0x5F/* _ */) {
17 |     return false
18 |   }
19 | 
20 |   // markers can be mixed with spaces, but there should be at least 3 of them
21 | 
22 |   let cnt = 1
23 |   while (pos < max) {
24 |     const ch = state.src.charCodeAt(pos++)
25 |     if (ch !== marker && !isSpace(ch)) { return false }
26 |     if (ch === marker) { cnt++ }
27 |   }
28 | 
29 |   if (cnt < 3) { return false }
30 | 
31 |   if (silent) { return true }
32 | 
33 |   state.line = startLine + 1
34 | 
35 |   const token  = state.push('hr', 'hr', 0)
36 |   token.map    = [startLine, state.line]
37 |   token.markup = Array(cnt + 1).join(String.fromCharCode(marker))
38 | 
39 |   return true
40 | }
41 | 


--------------------------------------------------------------------------------
/lib/rules_block/html_block.mjs:
--------------------------------------------------------------------------------
 1 | // HTML block
 2 | 
 3 | import block_names from '../common/html_blocks.mjs'
 4 | import { HTML_OPEN_CLOSE_TAG_RE } from '../common/html_re.mjs'
 5 | 
 6 | // An array of opening and corresponding closing sequences for html tags,
 7 | // last argument defines whether it can terminate a paragraph or not
 8 | //
 9 | const HTML_SEQUENCES = [
10 |   [/^<(script|pre|style|textarea)(?=(\s|>|$))/i, /<\/(script|pre|style|textarea)>/i, true],
11 |   [/^<!--/,        /-->/,   true],
12 |   [/^<\?/,         /\?>/,   true],
13 |   [/^<![A-Z]/,     />/,     true],
14 |   [/^<!\[CDATA\[/, /\]\]>/, true],
15 |   [new RegExp('^</?(' + block_names.join('|') + ')(?=(\\s|/?>|$))', 'i'), /^$/, true],
16 |   [new RegExp(HTML_OPEN_CLOSE_TAG_RE.source + '\\s*
#39;),  /^$/, false]
17 | ]
18 | 
19 | export default function html_block (state, startLine, endLine, silent) {
20 |   let pos = state.bMarks[startLine] + state.tShift[startLine]
21 |   let max = state.eMarks[startLine]
22 | 
23 |   // if it's indented more than 3 spaces, it should be a code block
24 |   if (state.sCount[startLine] - state.blkIndent >= 4) { return false }
25 | 
26 |   if (!state.md.options.html) { return false }
27 | 
28 |   if (state.src.charCodeAt(pos) !== 0x3C/* < */) { return false }
29 | 
30 |   let lineText = state.src.slice(pos, max)
31 | 
32 |   let i = 0
33 |   for (; i < HTML_SEQUENCES.length; i++) {
34 |     if (HTML_SEQUENCES[i][0].test(lineText)) { break }
35 |   }
36 |   if (i === HTML_SEQUENCES.length) { return false }
37 | 
38 |   if (silent) {
39 |     // true if this sequence can be a terminator, false otherwise
40 |     return HTML_SEQUENCES[i][2]
41 |   }
42 | 
43 |   let nextLine = startLine + 1
44 | 
45 |   // If we are here - we detected HTML block.
46 |   // Let's roll down till block end.
47 |   if (!HTML_SEQUENCES[i][1].test(lineText)) {
48 |     for (; nextLine < endLine; nextLine++) {
49 |       if (state.sCount[nextLine] < state.blkIndent) { break }
50 | 
51 |       pos = state.bMarks[nextLine] + state.tShift[nextLine]
52 |       max = state.eMarks[nextLine]
53 |       lineText = state.src.slice(pos, max)
54 | 
55 |       if (HTML_SEQUENCES[i][1].test(lineText)) {
56 |         if (lineText.length !== 0) { nextLine++ }
57 |         break
58 |       }
59 |     }
60 |   }
61 | 
62 |   state.line = nextLine
63 | 
64 |   const token   = state.push('html_block', '', 0)
65 |   token.map     = [startLine, nextLine]
66 |   token.content = state.getLines(startLine, nextLine, state.blkIndent, true)
67 | 
68 |   return true
69 | }
70 | 


--------------------------------------------------------------------------------
/lib/rules_block/lheading.mjs:
--------------------------------------------------------------------------------
 1 | // lheading (---, ===)
 2 | 
 3 | export default function lheading (state, startLine, endLine/*, silent */) {
 4 |   const terminatorRules = state.md.block.ruler.getRules('paragraph')
 5 | 
 6 |   // if it's indented more than 3 spaces, it should be a code block
 7 |   if (state.sCount[startLine] - state.blkIndent >= 4) { return false }
 8 | 
 9 |   const oldParentType = state.parentType
10 |   state.parentType = 'paragraph' // use paragraph to match terminatorRules
11 | 
12 |   // jump line-by-line until empty one or EOF
13 |   let level = 0
14 |   let marker
15 |   let nextLine = startLine + 1
16 | 
17 |   for (; nextLine < endLine && !state.isEmpty(nextLine); nextLine++) {
18 |     // this would be a code block normally, but after paragraph
19 |     // it's considered a lazy continuation regardless of what's there
20 |     if (state.sCount[nextLine] - state.blkIndent > 3) { continue }
21 | 
22 |     //
23 |     // Check for underline in setext header
24 |     //
25 |     if (state.sCount[nextLine] >= state.blkIndent) {
26 |       let pos = state.bMarks[nextLine] + state.tShift[nextLine]
27 |       const max = state.eMarks[nextLine]
28 | 
29 |       if (pos < max) {
30 |         marker = state.src.charCodeAt(pos)
31 | 
32 |         if (marker === 0x2D/* - */ || marker === 0x3D/* = */) {
33 |           pos = state.skipChars(pos, marker)
34 |           pos = state.skipSpaces(pos)
35 | 
36 |           if (pos >= max) {
37 |             level = (marker === 0x3D/* = */ ? 1 : 2)
38 |             break
39 |           }
40 |         }
41 |       }
42 |     }
43 | 
44 |     // quirk for blockquotes, this line should already be checked by that rule
45 |     if (state.sCount[nextLine] < 0) { continue }
46 | 
47 |     // Some tags can terminate paragraph without empty line.
48 |     let terminate = false
49 |     for (let i = 0, l = terminatorRules.length; i < l; i++) {
50 |       if (terminatorRules[i](state, nextLine, endLine, true)) {
51 |         terminate = true
52 |         break
53 |       }
54 |     }
55 |     if (terminate) { break }
56 |   }
57 | 
58 |   if (!level) {
59 |     // Didn't find valid underline
60 |     return false
61 |   }
62 | 
63 |   const content = state.getLines(startLine, nextLine, state.blkIndent, false).trim()
64 | 
65 |   state.line = nextLine + 1
66 | 
67 |   const token_o    = state.push('heading_open', 'h' + String(level), 1)
68 |   token_o.markup   = String.fromCharCode(marker)
69 |   token_o.map      = [startLine, state.line]
70 | 
71 |   const token_i    = state.push('inline', '', 0)
72 |   token_i.content  = content
73 |   token_i.map      = [startLine, state.line - 1]
74 |   token_i.children = []
75 | 
76 |   const token_c    = state.push('heading_close', 'h' + String(level), -1)
77 |   token_c.markup   = String.fromCharCode(marker)
78 | 
79 |   state.parentType = oldParentType
80 | 
81 |   return true
82 | }
83 | 


--------------------------------------------------------------------------------
/lib/rules_block/paragraph.mjs:
--------------------------------------------------------------------------------
 1 | // Paragraph
 2 | 
 3 | export default function paragraph (state, startLine, endLine) {
 4 |   const terminatorRules = state.md.block.ruler.getRules('paragraph')
 5 |   const oldParentType = state.parentType
 6 |   let nextLine = startLine + 1
 7 |   state.parentType = 'paragraph'
 8 | 
 9 |   // jump line-by-line until empty one or EOF
10 |   for (; nextLine < endLine && !state.isEmpty(nextLine); nextLine++) {
11 |     // this would be a code block normally, but after paragraph
12 |     // it's considered a lazy continuation regardless of what's there
13 |     if (state.sCount[nextLine] - state.blkIndent > 3) { continue }
14 | 
15 |     // quirk for blockquotes, this line should already be checked by that rule
16 |     if (state.sCount[nextLine] < 0) { continue }
17 | 
18 |     // Some tags can terminate paragraph without empty line.
19 |     let terminate = false
20 |     for (let i = 0, l = terminatorRules.length; i < l; i++) {
21 |       if (terminatorRules[i](state, nextLine, endLine, true)) {
22 |         terminate = true
23 |         break
24 |       }
25 |     }
26 |     if (terminate) { break }
27 |   }
28 | 
29 |   const content = state.getLines(startLine, nextLine, state.blkIndent, false).trim()
30 | 
31 |   state.line = nextLine
32 | 
33 |   const token_o    = state.push('paragraph_open', 'p', 1)
34 |   token_o.map      = [startLine, state.line]
35 | 
36 |   const token_i    = state.push('inline', '', 0)
37 |   token_i.content  = content
38 |   token_i.map      = [startLine, state.line]
39 |   token_i.children = []
40 | 
41 |   state.push('paragraph_close', 'p', -1)
42 | 
43 |   state.parentType = oldParentType
44 | 
45 |   return true
46 | }
47 | 


--------------------------------------------------------------------------------
/lib/rules_block/reference.mjs:
--------------------------------------------------------------------------------
  1 | import { isSpace, normalizeReference } from '../common/utils.mjs'
  2 | 
  3 | export default function reference (state, startLine, _endLine, silent) {
  4 |   let pos = state.bMarks[startLine] + state.tShift[startLine]
  5 |   let max = state.eMarks[startLine]
  6 |   let nextLine = startLine + 1
  7 | 
  8 |   // if it's indented more than 3 spaces, it should be a code block
  9 |   if (state.sCount[startLine] - state.blkIndent >= 4) { return false }
 10 | 
 11 |   if (state.src.charCodeAt(pos) !== 0x5B/* [ */) { return false }
 12 | 
 13 |   function getNextLine (nextLine) {
 14 |     const endLine = state.lineMax
 15 | 
 16 |     if (nextLine >= endLine || state.isEmpty(nextLine)) {
 17 |       // empty line or end of input
 18 |       return null
 19 |     }
 20 | 
 21 |     let isContinuation = false
 22 | 
 23 |     // this would be a code block normally, but after paragraph
 24 |     // it's considered a lazy continuation regardless of what's there
 25 |     if (state.sCount[nextLine] - state.blkIndent > 3) { isContinuation = true }
 26 | 
 27 |     // quirk for blockquotes, this line should already be checked by that rule
 28 |     if (state.sCount[nextLine] < 0) { isContinuation = true }
 29 | 
 30 |     if (!isContinuation) {
 31 |       const terminatorRules = state.md.block.ruler.getRules('reference')
 32 |       const oldParentType = state.parentType
 33 |       state.parentType = 'reference'
 34 | 
 35 |       // Some tags can terminate paragraph without empty line.
 36 |       let terminate = false
 37 |       for (let i = 0, l = terminatorRules.length; i < l; i++) {
 38 |         if (terminatorRules[i](state, nextLine, endLine, true)) {
 39 |           terminate = true
 40 |           break
 41 |         }
 42 |       }
 43 | 
 44 |       state.parentType = oldParentType
 45 |       if (terminate) {
 46 |         // terminated by another block
 47 |         return null
 48 |       }
 49 |     }
 50 | 
 51 |     const pos = state.bMarks[nextLine] + state.tShift[nextLine]
 52 |     const max = state.eMarks[nextLine]
 53 | 
 54 |     // max + 1 explicitly includes the newline
 55 |     return state.src.slice(pos, max + 1)
 56 |   }
 57 | 
 58 |   let str = state.src.slice(pos, max + 1)
 59 | 
 60 |   max = str.length
 61 |   let labelEnd = -1
 62 | 
 63 |   for (pos = 1; pos < max; pos++) {
 64 |     const ch = str.charCodeAt(pos)
 65 |     if (ch === 0x5B /* [ */) {
 66 |       return false
 67 |     } else if (ch === 0x5D /* ] */) {
 68 |       labelEnd = pos
 69 |       break
 70 |     } else if (ch === 0x0A /* \n */) {
 71 |       const lineContent = getNextLine(nextLine)
 72 |       if (lineContent !== null) {
 73 |         str += lineContent
 74 |         max = str.length
 75 |         nextLine++
 76 |       }
 77 |     } else if (ch === 0x5C /* \ */) {
 78 |       pos++
 79 |       if (pos < max && str.charCodeAt(pos) === 0x0A) {
 80 |         const lineContent = getNextLine(nextLine)
 81 |         if (lineContent !== null) {
 82 |           str += lineContent
 83 |           max = str.length
 84 |           nextLine++
 85 |         }
 86 |       }
 87 |     }
 88 |   }
 89 | 
 90 |   if (labelEnd < 0 || str.charCodeAt(labelEnd + 1) !== 0x3A/* : */) { return false }
 91 | 
 92 |   // [label]:   destination   'title'
 93 |   //         ^^^ skip optional whitespace here
 94 |   for (pos = labelEnd + 2; pos < max; pos++) {
 95 |     const ch = str.charCodeAt(pos)
 96 |     if (ch === 0x0A) {
 97 |       const lineContent = getNextLine(nextLine)
 98 |       if (lineContent !== null) {
 99 |         str += lineContent
100 |         max = str.length
101 |         nextLine++
102 |       }
103 |     } else if (isSpace(ch)) {
104 |       /* eslint no-empty:0 */
105 |     } else {
106 |       break
107 |     }
108 |   }
109 | 
110 |   // [label]:   destination   'title'
111 |   //            ^^^^^^^^^^^ parse this
112 |   const destRes = state.md.helpers.parseLinkDestination(str, pos, max)
113 |   if (!destRes.ok) { return false }
114 | 
115 |   const href = state.md.normalizeLink(destRes.str)
116 |   if (!state.md.validateLink(href)) { return false }
117 | 
118 |   pos = destRes.pos
119 | 
120 |   // save cursor state, we could require to rollback later
121 |   const destEndPos = pos
122 |   const destEndLineNo = nextLine
123 | 
124 |   // [label]:   destination   'title'
125 |   //                       ^^^ skipping those spaces
126 |   const start = pos
127 |   for (; pos < max; pos++) {
128 |     const ch = str.charCodeAt(pos)
129 |     if (ch === 0x0A) {
130 |       const lineContent = getNextLine(nextLine)
131 |       if (lineContent !== null) {
132 |         str += lineContent
133 |         max = str.length
134 |         nextLine++
135 |       }
136 |     } else if (isSpace(ch)) {
137 |       /* eslint no-empty:0 */
138 |     } else {
139 |       break
140 |     }
141 |   }
142 | 
143 |   // [label]:   destination   'title'
144 |   //                          ^^^^^^^ parse this
145 |   let titleRes = state.md.helpers.parseLinkTitle(str, pos, max)
146 |   while (titleRes.can_continue) {
147 |     const lineContent = getNextLine(nextLine)
148 |     if (lineContent === null) break
149 |     str += lineContent
150 |     pos = max
151 |     max = str.length
152 |     nextLine++
153 |     titleRes = state.md.helpers.parseLinkTitle(str, pos, max, titleRes)
154 |   }
155 |   let title
156 | 
157 |   if (pos < max && start !== pos && titleRes.ok) {
158 |     title = titleRes.str
159 |     pos = titleRes.pos
160 |   } else {
161 |     title = ''
162 |     pos = destEndPos
163 |     nextLine = destEndLineNo
164 |   }
165 | 
166 |   // skip trailing spaces until the rest of the line
167 |   while (pos < max) {
168 |     const ch = str.charCodeAt(pos)
169 |     if (!isSpace(ch)) { break }
170 |     pos++
171 |   }
172 | 
173 |   if (pos < max && str.charCodeAt(pos) !== 0x0A) {
174 |     if (title) {
175 |       // garbage at the end of the line after title,
176 |       // but it could still be a valid reference if we roll back
177 |       title = ''
178 |       pos = destEndPos
179 |       nextLine = destEndLineNo
180 |       while (pos < max) {
181 |         const ch = str.charCodeAt(pos)
182 |         if (!isSpace(ch)) { break }
183 |         pos++
184 |       }
185 |     }
186 |   }
187 | 
188 |   if (pos < max && str.charCodeAt(pos) !== 0x0A) {
189 |     // garbage at the end of the line
190 |     return false
191 |   }
192 | 
193 |   const label = normalizeReference(str.slice(1, labelEnd))
194 |   if (!label) {
195 |     // CommonMark 0.20 disallows empty labels
196 |     return false
197 |   }
198 | 
199 |   // Reference can not terminate anything. This check is for safety only.
200 |   /* istanbul ignore if */
201 |   if (silent) { return true }
202 | 
203 |   if (typeof state.env.references === 'undefined') {
204 |     state.env.references = {}
205 |   }
206 |   if (typeof state.env.references[label] === 'undefined') {
207 |     state.env.references[label] = { title, href }
208 |   }
209 | 
210 |   state.line = nextLine
211 |   return true
212 | }
213 | 


--------------------------------------------------------------------------------
/lib/rules_core/block.mjs:
--------------------------------------------------------------------------------
 1 | export default function block (state) {
 2 |   let token
 3 | 
 4 |   if (state.inlineMode) {
 5 |     token          = new state.Token('inline', '', 0)
 6 |     token.content  = state.src
 7 |     token.map      = [0, 1]
 8 |     token.children = []
 9 |     state.tokens.push(token)
10 |   } else {
11 |     state.md.block.parse(state.src, state.md, state.env, state.tokens)
12 |   }
13 | }
14 | 


--------------------------------------------------------------------------------
/lib/rules_core/inline.mjs:
--------------------------------------------------------------------------------
 1 | export default function inline (state) {
 2 |   const tokens = state.tokens
 3 | 
 4 |   // Parse inlines
 5 |   for (let i = 0, l = tokens.length; i < l; i++) {
 6 |     const tok = tokens[i]
 7 |     if (tok.type === 'inline') {
 8 |       state.md.inline.parse(tok.content, state.md, state.env, tok.children)
 9 |     }
10 |   }
11 | }
12 | 


--------------------------------------------------------------------------------
/lib/rules_core/linkify.mjs:
--------------------------------------------------------------------------------
  1 | // Replace link-like texts with link nodes.
  2 | //
  3 | // Currently restricted by `md.validateLink()` to http/https/ftp
  4 | //
  5 | 
  6 | import { arrayReplaceAt } from '../common/utils.mjs'
  7 | 
  8 | function isLinkOpen (str) {
  9 |   return /^<a[>\s]/i.test(str)
 10 | }
 11 | function isLinkClose (str) {
 12 |   return /^<\/a\s*>/i.test(str)
 13 | }
 14 | 
 15 | export default function linkify (state) {
 16 |   const blockTokens = state.tokens
 17 | 
 18 |   if (!state.md.options.linkify) { return }
 19 | 
 20 |   for (let j = 0, l = blockTokens.length; j < l; j++) {
 21 |     if (blockTokens[j].type !== 'inline' ||
 22 |         !state.md.linkify.pretest(blockTokens[j].content)) {
 23 |       continue
 24 |     }
 25 | 
 26 |     let tokens = blockTokens[j].children
 27 | 
 28 |     let htmlLinkLevel = 0
 29 | 
 30 |     // We scan from the end, to keep position when new tags added.
 31 |     // Use reversed logic in links start/end match
 32 |     for (let i = tokens.length - 1; i >= 0; i--) {
 33 |       const currentToken = tokens[i]
 34 | 
 35 |       // Skip content of markdown links
 36 |       if (currentToken.type === 'link_close') {
 37 |         i--
 38 |         while (tokens[i].level !== currentToken.level && tokens[i].type !== 'link_open') {
 39 |           i--
 40 |         }
 41 |         continue
 42 |       }
 43 | 
 44 |       // Skip content of html tag links
 45 |       if (currentToken.type === 'html_inline') {
 46 |         if (isLinkOpen(currentToken.content) && htmlLinkLevel > 0) {
 47 |           htmlLinkLevel--
 48 |         }
 49 |         if (isLinkClose(currentToken.content)) {
 50 |           htmlLinkLevel++
 51 |         }
 52 |       }
 53 |       if (htmlLinkLevel > 0) { continue }
 54 | 
 55 |       if (currentToken.type === 'text' && state.md.linkify.test(currentToken.content)) {
 56 |         const text = currentToken.content
 57 |         let links = state.md.linkify.match(text)
 58 | 
 59 |         // Now split string to nodes
 60 |         const nodes = []
 61 |         let level = currentToken.level
 62 |         let lastPos = 0
 63 | 
 64 |         // forbid escape sequence at the start of the string,
 65 |         // this avoids http\://example.com/ from being linkified as
 66 |         // http:<a href="//example.com/">//example.com/</a>
 67 |         if (links.length > 0 &&
 68 |             links[0].index === 0 &&
 69 |             i > 0 &&
 70 |             tokens[i - 1].type === 'text_special') {
 71 |           links = links.slice(1)
 72 |         }
 73 | 
 74 |         for (let ln = 0; ln < links.length; ln++) {
 75 |           const url = links[ln].url
 76 |           const fullUrl = state.md.normalizeLink(url)
 77 |           if (!state.md.validateLink(fullUrl)) { continue }
 78 | 
 79 |           let urlText = links[ln].text
 80 | 
 81 |           // Linkifier might send raw hostnames like "example.com", where url
 82 |           // starts with domain name. So we prepend http:// in those cases,
 83 |           // and remove it afterwards.
 84 |           //
 85 |           if (!links[ln].schema) {
 86 |             urlText = state.md.normalizeLinkText('http://' + urlText).replace(/^http:\/\//, '')
 87 |           } else if (links[ln].schema === 'mailto:' && !/^mailto:/i.test(urlText)) {
 88 |             urlText = state.md.normalizeLinkText('mailto:' + urlText).replace(/^mailto:/, '')
 89 |           } else {
 90 |             urlText = state.md.normalizeLinkText(urlText)
 91 |           }
 92 | 
 93 |           const pos = links[ln].index
 94 | 
 95 |           if (pos > lastPos) {
 96 |             const token   = new state.Token('text', '', 0)
 97 |             token.content = text.slice(lastPos, pos)
 98 |             token.level   = level
 99 |             nodes.push(token)
100 |           }
101 | 
102 |           const token_o   = new state.Token('link_open', 'a', 1)
103 |           token_o.attrs   = [['href', fullUrl]]
104 |           token_o.level   = level++
105 |           token_o.markup  = 'linkify'
106 |           token_o.info    = 'auto'
107 |           nodes.push(token_o)
108 | 
109 |           const token_t   = new state.Token('text', '', 0)
110 |           token_t.content = urlText
111 |           token_t.level   = level
112 |           nodes.push(token_t)
113 | 
114 |           const token_c   = new state.Token('link_close', 'a', -1)
115 |           token_c.level   = --level
116 |           token_c.markup  = 'linkify'
117 |           token_c.info    = 'auto'
118 |           nodes.push(token_c)
119 | 
120 |           lastPos = links[ln].lastIndex
121 |         }
122 |         if (lastPos < text.length) {
123 |           const token   = new state.Token('text', '', 0)
124 |           token.content = text.slice(lastPos)
125 |           token.level   = level
126 |           nodes.push(token)
127 |         }
128 | 
129 |         // replace current node
130 |         blockTokens[j].children = tokens = arrayReplaceAt(tokens, i, nodes)
131 |       }
132 |     }
133 |   }
134 | }
135 | 


--------------------------------------------------------------------------------
/lib/rules_core/normalize.mjs:
--------------------------------------------------------------------------------
 1 | // Normalize input string
 2 | 
 3 | // https://spec.commonmark.org/0.29/#line-ending
 4 | const NEWLINES_RE  = /\r\n?|\n/g
 5 | const NULL_RE      = /\0/g
 6 | 
 7 | export default function normalize (state) {
 8 |   let str
 9 | 
10 |   // Normalize newlines
11 |   str = state.src.replace(NEWLINES_RE, '\n')
12 | 
13 |   // Replace NULL characters
14 |   str = str.replace(NULL_RE, '\uFFFD')
15 | 
16 |   state.src = str
17 | }
18 | 


--------------------------------------------------------------------------------
/lib/rules_core/replacements.mjs:
--------------------------------------------------------------------------------
  1 | // Simple typographic replacements
  2 | //
  3 | // (c) (C) → ©
  4 | // (tm) (TM) → ™
  5 | // (r) (R) → ®
  6 | // +- → ±
  7 | // ... → … (also ?.... → ?.., !.... → !..)
  8 | // ???????? → ???, !!!!! → !!!, `,,` → `,`
  9 | // -- → &ndash;, --- → &mdash;
 10 | //
 11 | 
 12 | // TODO:
 13 | // - fractionals 1/2, 1/4, 3/4 -> ½, ¼, ¾
 14 | // - multiplications 2 x 4 -> 2 × 4
 15 | 
 16 | const RARE_RE = /\+-|\.\.|\?\?\?\?|!!!!|,,|--/
 17 | 
 18 | // Workaround for phantomjs - need regex without /g flag,
 19 | // or root check will fail every second time
 20 | const SCOPED_ABBR_TEST_RE = /\((c|tm|r)\)/i
 21 | 
 22 | const SCOPED_ABBR_RE = /\((c|tm|r)\)/ig
 23 | const SCOPED_ABBR = {
 24 |   c: '©',
 25 |   r: '®',
 26 |   tm: '™'
 27 | }
 28 | 
 29 | function replaceFn (match, name) {
 30 |   return SCOPED_ABBR[name.toLowerCase()]
 31 | }
 32 | 
 33 | function replace_scoped (inlineTokens) {
 34 |   let inside_autolink = 0
 35 | 
 36 |   for (let i = inlineTokens.length - 1; i >= 0; i--) {
 37 |     const token = inlineTokens[i]
 38 | 
 39 |     if (token.type === 'text' && !inside_autolink) {
 40 |       token.content = token.content.replace(SCOPED_ABBR_RE, replaceFn)
 41 |     }
 42 | 
 43 |     if (token.type === 'link_open' && token.info === 'auto') {
 44 |       inside_autolink--
 45 |     }
 46 | 
 47 |     if (token.type === 'link_close' && token.info === 'auto') {
 48 |       inside_autolink++
 49 |     }
 50 |   }
 51 | }
 52 | 
 53 | function replace_rare (inlineTokens) {
 54 |   let inside_autolink = 0
 55 | 
 56 |   for (let i = inlineTokens.length - 1; i >= 0; i--) {
 57 |     const token = inlineTokens[i]
 58 | 
 59 |     if (token.type === 'text' && !inside_autolink) {
 60 |       if (RARE_RE.test(token.content)) {
 61 |         token.content = token.content
 62 |           .replace(/\+-/g, '±')
 63 |           // .., ..., ....... -> …
 64 |           // but ?..... & !..... -> ?.. & !..
 65 |           .replace(/\.{2,}/g, '…').replace(/([?!])…/g, '$1..')
 66 |           .replace(/([?!]){4,}/g, '$1$1$1').replace(/,{2,}/g, ',')
 67 |           // em-dash
 68 |           .replace(/(^|[^-])---(?=[^-]|$)/mg, '$1\u2014')
 69 |           // en-dash
 70 |           .replace(/(^|\s)--(?=\s|$)/mg, '$1\u2013')
 71 |           .replace(/(^|[^-\s])--(?=[^-\s]|$)/mg, '$1\u2013')
 72 |       }
 73 |     }
 74 | 
 75 |     if (token.type === 'link_open' && token.info === 'auto') {
 76 |       inside_autolink--
 77 |     }
 78 | 
 79 |     if (token.type === 'link_close' && token.info === 'auto') {
 80 |       inside_autolink++
 81 |     }
 82 |   }
 83 | }
 84 | 
 85 | export default function replace (state) {
 86 |   let blkIdx
 87 | 
 88 |   if (!state.md.options.typographer) { return }
 89 | 
 90 |   for (blkIdx = state.tokens.length - 1; blkIdx >= 0; blkIdx--) {
 91 |     if (state.tokens[blkIdx].type !== 'inline') { continue }
 92 | 
 93 |     if (SCOPED_ABBR_TEST_RE.test(state.tokens[blkIdx].content)) {
 94 |       replace_scoped(state.tokens[blkIdx].children)
 95 |     }
 96 | 
 97 |     if (RARE_RE.test(state.tokens[blkIdx].content)) {
 98 |       replace_rare(state.tokens[blkIdx].children)
 99 |     }
100 |   }
101 | }
102 | 


--------------------------------------------------------------------------------
/lib/rules_core/smartquotes.mjs:
--------------------------------------------------------------------------------
  1 | // Convert straight quotation marks to typographic ones
  2 | //
  3 | 
  4 | import { isWhiteSpace, isPunctChar, isMdAsciiPunct } from '../common/utils.mjs'
  5 | 
  6 | const QUOTE_TEST_RE = /['"]/
  7 | const QUOTE_RE = /['"]/g
  8 | const APOSTROPHE = '\u2019' /* ’ */
  9 | 
 10 | function replaceAt (str, index, ch) {
 11 |   return str.slice(0, index) + ch + str.slice(index + 1)
 12 | }
 13 | 
 14 | function process_inlines (tokens, state) {
 15 |   let j
 16 | 
 17 |   const stack = []
 18 | 
 19 |   for (let i = 0; i < tokens.length; i++) {
 20 |     const token = tokens[i]
 21 | 
 22 |     const thisLevel = tokens[i].level
 23 | 
 24 |     for (j = stack.length - 1; j >= 0; j--) {
 25 |       if (stack[j].level <= thisLevel) { break }
 26 |     }
 27 |     stack.length = j + 1
 28 | 
 29 |     if (token.type !== 'text') { continue }
 30 | 
 31 |     let text = token.content
 32 |     let pos = 0
 33 |     let max = text.length
 34 | 
 35 |     /* eslint no-labels:0,block-scoped-var:0 */
 36 |     OUTER:
 37 |     while (pos < max) {
 38 |       QUOTE_RE.lastIndex = pos
 39 |       const t = QUOTE_RE.exec(text)
 40 |       if (!t) { break }
 41 | 
 42 |       let canOpen = true
 43 |       let canClose = true
 44 |       pos = t.index + 1
 45 |       const isSingle = (t[0] === "'")
 46 | 
 47 |       // Find previous character,
 48 |       // default to space if it's the beginning of the line
 49 |       //
 50 |       let lastChar = 0x20
 51 | 
 52 |       if (t.index - 1 >= 0) {
 53 |         lastChar = text.charCodeAt(t.index - 1)
 54 |       } else {
 55 |         for (j = i - 1; j >= 0; j--) {
 56 |           if (tokens[j].type === 'softbreak' || tokens[j].type === 'hardbreak') break // lastChar defaults to 0x20
 57 |           if (!tokens[j].content) continue // should skip all tokens except 'text', 'html_inline' or 'code_inline'
 58 | 
 59 |           lastChar = tokens[j].content.charCodeAt(tokens[j].content.length - 1)
 60 |           break
 61 |         }
 62 |       }
 63 | 
 64 |       // Find next character,
 65 |       // default to space if it's the end of the line
 66 |       //
 67 |       let nextChar = 0x20
 68 | 
 69 |       if (pos < max) {
 70 |         nextChar = text.charCodeAt(pos)
 71 |       } else {
 72 |         for (j = i + 1; j < tokens.length; j++) {
 73 |           if (tokens[j].type === 'softbreak' || tokens[j].type === 'hardbreak') break // nextChar defaults to 0x20
 74 |           if (!tokens[j].content) continue // should skip all tokens except 'text', 'html_inline' or 'code_inline'
 75 | 
 76 |           nextChar = tokens[j].content.charCodeAt(0)
 77 |           break
 78 |         }
 79 |       }
 80 | 
 81 |       const isLastPunctChar = isMdAsciiPunct(lastChar) || isPunctChar(String.fromCharCode(lastChar))
 82 |       const isNextPunctChar = isMdAsciiPunct(nextChar) || isPunctChar(String.fromCharCode(nextChar))
 83 | 
 84 |       const isLastWhiteSpace = isWhiteSpace(lastChar)
 85 |       const isNextWhiteSpace = isWhiteSpace(nextChar)
 86 | 
 87 |       if (isNextWhiteSpace) {
 88 |         canOpen = false
 89 |       } else if (isNextPunctChar) {
 90 |         if (!(isLastWhiteSpace || isLastPunctChar)) {
 91 |           canOpen = false
 92 |         }
 93 |       }
 94 | 
 95 |       if (isLastWhiteSpace) {
 96 |         canClose = false
 97 |       } else if (isLastPunctChar) {
 98 |         if (!(isNextWhiteSpace || isNextPunctChar)) {
 99 |           canClose = false
100 |         }
101 |       }
102 | 
103 |       if (nextChar === 0x22 /* " */ && t[0] === '"') {
104 |         if (lastChar >= 0x30 /* 0 */ && lastChar <= 0x39 /* 9 */) {
105 |           // special case: 1"" - count first quote as an inch
106 |           canClose = canOpen = false
107 |         }
108 |       }
109 | 
110 |       if (canOpen && canClose) {
111 |         // Replace quotes in the middle of punctuation sequence, but not
112 |         // in the middle of the words, i.e.:
113 |         //
114 |         // 1. foo " bar " baz - not replaced
115 |         // 2. foo-"-bar-"-baz - replaced
116 |         // 3. foo"bar"baz     - not replaced
117 |         //
118 |         canOpen = isLastPunctChar
119 |         canClose = isNextPunctChar
120 |       }
121 | 
122 |       if (!canOpen && !canClose) {
123 |         // middle of word
124 |         if (isSingle) {
125 |           token.content = replaceAt(token.content, t.index, APOSTROPHE)
126 |         }
127 |         continue
128 |       }
129 | 
130 |       if (canClose) {
131 |         // this could be a closing quote, rewind the stack to get a match
132 |         for (j = stack.length - 1; j >= 0; j--) {
133 |           let item = stack[j]
134 |           if (stack[j].level < thisLevel) { break }
135 |           if (item.single === isSingle && stack[j].level === thisLevel) {
136 |             item = stack[j]
137 | 
138 |             let openQuote
139 |             let closeQuote
140 |             if (isSingle) {
141 |               openQuote = state.md.options.quotes[2]
142 |               closeQuote = state.md.options.quotes[3]
143 |             } else {
144 |               openQuote = state.md.options.quotes[0]
145 |               closeQuote = state.md.options.quotes[1]
146 |             }
147 | 
148 |             // replace token.content *before* tokens[item.token].content,
149 |             // because, if they are pointing at the same token, replaceAt
150 |             // could mess up indices when quote length != 1
151 |             token.content = replaceAt(token.content, t.index, closeQuote)
152 |             tokens[item.token].content = replaceAt(
153 |               tokens[item.token].content, item.pos, openQuote)
154 | 
155 |             pos += closeQuote.length - 1
156 |             if (item.token === i) { pos += openQuote.length - 1 }
157 | 
158 |             text = token.content
159 |             max = text.length
160 | 
161 |             stack.length = j
162 |             continue OUTER
163 |           }
164 |         }
165 |       }
166 | 
167 |       if (canOpen) {
168 |         stack.push({
169 |           token: i,
170 |           pos: t.index,
171 |           single: isSingle,
172 |           level: thisLevel
173 |         })
174 |       } else if (canClose && isSingle) {
175 |         token.content = replaceAt(token.content, t.index, APOSTROPHE)
176 |       }
177 |     }
178 |   }
179 | }
180 | 
181 | export default function smartquotes (state) {
182 |   /* eslint max-depth:0 */
183 |   if (!state.md.options.typographer) { return }
184 | 
185 |   for (let blkIdx = state.tokens.length - 1; blkIdx >= 0; blkIdx--) {
186 |     if (state.tokens[blkIdx].type !== 'inline' ||
187 |         !QUOTE_TEST_RE.test(state.tokens[blkIdx].content)) {
188 |       continue
189 |     }
190 | 
191 |     process_inlines(state.tokens[blkIdx].children, state)
192 |   }
193 | }
194 | 


--------------------------------------------------------------------------------
/lib/rules_core/state_core.mjs:
--------------------------------------------------------------------------------
 1 | // Core state object
 2 | //
 3 | 
 4 | import Token from '../token.mjs'
 5 | 
 6 | function StateCore (src, md, env) {
 7 |   this.src = src
 8 |   this.env = env
 9 |   this.tokens = []
10 |   this.inlineMode = false
11 |   this.md = md // link to parser instance
12 | }
13 | 
14 | // re-export Token class to use in core rules
15 | StateCore.prototype.Token = Token
16 | 
17 | export default StateCore
18 | 


--------------------------------------------------------------------------------
/lib/rules_core/text_join.mjs:
--------------------------------------------------------------------------------
 1 | // Join raw text tokens with the rest of the text
 2 | //
 3 | // This is set as a separate rule to provide an opportunity for plugins
 4 | // to run text replacements after text join, but before escape join.
 5 | //
 6 | // For example, `\:)` shouldn't be replaced with an emoji.
 7 | //
 8 | 
 9 | export default function text_join (state) {
10 |   let curr, last
11 |   const blockTokens = state.tokens
12 |   const l = blockTokens.length
13 | 
14 |   for (let j = 0; j < l; j++) {
15 |     if (blockTokens[j].type !== 'inline') continue
16 | 
17 |     const tokens = blockTokens[j].children
18 |     const max = tokens.length
19 | 
20 |     for (curr = 0; curr < max; curr++) {
21 |       if (tokens[curr].type === 'text_special') {
22 |         tokens[curr].type = 'text'
23 |       }
24 |     }
25 | 
26 |     for (curr = last = 0; curr < max; curr++) {
27 |       if (tokens[curr].type === 'text' &&
28 |           curr + 1 < max &&
29 |           tokens[curr + 1].type === 'text') {
30 |         // collapse two adjacent text nodes
31 |         tokens[curr + 1].content = tokens[curr].content + tokens[curr + 1].content
32 |       } else {
33 |         if (curr !== last) { tokens[last] = tokens[curr] }
34 | 
35 |         last++
36 |       }
37 |     }
38 | 
39 |     if (curr !== last) {
40 |       tokens.length = last
41 |     }
42 |   }
43 | }
44 | 


--------------------------------------------------------------------------------
/lib/rules_inline/autolink.mjs:
--------------------------------------------------------------------------------
 1 | // Process autolinks '<protocol:...>'
 2 | 
 3 | /* eslint max-len:0 */
 4 | const EMAIL_RE    = /^([a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*)$/
 5 | /* eslint-disable-next-line no-control-regex */
 6 | const AUTOLINK_RE = /^([a-zA-Z][a-zA-Z0-9+.-]{1,31}):([^<>\x00-\x20]*)$/
 7 | 
 8 | export default function autolink (state, silent) {
 9 |   let pos = state.pos
10 | 
11 |   if (state.src.charCodeAt(pos) !== 0x3C/* < */) { return false }
12 | 
13 |   const start = state.pos
14 |   const max = state.posMax
15 | 
16 |   for (;;) {
17 |     if (++pos >= max) return false
18 | 
19 |     const ch = state.src.charCodeAt(pos)
20 | 
21 |     if (ch === 0x3C /* < */) return false
22 |     if (ch === 0x3E /* > */) break
23 |   }
24 | 
25 |   const url = state.src.slice(start + 1, pos)
26 | 
27 |   if (AUTOLINK_RE.test(url)) {
28 |     const fullUrl = state.md.normalizeLink(url)
29 |     if (!state.md.validateLink(fullUrl)) { return false }
30 | 
31 |     if (!silent) {
32 |       const token_o   = state.push('link_open', 'a', 1)
33 |       token_o.attrs   = [['href', fullUrl]]
34 |       token_o.markup  = 'autolink'
35 |       token_o.info    = 'auto'
36 | 
37 |       const token_t   = state.push('text', '', 0)
38 |       token_t.content = state.md.normalizeLinkText(url)
39 | 
40 |       const token_c   = state.push('link_close', 'a', -1)
41 |       token_c.markup  = 'autolink'
42 |       token_c.info    = 'auto'
43 |     }
44 | 
45 |     state.pos += url.length + 2
46 |     return true
47 |   }
48 | 
49 |   if (EMAIL_RE.test(url)) {
50 |     const fullUrl = state.md.normalizeLink('mailto:' + url)
51 |     if (!state.md.validateLink(fullUrl)) { return false }
52 | 
53 |     if (!silent) {
54 |       const token_o   = state.push('link_open', 'a', 1)
55 |       token_o.attrs   = [['href', fullUrl]]
56 |       token_o.markup  = 'autolink'
57 |       token_o.info    = 'auto'
58 | 
59 |       const token_t   = state.push('text', '', 0)
60 |       token_t.content = state.md.normalizeLinkText(url)
61 | 
62 |       const token_c   = state.push('link_close', 'a', -1)
63 |       token_c.markup  = 'autolink'
64 |       token_c.info    = 'auto'
65 |     }
66 | 
67 |     state.pos += url.length + 2
68 |     return true
69 |   }
70 | 
71 |   return false
72 | }
73 | 


--------------------------------------------------------------------------------
/lib/rules_inline/backticks.mjs:
--------------------------------------------------------------------------------
 1 | // Parse backticks
 2 | 
 3 | export default function backtick (state, silent) {
 4 |   let pos = state.pos
 5 |   const ch = state.src.charCodeAt(pos)
 6 | 
 7 |   if (ch !== 0x60/* ` */) { return false }
 8 | 
 9 |   const start = pos
10 |   pos++
11 |   const max = state.posMax
12 | 
13 |   // scan marker length
14 |   while (pos < max && state.src.charCodeAt(pos) === 0x60/* ` */) { pos++ }
15 | 
16 |   const marker = state.src.slice(start, pos)
17 |   const openerLength = marker.length
18 | 
19 |   if (state.backticksScanned && (state.backticks[openerLength] || 0) <= start) {
20 |     if (!silent) state.pending += marker
21 |     state.pos += openerLength
22 |     return true
23 |   }
24 | 
25 |   let matchEnd = pos
26 |   let matchStart
27 | 
28 |   // Nothing found in the cache, scan until the end of the line (or until marker is found)
29 |   while ((matchStart = state.src.indexOf('`', matchEnd)) !== -1) {
30 |     matchEnd = matchStart + 1
31 | 
32 |     // scan marker length
33 |     while (matchEnd < max && state.src.charCodeAt(matchEnd) === 0x60/* ` */) { matchEnd++ }
34 | 
35 |     const closerLength = matchEnd - matchStart
36 | 
37 |     if (closerLength === openerLength) {
38 |       // Found matching closer length.
39 |       if (!silent) {
40 |         const token = state.push('code_inline', 'code', 0)
41 |         token.markup = marker
42 |         token.content = state.src.slice(pos, matchStart)
43 |           .replace(/\n/g, ' ')
44 |           .replace(/^ (.+) $/, '$1')
45 |       }
46 |       state.pos = matchEnd
47 |       return true
48 |     }
49 | 
50 |     // Some different length found, put it in cache as upper limit of where closer can be found
51 |     state.backticks[closerLength] = matchStart
52 |   }
53 | 
54 |   // Scanned through the end, didn't find anything
55 |   state.backticksScanned = true
56 | 
57 |   if (!silent) state.pending += marker
58 |   state.pos += openerLength
59 |   return true
60 | }
61 | 


--------------------------------------------------------------------------------
/lib/rules_inline/balance_pairs.mjs:
--------------------------------------------------------------------------------
  1 | // For each opening emphasis-like marker find a matching closing one
  2 | //
  3 | 
  4 | function processDelimiters (delimiters) {
  5 |   const openersBottom = {}
  6 |   const max = delimiters.length
  7 | 
  8 |   if (!max) return
  9 | 
 10 |   // headerIdx is the first delimiter of the current (where closer is) delimiter run
 11 |   let headerIdx = 0
 12 |   let lastTokenIdx = -2 // needs any value lower than -1
 13 |   const jumps = []
 14 | 
 15 |   for (let closerIdx = 0; closerIdx < max; closerIdx++) {
 16 |     const closer = delimiters[closerIdx]
 17 | 
 18 |     jumps.push(0)
 19 | 
 20 |     // markers belong to same delimiter run if:
 21 |     //  - they have adjacent tokens
 22 |     //  - AND markers are the same
 23 |     //
 24 |     if (delimiters[headerIdx].marker !== closer.marker || lastTokenIdx !== closer.token - 1) {
 25 |       headerIdx = closerIdx
 26 |     }
 27 | 
 28 |     lastTokenIdx = closer.token
 29 | 
 30 |     // Length is only used for emphasis-specific "rule of 3",
 31 |     // if it's not defined (in strikethrough or 3rd party plugins),
 32 |     // we can default it to 0 to disable those checks.
 33 |     //
 34 |     closer.length = closer.length || 0
 35 | 
 36 |     if (!closer.close) continue
 37 | 
 38 |     // Previously calculated lower bounds (previous fails)
 39 |     // for each marker, each delimiter length modulo 3,
 40 |     // and for whether this closer can be an opener;
 41 |     // https://github.com/commonmark/cmark/commit/34250e12ccebdc6372b8b49c44fab57c72443460
 42 |     /* eslint-disable-next-line no-prototype-builtins */
 43 |     if (!openersBottom.hasOwnProperty(closer.marker)) {
 44 |       openersBottom[closer.marker] = [-1, -1, -1, -1, -1, -1]
 45 |     }
 46 | 
 47 |     const minOpenerIdx = openersBottom[closer.marker][(closer.open ? 3 : 0) + (closer.length % 3)]
 48 | 
 49 |     let openerIdx = headerIdx - jumps[headerIdx] - 1
 50 | 
 51 |     let newMinOpenerIdx = openerIdx
 52 | 
 53 |     for (; openerIdx > minOpenerIdx; openerIdx -= jumps[openerIdx] + 1) {
 54 |       const opener = delimiters[openerIdx]
 55 | 
 56 |       if (opener.marker !== closer.marker) continue
 57 | 
 58 |       if (opener.open && opener.end < 0) {
 59 |         let isOddMatch = false
 60 | 
 61 |         // from spec:
 62 |         //
 63 |         // If one of the delimiters can both open and close emphasis, then the
 64 |         // sum of the lengths of the delimiter runs containing the opening and
 65 |         // closing delimiters must not be a multiple of 3 unless both lengths
 66 |         // are multiples of 3.
 67 |         //
 68 |         if (opener.close || closer.open) {
 69 |           if ((opener.length + closer.length) % 3 === 0) {
 70 |             if (opener.length % 3 !== 0 || closer.length % 3 !== 0) {
 71 |               isOddMatch = true
 72 |             }
 73 |           }
 74 |         }
 75 | 
 76 |         if (!isOddMatch) {
 77 |           // If previous delimiter cannot be an opener, we can safely skip
 78 |           // the entire sequence in future checks. This is required to make
 79 |           // sure algorithm has linear complexity (see *_*_*_*_*_... case).
 80 |           //
 81 |           const lastJump = openerIdx > 0 && !delimiters[openerIdx - 1].open
 82 |             ? jumps[openerIdx - 1] + 1
 83 |             : 0
 84 | 
 85 |           jumps[closerIdx] = closerIdx - openerIdx + lastJump
 86 |           jumps[openerIdx] = lastJump
 87 | 
 88 |           closer.open  = false
 89 |           opener.end   = closerIdx
 90 |           opener.close = false
 91 |           newMinOpenerIdx = -1
 92 |           // treat next token as start of run,
 93 |           // it optimizes skips in **<...>**a**<...>** pathological case
 94 |           lastTokenIdx = -2
 95 |           break
 96 |         }
 97 |       }
 98 |     }
 99 | 
100 |     if (newMinOpenerIdx !== -1) {
101 |       // If match for this delimiter run failed, we want to set lower bound for
102 |       // future lookups. This is required to make sure algorithm has linear
103 |       // complexity.
104 |       //
105 |       // See details here:
106 |       // https://github.com/commonmark/cmark/issues/178#issuecomment-270417442
107 |       //
108 |       openersBottom[closer.marker][(closer.open ? 3 : 0) + ((closer.length || 0) % 3)] = newMinOpenerIdx
109 |     }
110 |   }
111 | }
112 | 
113 | export default function link_pairs (state) {
114 |   const tokens_meta = state.tokens_meta
115 |   const max = state.tokens_meta.length
116 | 
117 |   processDelimiters(state.delimiters)
118 | 
119 |   for (let curr = 0; curr < max; curr++) {
120 |     if (tokens_meta[curr] && tokens_meta[curr].delimiters) {
121 |       processDelimiters(tokens_meta[curr].delimiters)
122 |     }
123 |   }
124 | }
125 | 


--------------------------------------------------------------------------------
/lib/rules_inline/emphasis.mjs:
--------------------------------------------------------------------------------
  1 | // Process *this* and _that_
  2 | //
  3 | 
  4 | // Insert each marker as a separate text token, and add it to delimiter list
  5 | //
  6 | function emphasis_tokenize (state, silent) {
  7 |   const start = state.pos
  8 |   const marker = state.src.charCodeAt(start)
  9 | 
 10 |   if (silent) { return false }
 11 | 
 12 |   if (marker !== 0x5F /* _ */ && marker !== 0x2A /* * */) { return false }
 13 | 
 14 |   const scanned = state.scanDelims(state.pos, marker === 0x2A)
 15 | 
 16 |   for (let i = 0; i < scanned.length; i++) {
 17 |     const token = state.push('text', '', 0)
 18 |     token.content = String.fromCharCode(marker)
 19 | 
 20 |     state.delimiters.push({
 21 |       // Char code of the starting marker (number).
 22 |       //
 23 |       marker,
 24 | 
 25 |       // Total length of these series of delimiters.
 26 |       //
 27 |       length: scanned.length,
 28 | 
 29 |       // A position of the token this delimiter corresponds to.
 30 |       //
 31 |       token: state.tokens.length - 1,
 32 | 
 33 |       // If this delimiter is matched as a valid opener, `end` will be
 34 |       // equal to its position, otherwise it's `-1`.
 35 |       //
 36 |       end: -1,
 37 | 
 38 |       // Boolean flags that determine if this delimiter could open or close
 39 |       // an emphasis.
 40 |       //
 41 |       open: scanned.can_open,
 42 |       close: scanned.can_close
 43 |     })
 44 |   }
 45 | 
 46 |   state.pos += scanned.length
 47 | 
 48 |   return true
 49 | }
 50 | 
 51 | function postProcess (state, delimiters) {
 52 |   const max = delimiters.length
 53 | 
 54 |   for (let i = max - 1; i >= 0; i--) {
 55 |     const startDelim = delimiters[i]
 56 | 
 57 |     if (startDelim.marker !== 0x5F/* _ */ && startDelim.marker !== 0x2A/* * */) {
 58 |       continue
 59 |     }
 60 | 
 61 |     // Process only opening markers
 62 |     if (startDelim.end === -1) {
 63 |       continue
 64 |     }
 65 | 
 66 |     const endDelim = delimiters[startDelim.end]
 67 | 
 68 |     // If the previous delimiter has the same marker and is adjacent to this one,
 69 |     // merge those into one strong delimiter.
 70 |     //
 71 |     // `<em><em>whatever</em></em>` -> `<strong>whatever</strong>`
 72 |     //
 73 |     const isStrong = i > 0 &&
 74 |                delimiters[i - 1].end === startDelim.end + 1 &&
 75 |                // check that first two markers match and adjacent
 76 |                delimiters[i - 1].marker === startDelim.marker &&
 77 |                delimiters[i - 1].token === startDelim.token - 1 &&
 78 |                // check that last two markers are adjacent (we can safely assume they match)
 79 |                delimiters[startDelim.end + 1].token === endDelim.token + 1
 80 | 
 81 |     const ch = String.fromCharCode(startDelim.marker)
 82 | 
 83 |     const token_o   = state.tokens[startDelim.token]
 84 |     token_o.type    = isStrong ? 'strong_open' : 'em_open'
 85 |     token_o.tag     = isStrong ? 'strong' : 'em'
 86 |     token_o.nesting = 1
 87 |     token_o.markup  = isStrong ? ch + ch : ch
 88 |     token_o.content = ''
 89 | 
 90 |     const token_c   = state.tokens[endDelim.token]
 91 |     token_c.type    = isStrong ? 'strong_close' : 'em_close'
 92 |     token_c.tag     = isStrong ? 'strong' : 'em'
 93 |     token_c.nesting = -1
 94 |     token_c.markup  = isStrong ? ch + ch : ch
 95 |     token_c.content = ''
 96 | 
 97 |     if (isStrong) {
 98 |       state.tokens[delimiters[i - 1].token].content = ''
 99 |       state.tokens[delimiters[startDelim.end + 1].token].content = ''
100 |       i--
101 |     }
102 |   }
103 | }
104 | 
105 | // Walk through delimiter list and replace text tokens with tags
106 | //
107 | function emphasis_post_process (state) {
108 |   const tokens_meta = state.tokens_meta
109 |   const max = state.tokens_meta.length
110 | 
111 |   postProcess(state, state.delimiters)
112 | 
113 |   for (let curr = 0; curr < max; curr++) {
114 |     if (tokens_meta[curr] && tokens_meta[curr].delimiters) {
115 |       postProcess(state, tokens_meta[curr].delimiters)
116 |     }
117 |   }
118 | }
119 | 
120 | export default {
121 |   tokenize: emphasis_tokenize,
122 |   postProcess: emphasis_post_process
123 | }
124 | 


--------------------------------------------------------------------------------
/lib/rules_inline/entity.mjs:
--------------------------------------------------------------------------------
 1 | // Process html entity - &#123;, &#xAF;, &quot;, ...
 2 | 
 3 | import { decodeHTML } from 'entities'
 4 | import { isValidEntityCode, fromCodePoint } from '../common/utils.mjs'
 5 | 
 6 | const DIGITAL_RE = /^&#((?:x[a-f0-9]{1,6}|[0-9]{1,7}));/i
 7 | const NAMED_RE   = /^&([a-z][a-z0-9]{1,31});/i
 8 | 
 9 | export default function entity (state, silent) {
10 |   const pos = state.pos
11 |   const max = state.posMax
12 | 
13 |   if (state.src.charCodeAt(pos) !== 0x26/* & */) return false
14 | 
15 |   if (pos + 1 >= max) return false
16 | 
17 |   const ch = state.src.charCodeAt(pos + 1)
18 | 
19 |   if (ch === 0x23 /* # */) {
20 |     const match = state.src.slice(pos).match(DIGITAL_RE)
21 |     if (match) {
22 |       if (!silent) {
23 |         const code = match[1][0].toLowerCase() === 'x' ? parseInt(match[1].slice(1), 16) : parseInt(match[1], 10)
24 | 
25 |         const token   = state.push('text_special', '', 0)
26 |         token.content = isValidEntityCode(code) ? fromCodePoint(code) : fromCodePoint(0xFFFD)
27 |         token.markup  = match[0]
28 |         token.info    = 'entity'
29 |       }
30 |       state.pos += match[0].length
31 |       return true
32 |     }
33 |   } else {
34 |     const match = state.src.slice(pos).match(NAMED_RE)
35 |     if (match) {
36 |       const decoded = decodeHTML(match[0])
37 |       if (decoded !== match[0]) {
38 |         if (!silent) {
39 |           const token   = state.push('text_special', '', 0)
40 |           token.content = decoded
41 |           token.markup  = match[0]
42 |           token.info    = 'entity'
43 |         }
44 |         state.pos += match[0].length
45 |         return true
46 |       }
47 |     }
48 |   }
49 | 
50 |   return false
51 | }
52 | 


--------------------------------------------------------------------------------
/lib/rules_inline/escape.mjs:
--------------------------------------------------------------------------------
 1 | // Process escaped chars and hardbreaks
 2 | 
 3 | import { isSpace } from '../common/utils.mjs'
 4 | 
 5 | const ESCAPED = []
 6 | 
 7 | for (let i = 0; i < 256; i++) { ESCAPED.push(0) }
 8 | 
 9 | '\\!"#$%&\'()*+,./:;<=>?@[]^_`{|}~-'
10 |   .split('').forEach(function (ch) { ESCAPED[ch.charCodeAt(0)] = 1 })
11 | 
12 | export default function escape (state, silent) {
13 |   let pos = state.pos
14 |   const max = state.posMax
15 | 
16 |   if (state.src.charCodeAt(pos) !== 0x5C/* \ */) return false
17 |   pos++
18 | 
19 |   // '\' at the end of the inline block
20 |   if (pos >= max) return false
21 | 
22 |   let ch1 = state.src.charCodeAt(pos)
23 | 
24 |   if (ch1 === 0x0A) {
25 |     if (!silent) {
26 |       state.push('hardbreak', 'br', 0)
27 |     }
28 | 
29 |     pos++
30 |     // skip leading whitespaces from next line
31 |     while (pos < max) {
32 |       ch1 = state.src.charCodeAt(pos)
33 |       if (!isSpace(ch1)) break
34 |       pos++
35 |     }
36 | 
37 |     state.pos = pos
38 |     return true
39 |   }
40 | 
41 |   let escapedStr = state.src[pos]
42 | 
43 |   if (ch1 >= 0xD800 && ch1 <= 0xDBFF && pos + 1 < max) {
44 |     const ch2 = state.src.charCodeAt(pos + 1)
45 | 
46 |     if (ch2 >= 0xDC00 && ch2 <= 0xDFFF) {
47 |       escapedStr += state.src[pos + 1]
48 |       pos++
49 |     }
50 |   }
51 | 
52 |   const origStr = '\\' + escapedStr
53 | 
54 |   if (!silent) {
55 |     const token = state.push('text_special', '', 0)
56 | 
57 |     if (ch1 < 256 && ESCAPED[ch1] !== 0) {
58 |       token.content = escapedStr
59 |     } else {
60 |       token.content = origStr
61 |     }
62 | 
63 |     token.markup = origStr
64 |     token.info   = 'escape'
65 |   }
66 | 
67 |   state.pos = pos + 1
68 |   return true
69 | }
70 | 


--------------------------------------------------------------------------------
/lib/rules_inline/fragments_join.mjs:
--------------------------------------------------------------------------------
 1 | // Clean up tokens after emphasis and strikethrough postprocessing:
 2 | // merge adjacent text nodes into one and re-calculate all token levels
 3 | //
 4 | // This is necessary because initially emphasis delimiter markers (*, _, ~)
 5 | // are treated as their own separate text tokens. Then emphasis rule either
 6 | // leaves them as text (needed to merge with adjacent text) or turns them
 7 | // into opening/closing tags (which messes up levels inside).
 8 | //
 9 | 
10 | export default function fragments_join (state) {
11 |   let curr, last
12 |   let level = 0
13 |   const tokens = state.tokens
14 |   const max = state.tokens.length
15 | 
16 |   for (curr = last = 0; curr < max; curr++) {
17 |     // re-calculate levels after emphasis/strikethrough turns some text nodes
18 |     // into opening/closing tags
19 |     if (tokens[curr].nesting < 0) level-- // closing tag
20 |     tokens[curr].level = level
21 |     if (tokens[curr].nesting > 0) level++ // opening tag
22 | 
23 |     if (tokens[curr].type === 'text' &&
24 |         curr + 1 < max &&
25 |         tokens[curr + 1].type === 'text') {
26 |       // collapse two adjacent text nodes
27 |       tokens[curr + 1].content = tokens[curr].content + tokens[curr + 1].content
28 |     } else {
29 |       if (curr !== last) { tokens[last] = tokens[curr] }
30 | 
31 |       last++
32 |     }
33 |   }
34 | 
35 |   if (curr !== last) {
36 |     tokens.length = last
37 |   }
38 | }
39 | 


--------------------------------------------------------------------------------
/lib/rules_inline/html_inline.mjs:
--------------------------------------------------------------------------------
 1 | // Process html tags
 2 | 
 3 | import { HTML_TAG_RE } from '../common/html_re.mjs'
 4 | 
 5 | function isLinkOpen (str) {
 6 |   return /^<a[>\s]/i.test(str)
 7 | }
 8 | function isLinkClose (str) {
 9 |   return /^<\/a\s*>/i.test(str)
10 | }
11 | 
12 | function isLetter (ch) {
13 |   /* eslint no-bitwise:0 */
14 |   const lc = ch | 0x20 // to lower case
15 |   return (lc >= 0x61/* a */) && (lc <= 0x7a/* z */)
16 | }
17 | 
18 | export default function html_inline (state, silent) {
19 |   if (!state.md.options.html) { return false }
20 | 
21 |   // Check start
22 |   const max = state.posMax
23 |   const pos = state.pos
24 |   if (state.src.charCodeAt(pos) !== 0x3C/* < */ ||
25 |       pos + 2 >= max) {
26 |     return false
27 |   }
28 | 
29 |   // Quick fail on second char
30 |   const ch = state.src.charCodeAt(pos + 1)
31 |   if (ch !== 0x21/* ! */ &&
32 |       ch !== 0x3F/* ? */ &&
33 |       ch !== 0x2F/* / */ &&
34 |       !isLetter(ch)) {
35 |     return false
36 |   }
37 | 
38 |   const match = state.src.slice(pos).match(HTML_TAG_RE)
39 |   if (!match) { return false }
40 | 
41 |   if (!silent) {
42 |     const token = state.push('html_inline', '', 0)
43 |     token.content = match[0]
44 | 
45 |     if (isLinkOpen(token.content))  state.linkLevel++
46 |     if (isLinkClose(token.content)) state.linkLevel--
47 |   }
48 |   state.pos += match[0].length
49 |   return true
50 | }
51 | 


--------------------------------------------------------------------------------
/lib/rules_inline/image.mjs:
--------------------------------------------------------------------------------
  1 | // Process ![image](<src> "title")
  2 | 
  3 | import { normalizeReference, isSpace } from '../common/utils.mjs'
  4 | 
  5 | export default function image (state, silent) {
  6 |   let code, content, label, pos, ref, res, title, start
  7 |   let href = ''
  8 |   const oldPos = state.pos
  9 |   const max = state.posMax
 10 | 
 11 |   if (state.src.charCodeAt(state.pos) !== 0x21/* ! */) { return false }
 12 |   if (state.src.charCodeAt(state.pos + 1) !== 0x5B/* [ */) { return false }
 13 | 
 14 |   const labelStart = state.pos + 2
 15 |   const labelEnd = state.md.helpers.parseLinkLabel(state, state.pos + 1, false)
 16 | 
 17 |   // parser failed to find ']', so it's not a valid link
 18 |   if (labelEnd < 0) { return false }
 19 | 
 20 |   pos = labelEnd + 1
 21 |   if (pos < max && state.src.charCodeAt(pos) === 0x28/* ( */) {
 22 |     //
 23 |     // Inline link
 24 |     //
 25 | 
 26 |     // [link](  <href>  "title"  )
 27 |     //        ^^ skipping these spaces
 28 |     pos++
 29 |     for (; pos < max; pos++) {
 30 |       code = state.src.charCodeAt(pos)
 31 |       if (!isSpace(code) && code !== 0x0A) { break }
 32 |     }
 33 |     if (pos >= max) { return false }
 34 | 
 35 |     // [link](  <href>  "title"  )
 36 |     //          ^^^^^^ parsing link destination
 37 |     start = pos
 38 |     res = state.md.helpers.parseLinkDestination(state.src, pos, state.posMax)
 39 |     if (res.ok) {
 40 |       href = state.md.normalizeLink(res.str)
 41 |       if (state.md.validateLink(href)) {
 42 |         pos = res.pos
 43 |       } else {
 44 |         href = ''
 45 |       }
 46 |     }
 47 | 
 48 |     // [link](  <href>  "title"  )
 49 |     //                ^^ skipping these spaces
 50 |     start = pos
 51 |     for (; pos < max; pos++) {
 52 |       code = state.src.charCodeAt(pos)
 53 |       if (!isSpace(code) && code !== 0x0A) { break }
 54 |     }
 55 | 
 56 |     // [link](  <href>  "title"  )
 57 |     //                  ^^^^^^^ parsing link title
 58 |     res = state.md.helpers.parseLinkTitle(state.src, pos, state.posMax)
 59 |     if (pos < max && start !== pos && res.ok) {
 60 |       title = res.str
 61 |       pos = res.pos
 62 | 
 63 |       // [link](  <href>  "title"  )
 64 |       //                         ^^ skipping these spaces
 65 |       for (; pos < max; pos++) {
 66 |         code = state.src.charCodeAt(pos)
 67 |         if (!isSpace(code) && code !== 0x0A) { break }
 68 |       }
 69 |     } else {
 70 |       title = ''
 71 |     }
 72 | 
 73 |     if (pos >= max || state.src.charCodeAt(pos) !== 0x29/* ) */) {
 74 |       state.pos = oldPos
 75 |       return false
 76 |     }
 77 |     pos++
 78 |   } else {
 79 |     //
 80 |     // Link reference
 81 |     //
 82 |     if (typeof state.env.references === 'undefined') { return false }
 83 | 
 84 |     if (pos < max && state.src.charCodeAt(pos) === 0x5B/* [ */) {
 85 |       start = pos + 1
 86 |       pos = state.md.helpers.parseLinkLabel(state, pos)
 87 |       if (pos >= 0) {
 88 |         label = state.src.slice(start, pos++)
 89 |       } else {
 90 |         pos = labelEnd + 1
 91 |       }
 92 |     } else {
 93 |       pos = labelEnd + 1
 94 |     }
 95 | 
 96 |     // covers label === '' and label === undefined
 97 |     // (collapsed reference link and shortcut reference link respectively)
 98 |     if (!label) { label = state.src.slice(labelStart, labelEnd) }
 99 | 
100 |     ref = state.env.references[normalizeReference(label)]
101 |     if (!ref) {
102 |       state.pos = oldPos
103 |       return false
104 |     }
105 |     href = ref.href
106 |     title = ref.title
107 |   }
108 | 
109 |   //
110 |   // We found the end of the link, and know for a fact it's a valid link;
111 |   // so all that's left to do is to call tokenizer.
112 |   //
113 |   if (!silent) {
114 |     content = state.src.slice(labelStart, labelEnd)
115 | 
116 |     const tokens = []
117 |     state.md.inline.parse(
118 |       content,
119 |       state.md,
120 |       state.env,
121 |       tokens
122 |     )
123 | 
124 |     const token = state.push('image', 'img', 0)
125 |     const attrs = [['src', href], ['alt', '']]
126 |     token.attrs = attrs
127 |     token.children = tokens
128 |     token.content = content
129 | 
130 |     if (title) {
131 |       attrs.push(['title', title])
132 |     }
133 |   }
134 | 
135 |   state.pos = pos
136 |   state.posMax = max
137 |   return true
138 | }
139 | 


--------------------------------------------------------------------------------
/lib/rules_inline/link.mjs:
--------------------------------------------------------------------------------
  1 | // Process [link](<to> "stuff")
  2 | 
  3 | import { normalizeReference, isSpace } from '../common/utils.mjs'
  4 | 
  5 | export default function link (state, silent) {
  6 |   let code, label, res, ref
  7 |   let href = ''
  8 |   let title = ''
  9 |   let start = state.pos
 10 |   let parseReference = true
 11 | 
 12 |   if (state.src.charCodeAt(state.pos) !== 0x5B/* [ */) { return false }
 13 | 
 14 |   const oldPos = state.pos
 15 |   const max = state.posMax
 16 |   const labelStart = state.pos + 1
 17 |   const labelEnd = state.md.helpers.parseLinkLabel(state, state.pos, true)
 18 | 
 19 |   // parser failed to find ']', so it's not a valid link
 20 |   if (labelEnd < 0) { return false }
 21 | 
 22 |   let pos = labelEnd + 1
 23 |   if (pos < max && state.src.charCodeAt(pos) === 0x28/* ( */) {
 24 |     //
 25 |     // Inline link
 26 |     //
 27 | 
 28 |     // might have found a valid shortcut link, disable reference parsing
 29 |     parseReference = false
 30 | 
 31 |     // [link](  <href>  "title"  )
 32 |     //        ^^ skipping these spaces
 33 |     pos++
 34 |     for (; pos < max; pos++) {
 35 |       code = state.src.charCodeAt(pos)
 36 |       if (!isSpace(code) && code !== 0x0A) { break }
 37 |     }
 38 |     if (pos >= max) { return false }
 39 | 
 40 |     // [link](  <href>  "title"  )
 41 |     //          ^^^^^^ parsing link destination
 42 |     start = pos
 43 |     res = state.md.helpers.parseLinkDestination(state.src, pos, state.posMax)
 44 |     if (res.ok) {
 45 |       href = state.md.normalizeLink(res.str)
 46 |       if (state.md.validateLink(href)) {
 47 |         pos = res.pos
 48 |       } else {
 49 |         href = ''
 50 |       }
 51 | 
 52 |       // [link](  <href>  "title"  )
 53 |       //                ^^ skipping these spaces
 54 |       start = pos
 55 |       for (; pos < max; pos++) {
 56 |         code = state.src.charCodeAt(pos)
 57 |         if (!isSpace(code) && code !== 0x0A) { break }
 58 |       }
 59 | 
 60 |       // [link](  <href>  "title"  )
 61 |       //                  ^^^^^^^ parsing link title
 62 |       res = state.md.helpers.parseLinkTitle(state.src, pos, state.posMax)
 63 |       if (pos < max && start !== pos && res.ok) {
 64 |         title = res.str
 65 |         pos = res.pos
 66 | 
 67 |         // [link](  <href>  "title"  )
 68 |         //                         ^^ skipping these spaces
 69 |         for (; pos < max; pos++) {
 70 |           code = state.src.charCodeAt(pos)
 71 |           if (!isSpace(code) && code !== 0x0A) { break }
 72 |         }
 73 |       }
 74 |     }
 75 | 
 76 |     if (pos >= max || state.src.charCodeAt(pos) !== 0x29/* ) */) {
 77 |       // parsing a valid shortcut link failed, fallback to reference
 78 |       parseReference = true
 79 |     }
 80 |     pos++
 81 |   }
 82 | 
 83 |   if (parseReference) {
 84 |     //
 85 |     // Link reference
 86 |     //
 87 |     if (typeof state.env.references === 'undefined') { return false }
 88 | 
 89 |     if (pos < max && state.src.charCodeAt(pos) === 0x5B/* [ */) {
 90 |       start = pos + 1
 91 |       pos = state.md.helpers.parseLinkLabel(state, pos)
 92 |       if (pos >= 0) {
 93 |         label = state.src.slice(start, pos++)
 94 |       } else {
 95 |         pos = labelEnd + 1
 96 |       }
 97 |     } else {
 98 |       pos = labelEnd + 1
 99 |     }
100 | 
101 |     // covers label === '' and label === undefined
102 |     // (collapsed reference link and shortcut reference link respectively)
103 |     if (!label) { label = state.src.slice(labelStart, labelEnd) }
104 | 
105 |     ref = state.env.references[normalizeReference(label)]
106 |     if (!ref) {
107 |       state.pos = oldPos
108 |       return false
109 |     }
110 |     href = ref.href
111 |     title = ref.title
112 |   }
113 | 
114 |   //
115 |   // We found the end of the link, and know for a fact it's a valid link;
116 |   // so all that's left to do is to call tokenizer.
117 |   //
118 |   if (!silent) {
119 |     state.pos = labelStart
120 |     state.posMax = labelEnd
121 | 
122 |     const token_o = state.push('link_open', 'a', 1)
123 |     const attrs = [['href', href]]
124 |     token_o.attrs  = attrs
125 |     if (title) {
126 |       attrs.push(['title', title])
127 |     }
128 | 
129 |     state.linkLevel++
130 |     state.md.inline.tokenize(state)
131 |     state.linkLevel--
132 | 
133 |     state.push('link_close', 'a', -1)
134 |   }
135 | 
136 |   state.pos = pos
137 |   state.posMax = max
138 |   return true
139 | }
140 | 


--------------------------------------------------------------------------------
/lib/rules_inline/linkify.mjs:
--------------------------------------------------------------------------------
 1 | // Process links like https://example.org/
 2 | 
 3 | // RFC3986: scheme = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." )
 4 | const SCHEME_RE = /(?:^|[^a-z0-9.+-])([a-z][a-z0-9.+-]*)$/i
 5 | 
 6 | export default function linkify (state, silent) {
 7 |   if (!state.md.options.linkify) return false
 8 |   if (state.linkLevel > 0) return false
 9 | 
10 |   const pos = state.pos
11 |   const max = state.posMax
12 | 
13 |   if (pos + 3 > max) return false
14 |   if (state.src.charCodeAt(pos) !== 0x3A/* : */) return false
15 |   if (state.src.charCodeAt(pos + 1) !== 0x2F/* / */) return false
16 |   if (state.src.charCodeAt(pos + 2) !== 0x2F/* / */) return false
17 | 
18 |   const match = state.pending.match(SCHEME_RE)
19 |   if (!match) return false
20 | 
21 |   const proto = match[1]
22 | 
23 |   const link = state.md.linkify.matchAtStart(state.src.slice(pos - proto.length))
24 |   if (!link) return false
25 | 
26 |   let url = link.url
27 | 
28 |   // invalid link, but still detected by linkify somehow;
29 |   // need to check to prevent infinite loop below
30 |   if (url.length <= proto.length) return false
31 | 
32 |   // disallow '*' at the end of the link (conflicts with emphasis)
33 |   url = url.replace(/\*+$/, '')
34 | 
35 |   const fullUrl = state.md.normalizeLink(url)
36 |   if (!state.md.validateLink(fullUrl)) return false
37 | 
38 |   if (!silent) {
39 |     state.pending = state.pending.slice(0, -proto.length)
40 | 
41 |     const token_o = state.push('link_open', 'a', 1)
42 |     token_o.attrs = [['href', fullUrl]]
43 |     token_o.markup = 'linkify'
44 |     token_o.info = 'auto'
45 | 
46 |     const token_t = state.push('text', '', 0)
47 |     token_t.content = state.md.normalizeLinkText(url)
48 | 
49 |     const token_c = state.push('link_close', 'a', -1)
50 |     token_c.markup = 'linkify'
51 |     token_c.info = 'auto'
52 |   }
53 | 
54 |   state.pos += url.length - proto.length
55 |   return true
56 | }
57 | 


--------------------------------------------------------------------------------
/lib/rules_inline/newline.mjs:
--------------------------------------------------------------------------------
 1 | // Proceess '\n'
 2 | 
 3 | import { isSpace } from '../common/utils.mjs'
 4 | 
 5 | export default function newline (state, silent) {
 6 |   let pos = state.pos
 7 | 
 8 |   if (state.src.charCodeAt(pos) !== 0x0A/* \n */) { return false }
 9 | 
10 |   const pmax = state.pending.length - 1
11 |   const max = state.posMax
12 | 
13 |   // '  \n' -> hardbreak
14 |   // Lookup in pending chars is bad practice! Don't copy to other rules!
15 |   // Pending string is stored in concat mode, indexed lookups will cause
16 |   // convertion to flat mode.
17 |   if (!silent) {
18 |     if (pmax >= 0 && state.pending.charCodeAt(pmax) === 0x20) {
19 |       if (pmax >= 1 && state.pending.charCodeAt(pmax - 1) === 0x20) {
20 |         // Find whitespaces tail of pending chars.
21 |         let ws = pmax - 1
22 |         while (ws >= 1 && state.pending.charCodeAt(ws - 1) === 0x20) ws--
23 | 
24 |         state.pending = state.pending.slice(0, ws)
25 |         state.push('hardbreak', 'br', 0)
26 |       } else {
27 |         state.pending = state.pending.slice(0, -1)
28 |         state.push('softbreak', 'br', 0)
29 |       }
30 |     } else {
31 |       state.push('softbreak', 'br', 0)
32 |     }
33 |   }
34 | 
35 |   pos++
36 | 
37 |   // skip heading spaces for next line
38 |   while (pos < max && isSpace(state.src.charCodeAt(pos))) { pos++ }
39 | 
40 |   state.pos = pos
41 |   return true
42 | }
43 | 


--------------------------------------------------------------------------------
/lib/rules_inline/state_inline.mjs:
--------------------------------------------------------------------------------
  1 | // Inline parser state
  2 | 
  3 | import Token from '../token.mjs'
  4 | import { isWhiteSpace, isPunctChar, isMdAsciiPunct } from '../common/utils.mjs'
  5 | 
  6 | function StateInline (src, md, env, outTokens) {
  7 |   this.src = src
  8 |   this.env = env
  9 |   this.md = md
 10 |   this.tokens = outTokens
 11 |   this.tokens_meta = Array(outTokens.length)
 12 | 
 13 |   this.pos = 0
 14 |   this.posMax = this.src.length
 15 |   this.level = 0
 16 |   this.pending = ''
 17 |   this.pendingLevel = 0
 18 | 
 19 |   // Stores { start: end } pairs. Useful for backtrack
 20 |   // optimization of pairs parse (emphasis, strikes).
 21 |   this.cache = {}
 22 | 
 23 |   // List of emphasis-like delimiters for current tag
 24 |   this.delimiters = []
 25 | 
 26 |   // Stack of delimiter lists for upper level tags
 27 |   this._prev_delimiters = []
 28 | 
 29 |   // backtick length => last seen position
 30 |   this.backticks = {}
 31 |   this.backticksScanned = false
 32 | 
 33 |   // Counter used to disable inline linkify-it execution
 34 |   // inside <a> and markdown links
 35 |   this.linkLevel = 0
 36 | }
 37 | 
 38 | // Flush pending text
 39 | //
 40 | StateInline.prototype.pushPending = function () {
 41 |   const token = new Token('text', '', 0)
 42 |   token.content = this.pending
 43 |   token.level = this.pendingLevel
 44 |   this.tokens.push(token)
 45 |   this.pending = ''
 46 |   return token
 47 | }
 48 | 
 49 | // Push new token to "stream".
 50 | // If pending text exists - flush it as text token
 51 | //
 52 | StateInline.prototype.push = function (type, tag, nesting) {
 53 |   if (this.pending) {
 54 |     this.pushPending()
 55 |   }
 56 | 
 57 |   const token = new Token(type, tag, nesting)
 58 |   let token_meta = null
 59 | 
 60 |   if (nesting < 0) {
 61 |     // closing tag
 62 |     this.level--
 63 |     this.delimiters = this._prev_delimiters.pop()
 64 |   }
 65 | 
 66 |   token.level = this.level
 67 | 
 68 |   if (nesting > 0) {
 69 |     // opening tag
 70 |     this.level++
 71 |     this._prev_delimiters.push(this.delimiters)
 72 |     this.delimiters = []
 73 |     token_meta = { delimiters: this.delimiters }
 74 |   }
 75 | 
 76 |   this.pendingLevel = this.level
 77 |   this.tokens.push(token)
 78 |   this.tokens_meta.push(token_meta)
 79 |   return token
 80 | }
 81 | 
 82 | // Scan a sequence of emphasis-like markers, and determine whether
 83 | // it can start an emphasis sequence or end an emphasis sequence.
 84 | //
 85 | //  - start - position to scan from (it should point at a valid marker);
 86 | //  - canSplitWord - determine if these markers can be found inside a word
 87 | //
 88 | StateInline.prototype.scanDelims = function (start, canSplitWord) {
 89 |   const max = this.posMax
 90 |   const marker = this.src.charCodeAt(start)
 91 | 
 92 |   // treat beginning of the line as a whitespace
 93 |   const lastChar = start > 0 ? this.src.charCodeAt(start - 1) : 0x20
 94 | 
 95 |   let pos = start
 96 |   while (pos < max && this.src.charCodeAt(pos) === marker) { pos++ }
 97 | 
 98 |   const count = pos - start
 99 | 
100 |   // treat end of the line as a whitespace
101 |   const nextChar = pos < max ? this.src.charCodeAt(pos) : 0x20
102 | 
103 |   const isLastPunctChar = isMdAsciiPunct(lastChar) || isPunctChar(String.fromCharCode(lastChar))
104 |   const isNextPunctChar = isMdAsciiPunct(nextChar) || isPunctChar(String.fromCharCode(nextChar))
105 | 
106 |   const isLastWhiteSpace = isWhiteSpace(lastChar)
107 |   const isNextWhiteSpace = isWhiteSpace(nextChar)
108 | 
109 |   const left_flanking =
110 |     !isNextWhiteSpace && (!isNextPunctChar || isLastWhiteSpace || isLastPunctChar)
111 |   const right_flanking =
112 |     !isLastWhiteSpace && (!isLastPunctChar || isNextWhiteSpace || isNextPunctChar)
113 | 
114 |   const can_open  = left_flanking  && (canSplitWord || !right_flanking || isLastPunctChar)
115 |   const can_close = right_flanking && (canSplitWord || !left_flanking  || isNextPunctChar)
116 | 
117 |   return { can_open, can_close, length: count }
118 | }
119 | 
120 | // re-export Token class to use in block rules
121 | StateInline.prototype.Token = Token
122 | 
123 | export default StateInline
124 | 


--------------------------------------------------------------------------------
/lib/rules_inline/strikethrough.mjs:
--------------------------------------------------------------------------------
  1 | // ~~strike through~~
  2 | //
  3 | 
  4 | // Insert each marker as a separate text token, and add it to delimiter list
  5 | //
  6 | function strikethrough_tokenize (state, silent) {
  7 |   const start = state.pos
  8 |   const marker = state.src.charCodeAt(start)
  9 | 
 10 |   if (silent) { return false }
 11 | 
 12 |   if (marker !== 0x7E/* ~ */) { return false }
 13 | 
 14 |   const scanned = state.scanDelims(state.pos, true)
 15 |   let len = scanned.length
 16 |   const ch = String.fromCharCode(marker)
 17 | 
 18 |   if (len < 2) { return false }
 19 | 
 20 |   let token
 21 | 
 22 |   if (len % 2) {
 23 |     token         = state.push('text', '', 0)
 24 |     token.content = ch
 25 |     len--
 26 |   }
 27 | 
 28 |   for (let i = 0; i < len; i += 2) {
 29 |     token         = state.push('text', '', 0)
 30 |     token.content = ch + ch
 31 | 
 32 |     state.delimiters.push({
 33 |       marker,
 34 |       length: 0,     // disable "rule of 3" length checks meant for emphasis
 35 |       token: state.tokens.length - 1,
 36 |       end: -1,
 37 |       open: scanned.can_open,
 38 |       close: scanned.can_close
 39 |     })
 40 |   }
 41 | 
 42 |   state.pos += scanned.length
 43 | 
 44 |   return true
 45 | }
 46 | 
 47 | function postProcess (state, delimiters) {
 48 |   let token
 49 |   const loneMarkers = []
 50 |   const max = delimiters.length
 51 | 
 52 |   for (let i = 0; i < max; i++) {
 53 |     const startDelim = delimiters[i]
 54 | 
 55 |     if (startDelim.marker !== 0x7E/* ~ */) {
 56 |       continue
 57 |     }
 58 | 
 59 |     if (startDelim.end === -1) {
 60 |       continue
 61 |     }
 62 | 
 63 |     const endDelim = delimiters[startDelim.end]
 64 | 
 65 |     token         = state.tokens[startDelim.token]
 66 |     token.type    = 's_open'
 67 |     token.tag     = 's'
 68 |     token.nesting = 1
 69 |     token.markup  = '~~'
 70 |     token.content = ''
 71 | 
 72 |     token         = state.tokens[endDelim.token]
 73 |     token.type    = 's_close'
 74 |     token.tag     = 's'
 75 |     token.nesting = -1
 76 |     token.markup  = '~~'
 77 |     token.content = ''
 78 | 
 79 |     if (state.tokens[endDelim.token - 1].type === 'text' &&
 80 |         state.tokens[endDelim.token - 1].content === '~') {
 81 |       loneMarkers.push(endDelim.token - 1)
 82 |     }
 83 |   }
 84 | 
 85 |   // If a marker sequence has an odd number of characters, it's splitted
 86 |   // like this: `~~~~~` -> `~` + `~~` + `~~`, leaving one marker at the
 87 |   // start of the sequence.
 88 |   //
 89 |   // So, we have to move all those markers after subsequent s_close tags.
 90 |   //
 91 |   while (loneMarkers.length) {
 92 |     const i = loneMarkers.pop()
 93 |     let j = i + 1
 94 | 
 95 |     while (j < state.tokens.length && state.tokens[j].type === 's_close') {
 96 |       j++
 97 |     }
 98 | 
 99 |     j--
100 | 
101 |     if (i !== j) {
102 |       token = state.tokens[j]
103 |       state.tokens[j] = state.tokens[i]
104 |       state.tokens[i] = token
105 |     }
106 |   }
107 | }
108 | 
109 | // Walk through delimiter list and replace text tokens with tags
110 | //
111 | function strikethrough_postProcess (state) {
112 |   const tokens_meta = state.tokens_meta
113 |   const max = state.tokens_meta.length
114 | 
115 |   postProcess(state, state.delimiters)
116 | 
117 |   for (let curr = 0; curr < max; curr++) {
118 |     if (tokens_meta[curr] && tokens_meta[curr].delimiters) {
119 |       postProcess(state, tokens_meta[curr].delimiters)
120 |     }
121 |   }
122 | }
123 | 
124 | export default {
125 |   tokenize: strikethrough_tokenize,
126 |   postProcess: strikethrough_postProcess
127 | }
128 | 


--------------------------------------------------------------------------------
/lib/rules_inline/text.mjs:
--------------------------------------------------------------------------------
 1 | // Skip text characters for text token, place those to pending buffer
 2 | // and increment current pos
 3 | 
 4 | // Rule to skip pure text
 5 | // '{}$%@~+=:' reserved for extentions
 6 | 
 7 | // !, ", #, $, %, &, ', (, ), *, +, ,, -, ., /, :, ;, <, =, >, ?, @, [, \, ], ^, _, `, {, |, }, or ~
 8 | 
 9 | // !!!! Don't confuse with "Markdown ASCII Punctuation" chars
10 | // http://spec.commonmark.org/0.15/#ascii-punctuation-character
11 | function isTerminatorChar (ch) {
12 |   switch (ch) {
13 |     case 0x0A/* \n */:
14 |     case 0x21/* ! */:
15 |     case 0x23/* # */:
16 |     case 0x24/* $ */:
17 |     case 0x25/* % */:
18 |     case 0x26/* & */:
19 |     case 0x2A/* * */:
20 |     case 0x2B/* + */:
21 |     case 0x2D/* - */:
22 |     case 0x3A/* : */:
23 |     case 0x3C/* < */:
24 |     case 0x3D/* = */:
25 |     case 0x3E/* > */:
26 |     case 0x40/* @ */:
27 |     case 0x5B/* [ */:
28 |     case 0x5C/* \ */:
29 |     case 0x5D/* ] */:
30 |     case 0x5E/* ^ */:
31 |     case 0x5F/* _ */:
32 |     case 0x60/* ` */:
33 |     case 0x7B/* { */:
34 |     case 0x7D/* } */:
35 |     case 0x7E/* ~ */:
36 |       return true
37 |     default:
38 |       return false
39 |   }
40 | }
41 | 
42 | export default function text (state, silent) {
43 |   let pos = state.pos
44 | 
45 |   while (pos < state.posMax && !isTerminatorChar(state.src.charCodeAt(pos))) {
46 |     pos++
47 |   }
48 | 
49 |   if (pos === state.pos) { return false }
50 | 
51 |   if (!silent) { state.pending += state.src.slice(state.pos, pos) }
52 | 
53 |   state.pos = pos
54 | 
55 |   return true
56 | }
57 | 
58 | // Alternative implementation, for memory.
59 | //
60 | // It costs 10% of performance, but allows extend terminators list, if place it
61 | // to `ParserInline` property. Probably, will switch to it sometime, such
62 | // flexibility required.
63 | 
64 | /*
65 | var TERMINATOR_RE = /[\n!#$%&*+\-:<=>@[\\\]^_`{}~]/;
66 | 
67 | module.exports = function text(state, silent) {
68 |   var pos = state.pos,
69 |       idx = state.src.slice(pos).search(TERMINATOR_RE);
70 | 
71 |   // first char is terminator -> empty text
72 |   if (idx === 0) { return false; }
73 | 
74 |   // no terminator -> text till end of string
75 |   if (idx < 0) {
76 |     if (!silent) { state.pending += state.src.slice(pos); }
77 |     state.pos = state.src.length;
78 |     return true;
79 |   }
80 | 
81 |   if (!silent) { state.pending += state.src.slice(pos, pos + idx); }
82 | 
83 |   state.pos += idx;
84 | 
85 |   return true;
86 | }; */
87 | 


--------------------------------------------------------------------------------
/lib/token.mjs:
--------------------------------------------------------------------------------
  1 | // Token class
  2 | 
  3 | /**
  4 |  * class Token
  5 |  **/
  6 | 
  7 | /**
  8 |  * new Token(type, tag, nesting)
  9 |  *
 10 |  * Create new token and fill passed properties.
 11 |  **/
 12 | function Token (type, tag, nesting) {
 13 |   /**
 14 |    * Token#type -> String
 15 |    *
 16 |    * Type of the token (string, e.g. "paragraph_open")
 17 |    **/
 18 |   this.type     = type
 19 | 
 20 |   /**
 21 |    * Token#tag -> String
 22 |    *
 23 |    * html tag name, e.g. "p"
 24 |    **/
 25 |   this.tag      = tag
 26 | 
 27 |   /**
 28 |    * Token#attrs -> Array
 29 |    *
 30 |    * Html attributes. Format: `[ [ name1, value1 ], [ name2, value2 ] ]`
 31 |    **/
 32 |   this.attrs    = null
 33 | 
 34 |   /**
 35 |    * Token#map -> Array
 36 |    *
 37 |    * Source map info. Format: `[ line_begin, line_end ]`
 38 |    **/
 39 |   this.map      = null
 40 | 
 41 |   /**
 42 |    * Token#nesting -> Number
 43 |    *
 44 |    * Level change (number in {-1, 0, 1} set), where:
 45 |    *
 46 |    * -  `1` means the tag is opening
 47 |    * -  `0` means the tag is self-closing
 48 |    * - `-1` means the tag is closing
 49 |    **/
 50 |   this.nesting  = nesting
 51 | 
 52 |   /**
 53 |    * Token#level -> Number
 54 |    *
 55 |    * nesting level, the same as `state.level`
 56 |    **/
 57 |   this.level    = 0
 58 | 
 59 |   /**
 60 |    * Token#children -> Array
 61 |    *
 62 |    * An array of child nodes (inline and img tokens)
 63 |    **/
 64 |   this.children = null
 65 | 
 66 |   /**
 67 |    * Token#content -> String
 68 |    *
 69 |    * In a case of self-closing tag (code, html, fence, etc.),
 70 |    * it has contents of this tag.
 71 |    **/
 72 |   this.content  = ''
 73 | 
 74 |   /**
 75 |    * Token#markup -> String
 76 |    *
 77 |    * '*' or '_' for emphasis, fence string for fence, etc.
 78 |    **/
 79 |   this.markup   = ''
 80 | 
 81 |   /**
 82 |    * Token#info -> String
 83 |    *
 84 |    * Additional information:
 85 |    *
 86 |    * - Info string for "fence" tokens
 87 |    * - The value "auto" for autolink "link_open" and "link_close" tokens
 88 |    * - The string value of the item marker for ordered-list "list_item_open" tokens
 89 |    **/
 90 |   this.info     = ''
 91 | 
 92 |   /**
 93 |    * Token#meta -> Object
 94 |    *
 95 |    * A place for plugins to store an arbitrary data
 96 |    **/
 97 |   this.meta     = null
 98 | 
 99 |   /**
100 |    * Token#block -> Boolean
101 |    *
102 |    * True for block-level tokens, false for inline tokens.
103 |    * Used in renderer to calculate line breaks
104 |    **/
105 |   this.block    = false
106 | 
107 |   /**
108 |    * Token#hidden -> Boolean
109 |    *
110 |    * If it's true, ignore this element when rendering. Used for tight lists
111 |    * to hide paragraphs.
112 |    **/
113 |   this.hidden   = false
114 | }
115 | 
116 | /**
117 |  * Token.attrIndex(name) -> Number
118 |  *
119 |  * Search attribute index by name.
120 |  **/
121 | Token.prototype.attrIndex = function attrIndex (name) {
122 |   if (!this.attrs) { return -1 }
123 | 
124 |   const attrs = this.attrs
125 | 
126 |   for (let i = 0, len = attrs.length; i < len; i++) {
127 |     if (attrs[i][0] === name) { return i }
128 |   }
129 |   return -1
130 | }
131 | 
132 | /**
133 |  * Token.attrPush(attrData)
134 |  *
135 |  * Add `[ name, value ]` attribute to list. Init attrs if necessary
136 |  **/
137 | Token.prototype.attrPush = function attrPush (attrData) {
138 |   if (this.attrs) {
139 |     this.attrs.push(attrData)
140 |   } else {
141 |     this.attrs = [attrData]
142 |   }
143 | }
144 | 
145 | /**
146 |  * Token.attrSet(name, value)
147 |  *
148 |  * Set `name` attribute to `value`. Override old value if exists.
149 |  **/
150 | Token.prototype.attrSet = function attrSet (name, value) {
151 |   const idx = this.attrIndex(name)
152 |   const attrData = [name, value]
153 | 
154 |   if (idx < 0) {
155 |     this.attrPush(attrData)
156 |   } else {
157 |     this.attrs[idx] = attrData
158 |   }
159 | }
160 | 
161 | /**
162 |  * Token.attrGet(name)
163 |  *
164 |  * Get the value of attribute `name`, or null if it does not exist.
165 |  **/
166 | Token.prototype.attrGet = function attrGet (name) {
167 |   const idx = this.attrIndex(name)
168 |   let value = null
169 |   if (idx >= 0) {
170 |     value = this.attrs[idx][1]
171 |   }
172 |   return value
173 | }
174 | 
175 | /**
176 |  * Token.attrJoin(name, value)
177 |  *
178 |  * Join value to existing attribute via space. Or create new attribute if not
179 |  * exists. Useful to operate with token classes.
180 |  **/
181 | Token.prototype.attrJoin = function attrJoin (name, value) {
182 |   const idx = this.attrIndex(name)
183 | 
184 |   if (idx < 0) {
185 |     this.attrPush([name, value])
186 |   } else {
187 |     this.attrs[idx][1] = this.attrs[idx][1] + ' ' + value
188 |   }
189 | }
190 | 
191 | export default Token
192 | 


--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
 1 | {
 2 |   "name": "markdown-it",
 3 |   "version": "14.1.0",
 4 |   "description": "Markdown-it - modern pluggable markdown parser.",
 5 |   "keywords": [
 6 |     "markdown",
 7 |     "parser",
 8 |     "commonmark",
 9 |     "markdown-it",
10 |     "markdown-it-plugin"
11 |   ],
12 |   "repository": "markdown-it/markdown-it",
13 |   "license": "MIT",
14 |   "main": "dist/index.cjs.js",
15 |   "module": "index.mjs",
16 |   "exports": {
17 |     ".": {
18 |       "import": "./index.mjs",
19 |       "require": "./dist/index.cjs.js"
20 |     },
21 |     "./*": {
22 |       "require": "./*",
23 |       "import": "./*"
24 |     }
25 |   },
26 |   "bin": {
27 |     "markdown-it": "bin/markdown-it.mjs"
28 |   },
29 |   "scripts": {
30 |     "lint": "eslint .",
31 |     "test": "npm run lint && CJS_ONLY=1 npm run build && c8 --exclude dist --exclude test -r text -r html -r lcov mocha && node support/specsplit.mjs",
32 |     "doc": "node support/build_doc.mjs",
33 |     "gh-doc": "npm run doc && gh-pages -d apidoc -f",
34 |     "demo": "npm run lint && node support/build_demo.mjs",
35 |     "gh-demo": "npm run demo && gh-pages -d demo -f -b master -r git@github.com:markdown-it/markdown-it.github.io.git",
36 |     "build": "rollup -c support/rollup.config.mjs",
37 |     "benchmark-deps": "npm install --prefix benchmark/extra/ -g marked@0.3.6 commonmark@0.26.0 markdown-it/markdown-it.git#2.2.1",
38 |     "specsplit": "support/specsplit.mjs good -o test/fixtures/commonmark/good.txt && support/specsplit.mjs bad -o test/fixtures/commonmark/bad.txt && support/specsplit.mjs",
39 |     "todo": "grep 'TODO' -n -r ./lib 2>/dev/null",
40 |     "prepublishOnly": "npm test && npm run build && npm run gh-demo && npm run gh-doc"
41 |   },
42 |   "files": [
43 |     "index.mjs",
44 |     "lib/",
45 |     "dist/"
46 |   ],
47 |   "dependencies": {
48 |     "argparse": "^2.0.1",
49 |     "entities": "^4.4.0",
50 |     "linkify-it": "^5.0.0",
51 |     "mdurl": "^2.0.0",
52 |     "punycode.js": "^2.3.1",
53 |     "uc.micro": "^2.1.0"
54 |   },
55 |   "devDependencies": {
56 |     "@rollup/plugin-babel": "^6.0.4",
57 |     "@rollup/plugin-commonjs": "^25.0.7",
58 |     "@rollup/plugin-node-resolve": "^15.2.3",
59 |     "@rollup/plugin-terser": "^0.4.4",
60 |     "ansi": "^0.3.0",
61 |     "benchmark": "~2.1.0",
62 |     "c8": "^8.0.1",
63 |     "chai": "^4.2.0",
64 |     "eslint": "^8.4.1",
65 |     "eslint-config-standard": "^17.1.0",
66 |     "express": "^4.14.0",
67 |     "gh-pages": "^6.1.0",
68 |     "highlight.js": "^11.9.0",
69 |     "jest-worker": "^29.7.0",
70 |     "markdown-it-abbr": "^2.0.0",
71 |     "markdown-it-container": "^4.0.0",
72 |     "markdown-it-deflist": "^3.0.0",
73 |     "markdown-it-emoji": "^3.0.0",
74 |     "markdown-it-footnote": "^4.0.0",
75 |     "markdown-it-for-inline": "^2.0.1",
76 |     "markdown-it-ins": "^4.0.0",
77 |     "markdown-it-mark": "^4.0.0",
78 |     "markdown-it-sub": "^2.0.0",
79 |     "markdown-it-sup": "^2.0.0",
80 |     "markdown-it-testgen": "^0.1.3",
81 |     "mocha": "^10.2.0",
82 |     "ndoc": "^6.0.0",
83 |     "needle": "^3.0.0",
84 |     "rollup": "^4.5.0",
85 |     "shelljs": "^0.8.4",
86 |     "supertest": "^6.0.1"
87 |   },
88 |   "mocha": {
89 |     "inline-diffs": true,
90 |     "timeout": 60000
91 |   }
92 | }
93 | 


--------------------------------------------------------------------------------
/support/api_header.md:
--------------------------------------------------------------------------------
  1 | # markdown-it
  2 | 
  3 | ## Install
  4 | 
  5 | **node.js**:
  6 | 
  7 | ```bash
  8 | npm install markdown-it
  9 | ```
 10 | 
 11 | **browser (CDN):**
 12 | 
 13 | - [jsDeliver CDN](http://www.jsdelivr.com/#!markdown-it "jsDelivr CDN")
 14 | - [cdnjs.com CDN](https://cdnjs.com/libraries/markdown-it "cdnjs.com")
 15 | 
 16 | 
 17 | ## Usage examples
 18 | 
 19 | See also:
 20 | 
 21 | - [Development info](https://github.com/markdown-it/markdown-it/tree/master/docs) -
 22 |   for plugins writers.
 23 | 
 24 | 
 25 | ### Simple
 26 | 
 27 | ```js
 28 | // node.js
 29 | // can use `require('markdown-it')` for CJS
 30 | import markdownit from 'markdown-it'
 31 | const md = markdownit()
 32 | const result = md.render('# markdown-it rulezz!');
 33 | 
 34 | // browser with UMD build, added to "window" on script load
 35 | // Note, there is no dash in "markdownit".
 36 | const md = window.markdownit();
 37 | const result = md.render('# markdown-it rulezz!');
 38 | ```
 39 | 
 40 | Single line rendering, without paragraph wrap:
 41 | 
 42 | ```js
 43 | import markdownit from 'markdown-it'
 44 | const md = markdownit()
 45 | const result = md.renderInline('__markdown-it__ rulezz!');
 46 | ```
 47 | 
 48 | 
 49 | ### Init with presets and options
 50 | 
 51 | (*) presets define combinations of active rules and options. Can be
 52 | `"commonmark"`, `"zero"` or `"default"` (if skipped). See
 53 | [API docs](https://markdown-it.github.io/markdown-it/#MarkdownIt.new) for more details.
 54 | 
 55 | ```js
 56 | import markdownit from 'markdown-it'
 57 | 
 58 | // commonmark mode
 59 | const md = markdownit('commonmark')
 60 | 
 61 | // default mode
 62 | const md = markdownit()
 63 | 
 64 | // enable everything
 65 | const md = markdownit({
 66 |   html: true,
 67 |   linkify: true,
 68 |   typographer: true
 69 | })
 70 | 
 71 | // full options list (defaults)
 72 | const md = markdownit({
 73 |   // Enable HTML tags in source
 74 |   html:         false,
 75 | 
 76 |   // Use '/' to close single tags (<br />).
 77 |   // This is only for full CommonMark compatibility.
 78 |   xhtmlOut:     false,
 79 | 
 80 |   // Convert '\n' in paragraphs into <br>
 81 |   breaks:       false,
 82 | 
 83 |   // CSS language prefix for fenced blocks. Can be
 84 |   // useful for external highlighters.
 85 |   langPrefix:   'language-',
 86 | 
 87 |   // Autoconvert URL-like text to links
 88 |   linkify:      false,
 89 | 
 90 |   // Enable some language-neutral replacement + quotes beautification
 91 |   // For the full list of replacements, see https://github.com/markdown-it/markdown-it/blob/master/lib/rules_core/replacements.mjs
 92 |   typographer:  false,
 93 | 
 94 |   // Double + single quotes replacement pairs, when typographer enabled,
 95 |   // and smartquotes on. Could be either a String or an Array.
 96 |   //
 97 |   // For example, you can use '«»„“' for Russian, '„“‚‘' for German,
 98 |   // and ['«\xA0', '\xA0»', '‹\xA0', '\xA0›'] for French (including nbsp).
 99 |   quotes: '“”‘’',
100 | 
101 |   // Highlighter function. Should return escaped HTML,
102 |   // or '' if the source string is not changed and should be escaped externally.
103 |   // If result starts with <pre... internal wrapper is skipped.
104 |   highlight: function (/*str, lang*/) { return ''; }
105 | });
106 | ```
107 | 
108 | 
109 | ### Plugins load
110 | 
111 | ```js
112 | import markdownit from 'markdown-it'
113 | 
114 | const md = markdownit
115 |   .use(plugin1)
116 |   .use(plugin2, opts, ...)
117 |   .use(plugin3);
118 | ```
119 | 
120 | 
121 | ### Syntax highlighting
122 | 
123 | Apply syntax highlighting to fenced code blocks with the `highlight` option:
124 | 
125 | ```js
126 | import markdownit from 'markdown-it'
127 | import hljs from 'highlight.js' // https://highlightjs.org
128 | 
129 | // Actual default values
130 | const md = markdownit({
131 |   highlight: function (str, lang) {
132 |     if (lang && hljs.getLanguage(lang)) {
133 |       try {
134 |         return hljs.highlight(str, { language: lang }).value;
135 |       } catch (__) {}
136 |     }
137 | 
138 |     return ''; // use external default escaping
139 |   }
140 | });
141 | ```
142 | 
143 | Or with full wrapper override (if you need assign class to `<pre>` or `<code>`):
144 | 
145 | ```js
146 | import markdownit from 'markdown-it'
147 | import hljs from 'highlight.js' // https://highlightjs.org
148 | 
149 | // Actual default values
150 | const md = markdownit({
151 |   highlight: function (str, lang) {
152 |     if (lang && hljs.getLanguage(lang)) {
153 |       try {
154 |         return '<pre><code class="hljs">' +
155 |                hljs.highlight(str, { language: lang, ignoreIllegals: true }).value +
156 |                '</code></pre>';
157 |       } catch (__) {}
158 |     }
159 | 
160 |     return '<pre><code class="hljs">' + md.utils.escapeHtml(str) + '</code></pre>';
161 |   }
162 | });
163 | ```
164 | 
165 | ### Linkify
166 | 
167 | `linkify: true` uses [linkify-it](https://github.com/markdown-it/linkify-it). To
168 | configure linkify-it, access the linkify instance through `md.linkify`:
169 | 
170 | ```js
171 | md.linkify.set({ fuzzyEmail: false });  // disables converting email to link
172 | ```
173 | 
174 | ## Syntax extensions
175 | 
176 | Embedded (enabled by default):
177 | 
178 | - [Tables](https://help.github.com/articles/organizing-information-with-tables/) (GFM)
179 | - [Strikethrough](https://help.github.com/articles/basic-writing-and-formatting-syntax/#styling-text) (GFM)
180 | 
181 | Via plugins:
182 | 
183 | - [subscript](https://github.com/markdown-it/markdown-it-sub)
184 | - [superscript](https://github.com/markdown-it/markdown-it-sup)
185 | - [footnote](https://github.com/markdown-it/markdown-it-footnote)
186 | - [definition list](https://github.com/markdown-it/markdown-it-deflist)
187 | - [abbreviation](https://github.com/markdown-it/markdown-it-abbr)
188 | - [emoji](https://github.com/markdown-it/markdown-it-emoji)
189 | - [custom container](https://github.com/markdown-it/markdown-it-container)
190 | - [insert](https://github.com/markdown-it/markdown-it-ins)
191 | - [mark](https://github.com/markdown-it/markdown-it-mark)
192 | - ... and [others](https://www.npmjs.org/browse/keyword/markdown-it-plugin)
193 | 
194 | 
195 | ### Manage rules
196 | 
197 | By default all rules are enabled, but can be restricted by options. On plugin
198 | load all its rules are enabled automatically.
199 | 
200 | ```js
201 | import markdownit from 'markdown-it'
202 | 
203 | // Activate/deactivate rules, with currying
204 | const md = markdownit()
205 |   .disable(['link', 'image'])
206 |   .enable(['link'])
207 |   .enable('image');
208 | 
209 | // Enable everything
210 | const md = markdownit({
211 |   html: true,
212 |   linkify: true,
213 |   typographer: true,
214 | });
215 | ```
216 | 


--------------------------------------------------------------------------------
/support/build_demo.mjs:
--------------------------------------------------------------------------------
 1 | #!/usr/bin/env node
 2 | 
 3 | import shell from 'shelljs'
 4 | import { readFileSync, writeFileSync } from 'fs'
 5 | 
 6 | function escape (input) {
 7 |   return input
 8 |     .replaceAll('&', '&amp;')
 9 |     .replaceAll('<', '&lt;')
10 |     .replaceAll('>', '&gt;')
11 |     .replaceAll('"', '&quot;')
12 |     // .replaceAll("'", '&#039;');
13 | }
14 | 
15 | shell.rm('-rf', 'demo')
16 | shell.mkdir('demo')
17 | 
18 | shell.cp('support/demo_template/README.md', 'demo/')
19 | shell.cp('support/demo_template/index.css', 'demo/')
20 | 
21 | // Read html template and inject escaped sample
22 | const html = readFileSync('support/demo_template/index.html', 'utf8')
23 | const sample = readFileSync('support/demo_template/sample.md', 'utf8')
24 | 
25 | const output = html.replace('<!--SAMPLE-->', escape(sample))
26 | writeFileSync('demo/index.html', output)
27 | 
28 | shell.exec('node_modules/.bin/rollup -c support/demo_template/rollup.config.mjs')
29 | 


--------------------------------------------------------------------------------
/support/build_doc.mjs:
--------------------------------------------------------------------------------
 1 | #!/usr/bin/env node
 2 | 
 3 | import shell from 'shelljs'
 4 | 
 5 | shell.rm('-rf', 'apidoc')
 6 | 
 7 | const head = shell.exec('git show-ref --hash HEAD').stdout.slice(0, 6)
 8 | 
 9 | const link_format = `https://github.com/{package.repository}/blob/${head}/{file}#L{line}`
10 | 
11 | shell.exec(`node node_modules/.bin/ndoc --alias mjs:js --link-format "${link_format}"`)
12 | 


--------------------------------------------------------------------------------
/support/demo_template/README.md:
--------------------------------------------------------------------------------
1 | This repo is generated from __[markdown-it](https://github.com/markdown-it/markdown-it)__ by script.
2 | 
3 | Please, use __[markdown-it](https://github.com/markdown-it)__ for all questions & PRs.
4 | 


--------------------------------------------------------------------------------
/support/demo_template/index.css:
--------------------------------------------------------------------------------
  1 | html,
  2 | body,
  3 | .full-height {
  4 |   height: 100%;
  5 | }
  6 | 
  7 | body {
  8 |   overflow-x: hidden;
  9 |   padding-bottom: 160px;
 10 |   background-color: #fbfbfb;
 11 | }
 12 | 
 13 | /* hack to allign emojies to line height */
 14 | .emoji {
 15 |   height: 1.2em;
 16 | }
 17 | 
 18 | .demo-options {
 19 |   margin-bottom: 30px;
 20 | }
 21 | 
 22 | .opt__strict .not-strict {
 23 |   opacity: 0.3;
 24 | }
 25 | 
 26 | .checkbox {
 27 |   margin-right: 10px;
 28 | }
 29 | 
 30 | .source {
 31 |   width: 100%;
 32 |   font-family: Menlo, Monaco, Consolas, "Courier New", monospace;
 33 |   font-size: 13px;
 34 |   padding: 2px;
 35 | }
 36 | 
 37 | .result-html {
 38 |   padding: 2px 10px;
 39 |   overflow: auto;
 40 |   background-color: #fff;
 41 |   border: 1px solid #ccc;
 42 |   border-radius: 4px;
 43 | }
 44 | .result-html img {
 45 |   max-width: 35%;
 46 | }
 47 | 
 48 | .result-src,
 49 | .result-debug {
 50 |   display: none;
 51 | }
 52 | 
 53 | .result-src-content,
 54 | .result-debug-content {
 55 |   white-space: pre;
 56 | }
 57 | 
 58 | .result-as-html .result-html { display: block; }
 59 | .result-as-html .result-src,
 60 | .result-as-html .result-debug { display: none; }
 61 | 
 62 | .result-as-src .result-src { display: block; }
 63 | .result-as-src .result-html,
 64 | .result-as-src .result-debug { display: none; }
 65 | 
 66 | .result-as-debug .result-debug { display: block; }
 67 | .result-as-debug .result-html,
 68 | .result-as-debug .result-src { display: none; }
 69 | 
 70 | .demo-control {
 71 |   position: absolute;
 72 |   right: 15px;
 73 |   top: -17px;
 74 |   border-radius: 6px 6px 0 0;
 75 |   font-size: 12px;
 76 |   background-color: #ddd;
 77 | }
 78 | .demo-control a {
 79 |   padding: 0 20px;
 80 | }
 81 | .demo-control a:first-child {
 82 |   padding-left: 30px;
 83 | }
 84 | .demo-control a:last-child {
 85 |   padding-right: 30px;
 86 | }
 87 | 
 88 | /* twbs fix */
 89 | .hljs {
 90 |   padding: 9.5px;
 91 | }
 92 | .hljs code {
 93 |   white-space: pre;
 94 | }
 95 | 
 96 | 
 97 | .footnotes {
 98 |   -moz-column-count: 2;
 99 |   column-count: 2;
100 | }
101 | .footnotes-list {
102 |   padding-left: 2em;
103 | }
104 | 
105 | /* custom container */
106 | .warning {
107 |   background-color: #ff8;
108 |   padding: 20px;
109 |   border-radius: 6px;
110 | }
111 | 
112 | .gh-ribbon {
113 |   display: block;
114 |   position: absolute;
115 |   right: -60px;
116 |   top: 44px;
117 |   transform: rotate(45deg);
118 |   width: 230px;
119 |   z-index: 10000;
120 |   white-space: nowrap;
121 |   font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
122 |   background-color: #686868;
123 |   box-shadow: 0 0 2px rgba(102,102,102,0.4);
124 |   padding: 1px 0;
125 | }
126 | .gh-ribbon a {
127 |   text-decoration: none !important;
128 |   border: 1px solid #ccc;
129 |   color: #fff;
130 |   display: block;
131 |   font-size: 13px;
132 |   font-weight: 700;
133 |   outline: medium none;
134 |   padding: 4px 50px 2px;
135 |   text-align: center;
136 | }
137 | 
138 | /* Override default responsiveness */
139 | .form-inline .radio,
140 | .form-inline .checkbox {
141 |   display: inline-block;
142 |   margin-bottom: 0;
143 |   margin-top: 0;
144 | }
145 | .form-inline .form-group {
146 |   display: inline-block;
147 |   margin-bottom: 0;
148 |   vertical-align: middle;
149 | }
150 | .form-inline .form-control {
151 |   display: inline-block;
152 |   vertical-align: middle;
153 |   width: auto;
154 | }
155 | 
156 | 


--------------------------------------------------------------------------------
/support/demo_template/index.html:
--------------------------------------------------------------------------------
 1 | <!DOCTYPE html>
 2 | <html>
 3 |   <head>
 4 |     <title>markdown-it demo</title>
 5 |     <meta charset="UTF-8">
 6 |     <meta name="viewport" content="width=device-width, initial-scale=1">
 7 |     <script src="https://cdn.jsdelivr.net/jquery/1.11.1/jquery.min.js"></script>
 8 |     <script src="https://cdn.jsdelivr.net/lodash/2.4.1/lodash.js"></script>
 9 |     <script src="https://cdn.jsdelivr.net/bootstrap/3.2.0/js/bootstrap.min.js"></script>
10 |     <link rel="stylesheet" href="https://cdn.jsdelivr.net/bootstrap/3.2.0/css/bootstrap.css">
11 |     <link rel="stylesheet" href="https://cdn.jsdelivr.net/highlight.js/9.1.0/styles/github.min.css">
12 |     <script src="markdown-it.js"></script>
13 |     <script src="https://unpkg.com/twemoji@14.0.2/dist/twemoji.min.js" crossorigin="anonymous"></script>
14 |     <link rel="stylesheet" href="index.css">
15 |     <script src="index.js"></script>
16 |   </head>
17 |   <body>
18 |     <div class="container">
19 |       <h1>
20 |         markdown-it <small>demo</small></h1>
21 |       <div class="form-inline demo-options">
22 |         <div class="checkbox not-strict">
23 |           <label class="_tip" title="enable html tags in source text">
24 |             <input id="html" type="checkbox"> html
25 |           </label>
26 |         </div>
27 |         <div class="checkbox not-strict">
28 |           <label class="_tip" title="produce xtml output (add / to single tags (&lt;br /&gt; instead of &lt;br&gt;)">
29 |             <input id="xhtmlOut" type="checkbox"> xhtmlOut
30 |           </label>
31 |         </div>
32 |         <div class="checkbox not-strict">
33 |           <label class="_tip" title="newlines in paragraphs are rendered as &lt;br&gt;">
34 |             <input id="breaks" type="checkbox"> breaks
35 |           </label>
36 |         </div>
37 |         <div class="checkbox not-strict">
38 |           <label class="_tip" title="autoconvert link-like texts to links">
39 |             <input id="linkify" type="checkbox"> linkify
40 |           </label>
41 |         </div>
42 |         <div class="checkbox not-strict">
43 |           <label class="_tip" title="do typographic replacements, (c) → © and so on">
44 |             <input id="typographer" type="checkbox"> typographer
45 |           </label>
46 |         </div>
47 |         <div class="checkbox not-strict">
48 |           <label class="_tip" title="enable output highlight for fenced blocks">
49 |             <input id="_highlight" type="checkbox"> highlight
50 |           </label>
51 |         </div>
52 |         <div class="form-group not-strict">
53 |           <input class="form-control _tip" id="langPrefix" type="input" placeholder="language prefix" title="css class language prefix for fenced code blocks">
54 |         </div>
55 |         <div class="checkbox">
56 |           <label class="_tip" title="force strict CommonMark mode - output will be equal to reference parser">
57 |             <input id="_strict" type="checkbox"> CommonMark strict
58 |           </label>
59 |         </div>
60 |       </div>
61 |     </div>
62 |     <div class="container full-height">
63 |       <div class="row full-height">
64 |         <div class="col-xs-6 full-height">
65 |           <div class="demo-control"><a class="source-clear" href="#">clear</a><a id="permalink" href="./" title="Share this snippet as link"><strong>permalink</strong></a></div>
66 |           <textarea class="source full-height"><!--SAMPLE--></textarea>
67 |         </div>
68 |         <section class="col-xs-6 full-height">
69 |           <div class="demo-control"><a href="#" data-result-as="html">html</a><a href="#" data-result-as="src">source</a><a href="#" data-result-as="debug">debug</a></div>
70 |           <div class="result-html full-height"></div>
71 |           <pre class="hljs result-src full-height"><code class="result-src-content full-height"></code></pre>
72 |           <pre class="hljs result-debug full-height"><code class="result-debug-content full-height"></code></pre>
73 |         </section>
74 |       </div>
75 |     </div>
76 |     <div class="gh-ribbon"><a href="https://github.com/markdown-it/markdown-it" target="_blank">Fork me on GitHub</a></div>
77 |   </body>
78 | </html>
79 | 


--------------------------------------------------------------------------------
/support/demo_template/rollup.config.mjs:
--------------------------------------------------------------------------------
 1 | import nodeResolve from '@rollup/plugin-node-resolve'
 2 | import commonjs from '@rollup/plugin-commonjs'
 3 | import terser from '@rollup/plugin-terser'
 4 | 
 5 | const plugins = [
 6 |   nodeResolve({ preferBuiltins: true }),
 7 |   commonjs(),
 8 |   // Here terser is used only to force ascii output
 9 |   terser({
10 |     mangle: false,
11 |     compress: false,
12 |     format: {
13 |       comments: 'all',
14 |       beautify: true,
15 |       ascii_only: true,
16 |       indent_level: 2
17 |     }
18 |   })
19 | ]
20 | 
21 | export default [
22 |   {
23 |     input: 'index.mjs',
24 |     output: {
25 |       file: 'demo/markdown-it.js',
26 |       format: 'umd',
27 |       name: 'markdownit'
28 |     },
29 |     plugins
30 |   },
31 |   {
32 |     input: 'support/demo_template/index.mjs',
33 |     output: {
34 |       file: 'demo/index.js',
35 |       format: 'iife',
36 |       name: 'demo'
37 |     },
38 |     plugins
39 |   }
40 | ]
41 | 


--------------------------------------------------------------------------------
/support/demo_template/sample.md:
--------------------------------------------------------------------------------
  1 | ---
  2 | __Advertisement :)__
  3 | 
  4 | - __[pica](https://nodeca.github.io/pica/demo/)__ - high quality and fast image
  5 |   resize in browser.
  6 | - __[babelfish](https://github.com/nodeca/babelfish/)__ - developer friendly
  7 |   i18n with plurals support and easy syntax.
  8 | 
  9 | You will like those projects!
 10 | 
 11 | ---
 12 | 
 13 | # h1 Heading 8-)
 14 | ## h2 Heading
 15 | ### h3 Heading
 16 | #### h4 Heading
 17 | ##### h5 Heading
 18 | ###### h6 Heading
 19 | 
 20 | 
 21 | ## Horizontal Rules
 22 | 
 23 | ___
 24 | 
 25 | ---
 26 | 
 27 | ***
 28 | 
 29 | 
 30 | ## Typographic replacements
 31 | 
 32 | Enable typographer option to see result.
 33 | 
 34 | (c) (C) (r) (R) (tm) (TM) (p) (P) +-
 35 | 
 36 | test.. test... test..... test?..... test!....
 37 | 
 38 | !!!!!! ???? ,,  -- ---
 39 | 
 40 | "Smartypants, double quotes" and 'single quotes'
 41 | 
 42 | 
 43 | ## Emphasis
 44 | 
 45 | **This is bold text**
 46 | 
 47 | __This is bold text__
 48 | 
 49 | *This is italic text*
 50 | 
 51 | _This is italic text_
 52 | 
 53 | ~~Strikethrough~~
 54 | 
 55 | 
 56 | ## Blockquotes
 57 | 
 58 | 
 59 | > Blockquotes can also be nested...
 60 | >> ...by using additional greater-than signs right next to each other...
 61 | > > > ...or with spaces between arrows.
 62 | 
 63 | 
 64 | ## Lists
 65 | 
 66 | Unordered
 67 | 
 68 | + Create a list by starting a line with `+`, `-`, or `*`
 69 | + Sub-lists are made by indenting 2 spaces:
 70 |   - Marker character change forces new list start:
 71 |     * Ac tristique libero volutpat at
 72 |     + Facilisis in pretium nisl aliquet
 73 |     - Nulla volutpat aliquam velit
 74 | + Very easy!
 75 | 
 76 | Ordered
 77 | 
 78 | 1. Lorem ipsum dolor sit amet
 79 | 2. Consectetur adipiscing elit
 80 | 3. Integer molestie lorem at massa
 81 | 
 82 | 
 83 | 1. You can use sequential numbers...
 84 | 1. ...or keep all the numbers as `1.`
 85 | 
 86 | Start numbering with offset:
 87 | 
 88 | 57. foo
 89 | 1. bar
 90 | 
 91 | 
 92 | ## Code
 93 | 
 94 | Inline `code`
 95 | 
 96 | Indented code
 97 | 
 98 |     // Some comments
 99 |     line 1 of code
100 |     line 2 of code
101 |     line 3 of code
102 | 
103 | 
104 | Block code "fences"
105 | 
106 | ```
107 | Sample text here...
108 | ```
109 | 
110 | Syntax highlighting
111 | 
112 | ``` js
113 | var foo = function (bar) {
114 |   return bar++;
115 | };
116 | 
117 | console.log(foo(5));
118 | ```
119 | 
120 | ## Tables
121 | 
122 | | Option | Description |
123 | | ------ | ----------- |
124 | | data   | path to data files to supply the data that will be passed into templates. |
125 | | engine | engine to be used for processing templates. Handlebars is the default. |
126 | | ext    | extension to be used for dest files. |
127 | 
128 | Right aligned columns
129 | 
130 | | Option | Description |
131 | | ------:| -----------:|
132 | | data   | path to data files to supply the data that will be passed into templates. |
133 | | engine | engine to be used for processing templates. Handlebars is the default. |
134 | | ext    | extension to be used for dest files. |
135 | 
136 | 
137 | ## Links
138 | 
139 | [link text](http://dev.nodeca.com)
140 | 
141 | [link with title](http://nodeca.github.io/pica/demo/ "title text!")
142 | 
143 | Autoconverted link https://github.com/nodeca/pica (enable linkify to see)
144 | 
145 | 
146 | ## Images
147 | 
148 | ![Minion](https://octodex.github.com/images/minion.png)
149 | ![Stormtroopocat](https://octodex.github.com/images/stormtroopocat.jpg "The Stormtroopocat")
150 | 
151 | Like links, Images also have a footnote style syntax
152 | 
153 | ![Alt text][id]
154 | 
155 | With a reference later in the document defining the URL location:
156 | 
157 | [id]: https://octodex.github.com/images/dojocat.jpg  "The Dojocat"
158 | 
159 | 
160 | ## Plugins
161 | 
162 | The killer feature of `markdown-it` is very effective support of
163 | [syntax plugins](https://www.npmjs.org/browse/keyword/markdown-it-plugin).
164 | 
165 | 
166 | ### [Emojies](https://github.com/markdown-it/markdown-it-emoji)
167 | 
168 | > Classic markup: :wink: :cry: :laughing: :yum:
169 | >
170 | > Shortcuts (emoticons): :-) :-( 8-) ;)
171 | 
172 | see [how to change output](https://github.com/markdown-it/markdown-it-emoji#change-output) with twemoji.
173 | 
174 | 
175 | ### [Subscript](https://github.com/markdown-it/markdown-it-sub) / [Superscript](https://github.com/markdown-it/markdown-it-sup)
176 | 
177 | - 19^th^
178 | - H~2~O
179 | 
180 | 
181 | ### [\<ins>](https://github.com/markdown-it/markdown-it-ins)
182 | 
183 | ++Inserted text++
184 | 
185 | 
186 | ### [\<mark>](https://github.com/markdown-it/markdown-it-mark)
187 | 
188 | ==Marked text==
189 | 
190 | 
191 | ### [Footnotes](https://github.com/markdown-it/markdown-it-footnote)
192 | 
193 | Footnote 1 link[^first].
194 | 
195 | Footnote 2 link[^second].
196 | 
197 | Inline footnote^[Text of inline footnote] definition.
198 | 
199 | Duplicated footnote reference[^second].
200 | 
201 | [^first]: Footnote **can have markup**
202 | 
203 |     and multiple paragraphs.
204 | 
205 | [^second]: Footnote text.
206 | 
207 | 
208 | ### [Definition lists](https://github.com/markdown-it/markdown-it-deflist)
209 | 
210 | Term 1
211 | 
212 | :   Definition 1
213 | with lazy continuation.
214 | 
215 | Term 2 with *inline markup*
216 | 
217 | :   Definition 2
218 | 
219 |         { some code, part of Definition 2 }
220 | 
221 |     Third paragraph of definition 2.
222 | 
223 | _Compact style:_
224 | 
225 | Term 1
226 |   ~ Definition 1
227 | 
228 | Term 2
229 |   ~ Definition 2a
230 |   ~ Definition 2b
231 | 
232 | 
233 | ### [Abbreviations](https://github.com/markdown-it/markdown-it-abbr)
234 | 
235 | This is HTML abbreviation example.
236 | 
237 | It converts "HTML", but keep intact partial entries like "xxxHTMLyyy" and so on.
238 | 
239 | *[HTML]: Hyper Text Markup Language
240 | 
241 | ### [Custom containers](https://github.com/markdown-it/markdown-it-container)
242 | 
243 | ::: warning
244 | *here be dragons*
245 | :::
246 | 


--------------------------------------------------------------------------------
/support/rollup.config.mjs:
--------------------------------------------------------------------------------
 1 | import resolve from '@rollup/plugin-node-resolve'
 2 | import terser from '@rollup/plugin-terser'
 3 | import { babel } from '@rollup/plugin-babel'
 4 | import { readFileSync } from 'fs'
 5 | 
 6 | const pkg = JSON.parse(readFileSync(new URL('../package.json', import.meta.url)))
 7 | 
 8 | const config_umd_full = {
 9 |   input: 'index.mjs',
10 |   output: [
11 |     {
12 |       file: `dist/${pkg.name}.js`,
13 |       format: 'umd',
14 |       name: 'markdownit',
15 |       plugins: [
16 |         // Here terser is used only to force ascii output
17 |         terser({
18 |           mangle: false,
19 |           compress: false,
20 |           format: { comments: 'all', beautify: true, ascii_only: true, indent_level: 2 }
21 |         })
22 |       ]
23 |     },
24 |     {
25 |       file: `dist/${pkg.name}.min.js`,
26 |       format: 'umd',
27 |       name: 'markdownit',
28 |       plugins: [
29 |         terser({
30 |           format: { ascii_only: true }
31 |         })
32 |       ]
33 |     }
34 |   ],
35 |   plugins: [
36 |     resolve(),
37 |     babel({ babelHelpers: 'bundled' }),
38 |     {
39 |       banner () {
40 |         return `/*! ${pkg.name} ${pkg.version} https://github.com/${pkg.repository} @license ${pkg.license} */`
41 |       }
42 |     }
43 |   ]
44 | }
45 | 
46 | const config_cjs_no_deps = {
47 |   input: 'index.mjs',
48 |   output: {
49 |     file: 'dist/index.cjs.js',
50 |     format: 'cjs'
51 |   },
52 |   external: Object.keys(pkg.dependencies),
53 |   plugins: [
54 |     resolve(),
55 |     babel({ babelHelpers: 'bundled' })
56 |   ]
57 | }
58 | 
59 | let config = [
60 |   config_umd_full,
61 |   config_cjs_no_deps
62 | ]
63 | 
64 | if (process.env.CJS_ONLY) config = [config_cjs_no_deps]
65 | 
66 | export default config
67 | 


--------------------------------------------------------------------------------
/support/specsplit.mjs:
--------------------------------------------------------------------------------
  1 | #!/usr/bin/env node
  2 | /* eslint no-console:0 */
  3 | 
  4 | // Fixtures generator from commonmark specs. Split spec to working / not working
  5 | // examples, or show total stat.
  6 | 
  7 | import fs from 'node:fs'
  8 | import argparse from 'argparse'
  9 | import markdownit from '../index.mjs'
 10 | 
 11 | const cli = new argparse.ArgumentParser({
 12 |   add_help: true
 13 | })
 14 | 
 15 | cli.add_argument('type', {
 16 |   help: 'type of examples to filter',
 17 |   nargs: '?',
 18 |   choices: ['good', 'bad']
 19 | })
 20 | 
 21 | cli.add_argument('-s', '--spec', {
 22 |   help: 'spec file to read',
 23 |   default: new URL('../test/fixtures/commonmark/spec.txt', import.meta.url)
 24 | })
 25 | 
 26 | cli.add_argument('-o', '--output', {
 27 |   help: 'output file, stdout if not set',
 28 |   default: '-'
 29 | })
 30 | 
 31 | const options = cli.parse_args()
 32 | 
 33 | function normalize (text) {
 34 |   return text.replace(/<blockquote>\n<\/blockquote>/g, '<blockquote></blockquote>')
 35 | }
 36 | 
 37 | function readFile (filename, encoding, callback) {
 38 |   if (options.file === '-') {
 39 |     // read from stdin
 40 | 
 41 |     const chunks = []
 42 | 
 43 |     process.stdin.on('data', function (chunk) {
 44 |       chunks.push(chunk)
 45 |     })
 46 | 
 47 |     process.stdin.on('end', function () {
 48 |       return callback(null, Buffer.concat(chunks).toString(encoding))
 49 |     })
 50 |   } else {
 51 |     fs.readFile(filename, encoding, callback)
 52 |   }
 53 | }
 54 | 
 55 | readFile(options.spec, 'utf8', function (error, input) {
 56 |   const good = []
 57 |   const bad = []
 58 |   const markdown = markdownit('commonmark')
 59 | 
 60 |   if (error) {
 61 |     if (error.code === 'ENOENT') {
 62 |       process.stderr.write('File not found: ' + options.spec)
 63 |       process.exit(2)
 64 |     }
 65 | 
 66 |     process.stderr.write(error.stack || error.message || String(error))
 67 |     process.exit(1)
 68 |   }
 69 | 
 70 |   input = input.replace(/→/g, '\t')
 71 | 
 72 |   markdown.parse(input, {})
 73 |     .filter(function (token) {
 74 |       return token.tag === 'code' &&
 75 |               token.info.trim() === 'example'
 76 |     })
 77 |     .forEach(function (token) {
 78 |       const arr  = token.content.split(/^\.\s*?$/m, 2)
 79 |       const md   = arr[0]
 80 |       const html = arr[1].replace(/^\n/, '')
 81 | 
 82 |       const result = {
 83 |         md,
 84 |         html,
 85 |         line: token.map[0],
 86 |         err: ''
 87 |       }
 88 | 
 89 |       if (markdown.render(md) === normalize(html)) {
 90 |         good.push(result)
 91 |       } else {
 92 |         result.err = markdown.render(md)
 93 |         bad.push(result)
 94 |       }
 95 |     })
 96 | 
 97 |   const out = []
 98 | 
 99 |   if (!options.type) {
100 |     out.push(`CM spec stat: passed samples - ${good.length}, failed samples - ${bad.length}`)
101 |   } else {
102 |     const data = options.type === 'good' ? good : bad
103 | 
104 |     data.forEach(function (sample) {
105 |       out.push(
106 |         '~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n' +
107 |         `src line: ${sample.line}\n\n.\n${sample.md}.\n${sample.html}.\n`
108 |       )
109 |       if (sample.err) {
110 |         out.push(`error:\n\n${sample.err}\n`)
111 |       }
112 |     })
113 |   }
114 | 
115 |   if (options.output !== '-') fs.writeFileSync(options.output, out.join('\n'))
116 |   else console.log(out.join('\n'))
117 | 
118 |   process.exit(0)
119 | })
120 | 


--------------------------------------------------------------------------------
/test/cjs.js:
--------------------------------------------------------------------------------
 1 | 'use strict'
 2 | 
 3 | const assert = require('assert')
 4 | const md = require('../')()
 5 | 
 6 | describe('CJS', () => {
 7 |   it('require', () => {
 8 |     assert.strictEqual(md.render('abc'), '<p>abc</p>\n')
 9 |   })
10 | })
11 | 


--------------------------------------------------------------------------------
/test/commonmark.mjs:
--------------------------------------------------------------------------------
 1 | import { fileURLToPath } from 'node:url'
 2 | import { relative } from 'node:path'
 3 | import { load } from 'markdown-it-testgen'
 4 | import markdownit from '../index.mjs'
 5 | import { assert } from 'chai'
 6 | 
 7 | function normalize (text) {
 8 |   return text.replace(/<blockquote>\n<\/blockquote>/g, '<blockquote></blockquote>')
 9 | }
10 | 
11 | function generate (path, md) {
12 |   load(path, function (data) {
13 |     data.meta = data.meta || {}
14 | 
15 |     const desc = data.meta.desc || relative(path, data.file);
16 | 
17 |     (data.meta.skip ? describe.skip : describe)(desc, function () {
18 |       data.fixtures.forEach(function (fixture) {
19 |         it(fixture.header ? fixture.header : 'line ' + (fixture.first.range[0] - 1), function () {
20 |           assert.strictEqual(md.render(fixture.first.text), normalize(fixture.second.text))
21 |         })
22 |       })
23 |     })
24 |   })
25 | }
26 | 
27 | describe('CommonMark', function () {
28 |   const md = markdownit('commonmark')
29 | 
30 |   generate(fileURLToPath(new URL('fixtures/commonmark/good.txt', import.meta.url)), md)
31 | })
32 | 


--------------------------------------------------------------------------------
/test/fixtures/commonmark/bad.txt:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/markdown-it/markdown-it/0fe7ccb4b7f30236fb05f623be6924961d296d3d/test/fixtures/commonmark/bad.txt


--------------------------------------------------------------------------------
/test/fixtures/markdown-it/fatal.txt:
--------------------------------------------------------------------------------
 1 | Should not throw exception on invalid chars in URL (`*` not allowed in path) [mailformed URI]
 2 | .
 3 | [foo](<&#x25;test>)
 4 | .
 5 | <p><a href="%25test">foo</a></p>
 6 | .
 7 | 
 8 | 
 9 | Should not throw exception on broken utf-8 sequence in URL [mailformed URI]
10 | .
11 | [foo](%C3)
12 | .
13 | <p><a href="%C3">foo</a></p>
14 | .
15 | 
16 | 
17 | Should not throw exception on broken utf-16 surrogates sequence in URL [mailformed URI]
18 | .
19 | [foo](&#xD800;)
20 | .
21 | <p><a href="&amp;#xD800;">foo</a></p>
22 | .
23 | 
24 | 
25 | Should not hang comments regexp
26 | .
27 | foo <!--- xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx ->
28 | 
29 | foo <!------------------------------------------------------------------->
30 | .
31 | <p>foo &lt;!— xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx -&gt;</p>
32 | <p>foo <!-------------------------------------------------------------------></p>
33 | .
34 | 
35 | 
36 | Should not hang cdata regexp
37 | .
38 | foo <![CDATA[ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx ]>
39 | .
40 | <p>foo &lt;![CDATA[ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx ]&gt;</p>
41 | .
42 | 


--------------------------------------------------------------------------------
/test/fixtures/markdown-it/linkify.txt:
--------------------------------------------------------------------------------
  1 | linkify
  2 | .
  3 | url http://www.youtube.com/watch?v=5Jt5GEr4AYg.
  4 | .
  5 | <p>url <a href="http://www.youtube.com/watch?v=5Jt5GEr4AYg">http://www.youtube.com/watch?v=5Jt5GEr4AYg</a>.</p>
  6 | .
  7 | 
  8 | 
  9 | don't touch text in links
 10 | .
 11 | [https://example.com](https://example.com)
 12 | .
 13 | <p><a href="https://example.com">https://example.com</a></p>
 14 | .
 15 | 
 16 | 
 17 | don't touch text in autolinks
 18 | .
 19 | <https://example.com>
 20 | .
 21 | <p><a href="https://example.com">https://example.com</a></p>
 22 | .
 23 | 
 24 | 
 25 | don't touch text in html <a> tags
 26 | .
 27 | <a href="https://example.com">https://example.com</a>
 28 | .
 29 | <p><a href="https://example.com">https://example.com</a></p>
 30 | .
 31 | 
 32 | 
 33 | entities inside raw links
 34 | .
 35 | https://example.com/foo&amp;bar
 36 | .
 37 | <p><a href="https://example.com/foo&amp;amp;bar">https://example.com/foo&amp;amp;bar</a></p>
 38 | .
 39 | 
 40 | 
 41 | emphasis inside raw links (asterisk, can happen in links with params)
 42 | .
 43 | https://example.com/foo*bar*baz
 44 | .
 45 | <p><a href="https://example.com/foo*bar*baz">https://example.com/foo*bar*baz</a></p>
 46 | .
 47 | 
 48 | 
 49 | emphasis inside raw links (underscore)
 50 | .
 51 | http://example.org/foo._bar_-_baz
 52 | .
 53 | <p><a href="http://example.org/foo._bar_-_baz">http://example.org/foo._bar_-_baz</a></p>
 54 | .
 55 | 
 56 | 
 57 | backticks inside raw links
 58 | .
 59 | https://example.com/foo`bar`baz
 60 | .
 61 | <p><a href="https://example.com/foo%60bar%60baz">https://example.com/foo`bar`baz</a></p>
 62 | .
 63 | 
 64 | 
 65 | links inside raw links
 66 | .
 67 | https://example.com/foo[123](456)bar
 68 | .
 69 | <p><a href="https://example.com/foo%5B123%5D(456)bar">https://example.com/foo[123](456)bar</a></p>
 70 | .
 71 | 
 72 | 
 73 | escapes not allowed at the start
 74 | .
 75 | \https://example.com
 76 | .
 77 | <p>\https://example.com</p>
 78 | .
 79 | 
 80 | 
 81 | escapes not allowed at comma
 82 | .
 83 | https\://example.com
 84 | .
 85 | <p>https://example.com</p>
 86 | .
 87 | 
 88 | 
 89 | escapes not allowed at slashes
 90 | .
 91 | https:\//aa.org https://bb.org
 92 | .
 93 | <p>https://aa.org <a href="https://bb.org">https://bb.org</a></p>
 94 | .
 95 | 
 96 | 
 97 | fuzzy link shouldn't match cc.org
 98 | .
 99 | https:/\/cc.org
100 | .
101 | <p>https://cc.org</p>
102 | .
103 | 
104 | 
105 | bold links (exclude markup of pairs from link tail)
106 | .
107 | **http://example.com/foobar**
108 | .
109 | <p><strong><a href="http://example.com/foobar">http://example.com/foobar</a></strong></p>
110 | .
111 | 
112 | 
113 | match links without protocol
114 | .
115 | www.example.org
116 | .
117 | <p><a href="http://www.example.org">www.example.org</a></p>
118 | .
119 | 
120 | 
121 | emails
122 | .
123 | test@example.com
124 | 
125 | mailto:test@example.com
126 | .
127 | <p><a href="mailto:test@example.com">test@example.com</a></p>
128 | <p><a href="mailto:test@example.com">mailto:test@example.com</a></p>
129 | .
130 | 
131 | 
132 | typorgapher should not break href
133 | .
134 | http://example.com/(c)
135 | .
136 | <p><a href="http://example.com/(c)">http://example.com/(c)</a></p>
137 | .
138 | 
139 | 
140 | coverage, prefix not valid
141 | .
142 | http:/example.com/
143 | .
144 | <p>http:/example.com/</p>
145 | .
146 | 
147 | 
148 | coverage, negative link level
149 | .
150 | </a>[https://example.com](https://example.com)
151 | .
152 | <p></a><a href="https://example.com"><a href="https://example.com">https://example.com</a></a></p>
153 | .
154 | 
155 | 
156 | emphasis with '*', real link:
157 | .
158 | http://cdecl.ridiculousfish.com/?q=int+%28*f%29+%28float+*%29%3B
159 | .
160 | <p><a href="http://cdecl.ridiculousfish.com/?q=int+%28*f%29+%28float+*%29%3B">http://cdecl.ridiculousfish.com/?q=int+(*f)+(float+*)%3B</a></p>
161 | .
162 | 
163 | 
164 | emphasis with '_', real link:
165 | .
166 | https://www.sell.fi/sites/default/files/elainlaakarilehti/tieteelliset_artikkelit/kahkonen_t._et_al.canine_pancreatitis-_review.pdf
167 | .
168 | <p><a href="https://www.sell.fi/sites/default/files/elainlaakarilehti/tieteelliset_artikkelit/kahkonen_t._et_al.canine_pancreatitis-_review.pdf">https://www.sell.fi/sites/default/files/elainlaakarilehti/tieteelliset_artikkelit/kahkonen_t._et_al.canine_pancreatitis-_review.pdf</a></p>
169 | .
170 | 
171 | regression test, invalid link:
172 | .
173 | i.org[x[x][xx: htt://a.b://a
174 | .
175 | <p><a href="http://i.org">i.org</a>[x[x][xx: htt://a.b://a</p>
176 | .
177 | 


--------------------------------------------------------------------------------
/test/fixtures/markdown-it/normalize.txt:
--------------------------------------------------------------------------------
  1 | 
  2 | Encode link destination, decode text inside it:
  3 | 
  4 | .
  5 | <http://example.com/α%CE%B2γ%CE%B4>
  6 | .
  7 | <p><a href="http://example.com/%CE%B1%CE%B2%CE%B3%CE%B4">http://example.com/αβγδ</a></p>
  8 | .
  9 | 
 10 | .
 11 | [foo](http://example.com/α%CE%B2γ%CE%B4)
 12 | .
 13 | <p><a href="http://example.com/%CE%B1%CE%B2%CE%B3%CE%B4">foo</a></p>
 14 | .
 15 | 
 16 | 
 17 | Keep %25 as is because decoding it may break urls, #720
 18 | .
 19 | <https://www.google.com/search?q=hello%2E%252Ehello>
 20 | .
 21 | <p><a href="https://www.google.com/search?q=hello%2E%252Ehello">https://www.google.com/search?q=hello.%252Ehello</a></p>
 22 | .
 23 | 
 24 | 
 25 | Should decode punycode:
 26 | 
 27 | .
 28 | <http://xn--n3h.net/>
 29 | .
 30 | <p><a href="http://xn--n3h.net/">http://☃.net/</a></p>
 31 | .
 32 | 
 33 | .
 34 | <http://☃.net/>
 35 | .
 36 | <p><a href="http://xn--n3h.net/">http://☃.net/</a></p>
 37 | .
 38 | 
 39 | Invalid punycode:
 40 | 
 41 | .
 42 | <http://xn--xn.com/>
 43 | .
 44 | <p><a href="http://xn--xn.com/">http://xn--xn.com/</a></p>
 45 | .
 46 | 
 47 | Invalid punycode (non-ascii):
 48 | 
 49 | .
 50 | <http://xn--γ.com/>
 51 | .
 52 | <p><a href="http://xn--xn---emd.com/">http://xn--γ.com/</a></p>
 53 | .
 54 | 
 55 | Two slashes should start a domain:
 56 | 
 57 | .
 58 | [](//☃.net/)
 59 | .
 60 | <p><a href="//xn--n3h.net/"></a></p>
 61 | .
 62 | 
 63 | Don't encode domains in unknown schemas:
 64 | 
 65 | .
 66 | [](skype:γγγ)
 67 | .
 68 | <p><a href="skype:%CE%B3%CE%B3%CE%B3"></a></p>
 69 | .
 70 | 
 71 | Should auto-add protocol to autolinks:
 72 | 
 73 | .
 74 | test google.com foo
 75 | .
 76 | <p>test <a href="http://google.com">google.com</a> foo</p>
 77 | .
 78 | 
 79 | Should support IDN in autolinks:
 80 | 
 81 | .
 82 | test http://xn--n3h.net/ foo
 83 | .
 84 | <p>test <a href="http://xn--n3h.net/">http://☃.net/</a> foo</p>
 85 | .
 86 | 
 87 | .
 88 | test http://☃.net/ foo
 89 | .
 90 | <p>test <a href="http://xn--n3h.net/">http://☃.net/</a> foo</p>
 91 | .
 92 | 
 93 | .
 94 | test //xn--n3h.net/ foo
 95 | .
 96 | <p>test <a href="//xn--n3h.net/">//☃.net/</a> foo</p>
 97 | .
 98 | 
 99 | .
100 | test xn--n3h.net foo
101 | .
102 | <p>test <a href="http://xn--n3h.net">☃.net</a> foo</p>
103 | .
104 | 
105 | .
106 | test xn--n3h@xn--n3h.net foo
107 | .
108 | <p>test <a href="mailto:xn--n3h@xn--n3h.net">xn--n3h@☃.net</a> foo</p>
109 | .
110 | 


--------------------------------------------------------------------------------
/test/fixtures/markdown-it/proto.txt:
--------------------------------------------------------------------------------
 1 | .
 2 | [__proto__]
 3 | 
 4 | [__proto__]: blah
 5 | .
 6 | <p><a href="blah"><strong>proto</strong></a></p>
 7 | .
 8 | 
 9 | 
10 | .
11 | [hasOwnProperty]
12 | 
13 | [hasOwnProperty]: blah
14 | .
15 | <p><a href="blah">hasOwnProperty</a></p>
16 | .
17 | 


--------------------------------------------------------------------------------
/test/fixtures/markdown-it/smartquotes.txt:
--------------------------------------------------------------------------------
  1 | Should parse nested quotes:
  2 | .
  3 | "foo 'bar' baz"
  4 | 
  5 | 'foo 'bar' baz'
  6 | .
  7 | <p>“foo ‘bar’ baz”</p>
  8 | <p>‘foo ‘bar’ baz’</p>
  9 | .
 10 | 
 11 | 
 12 | Should not overlap quotes:
 13 | .
 14 | 'foo "bar' baz"
 15 | .
 16 | <p>‘foo &quot;bar’ baz&quot;</p>
 17 | .
 18 | 
 19 | 
 20 | Should match quotes on the same level:
 21 | .
 22 | "foo *bar* baz"
 23 | .
 24 | <p>“foo <em>bar</em> baz”</p>
 25 | .
 26 | 
 27 | 
 28 | Should handle adjacent nested quotes:
 29 | .
 30 | '"double in single"'
 31 | 
 32 | "'single in double'"
 33 | .
 34 | <p>‘“double in single”’</p>
 35 | <p>“‘single in double’”</p>
 36 | .
 37 | 
 38 | 
 39 | 
 40 | Should not match quotes on different levels:
 41 | .
 42 | *"foo* bar"
 43 | 
 44 | "foo *bar"*
 45 | 
 46 | *"foo* bar *baz"*
 47 | .
 48 | <p><em>&quot;foo</em> bar&quot;</p>
 49 | <p>&quot;foo <em>bar&quot;</em></p>
 50 | <p><em>&quot;foo</em> bar <em>baz&quot;</em></p>
 51 | .
 52 | 
 53 | Smartquotes should not overlap with other tags:
 54 | .
 55 | *foo "bar* *baz" quux*
 56 | .
 57 | <p><em>foo &quot;bar</em> <em>baz&quot; quux</em></p>
 58 | .
 59 | 
 60 | 
 61 | Should try and find matching quote in this case:
 62 | .
 63 | "foo "bar 'baz"
 64 | .
 65 | <p>&quot;foo “bar 'baz”</p>
 66 | .
 67 | 
 68 | 
 69 | Should not touch 'inches' in quotes:
 70 | .
 71 | "Monitor 21"" and "Monitor""
 72 | .
 73 | <p>“Monitor 21&quot;” and “Monitor”&quot;</p>
 74 | .
 75 | 
 76 | 
 77 | Should render an apostrophe as a rsquo:
 78 | .
 79 | This isn't and can't be the best approach to implement this...
 80 | .
 81 | <p>This isn’t and can’t be the best approach to implement this…</p>
 82 | .
 83 | 
 84 | 
 85 | Apostrophe could end the word, that's why original smartypants replaces all of them as rsquo:
 86 | .
 87 | users' stuff
 88 | .
 89 | <p>users’ stuff</p>
 90 | .
 91 | 
 92 | Quotes between punctuation chars:
 93 | 
 94 | .
 95 | "(hai)".
 96 | .
 97 | <p>“(hai)”.</p>
 98 | .
 99 | 
100 | Quotes at the start/end of the tokens:
101 | .
102 | "*foo* bar"
103 | 
104 | "foo *bar*"
105 | 
106 | "*foo bar*"
107 | .
108 | <p>“<em>foo</em> bar”</p>
109 | <p>“foo <em>bar</em>”</p>
110 | <p>“<em>foo bar</em>”</p>
111 | .
112 | 
113 | Should treat softbreak as a space:
114 | .
115 | "this"
116 | and "that".
117 | 
118 | "this" and
119 | "that".
120 | .
121 | <p>“this”
122 | and “that”.</p>
123 | <p>“this” and
124 | “that”.</p>
125 | .
126 | 
127 | Should treat hardbreak as a space:
128 | .
129 | "this"\
130 | and "that".
131 | 
132 | "this" and\
133 | "that".
134 | .
135 | <p>“this”<br>
136 | and “that”.</p>
137 | <p>“this” and<br>
138 | “that”.</p>
139 | .
140 | 
141 | Should allow quotes adjacent to other punctuation characters, #643:
142 | .
143 | The dog---"'man's' best friend"
144 | .
145 | <p>The dog—“‘man’s’ best friend”</p>
146 | .
147 | 
148 | Should parse quotes adjacent to code block, #677:
149 | .
150 | "test `code`"
151 | 
152 | "`code` test"
153 | .
154 | <p>“test <code>code</code>”</p>
155 | <p>“<code>code</code> test”</p>
156 | .
157 | 
158 | Should parse quotes adjacent to inline html, #677:
159 | .
160 | "test <br>"
161 | 
162 | "<br> test"
163 | .
164 | <p>“test <br>”</p>
165 | <p>“<br> test”</p>
166 | .
167 | 
168 | Should be escapable:
169 | .
170 | "foo"
171 | 
172 | \"foo"
173 | 
174 | "foo\"
175 | .
176 | <p>“foo”</p>
177 | <p>&quot;foo&quot;</p>
178 | <p>&quot;foo&quot;</p>
179 | .
180 | 
181 | Should not replace entities:
182 | .
183 | &quot;foo&quot;
184 | 
185 | &quot;foo"
186 | 
187 | "foo&quot;
188 | .
189 | <p>&quot;foo&quot;</p>
190 | <p>&quot;foo&quot;</p>
191 | <p>&quot;foo&quot;</p>
192 | .
193 | 


--------------------------------------------------------------------------------
/test/fixtures/markdown-it/strikethrough.txt:
--------------------------------------------------------------------------------
  1 | .
  2 | ~~Strikeout~~
  3 | .
  4 | <p><s>Strikeout</s></p>
  5 | .
  6 | 
  7 | .
  8 | x ~~~~foo~~ bar~~
  9 | .
 10 | <p>x <s><s>foo</s> bar</s></p>
 11 | .
 12 | 
 13 | .
 14 | x ~~foo ~~bar~~~~
 15 | .
 16 | <p>x <s>foo <s>bar</s></s></p>
 17 | .
 18 | 
 19 | .
 20 | x ~~~~foo~~~~
 21 | .
 22 | <p>x <s><s>foo</s></s></p>
 23 | .
 24 | 
 25 | .
 26 | x ~~a ~~foo~~~~~~~~~~~bar~~ b~~
 27 | 
 28 | x ~~a ~~foo~~~~~~~~~~~~bar~~ b~~
 29 | .
 30 | <p>x <s>a <s>foo</s></s>~~~<s><s>bar</s> b</s></p>
 31 | <p>x <s>a <s>foo</s></s>~~~~<s><s>bar</s> b</s></p>
 32 | .
 33 | 
 34 | 
 35 | Strikeouts have the same priority as emphases:
 36 | .
 37 | **~~test**~~
 38 | 
 39 | ~~**test~~**
 40 | .
 41 | <p><strong>~~test</strong>~~</p>
 42 | <p><s>**test</s>**</p>
 43 | .
 44 | 
 45 | 
 46 | Strikeouts have the same priority as emphases with respect to links:
 47 | .
 48 | [~~link]()~~
 49 | 
 50 | ~~[link~~]()
 51 | .
 52 | <p><a href="">~~link</a>~~</p>
 53 | <p>~~<a href="">link~~</a></p>
 54 | .
 55 | 
 56 | 
 57 | Strikeouts have the same priority as emphases with respect to backticks:
 58 | .
 59 | ~~`code~~`
 60 | 
 61 | `~~code`~~
 62 | .
 63 | <p>~~<code>code~~</code></p>
 64 | <p><code>~~code</code>~~</p>
 65 | .
 66 | 
 67 | 
 68 | Nested strikeouts:
 69 | .
 70 | ~~foo ~~bar~~ baz~~
 71 | 
 72 | ~~f **o ~~o b~~ a** r~~
 73 | .
 74 | <p><s>foo <s>bar</s> baz</s></p>
 75 | <p><s>f <strong>o <s>o b</s> a</strong> r</s></p>
 76 | .
 77 | 
 78 | 
 79 | Should not have a whitespace between text and "~~":
 80 | .
 81 | foo ~~ bar ~~ baz
 82 | .
 83 | <p>foo ~~ bar ~~ baz</p>
 84 | .
 85 | 
 86 | 
 87 | Should parse strikethrough within link tags:
 88 | .
 89 | [~~foo~~]()
 90 | .
 91 | <p><a href=""><s>foo</s></a></p>
 92 | .
 93 | 
 94 | 
 95 | Newline should be considered a whitespace:
 96 | .
 97 | ~~test
 98 | ~~
 99 | 
100 | ~~
101 | test~~
102 | 
103 | ~~
104 | test
105 | ~~
106 | .
107 | <p>~~test
108 | ~~</p>
109 | <p>~~
110 | test~~</p>
111 | <p>~~
112 | test
113 | ~~</p>
114 | .
115 | 
116 | From CommonMark test suite, replacing `**` with our marker:
117 | 
118 | .
119 | a~~"foo"~~
120 | .
121 | <p>a~~“foo”~~</p>
122 | .
123 | 
124 | Coverage: single tilde
125 | .
126 | ~a~
127 | .
128 | <p>~a~</p>
129 | .
130 | 
131 | Regression test for #742:
132 | .
133 | -~~~~;~~~~~~
134 | .
135 | <p>-<s><s>;</s></s>~~</p>
136 | .
137 | 


--------------------------------------------------------------------------------
/test/fixtures/markdown-it/typographer.txt:
--------------------------------------------------------------------------------
  1 | .
  2 | (bad)
  3 | .
  4 | <p>(bad)</p>
  5 | .
  6 | 
  7 | 
  8 | copyright
  9 | .
 10 | (c) (C)
 11 | .
 12 | <p>© ©</p>
 13 | .
 14 | 
 15 | 
 16 | reserved
 17 | .
 18 | (r) (R)
 19 | .
 20 | <p>® ®</p>
 21 | .
 22 | 
 23 | 
 24 | trademark
 25 | .
 26 | (tm) (TM)
 27 | .
 28 | <p>™ ™</p>
 29 | .
 30 | 
 31 | 
 32 | plus-minus
 33 | .
 34 | +-5
 35 | .
 36 | <p>±5</p>
 37 | .
 38 | 
 39 | 
 40 | ellipsis
 41 | .
 42 | test.. test... test..... test?..... test!....
 43 | .
 44 | <p>test… test… test… test?.. test!..</p>
 45 | .
 46 | 
 47 | 
 48 | dupes
 49 | .
 50 | !!!!!! ???? ,,
 51 | .
 52 | <p>!!! ??? ,</p>
 53 | .
 54 | 
 55 | copyright should be escapable
 56 | .
 57 | \(c)
 58 | .
 59 | <p>(c)</p>
 60 | .
 61 | 
 62 | shouldn't replace entities
 63 | .
 64 | &#40;c) (c&#41; (c)
 65 | .
 66 | <p>(c) (c) ©</p>
 67 | .
 68 | 
 69 | 
 70 | dashes
 71 | .
 72 | ---markdownit --- super---
 73 | 
 74 | markdownit---awesome
 75 | 
 76 | abc ----
 77 | 
 78 | --markdownit -- super--
 79 | 
 80 | markdownit--awesome
 81 | .
 82 | <p>—markdownit — super—</p>
 83 | <p>markdownit—awesome</p>
 84 | <p>abc ----</p>
 85 | <p>–markdownit – super–</p>
 86 | <p>markdownit–awesome</p>
 87 | .
 88 | 
 89 | dashes should be escapable
 90 | .
 91 | foo \-- bar
 92 | 
 93 | foo -\- bar
 94 | .
 95 | <p>foo -- bar</p>
 96 | <p>foo -- bar</p>
 97 | .
 98 | 
 99 | regression tests for #624
100 | .
101 | 1---2---3
102 | 
103 | 1--2--3
104 | 
105 | 1 -- -- 3
106 | .
107 | <p>1—2—3</p>
108 | <p>1–2–3</p>
109 | <p>1 – – 3</p>
110 | .
111 | 


--------------------------------------------------------------------------------
/test/fixtures/markdown-it/xss.txt:
--------------------------------------------------------------------------------
  1 | .
  2 | [normal link](javascript)
  3 | .
  4 | <p><a href="javascript">normal link</a></p>
  5 | .
  6 | 
  7 | 
  8 | Should not allow some protocols in links and images
  9 | .
 10 | [xss link](javascript:alert(1))
 11 | 
 12 | [xss link](JAVASCRIPT:alert(1))
 13 | 
 14 | [xss link](vbscript:alert(1))
 15 | 
 16 | [xss link](VBSCRIPT:alert(1))
 17 | 
 18 | [xss link](file:///123)
 19 | .
 20 | <p>[xss link](javascript:alert(1))</p>
 21 | <p>[xss link](JAVASCRIPT:alert(1))</p>
 22 | <p>[xss link](vbscript:alert(1))</p>
 23 | <p>[xss link](VBSCRIPT:alert(1))</p>
 24 | <p>[xss link](file:///123)</p>
 25 | .
 26 | 
 27 | 
 28 | .
 29 | [xss link](&#34;&#62;&#60;script&#62;alert&#40;&#34;xss&#34;&#41;&#60;/script&#62;)
 30 | 
 31 | [xss link](&#74;avascript:alert(1))
 32 | 
 33 | [xss link](&#x26;#74;avascript:alert(1))
 34 | 
 35 | [xss link](\&#74;avascript:alert(1))
 36 | .
 37 | <p><a href="%22%3E%3Cscript%3Ealert(%22xss%22)%3C/script%3E">xss link</a></p>
 38 | <p>[xss link](Javascript:alert(1))</p>
 39 | <p><a href="&amp;#74;avascript:alert(1)">xss link</a></p>
 40 | <p><a href="&amp;#74;avascript:alert(1)">xss link</a></p>
 41 | .
 42 | 
 43 | .
 44 | [xss link](<javascript:alert(1)>)
 45 | .
 46 | <p>[xss link](&lt;javascript:alert(1)&gt;)</p>
 47 | .
 48 | 
 49 | .
 50 | [xss link](javascript&#x3A;alert(1))
 51 | .
 52 | <p>[xss link](javascript:alert(1))</p>
 53 | .
 54 | 
 55 | 
 56 | Should not allow data-uri except some whitelisted mimes
 57 | .
 58 | ![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7)
 59 | .
 60 | <p><img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" alt=""></p>
 61 | .
 62 | 
 63 | .
 64 | [xss link](data:text/html;base64,PHNjcmlwdD5hbGVydCgnWFNTJyk8L3NjcmlwdD4K)
 65 | .
 66 | <p>[xss link](data:text/html;base64,PHNjcmlwdD5hbGVydCgnWFNTJyk8L3NjcmlwdD4K)</p>
 67 | .
 68 | 
 69 | .
 70 | [normal link](/javascript:link)
 71 | .
 72 | <p><a href="/javascript:link">normal link</a></p>
 73 | .
 74 | 
 75 | 
 76 | Image parser use the same code base as link.
 77 | .
 78 | ![xss link](javascript:alert(1))
 79 | .
 80 | <p>![xss link](javascript:alert(1))</p>
 81 | .
 82 | 
 83 | 
 84 | Autolinks
 85 | .
 86 | <javascript&#x3A;alert(1)>
 87 | 
 88 | <javascript:alert(1)>
 89 | .
 90 | <p>&lt;javascript:alert(1)&gt;</p>
 91 | <p>&lt;javascript:alert(1)&gt;</p>
 92 | .
 93 | 
 94 | 
 95 | Linkifier
 96 | .
 97 | javascript&#x3A;alert(1)
 98 | 
 99 | javascript:alert(1)
100 | .
101 | <p>javascript:alert(1)</p>
102 | <p>javascript:alert(1)</p>
103 | .
104 | 
105 | 
106 | References
107 | .
108 | [test]: javascript:alert(1)
109 | .
110 | <p>[test]: javascript:alert(1)</p>
111 | .
112 | 
113 | 
114 | Make sure we decode entities before split:
115 | .
116 | ```js&#32;custom-class
117 | test1
118 | ```
119 | 
120 | ```js&#x0C;custom-class
121 | test2
122 | ```
123 | .
124 | <pre><code class="js">test1
125 | </code></pre>
126 | <pre><code class="js">test2
127 | </code></pre>
128 | .
129 | 


--------------------------------------------------------------------------------
/test/markdown-it.mjs:
--------------------------------------------------------------------------------
 1 | import { fileURLToPath } from 'node:url'
 2 | import generate from 'markdown-it-testgen'
 3 | import markdownit from '../index.mjs'
 4 | 
 5 | describe('markdown-it', function () {
 6 |   const md = markdownit({
 7 |     html: true,
 8 |     langPrefix: '',
 9 |     typographer: true,
10 |     linkify: true
11 |   })
12 | 
13 |   generate(fileURLToPath(new URL('fixtures/markdown-it', import.meta.url)), md)
14 | })
15 | 


--------------------------------------------------------------------------------
/test/pathological.json:
--------------------------------------------------------------------------------
1 | { "md5": "80e12450752e4667b3656fa2cd12e9d5" }
2 | 


--------------------------------------------------------------------------------
/test/pathological.mjs:
--------------------------------------------------------------------------------
  1 | import needle from 'needle'
  2 | import assert from 'node:assert'
  3 | import crypto from 'node:crypto'
  4 | import { Worker as JestWorker } from 'jest-worker'
  5 | import { readFileSync } from 'fs'
  6 | 
  7 | async function test_pattern (str) {
  8 |   const worker = new JestWorker(
  9 |     new URL('./pathological_worker.js', import.meta.url),
 10 |     {
 11 |       numWorkers: 1,
 12 |       enableWorkerThreads: true
 13 |     }
 14 |   )
 15 | 
 16 |   let result
 17 |   const ac = new AbortController()
 18 | 
 19 |   try {
 20 |     result = await Promise.race([
 21 |       worker.render(str),
 22 |       new Promise((resolve, reject) => {
 23 |         setTimeout(() => reject(new Error('Terminated (timeout exceeded)')), 3000).unref()
 24 |       })
 25 |     ])
 26 |   } finally {
 27 |     ac.abort()
 28 |     await worker.end()
 29 |   }
 30 | 
 31 |   return result
 32 | }
 33 | 
 34 | describe('Pathological sequences speed', () => {
 35 |   it('Integrity check', async () => {
 36 |     assert.strictEqual(
 37 |       await test_pattern('foo'),
 38 |       '<p>foo</p>\n'
 39 |     )
 40 |   })
 41 | 
 42 |   // Ported from cmark, https://github.com/commonmark/cmark/blob/master/test/pathological_tests.py
 43 |   describe('Cmark', () => {
 44 |     it('verify original source crc', async () => {
 45 |       /* eslint-disable  max-len */
 46 |       const src = await needle('get', 'https://raw.githubusercontent.com/commonmark/cmark/master/test/pathological_tests.py')
 47 |       const src_md5 = crypto.createHash('md5').update(src.body).digest('hex')
 48 |       const tracked_md5 = JSON.parse(readFileSync(new URL('./pathological.json', import.meta.url))).md5
 49 | 
 50 |       assert.strictEqual(
 51 |         src_md5,
 52 |         tracked_md5,
 53 |         'CRC or cmark pathological tests hanged. Verify and update pathological.json'
 54 |       )
 55 |     })
 56 | 
 57 |     it('nested inlines', async () => {
 58 |       await test_pattern('*'.repeat(60000) + 'a' + '*'.repeat(60000))
 59 |     })
 60 | 
 61 |     it('nested strong emph', async () => {
 62 |       await test_pattern('*a **a '.repeat(5000) + 'b' + ' a** a*'.repeat(5000))
 63 |     })
 64 | 
 65 |     it('many emph closers with no openers', async () => {
 66 |       await test_pattern('a_ '.repeat(30000))
 67 |     })
 68 | 
 69 |     it('many emph openers with no closers', async () => {
 70 |       await test_pattern('_a '.repeat(30000))
 71 |     })
 72 | 
 73 |     it('many link closers with no openers', async () => {
 74 |       await test_pattern('a]'.repeat(10000))
 75 |     })
 76 | 
 77 |     it('many link openers with no closers', async () => {
 78 |       await test_pattern('[a'.repeat(10000))
 79 |     })
 80 | 
 81 |     it('mismatched openers and closers', async () => {
 82 |       await test_pattern('*a_ '.repeat(50000))
 83 |     })
 84 | 
 85 |     it('commonmark/cmark#389', async () => {
 86 |       await test_pattern('*a '.repeat(20000) + '_a*_ '.repeat(20000))
 87 |     })
 88 | 
 89 |     it('openers and closers multiple of 3', async () => {
 90 |       await test_pattern('a**b' + ('c* '.repeat(50000)))
 91 |     })
 92 | 
 93 |     it('link openers and emph closers', async () => {
 94 |       await test_pattern('[ a_'.repeat(10000))
 95 |     })
 96 | 
 97 |     it('pattern [ (]( repeated', async () => {
 98 |       await test_pattern('[ (]('.repeat(40000))
 99 |     })
100 | 
101 |     it('pattern ![[]() repeated', async () => {
102 |       await test_pattern('![[]()'.repeat(20000))
103 |     })
104 | 
105 |     it('nested brackets', async () => {
106 |       await test_pattern('['.repeat(20000) + 'a' + ']'.repeat(20000))
107 |     })
108 | 
109 |     it('nested block quotes', async () => {
110 |       await test_pattern('> '.repeat(50000) + 'a')
111 |     })
112 | 
113 |     it('deeply nested lists', async () => {
114 |       await test_pattern(Array(1000).fill(0).map(function (_, x) { return '  '.repeat(x) + '* a\n' }).join(''))
115 |     })
116 | 
117 |     it('U+0000 in input', async () => {
118 |       await test_pattern('abc\u0000de\u0000'.repeat(100000))
119 |     })
120 | 
121 |     it('backticks', async () => {
122 |       await test_pattern(Array(3000).fill(0).map(function (_, x) { return 'e' + '`'.repeat(x) }).join(''))
123 |     })
124 | 
125 |     it('unclosed links A', async () => {
126 |       await test_pattern('[a](<b'.repeat(30000))
127 |     })
128 | 
129 |     it('unclosed links B', async () => {
130 |       await test_pattern('[a](b'.repeat(30000))
131 |     })
132 | 
133 |     it('unclosed <!--', async () => {
134 |       await test_pattern('</' + '<!--'.repeat(100000))
135 |     })
136 | 
137 |     it('empty lines in deeply nested lists', async () => {
138 |       await test_pattern('- '.repeat(30000) + 'x' + '\n'.repeat(30000))
139 |     })
140 | 
141 |     it('empty lines in deeply nested lists in blockquote', async () => {
142 |       await test_pattern('> ' + '- '.repeat(30000) + 'x\n' + '>\n'.repeat(30000))
143 |     })
144 | 
145 |     it('emph in deep blockquote', async () => {
146 |       await test_pattern('>'.repeat(100000) + 'a*'.repeat(100000))
147 |     })
148 |   })
149 | 
150 |   describe('Markdown-it', () => {
151 |     it('emphasis **_* pattern', async () => {
152 |       await test_pattern('**_* '.repeat(50000))
153 |     })
154 | 
155 |     it('backtick ``\\``\\`` pattern', async () => {
156 |       await test_pattern('``\\'.repeat(50000))
157 |     })
158 | 
159 |     it('autolinks <<<<...<<> pattern', async () => {
160 |       await test_pattern('<'.repeat(400000) + '>')
161 |     })
162 | 
163 |     it('hardbreak whitespaces pattern', async () => {
164 |       await test_pattern('x' + ' '.repeat(150000) + 'x  \nx')
165 |     })
166 |   })
167 | })
168 | 


--------------------------------------------------------------------------------
/test/pathological_worker.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | 
3 | exports.render = async (str) => {
4 |   return (await import('../index.mjs')).default().render(str)
5 | }
6 | 


--------------------------------------------------------------------------------
/test/ruler.mjs:
--------------------------------------------------------------------------------
  1 | import { assert } from 'chai'
  2 | import Ruler from '../lib/ruler.mjs'
  3 | 
  4 | describe('Ruler', function () {
  5 |   it('should replace rule (.at)', function () {
  6 |     const ruler = new Ruler()
  7 |     let res = 0
  8 | 
  9 |     ruler.push('test', function foo () { res = 1 })
 10 |     ruler.at('test', function bar () { res = 2 })
 11 | 
 12 |     const rules = ruler.getRules('')
 13 | 
 14 |     assert.strictEqual(rules.length, 1)
 15 |     rules[0]()
 16 |     assert.strictEqual(res, 2)
 17 |   })
 18 | 
 19 |   it('should inject before/after rule', function () {
 20 |     const ruler = new Ruler()
 21 |     let res = 0
 22 | 
 23 |     ruler.push('test', function foo () { res = 1 })
 24 |     ruler.before('test', 'before_test', function fooBefore () { res = -10 })
 25 |     ruler.after('test', 'after_test', function fooAfter () { res = 10 })
 26 | 
 27 |     const rules = ruler.getRules('')
 28 | 
 29 |     assert.strictEqual(rules.length, 3)
 30 |     rules[0]()
 31 |     assert.strictEqual(res, -10)
 32 |     rules[1]()
 33 |     assert.strictEqual(res, 1)
 34 |     rules[2]()
 35 |     assert.strictEqual(res, 10)
 36 |   })
 37 | 
 38 |   it('should enable/disable rule', function () {
 39 |     const ruler = new Ruler()
 40 |     let rules
 41 | 
 42 |     ruler.push('test', function foo () {})
 43 |     ruler.push('test2', function bar () {})
 44 | 
 45 |     rules = ruler.getRules('')
 46 |     assert.strictEqual(rules.length, 2)
 47 | 
 48 |     ruler.disable('test')
 49 |     rules = ruler.getRules('')
 50 |     assert.strictEqual(rules.length, 1)
 51 |     ruler.disable('test2')
 52 |     rules = ruler.getRules('')
 53 |     assert.strictEqual(rules.length, 0)
 54 | 
 55 |     ruler.enable('test')
 56 |     rules = ruler.getRules('')
 57 |     assert.strictEqual(rules.length, 1)
 58 |     ruler.enable('test2')
 59 |     rules = ruler.getRules('')
 60 |     assert.strictEqual(rules.length, 2)
 61 |   })
 62 | 
 63 |   it('should enable/disable multiple rule', function () {
 64 |     const ruler = new Ruler()
 65 |     let rules
 66 | 
 67 |     ruler.push('test', function foo () {})
 68 |     ruler.push('test2', function bar () {})
 69 | 
 70 |     ruler.disable(['test', 'test2'])
 71 |     rules = ruler.getRules('')
 72 |     assert.strictEqual(rules.length, 0)
 73 |     ruler.enable(['test', 'test2'])
 74 |     rules = ruler.getRules('')
 75 |     assert.strictEqual(rules.length, 2)
 76 |   })
 77 | 
 78 |   it('should enable rules by whitelist', function () {
 79 |     const ruler = new Ruler()
 80 | 
 81 |     ruler.push('test', function foo () {})
 82 |     ruler.push('test2', function bar () {})
 83 | 
 84 |     ruler.enableOnly('test')
 85 |     const rules = ruler.getRules('')
 86 |     assert.strictEqual(rules.length, 1)
 87 |   })
 88 | 
 89 |   it('should support multiple chains', function () {
 90 |     const ruler = new Ruler()
 91 |     let rules
 92 | 
 93 |     ruler.push('test', function foo () {})
 94 |     ruler.push('test2', function bar () {}, { alt: ['alt1'] })
 95 |     ruler.push('test2', function bar () {}, { alt: ['alt1', 'alt2'] })
 96 | 
 97 |     rules = ruler.getRules('')
 98 |     assert.strictEqual(rules.length, 3)
 99 |     rules = ruler.getRules('alt1')
100 |     assert.strictEqual(rules.length, 2)
101 |     rules = ruler.getRules('alt2')
102 |     assert.strictEqual(rules.length, 1)
103 |   })
104 | 
105 |   it('should fail on invalid rule name', function () {
106 |     const ruler = new Ruler()
107 | 
108 |     ruler.push('test', function foo () {})
109 | 
110 |     assert.throws(function () {
111 |       ruler.at('invalid name', function bar () {})
112 |     })
113 |     assert.throws(function () {
114 |       ruler.before('invalid name', function bar () {})
115 |     })
116 |     assert.throws(function () {
117 |       ruler.after('invalid name', function bar () {})
118 |     })
119 |     assert.throws(function () {
120 |       ruler.enable('invalid name')
121 |     })
122 |     assert.throws(function () {
123 |       ruler.disable('invalid name')
124 |     })
125 |   })
126 | 
127 |   it('should not fail on invalid rule name in silent mode', function () {
128 |     const ruler = new Ruler()
129 | 
130 |     ruler.push('test', function foo () {})
131 | 
132 |     assert.doesNotThrow(function () {
133 |       ruler.enable('invalid name', true)
134 |     })
135 |     assert.doesNotThrow(function () {
136 |       ruler.enableOnly('invalid name', true)
137 |     })
138 |     assert.doesNotThrow(function () {
139 |       ruler.disable('invalid name', true)
140 |     })
141 |   })
142 | })
143 | 


--------------------------------------------------------------------------------
/test/token.mjs:
--------------------------------------------------------------------------------
 1 | import { assert } from 'chai'
 2 | import Token from '../lib/token.mjs'
 3 | 
 4 | describe('Token', function () {
 5 |   it('attr', function () {
 6 |     const t = new Token('test_token', 'tok', 1)
 7 | 
 8 |     assert.strictEqual(t.attrs, null)
 9 |     assert.equal(t.attrIndex('foo'), -1)
10 | 
11 |     t.attrPush(['foo', 'bar'])
12 |     t.attrPush(['baz', 'bad'])
13 | 
14 |     assert.equal(t.attrIndex('foo'), 0)
15 |     assert.equal(t.attrIndex('baz'), 1)
16 |     assert.equal(t.attrIndex('none'), -1)
17 |   })
18 | })
19 | 


--------------------------------------------------------------------------------
/test/utils.mjs:
--------------------------------------------------------------------------------
 1 | import { assert } from 'chai'
 2 | import * as utils from '../lib/common/utils.mjs'
 3 | 
 4 | describe('Utils', function () {
 5 |   it('fromCodePoint', function () {
 6 |     const fromCodePoint = utils.fromCodePoint
 7 | 
 8 |     assert.strictEqual(fromCodePoint(0x20), ' ')
 9 |     assert.strictEqual(fromCodePoint(0x1F601), '😁')
10 |   })
11 | 
12 |   it('isValidEntityCode', function () {
13 |     const isValidEntityCode = utils.isValidEntityCode
14 | 
15 |     assert.strictEqual(isValidEntityCode(0x20), true)
16 |     assert.strictEqual(isValidEntityCode(0xD800), false)
17 |     assert.strictEqual(isValidEntityCode(0xFDD0), false)
18 |     assert.strictEqual(isValidEntityCode(0x1FFFF), false)
19 |     assert.strictEqual(isValidEntityCode(0x1FFFE), false)
20 |     assert.strictEqual(isValidEntityCode(0x00), false)
21 |     assert.strictEqual(isValidEntityCode(0x0B), false)
22 |     assert.strictEqual(isValidEntityCode(0x0E), false)
23 |     assert.strictEqual(isValidEntityCode(0x7F), false)
24 |   })
25 | 
26 |   /* it('replaceEntities', function () {
27 |     var replaceEntities = utils.replaceEntities;
28 | 
29 |     assert.strictEqual(replaceEntities('&amp;'), '&');
30 |     assert.strictEqual(replaceEntities('&#32;'), ' ');
31 |     assert.strictEqual(replaceEntities('&#x20;'), ' ');
32 |     assert.strictEqual(replaceEntities('&amp;&amp;'), '&&');
33 | 
34 |     assert.strictEqual(replaceEntities('&am;'), '&am;');
35 |     assert.strictEqual(replaceEntities('&#00;'), '&#00;');
36 |   }); */
37 | 
38 |   it('assign', function () {
39 |     const assign = utils.assign
40 | 
41 |     assert.deepEqual(assign({ a: 1 }, null, { b: 2 }), { a: 1, b: 2 })
42 |     assert.throws(function () {
43 |       assign({}, 123)
44 |     })
45 |   })
46 | 
47 |   it('escapeRE', function () {
48 |     const escapeRE = utils.escapeRE
49 | 
50 |     assert.strictEqual(escapeRE(' .?*+^$[]\\(){}|-'), ' \\.\\?\\*\\+\\^\\$\\[\\]\\\\\\(\\)\\{\\}\\|\\-')
51 |   })
52 | 
53 |   it('isWhiteSpace', function () {
54 |     const isWhiteSpace = utils.isWhiteSpace
55 | 
56 |     assert.strictEqual(isWhiteSpace(0x2000), true)
57 |     assert.strictEqual(isWhiteSpace(0x09), true)
58 | 
59 |     assert.strictEqual(isWhiteSpace(0x30), false)
60 |   })
61 | 
62 |   it('isMdAsciiPunct', function () {
63 |     const isMdAsciiPunct = utils.isMdAsciiPunct
64 | 
65 |     assert.strictEqual(isMdAsciiPunct(0x30), false)
66 | 
67 |     '!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~'.split('').forEach(function (ch) {
68 |       assert.strictEqual(isMdAsciiPunct(ch.charCodeAt(0)), true)
69 |     })
70 |   })
71 | 
72 |   it('unescapeMd', function () {
73 |     const unescapeMd = utils.unescapeMd
74 | 
75 |     assert.strictEqual(unescapeMd('\\foo'), '\\foo')
76 |     assert.strictEqual(unescapeMd('foo'), 'foo')
77 | 
78 |     '!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~'.split('').forEach(function (ch) {
79 |       assert.strictEqual(unescapeMd('\\' + ch), ch)
80 |     })
81 |   })
82 | })
83 | 


--------------------------------------------------------------------------------