├── .remarkignore ├── .npmrc ├── .prettierignore ├── .gitattributes ├── .gitignore ├── index.js ├── test ├── fixture │ ├── algorithm-2d.md │ ├── algorithm-2e.md │ ├── links-autolink-literals-and-characters.md │ ├── algorithm-2e.html │ ├── algorithm-2d.html │ ├── links-autolink-literals-and-characters.html │ ├── algorithm-2c.md │ ├── brackets.md │ ├── www-domain-start.md │ ├── www-domain-continue.md │ ├── path-or-link-end.md │ ├── www-domain-dot.md │ ├── www-path-start.md │ ├── www-path-continue.md │ ├── http-domain-start.md │ ├── domain-character-reference-like-named.md │ ├── http-domain-continue.md │ ├── http-domain-start.html │ ├── combined-with-images.md │ ├── http-path-start.md │ ├── path-character-reference-like-named.md │ ├── domain-character-reference-like-numerical.md │ ├── http-path-continue.md │ ├── algorithm-2c.html │ ├── path-character-reference-like-numerical.md │ ├── brackets.html │ ├── email-tld-digits.md │ ├── path-or-link-end.html │ ├── algorithm-2b.md │ ├── combined-with-images.html │ ├── email-tld-digits.html │ ├── www-domain-start.html │ ├── http-domain-continue.html │ ├── www-domain-continue.html │ ├── http-path-start.html │ ├── www-domain-dot.html │ ├── www-path-start.html │ ├── combined-with-links.md │ ├── http-path-continue.html │ ├── previous.md │ ├── www-path-continue.html │ ├── domain-character-reference-like-named.html │ ├── algorithm-2.md │ ├── path-character-reference-like-named.html │ ├── domain-character-reference-like-numerical.html │ ├── path-character-reference-like-numerical.html │ ├── previous-complex.md │ ├── base.md │ ├── algorithm-2b.html │ ├── combined-with-links.html │ ├── previous.html │ ├── base.html │ ├── algorithm-2.html │ └── previous-complex.html └── index.js ├── .editorconfig ├── .github └── workflows │ ├── bb.yml │ └── main.yml ├── tsconfig.json ├── license ├── package.json ├── lib └── index.js └── readme.md /.remarkignore: -------------------------------------------------------------------------------- 1 | test/ 2 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | ignore-scripts=true 2 | package-lock=false 3 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | coverage/ 2 | *.html 3 | *.md 4 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # https://github.com/github/linguist/blob/HEAD/docs/overrides.md 2 | test/**/*.html linguist-vendored 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.d.ts 2 | *.log 3 | *.map 4 | *.tsbuildinfo 5 | .DS_Store 6 | coverage/ 7 | node_modules/ 8 | yarn.lock 9 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | export { 2 | gfmAutolinkLiteralFromMarkdown, 3 | gfmAutolinkLiteralToMarkdown 4 | } from './lib/index.js' 5 | -------------------------------------------------------------------------------- /test/fixture/algorithm-2d.md: -------------------------------------------------------------------------------- 1 | [ www.commonmark.org/he[ https://example.com 2 | [ https://example.com 3 | https://example.com 4 | https://example.com

5 | -------------------------------------------------------------------------------- /test/fixture/algorithm-2d.html: -------------------------------------------------------------------------------- 1 |

[ www.commonmark.org/he<lp

2 |

[ www.commonmark.org/he<.lp

3 |

[ www.commonmark.org/he.<lp

4 | -------------------------------------------------------------------------------- /.github/workflows/bb.yml: -------------------------------------------------------------------------------- 1 | jobs: 2 | main: 3 | runs-on: ubuntu-latest 4 | steps: 5 | - uses: unifiedjs/beep-boop-beta@main 6 | with: 7 | repo-token: ${{secrets.GITHUB_TOKEN}} 8 | name: bb 9 | on: 10 | issues: 11 | types: [closed, edited, labeled, opened, reopened, unlabeled] 12 | pull_request_target: 13 | types: [closed, edited, labeled, opened, reopened, unlabeled] 14 | -------------------------------------------------------------------------------- /test/fixture/links-autolink-literals-and-characters.html: -------------------------------------------------------------------------------- 1 |

www.example.com/a©

2 |

www.example.com/a©

3 |

www.example.com/a&bogus;

4 |

www.example.com/a&bogus;

5 |

www.example.com/a.

6 |

www.example.com/a\.

7 | -------------------------------------------------------------------------------- /test/fixture/algorithm-2c.md: -------------------------------------------------------------------------------- 1 | [ http://a.com/search?q=commonmark&hl;=en 2 | 3 | [ http://a.com/search?q=commonmark&hl; 4 | 5 | [ http://a.com/search?q=commonmark&hl;b 6 | 7 | [ http://a.com/search?q=commonmark&hl;d 8 | 9 | [ http://a.com/search?q=commonmark©=en 10 | 11 | [ http://a.com/search?q=commonmark© 12 | 13 | [ http://a.com/search?q=commonmark©b 14 | 15 | [ http://a.com/search?q=commonmark©d 16 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "checkJs": true, 4 | "customConditions": ["development"], 5 | "declarationMap": true, 6 | "declaration": true, 7 | "emitDeclarationOnly": true, 8 | "exactOptionalPropertyTypes": true, 9 | "lib": ["es2022"], 10 | "module": "node16", 11 | "strict": true, 12 | "target": "es2022" 13 | }, 14 | "exclude": ["coverage/", "node_modules/"], 15 | "include": ["**/*.js"] 16 | } 17 | -------------------------------------------------------------------------------- /test/fixture/brackets.md: -------------------------------------------------------------------------------- 1 | H0. 2 | 3 | [https://a.com©b 4 | 5 | [www.a.com©b 6 | 7 | H1. 8 | 9 | []https://a.com©b 10 | 11 | []www.a.com©b 12 | 13 | H2. 14 | 15 | [] https://a.com©b 16 | 17 | [] www.a.com©b 18 | 19 | H3. 20 | 21 | [[https://a.com©b 22 | 23 | [[www.a.com©b 24 | 25 | H4. 26 | 27 | [[]https://a.com©b 28 | 29 | [[]www.a.com©b 30 | 31 | H5. 32 | 33 | [[]]https://a.com©b 34 | 35 | [[]]www.a.com©b 36 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | jobs: 2 | main: 3 | name: ${{matrix.node}} 4 | runs-on: ubuntu-latest 5 | steps: 6 | - uses: actions/checkout@v4 7 | - uses: actions/setup-node@v4 8 | with: 9 | node-version: ${{matrix.node}} 10 | - run: npm install 11 | - run: npm test 12 | - uses: codecov/codecov-action@v5 13 | strategy: 14 | matrix: 15 | node: 16 | - lts/hydrogen 17 | - node 18 | name: main 19 | on: 20 | - pull_request 21 | - push 22 | -------------------------------------------------------------------------------- /test/fixture/www-domain-start.md: -------------------------------------------------------------------------------- 1 | # wwwtf? 2 | 3 | www. (space) 4 | 5 | www.! 6 | 7 | www." 8 | 9 | www.# 10 | 11 | www.$ 12 | 13 | www.% 14 | 15 | www.& 16 | 17 | www.' 18 | 19 | www.( 20 | 21 | www.) 22 | 23 | www.* 24 | 25 | www.+ 26 | 27 | www., 28 | 29 | www.- 30 | 31 | www. 32 | 33 | www.. 34 | 35 | www./ 36 | 37 | www.: 38 | 39 | www.; 40 | 41 | www.< 42 | 43 | www.= 44 | 45 | www.> 46 | 47 | www.? 48 | 49 | www.@ 50 | 51 | www.[ 52 | 53 | www.\ 54 | 55 | www.] 56 | 57 | www.^ 58 | 59 | www._ 60 | 61 | www.` 62 | 63 | www.{ 64 | 65 | www.| 66 | 67 | www.} 68 | 69 | www.~ 70 | -------------------------------------------------------------------------------- /test/fixture/www-domain-continue.md: -------------------------------------------------------------------------------- 1 | # wwwtf 2? 2 | 3 | www.a (space) 4 | 5 | www.a! 6 | 7 | www.a" 8 | 9 | www.a# 10 | 11 | www.a$ 12 | 13 | www.a% 14 | 15 | www.a& 16 | 17 | www.a' 18 | 19 | www.a( 20 | 21 | www.a) 22 | 23 | www.a* 24 | 25 | www.a+ 26 | 27 | www.a, 28 | 29 | www.a- 30 | 31 | www.a 32 | 33 | www.a. 34 | 35 | www.a/ 36 | 37 | www.a: 38 | 39 | www.a; 40 | 41 | www.a< 42 | 43 | www.a= 44 | 45 | www.a> 46 | 47 | www.a? 48 | 49 | www.a@ 50 | 51 | www.a[ 52 | 53 | www.a\ 54 | 55 | www.a] 56 | 57 | www.a^ 58 | 59 | www.a_ 60 | 61 | www.a` 62 | 63 | www.a{ 64 | 65 | www.a| 66 | 67 | www.a} 68 | 69 | www.a~ 70 | -------------------------------------------------------------------------------- /test/fixture/path-or-link-end.md: -------------------------------------------------------------------------------- 1 | In autolink literal path or link end? 2 | 3 | [https://a.com/d]() 4 | 5 | [http://a.com/d]() 6 | 7 | [www.a.com/d]() 8 | 9 | https://a.com/d]() 10 | 11 | http://a.com/d]() 12 | 13 | www.a.com/d]() 14 | 15 | In autolink literal search or link end? 16 | 17 | [https://a.com?d]() 18 | 19 | [http://a.com?d]() 20 | 21 | [www.a.com?d]() 22 | 23 | https://a.com?d]() 24 | 25 | http://a.com?d]() 26 | 27 | www.a.com?d]() 28 | 29 | In autolink literal hash or link end? 30 | 31 | [https://a.com#d]() 32 | 33 | [http://a.com#d]() 34 | 35 | [www.a.com#d]() 36 | 37 | https://a.com#d]() 38 | 39 | http://a.com#d]() 40 | 41 | www.a.com#d]() 42 | -------------------------------------------------------------------------------- /test/fixture/www-domain-dot.md: -------------------------------------------------------------------------------- 1 | # wwwtf 5? 2 | 3 | www.a. (space) 4 | 5 | www.a.! 6 | 7 | www.a." 8 | 9 | www.a.# 10 | 11 | www.a.$ 12 | 13 | www.a.% 14 | 15 | www.a.& 16 | 17 | www.a.' 18 | 19 | www.a.( 20 | 21 | www.a.) 22 | 23 | www.a.* 24 | 25 | www.a.+ 26 | 27 | www.a., 28 | 29 | www.a.- 30 | 31 | www.a. 32 | 33 | www.a.. 34 | 35 | www.a./ 36 | 37 | www.a.: 38 | 39 | www.a.; 40 | 41 | www.a.< 42 | 43 | www.a.= 44 | 45 | www.a.> 46 | 47 | www.a.? 48 | 49 | www.a.@ 50 | 51 | www.a.[ 52 | 53 | www.a.\ 54 | 55 | www.a.] 56 | 57 | www.a.^ 58 | 59 | www.a._ 60 | 61 | www.a.` 62 | 63 | www.a.{ 64 | 65 | www.a.| 66 | 67 | www.a.} 68 | 69 | www.a.~ 70 | -------------------------------------------------------------------------------- /test/fixture/www-path-start.md: -------------------------------------------------------------------------------- 1 | # wwwtf? (3) 2 | 3 | www.a/ (space) 4 | 5 | www.a/! 6 | 7 | www.a/" 8 | 9 | www.a/# 10 | 11 | www.a/$ 12 | 13 | www.a/% 14 | 15 | www.a/& 16 | 17 | www.a/' 18 | 19 | www.a/( 20 | 21 | www.a/) 22 | 23 | www.a/* 24 | 25 | www.a/+ 26 | 27 | www.a/, 28 | 29 | www.a/- 30 | 31 | www.a/ 32 | 33 | www.a/. 34 | 35 | www.a// 36 | 37 | www.a/: 38 | 39 | www.a/; 40 | 41 | www.a/< 42 | 43 | www.a/= 44 | 45 | www.a/> 46 | 47 | www.a/? 48 | 49 | www.a/@ 50 | 51 | www.a/[ 52 | 53 | www.a/\ 54 | 55 | www.a/] 56 | 57 | www.a/^ 58 | 59 | www.a/_ 60 | 61 | www.a/` 62 | 63 | www.a/{ 64 | 65 | www.a/| 66 | 67 | www.a/} 68 | 69 | www.a/~ 70 | -------------------------------------------------------------------------------- /test/fixture/www-path-continue.md: -------------------------------------------------------------------------------- 1 | # wwwtf? (4) 2 | 3 | www.a/b (space) 4 | 5 | www.a/b! 6 | 7 | www.a/b" 8 | 9 | www.a/b# 10 | 11 | www.a/b$ 12 | 13 | www.a/b% 14 | 15 | www.a/b& 16 | 17 | www.a/b' 18 | 19 | www.a/b( 20 | 21 | www.a/b) 22 | 23 | www.a/b* 24 | 25 | www.a/b+ 26 | 27 | www.a/b, 28 | 29 | www.a/b- 30 | 31 | www.a/b 32 | 33 | www.a/b. 34 | 35 | www.a/b/ 36 | 37 | www.a/b: 38 | 39 | www.a/b; 40 | 41 | www.a/b< 42 | 43 | www.a/b= 44 | 45 | www.a/b> 46 | 47 | www.a/b? 48 | 49 | www.a/b@ 50 | 51 | www.a/b[ 52 | 53 | www.a/b\ 54 | 55 | www.a/b] 56 | 57 | www.a/b^ 58 | 59 | www.a/b_ 60 | 61 | www.a/b` 62 | 63 | www.a/b{ 64 | 65 | www.a/b| 66 | 67 | www.a/b} 68 | 69 | www.a/b~ 70 | -------------------------------------------------------------------------------- /test/fixture/http-domain-start.md: -------------------------------------------------------------------------------- 1 | # httpshhh? (1) 2 | 3 | http:// (space) 4 | 5 | http://! 6 | 7 | http://" 8 | 9 | http://# 10 | 11 | http://$ 12 | 13 | http://% 14 | 15 | http://& 16 | 17 | http://' 18 | 19 | http://( 20 | 21 | http://) 22 | 23 | http://* 24 | 25 | http://+ 26 | 27 | http://, 28 | 29 | http://- 30 | 31 | http:// 32 | 33 | http://. 34 | 35 | http:/// 36 | 37 | http://: 38 | 39 | http://; 40 | 41 | http://< 42 | 43 | http://= 44 | 45 | http://> 46 | 47 | http://? 48 | 49 | http://@ 50 | 51 | http://[ 52 | 53 | http://\ 54 | 55 | http://] 56 | 57 | http://^ 58 | 59 | http://_ 60 | 61 | http://` 62 | 63 | http://{ 64 | 65 | http://| 66 | 67 | http://} 68 | 69 | http://~ 70 | -------------------------------------------------------------------------------- /test/fixture/domain-character-reference-like-named.md: -------------------------------------------------------------------------------- 1 | # “character reference” 2 | 3 | www.a&b (space) 4 | 5 | www.a&b! 6 | 7 | www.a&b" 8 | 9 | www.a&b# 10 | 11 | www.a&b$ 12 | 13 | www.a&b% 14 | 15 | www.a&b& 16 | 17 | www.a&b' 18 | 19 | www.a&b( 20 | 21 | www.a&b) 22 | 23 | www.a&b* 24 | 25 | www.a&b+ 26 | 27 | www.a&b, 28 | 29 | www.a&b- 30 | 31 | www.a&b 32 | 33 | www.a&b. 34 | 35 | www.a&b/ 36 | 37 | www.a&b: 38 | 39 | www.a&b; 40 | 41 | www.a&b< 42 | 43 | www.a&b= 44 | 45 | www.a&b> 46 | 47 | www.a&b? 48 | 49 | www.a&b@ 50 | 51 | www.a&b[ 52 | 53 | www.a&b\ 54 | 55 | www.a&b] 56 | 57 | www.a&b^ 58 | 59 | www.a&b_ 60 | 61 | www.a&b` 62 | 63 | www.a&b{ 64 | 65 | www.a&b| 66 | 67 | www.a&b} 68 | 69 | www.a&b~ 70 | -------------------------------------------------------------------------------- /test/fixture/http-domain-continue.md: -------------------------------------------------------------------------------- 1 | # httpshhh? (2) 2 | 3 | http://a (space) 4 | 5 | http://a! 6 | 7 | http://a" 8 | 9 | http://a# 10 | 11 | http://a$ 12 | 13 | http://a% 14 | 15 | http://a& 16 | 17 | http://a' 18 | 19 | http://a( 20 | 21 | http://a) 22 | 23 | http://a* 24 | 25 | http://a+ 26 | 27 | http://a, 28 | 29 | http://a- 30 | 31 | http://a 32 | 33 | http://a. 34 | 35 | http://a/ 36 | 37 | http://a: 38 | 39 | http://a; 40 | 41 | http://a< 42 | 43 | http://a= 44 | 45 | http://a> 46 | 47 | http://a? 48 | 49 | http://a@ 50 | 51 | http://a[ 52 | 53 | http://a\ 54 | 55 | http://a] 56 | 57 | http://a^ 58 | 59 | http://a_ 60 | 61 | http://a` 62 | 63 | http://a{ 64 | 65 | http://a| 66 | 67 | http://a} 68 | 69 | http://a~ 70 | -------------------------------------------------------------------------------- /test/fixture/http-domain-start.html: -------------------------------------------------------------------------------- 1 |

httpshhh? (1)

2 |

http:// (space)

3 |

http://!

4 |

http://"

5 |

http://#

6 |

http://$

7 |

http://%

8 |

http://&

9 |

http://'

10 |

http://(

11 |

http://)

12 |

http://*

13 |

http://+

14 |

http://,

15 |

http://-

16 |

http://

17 |

http://.

18 |

http:///

19 |

http://:

20 |

http://;

21 |

http://<

22 |

http://=

23 |

http://>

24 |

http://?

25 |

http://@

26 |

http://[

27 |

http://\

28 |

http://]

29 |

http://^

30 |

http://_

31 |

http://`

32 |

http://{

33 |

http://|

34 |

http://}

35 |

http://~

36 | -------------------------------------------------------------------------------- /test/fixture/combined-with-images.md: -------------------------------------------------------------------------------- 1 | Image start. 2 | 3 | ![https://a.com 4 | 5 | ![http://a.com 6 | 7 | ![www.a.com 8 | 9 | ![a@b.c 10 | 11 | Image start and label end. 12 | 13 | ![https://a.com] 14 | 15 | ![http://a.com] 16 | 17 | ![www.a.com] 18 | 19 | ![a@b.c] 20 | 21 | Image label with reference (note: GH cleans hashes here, but we keep them in). 22 | 23 | ![https://a.com][x] 24 | 25 | ![http://a.com][x] 26 | 27 | ![www.a.com][x] 28 | 29 | ![a@b.c][x] 30 | 31 | [x]: # 32 | 33 | Image label with resource. 34 | 35 | ![https://a.com]() 36 | 37 | ![http://a.com]() 38 | 39 | ![www.a.com]() 40 | 41 | ![a@b.c]() 42 | 43 | Autolink literal after image. 44 | 45 | ![a]() https://a.com 46 | 47 | ![a]() http://a.com 48 | 49 | ![a]() www.a.com 50 | 51 | ![a]() a@b.c 52 | -------------------------------------------------------------------------------- /test/fixture/http-path-start.md: -------------------------------------------------------------------------------- 1 | # httpshhh? (3) 2 | 3 | http://a/ (space) 4 | 5 | http://a/! 6 | 7 | http://a/" 8 | 9 | http://a/# 10 | 11 | http://a/$ 12 | 13 | http://a/% 14 | 15 | http://a/& 16 | 17 | http://a/' 18 | 19 | http://a/( 20 | 21 | http://a/) 22 | 23 | http://a/* 24 | 25 | http://a/+ 26 | 27 | http://a/, 28 | 29 | http://a/- 30 | 31 | http://a/ 32 | 33 | http://a/. 34 | 35 | http://a// 36 | 37 | http://a/: 38 | 39 | http://a/; 40 | 41 | http://a/< 42 | 43 | http://a/= 44 | 45 | http://a/> 46 | 47 | http://a/? 48 | 49 | http://a/@ 50 | 51 | http://a/[ 52 | 53 | http://a/\ 54 | 55 | http://a/] 56 | 57 | http://a/^ 58 | 59 | http://a/_ 60 | 61 | http://a/` 62 | 63 | http://a/{ 64 | 65 | http://a/| 66 | 67 | http://a/} 68 | 69 | http://a/~ 70 | -------------------------------------------------------------------------------- /test/fixture/path-character-reference-like-named.md: -------------------------------------------------------------------------------- 1 | # “character reference” 2 | 3 | www.a/b&c (space) 4 | 5 | www.a/b&c! 6 | 7 | www.a/b&c" 8 | 9 | www.a/b&c# 10 | 11 | www.a/b&c$ 12 | 13 | www.a/b&c% 14 | 15 | www.a/b&c& 16 | 17 | www.a/b&c' 18 | 19 | www.a/b&c( 20 | 21 | www.a/b&c) 22 | 23 | www.a/b&c* 24 | 25 | www.a/b&c+ 26 | 27 | www.a/b&c, 28 | 29 | www.a/b&c- 30 | 31 | www.a/b&c 32 | 33 | www.a/b&c. 34 | 35 | www.a/b&c/ 36 | 37 | www.a/b&c: 38 | 39 | www.a/b&c; 40 | 41 | www.a/b&c< 42 | 43 | www.a/b&c= 44 | 45 | www.a/b&c> 46 | 47 | www.a/b&c? 48 | 49 | www.a/b&c@ 50 | 51 | www.a/b&c[ 52 | 53 | www.a/b&c\ 54 | 55 | www.a/b&c] 56 | 57 | www.a/b&c^ 58 | 59 | www.a/b&c_ 60 | 61 | www.a/b&c` 62 | 63 | www.a/b&c{ 64 | 65 | www.a/b&c| 66 | 67 | www.a/b&c} 68 | 69 | www.a/b&c~ 70 | -------------------------------------------------------------------------------- /test/fixture/domain-character-reference-like-numerical.md: -------------------------------------------------------------------------------- 1 | # “character reference” 2 | 3 | www.a# (space) 4 | 5 | www.a#! 6 | 7 | www.a#" 8 | 9 | www.a## 10 | 11 | www.a#$ 12 | 13 | www.a#% 14 | 15 | www.a#& 16 | 17 | www.a#' 18 | 19 | www.a#( 20 | 21 | www.a#) 22 | 23 | www.a#* 24 | 25 | www.a#+ 26 | 27 | www.a#, 28 | 29 | www.a#- 30 | 31 | www.a# 32 | 33 | www.a#. 34 | 35 | www.a#/ 36 | 37 | www.a#: 38 | 39 | www.a# 40 | 41 | www.a#< 42 | 43 | www.a#= 44 | 45 | www.a#> 46 | 47 | www.a#? 48 | 49 | www.a#@ 50 | 51 | www.a#[ 52 | 53 | www.a#\ 54 | 55 | www.a#] 56 | 57 | www.a#^ 58 | 59 | www.a#_ 60 | 61 | www.a#` 62 | 63 | www.a#{ 64 | 65 | www.a#| 66 | 67 | www.a#} 68 | 69 | www.a#~ 70 | -------------------------------------------------------------------------------- /test/fixture/http-path-continue.md: -------------------------------------------------------------------------------- 1 | # httpshhh? (4) 2 | 3 | http://a/b (space) 4 | 5 | http://a/b! 6 | 7 | http://a/b" 8 | 9 | http://a/b# 10 | 11 | http://a/b$ 12 | 13 | http://a/b% 14 | 15 | http://a/b& 16 | 17 | http://a/b' 18 | 19 | http://a/b( 20 | 21 | http://a/b) 22 | 23 | http://a/b* 24 | 25 | http://a/b+ 26 | 27 | http://a/b, 28 | 29 | http://a/b- 30 | 31 | http://a/b 32 | 33 | http://a/b. 34 | 35 | http://a/b/ 36 | 37 | http://a/b: 38 | 39 | http://a/b; 40 | 41 | http://a/b< 42 | 43 | http://a/b= 44 | 45 | http://a/b> 46 | 47 | http://a/b? 48 | 49 | http://a/b@ 50 | 51 | http://a/b[ 52 | 53 | http://a/b\ 54 | 55 | http://a/b] 56 | 57 | http://a/b^ 58 | 59 | http://a/b_ 60 | 61 | http://a/b` 62 | 63 | http://a/b{ 64 | 65 | http://a/b| 66 | 67 | http://a/b} 68 | 69 | http://a/b~ 70 | -------------------------------------------------------------------------------- /test/fixture/algorithm-2c.html: -------------------------------------------------------------------------------- 1 |

[ http://a.com/search?q=commonmark&hl;=en

2 |

[ http://a.com/search?q=commonmark&hl;

3 |

[ http://a.com/search?q=commonmark&hl;b

4 |

[ http://a.com/search?q=commonmark&hl;d

5 |

[ http://a.com/search?q=commonmark©=en

6 |

[ http://a.com/search?q=commonmark©

7 |

[ http://a.com/search?q=commonmark©b

8 |

[ http://a.com/search?q=commonmark©d

9 | -------------------------------------------------------------------------------- /test/fixture/path-character-reference-like-numerical.md: -------------------------------------------------------------------------------- 1 | # “character reference” 2 | 3 | www.a/b# (space) 4 | 5 | www.a/b#! 6 | 7 | www.a/b#" 8 | 9 | www.a/b## 10 | 11 | www.a/b#$ 12 | 13 | www.a/b#% 14 | 15 | www.a/b#& 16 | 17 | www.a/b#' 18 | 19 | www.a/b#( 20 | 21 | www.a/b#) 22 | 23 | www.a/b#* 24 | 25 | www.a/b#+ 26 | 27 | www.a/b#, 28 | 29 | www.a/b#- 30 | 31 | www.a/b# 32 | 33 | www.a/b#. 34 | 35 | www.a/b#/ 36 | 37 | www.a/b#: 38 | 39 | www.a/b# 40 | 41 | www.a/b#< 42 | 43 | www.a/b#= 44 | 45 | www.a/b#> 46 | 47 | www.a/b#? 48 | 49 | www.a/b#@ 50 | 51 | www.a/b#[ 52 | 53 | www.a/b#\ 54 | 55 | www.a/b#] 56 | 57 | www.a/b#^ 58 | 59 | www.a/b#_ 60 | 61 | www.a/b#` 62 | 63 | www.a/b#{ 64 | 65 | www.a/b#| 66 | 67 | www.a/b#} 68 | 69 | www.a/b#~ 70 | -------------------------------------------------------------------------------- /test/fixture/brackets.html: -------------------------------------------------------------------------------- 1 |

H0.

2 |

[https://a.com©b

3 |

[www.a.com©b

4 |

H1.

5 |

[]https://a.com&copy;b

6 |

[]www.a.com&copy;b

7 |

H2.

8 |

[] https://a.com&copy;b

9 |

[] www.a.com&copy;b

10 |

H3.

11 |

[[https://a.com©b

12 |

[[www.a.com©b

13 |

H4.

14 |

[[]https://a.com©b

15 |

[[]www.a.com©b

16 |

H5.

17 |

[[]]https://a.com&copy;b

18 |

[[]]www.a.com&copy;b

19 | -------------------------------------------------------------------------------- /test/fixture/email-tld-digits.md: -------------------------------------------------------------------------------- 1 | a@0.0 2 | 3 | a@0.b 4 | 5 | a@a.29 6 | 7 | a@a.b 8 | 9 | a@0.0.c 10 | 11 | react@0.11.1 12 | 13 | react@0.12.0-rc1 14 | 15 | react@0.14.0-alpha1 16 | 17 | react@16.7.0-alpha.2 18 | 19 | react@0.0.0-experimental-aae83a4b9 20 | 21 | [ react@0.11.1 22 | 23 | [ react@0.12.0-rc1 24 | 25 | [ react@0.14.0-alpha1 26 | 27 | [ react@16.7.0-alpha.2 28 | 29 | [ react@0.0.0-experimental-aae83a4b9 30 | 31 | --- 32 | 33 | react@a 34 | 35 | react@1 36 | 37 | react@1.a 38 | 39 | react@1.1 40 | 41 | react@1.a-b 42 | 43 | react@1.a1b 44 | 45 | react@1.1-b 46 | 47 | react@1.1-alpha 48 | 49 | react@1.1-alpha1 50 | 51 | react@1.1-a 52 | 53 | react@1.a-1 54 | 55 | --- 56 | 57 | [ react@a 58 | 59 | [ react@1 60 | 61 | [ react@1.a 62 | 63 | [ react@1.1 64 | 65 | [ react@1.a-b 66 | 67 | [ react@1.a1b 68 | 69 | [ react@1.1-b 70 | 71 | [ react@1.1-alpha 72 | 73 | [ react@1.1-alpha1 74 | 75 | [ react@1.1-a 76 | 77 | [ react@1.a-1 78 | -------------------------------------------------------------------------------- /test/fixture/path-or-link-end.html: -------------------------------------------------------------------------------- 1 |

In autolink literal path or link end?

2 |

https://a.com/d

3 |

http://a.com/d

4 |

www.a.com/d

5 |

https://a.com/d]()

6 |

http://a.com/d]()

7 |

www.a.com/d]()

8 |

In autolink literal search or link end?

9 |

https://a.com?d

10 |

http://a.com?d

11 |

www.a.com?d

12 |

https://a.com?d]()

13 |

http://a.com?d]()

14 |

www.a.com?d]()

15 |

In autolink literal hash or link end?

16 |

https://a.com#d

17 |

http://a.com#d

18 |

www.a.com#d

19 |

https://a.com#d]()

20 |

http://a.com#d]()

21 |

www.a.com#d]()

22 | -------------------------------------------------------------------------------- /license: -------------------------------------------------------------------------------- 1 | (The MIT License) 2 | 3 | Copyright (c) Titus Wormer 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | 'Software'), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 21 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 22 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /test/fixture/algorithm-2b.md: -------------------------------------------------------------------------------- 1 | [https://a.com/(b 2 | 3 | [https://a.com/((b 4 | 5 | [https://a.com/b) 6 | 7 | [https://a.com/b)) 8 | 9 | [https://a.com/b).) 10 | 11 | [https://a.com/(b) 12 | 13 | [https://a.com/(b)) 14 | 15 | [https://a.com/((b) 16 | 17 | [https://a.com/((b)) 18 | 19 | [https://a.com/((b).) 20 | 21 | [https://a.com/(((b).).) 22 | 23 | [https://a.com/((((b).).).) 24 | 25 | [https://a.com/(((((b).).).).) 26 | 27 | [https://a.com/((((((b).).).).).) 28 | 29 | [https://a.com/b). 30 | 31 | [https://a.com/b.) 32 | 33 | [https://a.com/b).. 34 | 35 | [https://a.com/b..) 36 | 37 | [https://a.com/b(.) 38 | 39 | [https://a.com/b().. 40 | 41 | [https://a.com/b(..) 42 | 43 | --- 44 | 45 | [www.a.com/(b 46 | 47 | [www.a.com/((b 48 | 49 | [www.a.com/b) 50 | 51 | [www.a.com/b)) 52 | 53 | [www.a.com/b).) 54 | 55 | [www.a.com/(b) 56 | 57 | [www.a.com/(b)) 58 | 59 | [www.a.com/((b) 60 | 61 | [www.a.com/((b)) 62 | 63 | [www.a.com/((b).) 64 | 65 | [www.a.com/(((b).).) 66 | 67 | [www.a.com/((((b).).).) 68 | 69 | [www.a.com/(((((b).).).).) 70 | 71 | [www.a.com/((((((b).).).).).) 72 | 73 | [www.a.com/b). 74 | 75 | [www.a.com/b.) 76 | 77 | [www.a.com/b).. 78 | 79 | [www.a.com/b..) 80 | 81 | [www.a.com/b(.) 82 | 83 | [www.a.com/b().. 84 | 85 | [www.a.com/b(..) 86 | -------------------------------------------------------------------------------- /test/fixture/combined-with-images.html: -------------------------------------------------------------------------------- 1 |

Image start.

2 |

![https://a.com

3 |

![http://a.com

4 |

![www.a.com

5 |

![a@b.c

6 |

Image start and label end.

7 |

![https://a.com]

8 |

![http://a.com]

9 |

![www.a.com]

10 |

![a@b.c]

11 |

Image label with reference (note: GH cleans hashes here, but we keep them in).

12 |

https://a.com

13 |

http://a.com

14 |

www.a.com

15 |

a@b.c

16 |

Image label with resource.

17 |

https://a.com

18 |

http://a.com

19 |

www.a.com

20 |

a@b.c

21 |

Autolink literal after image.

22 |

a https://a.com

23 |

a http://a.com

24 |

a www.a.com

25 |

a a@b.c

26 | -------------------------------------------------------------------------------- /test/fixture/email-tld-digits.html: -------------------------------------------------------------------------------- 1 |

a@0.0

2 |

a@0.b

3 |

a@a.29

4 |

a@a.b

5 |

a@0.0.c

6 |

react@0.11.1

7 |

react@0.12.0-rc1

8 |

react@0.14.0-alpha1

9 |

react@16.7.0-alpha.2

10 |

react@0.0.0-experimental-aae83a4b9

11 |

[ react@0.11.1

12 |

[ react@0.12.0-rc1

13 |

[ react@0.14.0-alpha1

14 |

[ react@16.7.0-alpha.2

15 |

[ react@0.0.0-experimental-aae83a4b9

16 |
17 |

react@a

18 |

react@1

19 |

react@1.a

20 |

react@1.1

21 |

react@1.a-b

22 |

react@1.a1b

23 |

react@1.1-b

24 |

react@1.1-alpha

25 |

react@1.1-alpha1

26 |

react@1.1-a

27 |

react@1.a-1

28 |
29 |

[ react@a

30 |

[ react@1

31 |

[ react@1.a

32 |

[ react@1.1

33 |

[ react@1.a-b

34 |

[ react@1.a1b

35 |

[ react@1.1-b

36 |

[ react@1.1-alpha

37 |

[ react@1.1-alpha1

38 |

[ react@1.1-a

39 |

[ react@1.a-1

40 | -------------------------------------------------------------------------------- /test/fixture/www-domain-start.html: -------------------------------------------------------------------------------- 1 |

wwwtf?

2 |

www. (space)

3 |

www.!

4 |

www."

5 |

www.#

6 |

www.$

7 |

www.%

8 |

www.&

9 |

www.'

10 |

www.(

11 |

www.)

12 |

www.*

13 |

www.+

14 |

www.,

15 |

www.-

16 |

www.

17 |

www..

18 |

www./

19 |

www.:

20 |

www.;

21 |

www.<

22 |

www.=

23 |

www.>

24 |

www.?

25 |

www.@

26 |

www.[

27 |

www.\

28 |

www.]

29 |

www.^

30 |

www._

31 |

www.`

32 |

www.{

33 |

www.|

34 |

www.}

35 |

www.~

36 | -------------------------------------------------------------------------------- /test/fixture/http-domain-continue.html: -------------------------------------------------------------------------------- 1 |

httpshhh? (2)

2 |

http://a (space)

3 |

http://a!

4 |

http://a"

5 |

http://a#

6 |

http://a$

7 |

http://a%

8 |

http://a&

9 |

http://a'

10 |

http://a(

11 |

http://a)

12 |

http://a*

13 |

http://a+

14 |

http://a,

15 |

http://a-

16 |

http://a

17 |

http://a.

18 |

http://a/

19 |

http://a:

20 |

http://a;

21 |

http://a<

22 |

http://a=

23 |

http://a>

24 |

http://a?

25 |

http://a@

26 |

http://a[

27 |

http://a\

28 |

http://a]

29 |

http://a^

30 |

http://a_

31 |

http://a`

32 |

http://a{

33 |

http://a|

34 |

http://a}

35 |

http://a~

36 | -------------------------------------------------------------------------------- /test/fixture/www-domain-continue.html: -------------------------------------------------------------------------------- 1 |

wwwtf 2?

2 |

www.a (space)

3 |

www.a!

4 |

www.a"

5 |

www.a#

6 |

www.a$

7 |

www.a%

8 |

www.a&

9 |

www.a'

10 |

www.a(

11 |

www.a)

12 |

www.a*

13 |

www.a+

14 |

www.a,

15 |

www.a-

16 |

www.a

17 |

www.a.

18 |

www.a/

19 |

www.a:

20 |

www.a;

21 |

www.a<

22 |

www.a=

23 |

www.a>

24 |

www.a?

25 |

www.a@

26 |

www.a[

27 |

www.a\

28 |

www.a]

29 |

www.a^

30 |

www.a_

31 |

www.a`

32 |

www.a{

33 |

www.a|

34 |

www.a}

35 |

www.a~

36 | -------------------------------------------------------------------------------- /test/fixture/http-path-start.html: -------------------------------------------------------------------------------- 1 |

httpshhh? (3)

2 |

http://a/ (space)

3 |

http://a/!

4 |

http://a/"

5 |

http://a/#

6 |

http://a/$

7 |

http://a/%

8 |

http://a/&

9 |

http://a/'

10 |

http://a/(

11 |

http://a/)

12 |

http://a/*

13 |

http://a/+

14 |

http://a/,

15 |

http://a/-

16 |

http://a/

17 |

http://a/.

18 |

http://a//

19 |

http://a/:

20 |

http://a/;

21 |

http://a/<

22 |

http://a/=

23 |

http://a/>

24 |

http://a/?

25 |

http://a/@

26 |

http://a/[

27 |

http://a/\

28 |

http://a/]

29 |

http://a/^

30 |

http://a/_

31 |

http://a/`

32 |

http://a/{

33 |

http://a/|

34 |

http://a/}

35 |

http://a/~

36 | -------------------------------------------------------------------------------- /test/fixture/www-domain-dot.html: -------------------------------------------------------------------------------- 1 |

wwwtf 5?

2 |

www.a. (space)

3 |

www.a.!

4 |

www.a."

5 |

www.a.#

6 |

www.a.$

7 |

www.a.%

8 |

www.a.&

9 |

www.a.'

10 |

www.a.(

11 |

www.a.)

12 |

www.a.*

13 |

www.a.+

14 |

www.a.,

15 |

www.a.-

16 |

www.a.

17 |

www.a..

18 |

www.a./

19 |

www.a.:

20 |

www.a.;

21 |

www.a.<

22 |

www.a.=

23 |

www.a.>

24 |

www.a.?

25 |

www.a.@

26 |

www.a.[

27 |

www.a.\

28 |

www.a.]

29 |

www.a.^

30 |

www.a._

31 |

www.a.`

32 |

www.a.{

33 |

www.a.|

34 |

www.a.}

35 |

www.a.~

36 | -------------------------------------------------------------------------------- /test/fixture/www-path-start.html: -------------------------------------------------------------------------------- 1 |

wwwtf? (3)

2 |

www.a/ (space)

3 |

www.a/!

4 |

www.a/"

5 |

www.a/#

6 |

www.a/$

7 |

www.a/%

8 |

www.a/&

9 |

www.a/'

10 |

www.a/(

11 |

www.a/)

12 |

www.a/*

13 |

www.a/+

14 |

www.a/,

15 |

www.a/-

16 |

www.a/

17 |

www.a/.

18 |

www.a//

19 |

www.a/:

20 |

www.a/;

21 |

www.a/<

22 |

www.a/=

23 |

www.a/>

24 |

www.a/?

25 |

www.a/@

26 |

www.a/[

27 |

www.a/\

28 |

www.a/]

29 |

www.a/^

30 |

www.a/_

31 |

www.a/`

32 |

www.a/{

33 |

www.a/|

34 |

www.a/}

35 |

www.a/~

36 | -------------------------------------------------------------------------------- /test/fixture/combined-with-links.md: -------------------------------------------------------------------------------- 1 | Link start. 2 | 3 | [https://a.com 4 | 5 | [http://a.com 6 | 7 | [www.a.com 8 | 9 | [a@b.c 10 | 11 | Label end. 12 | 13 | https://a.com] 14 | 15 | http://a.com] 16 | 17 | www.a.com] 18 | 19 | a@b.c] 20 | 21 | Link start and label end. 22 | 23 | [https://a.com] 24 | 25 | [http://a.com] 26 | 27 | [www.a.com] 28 | 29 | [a@b.c] 30 | 31 | What naïvely seems like a label end (A). 32 | 33 | https://a.com`]` 34 | 35 | http://a.com`]` 36 | 37 | www.a.com`]` 38 | 39 | a@b.c`]` 40 | 41 | Link start and what naïvely seems like a balanced brace (B). 42 | 43 | [https://a.com`]` 44 | 45 | [http://a.com`]` 46 | 47 | [www.a.com`]` 48 | 49 | [a@b.c`]` 50 | 51 | What naïvely seems like a label end (C). 52 | 53 | https://a.com `]` 54 | 55 | http://a.com `]` 56 | 57 | www.a.com `]` 58 | 59 | a@b.c `]` 60 | 61 | Link start and what naïvely seems like a balanced brace (D). 62 | 63 | [https://a.com `]` 64 | 65 | [http://a.com `]` 66 | 67 | [www.a.com `]` 68 | 69 | [a@b.c `]` 70 | 71 | Link label with reference. 72 | 73 | [https://a.com][x] 74 | 75 | [http://a.com][x] 76 | 77 | [www.a.com][x] 78 | 79 | [a@b.c][x] 80 | 81 | [x]: # 82 | 83 | Link label with resource. 84 | 85 | [https://a.com]() 86 | 87 | [http://a.com]() 88 | 89 | [www.a.com]() 90 | 91 | [a@b.c]() 92 | 93 | More in link. 94 | 95 | [a https://b.com c]() 96 | 97 | [a http://b.com c]() 98 | 99 | [a www.b.com c]() 100 | 101 | [a b@c.d e]() 102 | 103 | Autolink literal after link. 104 | 105 | [a]() https://a.com 106 | 107 | [a]() http://a.com 108 | 109 | [a]() www.a.com 110 | 111 | [a]() a@b.c 112 | -------------------------------------------------------------------------------- /test/fixture/http-path-continue.html: -------------------------------------------------------------------------------- 1 |

httpshhh? (4)

2 |

http://a/b (space)

3 |

http://a/b!

4 |

http://a/b"

5 |

http://a/b#

6 |

http://a/b$

7 |

http://a/b%

8 |

http://a/b&

9 |

http://a/b'

10 |

http://a/b(

11 |

http://a/b)

12 |

http://a/b*

13 |

http://a/b+

14 |

http://a/b,

15 |

http://a/b-

16 |

http://a/b

17 |

http://a/b.

18 |

http://a/b/

19 |

http://a/b:

20 |

http://a/b;

21 |

http://a/b<

22 |

http://a/b=

23 |

http://a/b>

24 |

http://a/b?

25 |

http://a/b@

26 |

http://a/b[

27 |

http://a/b\

28 |

http://a/b]

29 |

http://a/b^

30 |

http://a/b_

31 |

http://a/b`

32 |

http://a/b{

33 |

http://a/b|

34 |

http://a/b}

35 |

http://a/b~

36 | -------------------------------------------------------------------------------- /test/fixture/previous.md: -------------------------------------------------------------------------------- 1 | # HTTP 2 | 3 | https://a.b can start after EOF 4 | 5 | Can start after EOL: 6 | https://a.b 7 | 8 | Can start after tab: https://a.b. 9 | 10 | Can start after space: https://a.b. 11 | 12 | Can start after left paren (https://a.b. 13 | 14 | Can start after asterisk *https://a.b. 15 | 16 | Can start after underscore *_https://a.b. 17 | 18 | Can start after tilde ~https://a.b. 19 | 20 | # www 21 | 22 | www.a.b can start after EOF 23 | 24 | Can start after EOL: 25 | www.a.b 26 | 27 | Can start after tab: www.a.b. 28 | 29 | Can start after space: www.a.b. 30 | 31 | Can start after left paren (www.a.b. 32 | 33 | Can start after asterisk *www.a.b. 34 | 35 | Can start after underscore *_www.a.b. 36 | 37 | Can start after tilde ~www.a.b. 38 | 39 | # Email 40 | 41 | ## Correct character before 42 | 43 | a@b.c can start after EOF 44 | 45 | Can start after EOL: 46 | a@b.c 47 | 48 | Can start after tab: a@b.c. 49 | 50 | Can start after space: a@b.c. 51 | 52 | Can start after left paren(a@b.c. 53 | 54 | Can start after asterisk*a@b.c. 55 | 56 | While theoretically it’s possible to start at an underscore, that underscore 57 | is part of the email, so it’s in fact part of the link: _a@b.c. 58 | 59 | Can start after tilde~a@b.c. 60 | 61 | ## Others characters before 62 | 63 | While other characters before the email aren’t allowed by GFM, they work on 64 | github.com: !a@b.c, "a@b.c, #a@b.c, $a@b.c, &a@b.c, 'a@b.c, )a@b.c, +a@b.c, 65 | ,a@b.c, -a@b.c, .a@b.c, /a@b.c, :a@b.c, ;a@b.c, a@b.c, ?a@b.c, 66 | @a@b.c, \a@b.c, ]a@b.c, ^a@b.c, `a@b.c, {a@b.c, }a@b.c. 67 | 68 | ## remarkjs/remark#678 69 | 70 | ,https://github.com 71 | 72 | [ ,https://github.com 73 | 74 | [asd] ,https://github.com 75 | -------------------------------------------------------------------------------- /test/fixture/www-path-continue.html: -------------------------------------------------------------------------------- 1 |

wwwtf? (4)

2 |

www.a/b (space)

3 |

www.a/b!

4 |

www.a/b"

5 |

www.a/b#

6 |

www.a/b$

7 |

www.a/b%

8 |

www.a/b&

9 |

www.a/b'

10 |

www.a/b(

11 |

www.a/b)

12 |

www.a/b*

13 |

www.a/b+

14 |

www.a/b,

15 |

www.a/b-

16 |

www.a/b

17 |

www.a/b.

18 |

www.a/b/

19 |

www.a/b:

20 |

www.a/b;

21 |

www.a/b<

22 |

www.a/b=

23 |

www.a/b>

24 |

www.a/b?

25 |

www.a/b@

26 |

www.a/b[

27 |

www.a/b\

28 |

www.a/b]

29 |

www.a/b^

30 |

www.a/b_

31 |

www.a/b`

32 |

www.a/b{

33 |

www.a/b|

34 |

www.a/b}

35 |

www.a/b~

36 | -------------------------------------------------------------------------------- /test/fixture/domain-character-reference-like-named.html: -------------------------------------------------------------------------------- 1 |

“character reference”

2 |

www.a&b (space)

3 |

www.a&b!

4 |

www.a&b"

5 |

www.a&b#

6 |

www.a&b$

7 |

www.a&b%

8 |

www.a&b&

9 |

www.a&b'

10 |

www.a&b(

11 |

www.a&b)

12 |

www.a&b*

13 |

www.a&b+

14 |

www.a&b,

15 |

www.a&b-

16 |

www.a&b

17 |

www.a&b.

18 |

www.a&b/

19 |

www.a&b:

20 |

www.a&b;

21 |

www.a&b<

22 |

www.a&b=

23 |

www.a&b>

24 |

www.a&b?

25 |

www.a&b@

26 |

www.a&b[

27 |

www.a&b\

28 |

www.a&b]

29 |

www.a&b^

30 |

www.a&b_

31 |

www.a&b`

32 |

www.a&b{

33 |

www.a&b|

34 |

www.a&b}

35 |

www.a&b~

36 | -------------------------------------------------------------------------------- /test/fixture/algorithm-2.md: -------------------------------------------------------------------------------- 1 | [https:// 2 | 3 | [https://a 4 | 5 | [https://. 6 | 7 | [https://a. 8 | 9 | [https://a.. 10 | 11 | [https://a..b 12 | 13 | [https://a.b 14 | 15 | [https://a.b. 16 | 17 | [https://a.b.. 18 | 19 | [https://a.b.c 20 | 21 | [https://a.b..c 22 | 23 | [https://a.b_.c 24 | 25 | [https://a_.b.c 26 | 27 | [https://a_.b_.c 28 | 29 | [https://a.b© 30 | 31 | [http://點看.com 32 | 33 | --- 34 | 35 | [http://a.b/c (space) 36 | 37 | [http://a.b/c! 38 | 39 | [http://a.b/c" 40 | 41 | [http://a.b/c# 42 | 43 | [http://a.b/c$ 44 | 45 | [http://a.b/c% 46 | 47 | [http://a.b/c& 48 | 49 | [http://a.b/c' 50 | 51 | [http://a.b/c( 52 | 53 | [http://a.b/c) 54 | 55 | [http://a.b/c* 56 | 57 | [http://a.b/c+ 58 | 59 | [http://a.b/c, 60 | 61 | [http://a.b/c- 62 | 63 | [http://a.b/c 64 | 65 | [http://a.b/c. 66 | 67 | [http://a.b/c/ 68 | 69 | [http://a.b/c: 70 | 71 | [http://a.b/c; 72 | 73 | [http://a.b/c< 74 | 75 | [http://a.b/c= 76 | 77 | [http://a.b/c> 78 | 79 | [http://a.b/c? 80 | 81 | [http://a.b/c@ 82 | 83 | [http://a.b/c[ 84 | 85 | [http://a.b/c\ 86 | 87 | [http://a.b/c] 88 | 89 | [http://a.b/c^ 90 | 91 | [http://a.b/c_ 92 | 93 | [http://a.b/c` 94 | 95 | [http://a.b/c{ 96 | 97 | [http://a.b/c| 98 | 99 | [http://a.b/c} 100 | 101 | [http://a.b/c~ 102 | 103 | --- 104 | 105 | [www. 106 | 107 | [www.a 108 | 109 | [www.. 110 | 111 | [www.a. 112 | 113 | [www.a.. 114 | 115 | [www.a..b 116 | 117 | [www.a.b 118 | 119 | [www.a.b. 120 | 121 | [www.a.b.. 122 | 123 | [www.a.b.c 124 | 125 | [www.a.b..c 126 | 127 | [www.a.b_.c 128 | 129 | [www.a_.b.c 130 | 131 | [www.a_.b_.c 132 | 133 | [www.a.b© 134 | 135 | [www.點看.com 136 | 137 | --- 138 | 139 | [a@b.c 140 | 141 | [a@b.ca 142 | 143 | [a@b.c. 144 | 145 | [a@b.ca. 146 | 147 | [a@b.ca.. 148 | 149 | [a@b.ca..b 150 | 151 | [a@b.ca.b 152 | 153 | [a@b.ca.b. 154 | 155 | [a@b.ca.b.. 156 | 157 | [a@b.ca.b.c 158 | 159 | [a@b.ca.b..c 160 | 161 | [a@b.ca.b_.c 162 | 163 | [a@b.ca_.b.c 164 | 165 | [a@b.ca_.b_.c 166 | 167 | [a@b.ca.b© 168 | 169 | [a@b點看.com 170 | 171 | [點看@b.com 172 | -------------------------------------------------------------------------------- /test/fixture/path-character-reference-like-named.html: -------------------------------------------------------------------------------- 1 |

“character reference”

2 |

www.a/b&c (space)

3 |

www.a/b&c!

4 |

www.a/b&c"

5 |

www.a/b&c#

6 |

www.a/b&c$

7 |

www.a/b&c%

8 |

www.a/b&c&

9 |

www.a/b&c'

10 |

www.a/b&c(

11 |

www.a/b&c)

12 |

www.a/b&c*

13 |

www.a/b&c+

14 |

www.a/b&c,

15 |

www.a/b&c-

16 |

www.a/b&c

17 |

www.a/b&c.

18 |

www.a/b&c/

19 |

www.a/b&c:

20 |

www.a/b&c;

21 |

www.a/b&c<

22 |

www.a/b&c=

23 |

www.a/b&c>

24 |

www.a/b&c?

25 |

www.a/b&c@

26 |

www.a/b&c[

27 |

www.a/b&c\

28 |

www.a/b&c]

29 |

www.a/b&c^

30 |

www.a/b&c_

31 |

www.a/b&c`

32 |

www.a/b&c{

33 |

www.a/b&c|

34 |

www.a/b&c}

35 |

www.a/b&c~

36 | -------------------------------------------------------------------------------- /test/fixture/domain-character-reference-like-numerical.html: -------------------------------------------------------------------------------- 1 |

“character reference”

2 |

www.a&#35 (space)

3 |

www.a&#35!

4 |

www.a&#35"

5 |

www.a&#35#

6 |

www.a&#35$

7 |

www.a&#35%

8 |

www.a&#35&

9 |

www.a&#35'

10 |

www.a&#35(

11 |

www.a&#35)

12 |

www.a&#35*

13 |

www.a&#35+

14 |

www.a&#35,

15 |

www.a&#35-

16 |

www.a&#35

17 |

www.a&#35.

18 |

www.a&#35/

19 |

www.a&#35:

20 |

www.a&#35;

21 |

www.a&#35<

22 |

www.a&#35=

23 |

www.a&#35>

24 |

www.a&#35?

25 |

www.a&#35@

26 |

www.a&#35[

27 |

www.a&#35\

28 |

www.a&#35]

29 |

www.a&#35^

30 |

www.a&#35_

31 |

www.a&#35`

32 |

www.a&#35{

33 |

www.a&#35|

34 |

www.a&#35}

35 |

www.a&#35~

36 | -------------------------------------------------------------------------------- /test/fixture/path-character-reference-like-numerical.html: -------------------------------------------------------------------------------- 1 |

“character reference”

2 |

www.a/b&#35 (space)

3 |

www.a/b&#35!

4 |

www.a/b&#35"

5 |

www.a/b&#35#

6 |

www.a/b&#35$

7 |

www.a/b&#35%

8 |

www.a/b&#35&

9 |

www.a/b&#35'

10 |

www.a/b&#35(

11 |

www.a/b&#35)

12 |

www.a/b&#35*

13 |

www.a/b&#35+

14 |

www.a/b&#35,

15 |

www.a/b&#35-

16 |

www.a/b&#35

17 |

www.a/b&#35.

18 |

www.a/b&#35/

19 |

www.a/b&#35:

20 |

www.a/b&#35;

21 |

www.a/b&#35<

22 |

www.a/b&#35=

23 |

www.a/b&#35>

24 |

www.a/b&#35?

25 |

www.a/b&#35@

26 |

www.a/b&#35[

27 |

www.a/b&#35\

28 |

www.a/b&#35]

29 |

www.a/b&#35^

30 |

www.a/b&#35_

31 |

www.a/b&#35`

32 |

www.a/b&#35{

33 |

www.a/b&#35|

34 |

www.a/b&#35}

35 |

www.a/b&#35~

36 | -------------------------------------------------------------------------------- /test/fixture/previous-complex.md: -------------------------------------------------------------------------------- 1 | Last non-markdown ASCII whitespace (FF): noreply@example.com, http://example.com, https://example.com, www.example.com 2 | 3 | Last non-whitespace ASCII control (US): noreply@example.com, http://example.com, https://example.com, www.example.com 4 | 5 | First punctuation after controls: !noreply@example.com, !http://example.com, !https://example.com, !www.example.com 6 | 7 | Last punctuation before digits: /noreply@example.com, /http://example.com, /https://example.com, /www.example.com 8 | 9 | First digit: 0noreply@example.com, 0http://example.com, 0https://example.com, 0www.example.com 10 | 11 | First punctuation after digits: :noreply@example.com, :http://example.com, :https://example.com, :www.example.com 12 | 13 | Last punctuation before caps: @noreply@example.com, @http://example.com, @https://example.com, @www.example.com 14 | 15 | First uppercase: Anoreply@example.com, Ahttp://example.com, Ahttps://example.com, Awww.example.com 16 | 17 | Punctuation after uppercase: \noreply@example.com, \http://example.com, \https://example.com, \www.example.com 18 | 19 | Last punctuation before lowercase (1): `noreply@example.com; 20 | 21 | (2) `http://example.com; 22 | 23 | (3) `https://example.com; 24 | 25 | (4) `www.example.com; (broken up to prevent code from forming) 26 | 27 | First lowercase: anoreply@example.com, ahttp://example.com, ahttps://example.com, awww.example.com 28 | 29 | First punctuation after lowercase: {noreply@example.com, {http://example.com, {https://example.com, {www.example.com 30 | 31 | Last punctuation: ~noreply@example.com, ~http://example.com, ~https://example.com, ~www.example.com 32 | 33 | First non-ASCII unicode whitespace (0x80): …noreply@example.com, …http://example.com, …https://example.com, …www.example.com 34 | 35 | Last non-ASCII unicode whitespace (0x3000):  noreply@example.com,  http://example.com,  https://example.com,  www.example.com 36 | 37 | First non-ASCII punctuation: ¡noreply@example.com, ¡http://example.com, ¡https://example.com, ¡www.example.com 38 | 39 | Last non-ASCII punctuation: ・noreply@example.com, ・http://example.com, ・https://example.com, ・www.example.com 40 | 41 | Some non-ascii: 中noreply@example.com, 中http://example.com, 中https://example.com, 中www.example.com 42 | 43 | Some more non-ascii: 🤷‍noreply@example.com, 🤷‍http://example.com, 🤷‍https://example.com, 🤷‍www.example.com 44 | -------------------------------------------------------------------------------- /test/fixture/base.md: -------------------------------------------------------------------------------- 1 | # Literal autolinks 2 | 3 | ## WWW autolinks 4 | 5 | w.commonmark.org 6 | 7 | ww.commonmark.org 8 | 9 | www.commonmark.org 10 | 11 | Www.commonmark.org 12 | 13 | wWw.commonmark.org 14 | 15 | wwW.commonmark.org 16 | 17 | WWW.COMMONMARK.ORG 18 | 19 | Visit www.commonmark.org/help for more information. 20 | 21 | Visit www.commonmark.org. 22 | 23 | Visit www.commonmark.org/a.b. 24 | 25 | www.aaa.bbb.ccc_ccc 26 | 27 | www.aaa_bbb.ccc 28 | 29 | www.aaa.bbb.ccc.ddd_ddd 30 | 31 | www.aaa.bbb.ccc_ccc.ddd 32 | 33 | www.aaa.bbb_bbb.ccc.ddd 34 | 35 | www.aaa_aaa.bbb.ccc.ddd 36 | 37 | Visit www.commonmark.org. 38 | 39 | Visit www.commonmark.org/a.b. 40 | 41 | www.google.com/search?q=Markup+(business) 42 | 43 | www.google.com/search?q=Markup+(business))) 44 | 45 | (www.google.com/search?q=Markup+(business)) 46 | 47 | (www.google.com/search?q=Markup+(business) 48 | 49 | www.google.com/search?q=(business))+ok 50 | 51 | www.google.com/search?q=commonmark&hl=en 52 | 53 | www.google.com/search?q=commonmark&hl;en 54 | 55 | www.google.com/search?q=commonmark&hl; 56 | 57 | www.commonmark.org/he should still be expanded. 120 | -------------------------------------------------------------------------------- /test/fixture/algorithm-2b.html: -------------------------------------------------------------------------------- 1 |

[https://a.com/(b

2 |

[https://a.com/((b

3 |

[https://a.com/b)

4 |

[https://a.com/b))

5 |

[https://a.com/b).)

6 |

[https://a.com/(b)

7 |

[https://a.com/(b))

8 |

[https://a.com/((b)

9 |

[https://a.com/((b))

10 |

[https://a.com/((b).)

11 |

[https://a.com/(((b).).)

12 |

[https://a.com/((((b).).).)

13 |

[https://a.com/(((((b).).).).)

14 |

[https://a.com/((((((b).).).).).)

15 |

[https://a.com/b).

16 |

[https://a.com/b.)

17 |

[https://a.com/b)..

18 |

[https://a.com/b..)

19 |

[https://a.com/b(.)

20 |

[https://a.com/b()..

21 |

[https://a.com/b(..)

22 |
23 |

[www.a.com/(b

24 |

[www.a.com/((b

25 |

[www.a.com/b)

26 |

[www.a.com/b))

27 |

[www.a.com/b).)

28 |

[www.a.com/(b)

29 |

[www.a.com/(b))

30 |

[www.a.com/((b)

31 |

[www.a.com/((b))

32 |

[www.a.com/((b).)

33 |

[www.a.com/(((b).).)

34 |

[www.a.com/((((b).).).)

35 |

[www.a.com/(((((b).).).).)

36 |

[www.a.com/((((((b).).).).).)

37 |

[www.a.com/b).

38 |

[www.a.com/b.)

39 |

[www.a.com/b)..

40 |

[www.a.com/b..)

41 |

[www.a.com/b(.)

42 |

[www.a.com/b()..

43 |

[www.a.com/b(..)

44 | -------------------------------------------------------------------------------- /test/fixture/combined-with-links.html: -------------------------------------------------------------------------------- 1 |

Link start.

2 |

[https://a.com

3 |

[http://a.com

4 |

[www.a.com

5 |

[a@b.c

6 |

Label end.

7 |

https://a.com]

8 |

http://a.com]

9 |

www.a.com]

10 |

a@b.c]

11 |

Link start and label end.

12 |

[https://a.com]

13 |

[http://a.com]

14 |

[www.a.com]

15 |

[a@b.c]

16 |

What naïvely seems like a label end (A).

17 |

https://a.com`]`

18 |

http://a.com`]`

19 |

www.a.com`]`

20 |

a@b.c]

21 |

Link start and what naïvely seems like a balanced brace (B).

22 |

[https://a.com]

23 |

[http://a.com]

24 |

[www.a.com]

25 |

[a@b.c]

26 |

What naïvely seems like a label end (C).

27 |

https://a.com ]

28 |

http://a.com ]

29 |

www.a.com ]

30 |

a@b.c ]

31 |

Link start and what naïvely seems like a balanced brace (D).

32 |

[https://a.com ]

33 |

[http://a.com ]

34 |

[www.a.com ]

35 |

[a@b.c ]

36 |

Link label with reference.

37 |

https://a.com

38 |

http://a.com

39 |

www.a.com

40 |

a@b.c

41 |

Link label with resource.

42 |

https://a.com

43 |

http://a.com

44 |

www.a.com

45 |

a@b.c

46 |

More in link.

47 |

a https://b.com c

48 |

a http://b.com c

49 |

a www.b.com c

50 |

a b@c.d e

51 |

Autolink literal after link.

52 |

a https://a.com

53 |

a http://a.com

54 |

a www.a.com

55 |

a a@b.c

56 | -------------------------------------------------------------------------------- /test/fixture/previous.html: -------------------------------------------------------------------------------- 1 |

HTTP

2 |

https://a.b can start after EOF

3 |

Can start after EOL: 4 | https://a.b

5 |

Can start after tab: https://a.b.

6 |

Can start after space: https://a.b.

7 |

Can start after left paren (https://a.b.

8 |

Can start after asterisk *https://a.b.

9 |

Can start after underscore *_https://a.b.

10 |

Can start after tilde ~https://a.b.

11 |

www

12 |

www.a.b can start after EOF

13 |

Can start after EOL: 14 | www.a.b

15 |

Can start after tab: www.a.b.

16 |

Can start after space: www.a.b.

17 |

Can start after left paren (www.a.b.

18 |

Can start after asterisk *www.a.b.

19 |

Can start after underscore *_www.a.b.

20 |

Can start after tilde ~www.a.b.

21 |

Email

22 |

Correct character before

23 |

a@b.c can start after EOF

24 |

Can start after EOL: 25 | a@b.c

26 |

Can start after tab: a@b.c.

27 |

Can start after space: a@b.c.

28 |

Can start after left paren(a@b.c.

29 |

Can start after asterisk*a@b.c.

30 |

While theoretically it’s possible to start at an underscore, that underscore 31 | is part of the email, so it’s in fact part of the link: _a@b.c.

32 |

Can start after tilde~a@b.c.

33 |

Others characters before

34 |

While other characters before the email aren’t allowed by GFM, they work on 35 | github.com: !a@b.c, "a@b.c, #a@b.c, $a@b.c, &a@b.c, 'a@b.c, )a@b.c, +a@b.c, 36 | ,a@b.c, -a@b.c, .a@b.c, /a@b.c, :a@b.c, ;a@b.c, <a@b.c, =a@b.c, >a@b.c, ?a@b.c, 37 | @a@b.c, \a@b.c, ]a@b.c, ^a@b.c, `a@b.c, {a@b.c, }a@b.c.

38 |

remarkjs/remark#678

39 |

,https://github.com

40 |

[ ,https://github.com

41 |

[asd] ,https://github.com

42 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": "Titus Wormer (https://wooorm.com)", 3 | "bugs": "https://github.com/syntax-tree/mdast-util-gfm-autolink-literal/issues", 4 | "contributors": [ 5 | "Titus Wormer (https://wooorm.com)" 6 | ], 7 | "dependencies": { 8 | "@types/mdast": "^4.0.0", 9 | "ccount": "^2.0.0", 10 | "devlop": "^1.0.0", 11 | "mdast-util-find-and-replace": "^3.0.0", 12 | "micromark-util-character": "^2.0.0" 13 | }, 14 | "description": "mdast extension to parse and serialize GFM autolink literals", 15 | "devDependencies": { 16 | "@types/node": "^22.0.0", 17 | "c8": "^10.0.0", 18 | "hast-util-to-html": "^9.0.0", 19 | "mdast-util-from-markdown": "^2.0.0", 20 | "mdast-util-to-hast": "^13.0.0", 21 | "mdast-util-to-markdown": "^2.0.0", 22 | "micromark-extension-gfm-autolink-literal": "^2.0.0", 23 | "prettier": "^3.0.0", 24 | "remark-cli": "^12.0.0", 25 | "remark-preset-wooorm": "^10.0.0", 26 | "type-coverage": "^2.0.0", 27 | "typescript": "^5.0.0", 28 | "xo": "^0.60.0" 29 | }, 30 | "exports": "./index.js", 31 | "files": [ 32 | "index.d.ts.map", 33 | "index.d.ts", 34 | "index.js", 35 | "lib/" 36 | ], 37 | "funding": { 38 | "type": "opencollective", 39 | "url": "https://opencollective.com/unified" 40 | }, 41 | "keywords": [ 42 | "autolink", 43 | "auto", 44 | "gfm", 45 | "link", 46 | "literal", 47 | "markdown", 48 | "markup", 49 | "mdast-util", 50 | "mdast", 51 | "raw", 52 | "unist", 53 | "url", 54 | "utility", 55 | "util" 56 | ], 57 | "license": "MIT", 58 | "name": "mdast-util-gfm-autolink-literal", 59 | "prettier": { 60 | "bracketSpacing": false, 61 | "semi": false, 62 | "singleQuote": true, 63 | "tabWidth": 2, 64 | "trailingComma": "none", 65 | "useTabs": false 66 | }, 67 | "remarkConfig": { 68 | "plugins": [ 69 | "remark-preset-wooorm" 70 | ] 71 | }, 72 | "repository": "syntax-tree/mdast-util-gfm-autolink-literal", 73 | "scripts": { 74 | "build": "tsc --build --clean && tsc --build && type-coverage", 75 | "format": "remark --frail --output --quiet -- . && prettier --log-level warn --write -- . && xo --fix", 76 | "test-api-dev": "node --conditions development test/index.js", 77 | "test-api-prod": "node --conditions production test/index.js", 78 | "test-api": "npm run test-api-dev && npm run test-api-prod", 79 | "test-coverage": "c8 --100 --reporter lcov -- npm run test-api", 80 | "test": "npm run build && npm run format && npm run test-coverage" 81 | }, 82 | "sideEffects": false, 83 | "typeCoverage": { 84 | "atLeast": 100, 85 | "strict": true 86 | }, 87 | "type": "module", 88 | "version": "2.0.1", 89 | "xo": { 90 | "overrides": [ 91 | { 92 | "files": "test/**/*.js", 93 | "rules": { 94 | "no-await-in-loop": "off" 95 | } 96 | } 97 | ], 98 | "prettier": true, 99 | "rules": { 100 | "unicorn/prefer-at": "off", 101 | "unicorn/prefer-code-point": "off" 102 | } 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /test/fixture/base.html: -------------------------------------------------------------------------------- 1 |

Literal autolinks

2 |

WWW autolinks

3 |

w.commonmark.org

4 |

ww.commonmark.org

5 |

www.commonmark.org

6 |

Www.commonmark.org

7 |

wWw.commonmark.org

8 |

wwW.commonmark.org

9 |

WWW.COMMONMARK.ORG

10 |

Visit www.commonmark.org/help for more information.

11 |

Visit www.commonmark.org.

12 |

Visit www.commonmark.org/a.b.

13 |

www.aaa.bbb.ccc_ccc

14 |

www.aaa_bbb.ccc

15 |

www.aaa.bbb.ccc.ddd_ddd

16 |

www.aaa.bbb.ccc_ccc.ddd

17 |

www.aaa.bbb_bbb.ccc.ddd

18 |

www.aaa_aaa.bbb.ccc.ddd

19 |

Visit www.commonmark.org.

20 |

Visit www.commonmark.org/a.b.

21 |

www.google.com/search?q=Markup+(business)

22 |

www.google.com/search?q=Markup+(business)))

23 |

(www.google.com/search?q=Markup+(business))

24 |

(www.google.com/search?q=Markup+(business)

25 |

www.google.com/search?q=(business))+ok

26 |

www.google.com/search?q=commonmark&hl=en

27 |

www.google.com/search?q=commonmark&hl;en

28 |

www.google.com/search?q=commonmark&hl;

29 |

www.commonmark.org/he<lp

30 |

HTTP autolinks

31 |

hexample.com

32 |

htexample.com

33 |

httexample.com

34 |

httpexample.com

35 |

http:example.com

36 |

http:/example.com

37 |

https:/example.com

38 |

http://example.com

39 |

https://example.com

40 |

https://example

41 |

http://commonmark.org

42 |

(Visit https://encrypted.google.com/search?q=Markup+(business))

43 |

Email autolinks

44 |

No dot: foo@barbaz

45 |

No dot: foo@barbaz.

46 |

foo@bar.baz

47 |

hello@mail+xyz.example isn’t valid, but hello+xyz@mail.example is.

48 |

a.b-c_d@a.b

49 |

a.b-c_d@a.b.

50 |

a.b-c_d@a.b-

51 |

a.b-c_d@a.b_

52 |

a@a_b.c

53 |

a@a-b.c

54 |

Can’t end in an underscore followed by a period: aaa@a.b_.

55 |

Can contain an underscore followed by a period: aaa@a.b_.c

56 |

Link text should not be expanded

57 |

Visit www.example.com please.

58 |

Visit http://www.example.com please.

59 |

Mail example@example.com please.

60 |

link http://autolink should still be expanded.

61 | -------------------------------------------------------------------------------- /test/fixture/algorithm-2.html: -------------------------------------------------------------------------------- 1 |

[https://

2 |

[https://a

3 |

[https://.

4 |

[https://a.

5 |

[https://a..

6 |

[https://a..b

7 |

[https://a.b

8 |

[https://a.b.

9 |

[https://a.b..

10 |

[https://a.b.c

11 |

[https://a.b..c

12 |

[https://a.b_.c

13 |

[https://a_.b.c

14 |

[https://a_.b_.c

15 |

[https://a.b©

16 |

[http://點看.com

17 |
18 |

[http://a.b/c (space)

19 |

[http://a.b/c!

20 |

[http://a.b/c"

21 |

[http://a.b/c#

22 |

[http://a.b/c$

23 |

[http://a.b/c%

24 |

[http://a.b/c&

25 |

[http://a.b/c'

26 |

[http://a.b/c(

27 |

[http://a.b/c)

28 |

[http://a.b/c*

29 |

[http://a.b/c+

30 |

[http://a.b/c,

31 |

[http://a.b/c-

32 |

[http://a.b/c

33 |

[http://a.b/c.

34 |

[http://a.b/c/

35 |

[http://a.b/c:

36 |

[http://a.b/c;

37 |

[http://a.b/c<

38 |

[http://a.b/c=

39 |

[http://a.b/c>

40 |

[http://a.b/c?

41 |

[http://a.b/c@

42 |

[http://a.b/c[

43 |

[http://a.b/c\

44 |

[http://a.b/c]

45 |

[http://a.b/c^

46 |

[http://a.b/c_

47 |

[http://a.b/c`

48 |

[http://a.b/c{

49 |

[http://a.b/c|

50 |

[http://a.b/c}

51 |

[http://a.b/c~

52 |
53 |

[www.

54 |

[www.a

55 |

[www..

56 |

[www.a.

57 |

[www.a..

58 |

[www.a..b

59 |

[www.a.b

60 |

[www.a.b.

61 |

[www.a.b..

62 |

[www.a.b.c

63 |

[www.a.b..c

64 |

[www.a.b_.c

65 |

[www.a_.b.c

66 |

[www.a_.b_.c

67 |

[www.a.b©

68 |

[www.點看.com

69 |
70 |

[a@b.c

71 |

[a@b.ca

72 |

[a@b.c.

73 |

[a@b.ca.

74 |

[a@b.ca..

75 |

[a@b.ca..b

76 |

[a@b.ca.b

77 |

[a@b.ca.b.

78 |

[a@b.ca.b..

79 |

[a@b.ca.b.c

80 |

[a@b.ca.b..c

81 |

[a@b.ca.b_.c

82 |

[a@b.ca_.b.c

83 |

[a@b.ca_.b_.c

84 |

[a@b.ca.b©

85 |

[a@b點看.com

86 |

[點看@b.com

87 | -------------------------------------------------------------------------------- /test/fixture/previous-complex.html: -------------------------------------------------------------------------------- 1 |

Last non-markdown ASCII whitespace (FF): noreply@example.com, http://example.com, https://example.com, www.example.com

2 |

Last non-whitespace ASCII control (US): noreply@example.com, http://example.com, https://example.com, www.example.com

3 |

First punctuation after controls: !noreply@example.com, !http://example.com, !https://example.com, !www.example.com

4 |

Last punctuation before digits: /noreply@example.com, /http://example.com, /https://example.com, /www.example.com

5 |

First digit: 0noreply@example.com, 0http://example.com, 0https://example.com, 0www.example.com

6 |

First punctuation after digits: :noreply@example.com, :http://example.com, :https://example.com, :www.example.com

7 |

Last punctuation before caps: @noreply@example.com, @http://example.com, @https://example.com, @www.example.com

8 |

First uppercase: Anoreply@example.com, Ahttp://example.com, Ahttps://example.com, Awww.example.com

9 |

Punctuation after uppercase: \noreply@example.com, \http://example.com, \https://example.com, \www.example.com

10 |

Last punctuation before lowercase (1): `noreply@example.com;

11 |

(2) `http://example.com;

12 |

(3) `https://example.com;

13 |

(4) `www.example.com; (broken up to prevent code from forming)

14 |

First lowercase: anoreply@example.com, ahttp://example.com, ahttps://example.com, awww.example.com

15 |

First punctuation after lowercase: {noreply@example.com, {http://example.com, {https://example.com, {www.example.com

16 |

Last punctuation: ~noreply@example.com, ~http://example.com, ~https://example.com, ~www.example.com

17 |

First non-ASCII unicode whitespace (0x80): …noreply@example.com, …http://example.com, …https://example.com, …www.example.com

18 |

Last non-ASCII unicode whitespace (0x3000):  noreply@example.com,  http://example.com,  https://example.com,  www.example.com

19 |

First non-ASCII punctuation: ¡noreply@example.com, ¡http://example.com, ¡https://example.com, ¡www.example.com

20 |

Last non-ASCII punctuation: ・noreply@example.com, ・http://example.com, ・https://example.com, ・www.example.com

21 |

Some non-ascii: 中noreply@example.com, 中http://example.com, 中https://example.com, 中www.example.com

22 |

Some more non-ascii: 🤷‍noreply@example.com, 🤷‍http://example.com, 🤷‍https://example.com, 🤷‍www.example.com

23 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @import {RegExpMatchObject, ReplaceFunction} from 'mdast-util-find-and-replace' 3 | * @import {CompileContext, Extension as FromMarkdownExtension, Handle as FromMarkdownHandle, Transform as FromMarkdownTransform} from 'mdast-util-from-markdown' 4 | * @import {ConstructName, Options as ToMarkdownExtension} from 'mdast-util-to-markdown' 5 | * @import {Link, PhrasingContent} from 'mdast' 6 | */ 7 | 8 | import {ccount} from 'ccount' 9 | import {ok as assert} from 'devlop' 10 | import {unicodePunctuation, unicodeWhitespace} from 'micromark-util-character' 11 | import {findAndReplace} from 'mdast-util-find-and-replace' 12 | 13 | /** @type {ConstructName} */ 14 | const inConstruct = 'phrasing' 15 | /** @type {Array} */ 16 | const notInConstruct = ['autolink', 'link', 'image', 'label'] 17 | 18 | /** 19 | * Create an extension for `mdast-util-from-markdown` to enable GFM autolink 20 | * literals in markdown. 21 | * 22 | * @returns {FromMarkdownExtension} 23 | * Extension for `mdast-util-to-markdown` to enable GFM autolink literals. 24 | */ 25 | export function gfmAutolinkLiteralFromMarkdown() { 26 | return { 27 | transforms: [transformGfmAutolinkLiterals], 28 | enter: { 29 | literalAutolink: enterLiteralAutolink, 30 | literalAutolinkEmail: enterLiteralAutolinkValue, 31 | literalAutolinkHttp: enterLiteralAutolinkValue, 32 | literalAutolinkWww: enterLiteralAutolinkValue 33 | }, 34 | exit: { 35 | literalAutolink: exitLiteralAutolink, 36 | literalAutolinkEmail: exitLiteralAutolinkEmail, 37 | literalAutolinkHttp: exitLiteralAutolinkHttp, 38 | literalAutolinkWww: exitLiteralAutolinkWww 39 | } 40 | } 41 | } 42 | 43 | /** 44 | * Create an extension for `mdast-util-to-markdown` to enable GFM autolink 45 | * literals in markdown. 46 | * 47 | * @returns {ToMarkdownExtension} 48 | * Extension for `mdast-util-to-markdown` to enable GFM autolink literals. 49 | */ 50 | export function gfmAutolinkLiteralToMarkdown() { 51 | return { 52 | unsafe: [ 53 | { 54 | character: '@', 55 | before: '[+\\-.\\w]', 56 | after: '[\\-.\\w]', 57 | inConstruct, 58 | notInConstruct 59 | }, 60 | { 61 | character: '.', 62 | before: '[Ww]', 63 | after: '[\\-.\\w]', 64 | inConstruct, 65 | notInConstruct 66 | }, 67 | { 68 | character: ':', 69 | before: '[ps]', 70 | after: '\\/', 71 | inConstruct, 72 | notInConstruct 73 | } 74 | ] 75 | } 76 | } 77 | 78 | /** 79 | * @this {CompileContext} 80 | * @type {FromMarkdownHandle} 81 | */ 82 | function enterLiteralAutolink(token) { 83 | this.enter({type: 'link', title: null, url: '', children: []}, token) 84 | } 85 | 86 | /** 87 | * @this {CompileContext} 88 | * @type {FromMarkdownHandle} 89 | */ 90 | function enterLiteralAutolinkValue(token) { 91 | this.config.enter.autolinkProtocol.call(this, token) 92 | } 93 | 94 | /** 95 | * @this {CompileContext} 96 | * @type {FromMarkdownHandle} 97 | */ 98 | function exitLiteralAutolinkHttp(token) { 99 | this.config.exit.autolinkProtocol.call(this, token) 100 | } 101 | 102 | /** 103 | * @this {CompileContext} 104 | * @type {FromMarkdownHandle} 105 | */ 106 | function exitLiteralAutolinkWww(token) { 107 | this.config.exit.data.call(this, token) 108 | const node = this.stack[this.stack.length - 1] 109 | assert(node.type === 'link') 110 | node.url = 'http://' + this.sliceSerialize(token) 111 | } 112 | 113 | /** 114 | * @this {CompileContext} 115 | * @type {FromMarkdownHandle} 116 | */ 117 | function exitLiteralAutolinkEmail(token) { 118 | this.config.exit.autolinkEmail.call(this, token) 119 | } 120 | 121 | /** 122 | * @this {CompileContext} 123 | * @type {FromMarkdownHandle} 124 | */ 125 | function exitLiteralAutolink(token) { 126 | this.exit(token) 127 | } 128 | 129 | /** @type {FromMarkdownTransform} */ 130 | function transformGfmAutolinkLiterals(tree) { 131 | findAndReplace( 132 | tree, 133 | [ 134 | [/(https?:\/\/|www(?=\.))([-.\w]+)([^ \t\r\n]*)/gi, findUrl], 135 | [/(?<=^|\s|\p{P}|\p{S})([-.\w+]+)@([-\w]+(?:\.[-\w]+)+)/gu, findEmail] 136 | ], 137 | {ignore: ['link', 'linkReference']} 138 | ) 139 | } 140 | 141 | /** 142 | * @type {ReplaceFunction} 143 | * @param {string} _ 144 | * @param {string} protocol 145 | * @param {string} domain 146 | * @param {string} path 147 | * @param {RegExpMatchObject} match 148 | * @returns {Array | Link | false} 149 | */ 150 | // eslint-disable-next-line max-params 151 | function findUrl(_, protocol, domain, path, match) { 152 | let prefix = '' 153 | 154 | // Not an expected previous character. 155 | if (!previous(match)) { 156 | return false 157 | } 158 | 159 | // Treat `www` as part of the domain. 160 | if (/^w/i.test(protocol)) { 161 | domain = protocol + domain 162 | protocol = '' 163 | prefix = 'http://' 164 | } 165 | 166 | if (!isCorrectDomain(domain)) { 167 | return false 168 | } 169 | 170 | const parts = splitUrl(domain + path) 171 | 172 | if (!parts[0]) return false 173 | 174 | /** @type {Link} */ 175 | const result = { 176 | type: 'link', 177 | title: null, 178 | url: prefix + protocol + parts[0], 179 | children: [{type: 'text', value: protocol + parts[0]}] 180 | } 181 | 182 | if (parts[1]) { 183 | return [result, {type: 'text', value: parts[1]}] 184 | } 185 | 186 | return result 187 | } 188 | 189 | /** 190 | * @type {ReplaceFunction} 191 | * @param {string} _ 192 | * @param {string} atext 193 | * @param {string} label 194 | * @param {RegExpMatchObject} match 195 | * @returns {Link | false} 196 | */ 197 | function findEmail(_, atext, label, match) { 198 | if ( 199 | // Not an expected previous character. 200 | !previous(match, true) || 201 | // Label ends in not allowed character. 202 | /[-\d_]$/.test(label) 203 | ) { 204 | return false 205 | } 206 | 207 | return { 208 | type: 'link', 209 | title: null, 210 | url: 'mailto:' + atext + '@' + label, 211 | children: [{type: 'text', value: atext + '@' + label}] 212 | } 213 | } 214 | 215 | /** 216 | * @param {string} domain 217 | * @returns {boolean} 218 | */ 219 | function isCorrectDomain(domain) { 220 | const parts = domain.split('.') 221 | 222 | if ( 223 | parts.length < 2 || 224 | (parts[parts.length - 1] && 225 | (/_/.test(parts[parts.length - 1]) || 226 | !/[a-zA-Z\d]/.test(parts[parts.length - 1]))) || 227 | (parts[parts.length - 2] && 228 | (/_/.test(parts[parts.length - 2]) || 229 | !/[a-zA-Z\d]/.test(parts[parts.length - 2]))) 230 | ) { 231 | return false 232 | } 233 | 234 | return true 235 | } 236 | 237 | /** 238 | * @param {string} url 239 | * @returns {[string, string | undefined]} 240 | */ 241 | function splitUrl(url) { 242 | const trailExec = /[!"&'),.:;<>?\]}]+$/.exec(url) 243 | 244 | if (!trailExec) { 245 | return [url, undefined] 246 | } 247 | 248 | url = url.slice(0, trailExec.index) 249 | 250 | let trail = trailExec[0] 251 | let closingParenIndex = trail.indexOf(')') 252 | const openingParens = ccount(url, '(') 253 | let closingParens = ccount(url, ')') 254 | 255 | while (closingParenIndex !== -1 && openingParens > closingParens) { 256 | url += trail.slice(0, closingParenIndex + 1) 257 | trail = trail.slice(closingParenIndex + 1) 258 | closingParenIndex = trail.indexOf(')') 259 | closingParens++ 260 | } 261 | 262 | return [url, trail] 263 | } 264 | 265 | /** 266 | * @param {RegExpMatchObject} match 267 | * @param {boolean | null | undefined} [email=false] 268 | * @returns {boolean} 269 | */ 270 | function previous(match, email) { 271 | const code = match.input.charCodeAt(match.index - 1) 272 | 273 | return ( 274 | (match.index === 0 || 275 | unicodeWhitespace(code) || 276 | unicodePunctuation(code)) && 277 | // If it’s an email, the previous character should not be a slash. 278 | (!email || code !== 47) 279 | ) 280 | } 281 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # mdast-util-gfm-autolink-literal 2 | 3 | [![Build][build-badge]][build] 4 | [![Coverage][coverage-badge]][coverage] 5 | [![Downloads][downloads-badge]][downloads] 6 | [![Size][size-badge]][size] 7 | [![Sponsors][sponsors-badge]][collective] 8 | [![Backers][backers-badge]][collective] 9 | [![Chat][chat-badge]][chat] 10 | 11 | [mdast][] extensions to parse and serialize [GFM][] autolink literals. 12 | 13 | ## Contents 14 | 15 | * [What is this?](#what-is-this) 16 | * [When to use this](#when-to-use-this) 17 | * [Install](#install) 18 | * [Use](#use) 19 | * [API](#api) 20 | * [`gfmAutolinkLiteralFromMarkdown()`](#gfmautolinkliteralfrommarkdown) 21 | * [`gfmAutolinkLiteralToMarkdown()`](#gfmautolinkliteraltomarkdown) 22 | * [HTML](#html) 23 | * [Syntax](#syntax) 24 | * [Syntax tree](#syntax-tree) 25 | * [Types](#types) 26 | * [Compatibility](#compatibility) 27 | * [Related](#related) 28 | * [Contribute](#contribute) 29 | * [License](#license) 30 | 31 | ## What is this? 32 | 33 | This package contains two extensions that add support for GFM autolink literals 34 | syntax in markdown to [mdast][]. 35 | These extensions plug into 36 | [`mdast-util-from-markdown`][mdast-util-from-markdown] (to support parsing 37 | GFM autolinks in markdown into a syntax tree) and 38 | [`mdast-util-to-markdown`][mdast-util-to-markdown] (to support serializing 39 | GFM autolinks in syntax trees to markdown). 40 | 41 | GitHub employs different algorithms to autolink: one at parse time and one at 42 | transform time (similar to how `@mentions` are done at transform time). 43 | This difference can be observed because character references and escapes are 44 | handled differently. 45 | But also because issues/PRs/comments omit (perhaps by accident?) the second 46 | algorithm for `www.`, `http://`, and `https://` links (but not for email links). 47 | 48 | As the corresponding micromark extension 49 | [`micromark-extension-gfm-autolink-literal`][extension] is a syntax extension, 50 | it can only perform the first algorithm. 51 | The tree extension `gfmAutolinkLiteralFromMarkdown` from this package can 52 | perform the second algorithm, and as they are combined, both are done. 53 | 54 | ## When to use this 55 | 56 | You can use these extensions when you are working with 57 | `mdast-util-from-markdown` and `mdast-util-to-markdown` already. 58 | 59 | When working with `mdast-util-from-markdown`, you must combine this package 60 | with 61 | [`micromark-extension-gfm-autolink-literal`][extension]. 62 | 63 | When you don’t need a syntax tree, you can use [`micromark`][micromark] 64 | directly with `micromark-extension-gfm-autolink-literal`. 65 | 66 | When you are working with syntax trees and want all of GFM, use 67 | [`mdast-util-gfm`][mdast-util-gfm] instead. 68 | 69 | All these packages are used [`remark-gfm`][remark-gfm], which 70 | focusses on making it easier to transform content by abstracting these 71 | internals away. 72 | 73 | This utility does not handle how markdown is turned to HTML. 74 | That’s done by [`mdast-util-to-hast`][mdast-util-to-hast]. 75 | 76 | ## Install 77 | 78 | This package is [ESM only][esm]. 79 | In Node.js (version 16+), install with [npm][]: 80 | 81 | ```sh 82 | npm install mdast-util-gfm-autolink-literal 83 | ``` 84 | 85 | In Deno with [`esm.sh`][esmsh]: 86 | 87 | ```js 88 | import {gfmAutolinkLiteralFromMarkdown, gfmAutolinkLiteralToMarkdown} from 'https://esm.sh/mdast-util-gfm-autolink-literal@2' 89 | ``` 90 | 91 | In browsers with [`esm.sh`][esmsh]: 92 | 93 | ```html 94 | 97 | ``` 98 | 99 | ## Use 100 | 101 | Say our document `example.md` contains: 102 | 103 | ```markdown 104 | www.example.com, https://example.com, and contact@example.com. 105 | ``` 106 | 107 | …and our module `example.js` looks as follows: 108 | 109 | ```js 110 | import fs from 'node:fs/promises' 111 | import {gfmAutolinkLiteral} from 'micromark-extension-gfm-autolink-literal' 112 | import {fromMarkdown} from 'mdast-util-from-markdown' 113 | import { 114 | gfmAutolinkLiteralFromMarkdown, 115 | gfmAutolinkLiteralToMarkdown 116 | } from 'mdast-util-gfm-autolink-literal' 117 | import {toMarkdown} from 'mdast-util-to-markdown' 118 | 119 | const document = await fs.readFile('example.md', 'utf8') 120 | 121 | const tree = fromMarkdown(document, { 122 | extensions: [gfmAutolinkLiteral()], 123 | mdastExtensions: [gfmAutolinkLiteralFromMarkdown()] 124 | }) 125 | 126 | console.log(tree) 127 | 128 | const out = toMarkdown(tree, {extensions: [gfmAutolinkLiteralToMarkdown()]}) 129 | 130 | console.log(out) 131 | ``` 132 | 133 | …now running `node example.js` yields (positional info removed for brevity): 134 | 135 | ```js 136 | { 137 | type: 'root', 138 | children: [ 139 | { 140 | type: 'paragraph', 141 | children: [ 142 | { 143 | type: 'link', 144 | title: null, 145 | url: 'http://www.example.com', 146 | children: [{type: 'text', value: 'www.example.com'}] 147 | }, 148 | {type: 'text', value: ', '}, 149 | { 150 | type: 'link', 151 | title: null, 152 | url: 'https://example.com', 153 | children: [{type: 'text', value: 'https://example.com'}] 154 | }, 155 | {type: 'text', value: ', and '}, 156 | { 157 | type: 'link', 158 | title: null, 159 | url: 'mailto:contact@example.com', 160 | children: [{type: 'text', value: 'contact@example.com'}] 161 | }, 162 | {type: 'text', value: '.'} 163 | ] 164 | } 165 | ] 166 | } 167 | ``` 168 | 169 | ```markdown 170 | [www.example.com](http://www.example.com), , and . 171 | ``` 172 | 173 | ## API 174 | 175 | This package exports the identifiers 176 | [`gfmAutolinkLiteralFromMarkdown`][api-gfm-autolink-literal-from-markdown] and 177 | [`gfmAutolinkLiteralToMarkdown`][api-gfm-autolink-literal-to-markdown]. 178 | There is no default export. 179 | 180 | ### `gfmAutolinkLiteralFromMarkdown()` 181 | 182 | Create an extension for [`mdast-util-from-markdown`][mdast-util-from-markdown] 183 | to enable GFM autolink literals in markdown. 184 | 185 | ###### Returns 186 | 187 | Extension for `mdast-util-to-markdown` to enable GFM autolink literals 188 | ([`FromMarkdownExtension`][from-markdown-extension]). 189 | 190 | ### `gfmAutolinkLiteralToMarkdown()` 191 | 192 | Create an extension for [`mdast-util-to-markdown`][mdast-util-to-markdown] to 193 | enable GFM autolink literals in markdown. 194 | 195 | ###### Returns 196 | 197 | Extension for `mdast-util-to-markdown` to enable GFM autolink literals 198 | ([`ToMarkdownExtension`][to-markdown-extension]). 199 | 200 | ## HTML 201 | 202 | This utility does not handle how markdown is turned to HTML. 203 | That’s done by [`mdast-util-to-hast`][mdast-util-to-hast]. 204 | 205 | ## Syntax 206 | 207 | See [Syntax in `micromark-extension-gfm-autolink-literal`][syntax]. 208 | 209 | ## Syntax tree 210 | 211 | There are no interfaces added to **[mdast][]** by this utility, as it reuses 212 | the existing **[Link][dfn-link]** interface. 213 | 214 | ## Types 215 | 216 | This package is fully typed with [TypeScript][]. 217 | It does not export additional types. 218 | 219 | The `Link` type of the mdast nodes is exposed from `@types/mdast`. 220 | 221 | ## Compatibility 222 | 223 | Projects maintained by the unified collective are compatible with maintained 224 | versions of Node.js. 225 | 226 | When we cut a new major release, we drop support for unmaintained versions of 227 | Node. 228 | This means we try to keep the current release line, 229 | `mdast-util-gfm-autolink-literal@^2`, compatible with Node.js 16. 230 | 231 | This utility works with `mdast-util-from-markdown` version 2+ and 232 | `mdast-util-to-markdown` version 2+. 233 | 234 | ## Related 235 | 236 | * [`remarkjs/remark-gfm`][remark-gfm] 237 | — remark plugin to support GFM 238 | * [`syntax-tree/mdast-util-gfm`][mdast-util-gfm] 239 | — same but all of GFM (autolink literals, footnotes, strikethrough, tables, 240 | tasklists) 241 | * [`micromark/micromark-extension-gfm-autolink-literal`][extension] 242 | — micromark extension to parse GFM autolink literals 243 | 244 | ## Contribute 245 | 246 | See [`contributing.md`][contributing] in [`syntax-tree/.github`][health] for 247 | ways to get started. 248 | See [`support.md`][support] for ways to get help. 249 | 250 | This project has a [code of conduct][coc]. 251 | By interacting with this repository, organization, or community you agree to 252 | abide by its terms. 253 | 254 | ## License 255 | 256 | [MIT][license] © [Titus Wormer][author] 257 | 258 | 259 | 260 | [build-badge]: https://github.com/syntax-tree/mdast-util-gfm-autolink-literal/workflows/main/badge.svg 261 | 262 | [build]: https://github.com/syntax-tree/mdast-util-gfm-autolink-literal/actions 263 | 264 | [coverage-badge]: https://img.shields.io/codecov/c/github/syntax-tree/mdast-util-gfm-autolink-literal.svg 265 | 266 | [coverage]: https://codecov.io/github/syntax-tree/mdast-util-gfm-autolink-literal 267 | 268 | [downloads-badge]: https://img.shields.io/npm/dm/mdast-util-gfm-autolink-literal.svg 269 | 270 | [downloads]: https://www.npmjs.com/package/mdast-util-gfm-autolink-literal 271 | 272 | [size-badge]: https://img.shields.io/badge/dynamic/json?label=minzipped%20size&query=$.size.compressedSize&url=https://deno.bundlejs.com/?q=mdast-util-gfm-autolink-literal 273 | 274 | [size]: https://bundlejs.com/?q=mdast-util-gfm-autolink-literal 275 | 276 | [sponsors-badge]: https://opencollective.com/unified/sponsors/badge.svg 277 | 278 | [backers-badge]: https://opencollective.com/unified/backers/badge.svg 279 | 280 | [collective]: https://opencollective.com/unified 281 | 282 | [chat-badge]: https://img.shields.io/badge/chat-discussions-success.svg 283 | 284 | [chat]: https://github.com/syntax-tree/unist/discussions 285 | 286 | [npm]: https://docs.npmjs.com/cli/install 287 | 288 | [esm]: https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c 289 | 290 | [esmsh]: https://esm.sh 291 | 292 | [typescript]: https://www.typescriptlang.org 293 | 294 | [license]: license 295 | 296 | [author]: https://wooorm.com 297 | 298 | [health]: https://github.com/syntax-tree/.github 299 | 300 | [contributing]: https://github.com/syntax-tree/.github/blob/HEAD/contributing.md 301 | 302 | [support]: https://github.com/syntax-tree/.github/blob/HEAD/support.md 303 | 304 | [coc]: https://github.com/syntax-tree/.github/blob/HEAD/code-of-conduct.md 305 | 306 | [mdast]: https://github.com/syntax-tree/mdast 307 | 308 | [mdast-util-gfm]: https://github.com/syntax-tree/mdast-util-gfm 309 | 310 | [mdast-util-from-markdown]: https://github.com/syntax-tree/mdast-util-from-markdown 311 | 312 | [mdast-util-to-markdown]: https://github.com/syntax-tree/mdast-util-to-markdown 313 | 314 | [mdast-util-to-hast]: https://github.com/syntax-tree/mdast-util-to-hast 315 | 316 | [remark-gfm]: https://github.com/remarkjs/remark-gfm 317 | 318 | [micromark]: https://github.com/micromark/micromark 319 | 320 | [extension]: https://github.com/micromark/micromark-extension-gfm-autolink-literal 321 | 322 | [syntax]: https://github.com/micromark/micromark-extension-gfm-autolink-literal#syntax 323 | 324 | [gfm]: https://github.github.com/gfm/ 325 | 326 | [dfn-link]: https://github.com/syntax-tree/mdast#link 327 | 328 | [from-markdown-extension]: https://github.com/syntax-tree/mdast-util-from-markdown#extension 329 | 330 | [to-markdown-extension]: https://github.com/syntax-tree/mdast-util-to-markdown#options 331 | 332 | [api-gfm-autolink-literal-from-markdown]: #gfmautolinkliteralfrommarkdown 333 | 334 | [api-gfm-autolink-literal-to-markdown]: #gfmautolinkliteraltomarkdown 335 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | import assert from 'node:assert/strict' 2 | import fs from 'node:fs/promises' 3 | import test from 'node:test' 4 | import {toHtml} from 'hast-util-to-html' 5 | import {toHast} from 'mdast-util-to-hast' 6 | import {fromMarkdown} from 'mdast-util-from-markdown' 7 | import { 8 | gfmAutolinkLiteralFromMarkdown, 9 | gfmAutolinkLiteralToMarkdown 10 | } from 'mdast-util-gfm-autolink-literal' 11 | import {toMarkdown} from 'mdast-util-to-markdown' 12 | import {gfmAutolinkLiteral} from 'micromark-extension-gfm-autolink-literal' 13 | 14 | test('core', async function (t) { 15 | await t.test('should expose the public api', async function () { 16 | assert.deepEqual( 17 | Object.keys(await import('mdast-util-gfm-autolink-literal')).sort(), 18 | ['gfmAutolinkLiteralFromMarkdown', 'gfmAutolinkLiteralToMarkdown'] 19 | ) 20 | }) 21 | }) 22 | 23 | test('gfmAutolinkLiteralFromMarkdown()', async function (t) { 24 | await t.test('should support autolink literals', async function () { 25 | assert.deepEqual( 26 | fromMarkdown( 27 | 'www.example.com, https://example.com, and contact@example.com.', 28 | { 29 | extensions: [gfmAutolinkLiteral()], 30 | mdastExtensions: [gfmAutolinkLiteralFromMarkdown()] 31 | } 32 | ), 33 | { 34 | type: 'root', 35 | children: [ 36 | { 37 | type: 'paragraph', 38 | children: [ 39 | { 40 | type: 'link', 41 | title: null, 42 | url: 'http://www.example.com', 43 | children: [ 44 | { 45 | type: 'text', 46 | value: 'www.example.com', 47 | position: { 48 | start: {line: 1, column: 1, offset: 0}, 49 | end: {line: 1, column: 16, offset: 15} 50 | } 51 | } 52 | ], 53 | position: { 54 | start: {line: 1, column: 1, offset: 0}, 55 | end: {line: 1, column: 16, offset: 15} 56 | } 57 | }, 58 | { 59 | type: 'text', 60 | value: ', ', 61 | position: { 62 | start: {line: 1, column: 16, offset: 15}, 63 | end: {line: 1, column: 18, offset: 17} 64 | } 65 | }, 66 | { 67 | type: 'link', 68 | title: null, 69 | url: 'https://example.com', 70 | children: [ 71 | { 72 | type: 'text', 73 | value: 'https://example.com', 74 | position: { 75 | start: {line: 1, column: 18, offset: 17}, 76 | end: {line: 1, column: 37, offset: 36} 77 | } 78 | } 79 | ], 80 | position: { 81 | start: {line: 1, column: 18, offset: 17}, 82 | end: {line: 1, column: 37, offset: 36} 83 | } 84 | }, 85 | { 86 | type: 'text', 87 | value: ', and ', 88 | position: { 89 | start: {line: 1, column: 37, offset: 36}, 90 | end: {line: 1, column: 43, offset: 42} 91 | } 92 | }, 93 | { 94 | type: 'link', 95 | title: null, 96 | url: 'mailto:contact@example.com', 97 | children: [ 98 | { 99 | type: 'text', 100 | value: 'contact@example.com', 101 | position: { 102 | start: {line: 1, column: 43, offset: 42}, 103 | end: {line: 1, column: 62, offset: 61} 104 | } 105 | } 106 | ], 107 | position: { 108 | start: {line: 1, column: 43, offset: 42}, 109 | end: {line: 1, column: 62, offset: 61} 110 | } 111 | }, 112 | { 113 | type: 'text', 114 | value: '.', 115 | position: { 116 | start: {line: 1, column: 62, offset: 61}, 117 | end: {line: 1, column: 63, offset: 62} 118 | } 119 | } 120 | ], 121 | position: { 122 | start: {line: 1, column: 1, offset: 0}, 123 | end: {line: 1, column: 63, offset: 62} 124 | } 125 | } 126 | ], 127 | position: { 128 | start: {line: 1, column: 1, offset: 0}, 129 | end: {line: 1, column: 63, offset: 62} 130 | } 131 | } 132 | ) 133 | }) 134 | 135 | await t.test('should support normal links', async function () { 136 | assert.deepEqual( 137 | fromMarkdown('[https://google.com](https://google.com)', { 138 | extensions: [gfmAutolinkLiteral()], 139 | mdastExtensions: [gfmAutolinkLiteralFromMarkdown()] 140 | }), 141 | { 142 | type: 'root', 143 | children: [ 144 | { 145 | type: 'paragraph', 146 | children: [ 147 | { 148 | type: 'link', 149 | title: null, 150 | url: 'https://google.com', 151 | children: [ 152 | { 153 | type: 'text', 154 | value: 'https://google.com', 155 | position: { 156 | start: {line: 1, column: 2, offset: 1}, 157 | end: {line: 1, column: 20, offset: 19} 158 | } 159 | } 160 | ], 161 | position: { 162 | start: {line: 1, column: 1, offset: 0}, 163 | end: {line: 1, column: 41, offset: 40} 164 | } 165 | } 166 | ], 167 | position: { 168 | start: {line: 1, column: 1, offset: 0}, 169 | end: {line: 1, column: 41, offset: 40} 170 | } 171 | } 172 | ], 173 | position: { 174 | start: {line: 1, column: 1, offset: 0}, 175 | end: {line: 1, column: 41, offset: 40} 176 | } 177 | } 178 | ) 179 | }) 180 | }) 181 | 182 | test('gfmAutolinkLiteralToMarkdown()', async function (t) { 183 | await t.test('should not serialize autolink literals', async function () { 184 | assert.deepEqual( 185 | toMarkdown( 186 | { 187 | type: 'paragraph', 188 | children: [ 189 | {type: 'text', value: 'a '}, 190 | { 191 | type: 'link', 192 | title: null, 193 | url: 'mailto:contact@example.com', 194 | children: [{type: 'text', value: 'contact@example.com'}] 195 | }, 196 | {type: 'text', value: ' c.'} 197 | ] 198 | }, 199 | {extensions: [gfmAutolinkLiteralToMarkdown()]} 200 | ), 201 | 'a c.\n' 202 | ) 203 | }) 204 | 205 | await t.test( 206 | 'should escape at signs if they appear in what looks like an email', 207 | async function () { 208 | assert.deepEqual( 209 | toMarkdown( 210 | {type: 'paragraph', children: [{type: 'text', value: 'a b@c.d'}]}, 211 | {extensions: [gfmAutolinkLiteralToMarkdown()]} 212 | ), 213 | 'a b\\@c.d\n' 214 | ) 215 | } 216 | ) 217 | 218 | await t.test( 219 | 'should not escape at signs if they appear in what can’t be an email', 220 | async function () { 221 | assert.deepEqual( 222 | toMarkdown( 223 | {type: 'paragraph', children: [{type: 'text', value: 'a @c'}]}, 224 | {extensions: [gfmAutolinkLiteralToMarkdown()]} 225 | ), 226 | 'a @c\n' 227 | ) 228 | } 229 | ) 230 | 231 | await t.test( 232 | 'should escape dots if they appear in what looks like a domain', 233 | async function () { 234 | assert.deepEqual( 235 | toMarkdown( 236 | {type: 'paragraph', children: [{type: 'text', value: 'a www.b.c'}]}, 237 | {extensions: [gfmAutolinkLiteralToMarkdown()]} 238 | ), 239 | 'a www\\.b.c\n' 240 | ) 241 | } 242 | ) 243 | 244 | await t.test( 245 | 'should not escape dots if they appear in what can’t be a domain', 246 | async function () { 247 | assert.deepEqual( 248 | toMarkdown( 249 | {type: 'paragraph', children: [{type: 'text', value: 'a.b'}]}, 250 | {extensions: [gfmAutolinkLiteralToMarkdown()]} 251 | ), 252 | 'a.b\n' 253 | ) 254 | } 255 | ) 256 | 257 | await t.test( 258 | 'should escape colons if they appear in what looks like a http protocol', 259 | async function () { 260 | assert.deepEqual( 261 | toMarkdown( 262 | {type: 'paragraph', children: [{type: 'text', value: 'https:/'}]}, 263 | {extensions: [gfmAutolinkLiteralToMarkdown()]} 264 | ), 265 | 'https\\:/\n' 266 | ) 267 | } 268 | ) 269 | 270 | await t.test( 271 | 'should not escape colons if they appear in what can’t be a http protocol', 272 | async function () { 273 | assert.deepEqual( 274 | toMarkdown( 275 | {type: 'paragraph', children: [{type: 'text', value: 'https:a'}]}, 276 | {extensions: [gfmAutolinkLiteralToMarkdown()]} 277 | ), 278 | 'https:a\n' 279 | ) 280 | } 281 | ) 282 | 283 | await t.test( 284 | 'should not escape colons in definition labels', 285 | async function () { 286 | assert.deepEqual( 287 | toMarkdown( 288 | {type: 'definition', label: 'http://a', identifier: '', url: ''}, 289 | {extensions: [gfmAutolinkLiteralToMarkdown()]} 290 | ), 291 | '[http://a]: <>\n' 292 | ) 293 | } 294 | ) 295 | 296 | await t.test( 297 | 'should not escape colons in link (reference) labels (shortcut)', 298 | async function () { 299 | assert.deepEqual( 300 | toMarkdown( 301 | { 302 | type: 'paragraph', 303 | children: [ 304 | { 305 | type: 'linkReference', 306 | label: 'http://a', 307 | identifier: '', 308 | referenceType: 'collapsed', 309 | children: [{type: 'text', value: 'http://a'}] 310 | } 311 | ] 312 | }, 313 | {extensions: [gfmAutolinkLiteralToMarkdown()]} 314 | ), 315 | '[http://a][]\n' 316 | ) 317 | } 318 | ) 319 | 320 | await t.test( 321 | 'should not escape colons in link (reference) labels (text)', 322 | async function () { 323 | assert.deepEqual( 324 | toMarkdown( 325 | { 326 | type: 'paragraph', 327 | children: [ 328 | { 329 | type: 'linkReference', 330 | label: 'a', 331 | identifier: '', 332 | referenceType: 'full', 333 | children: [{type: 'text', value: 'http://a'}] 334 | } 335 | ] 336 | }, 337 | {extensions: [gfmAutolinkLiteralToMarkdown()]} 338 | ), 339 | '[http://a][a]\n' 340 | ) 341 | } 342 | ) 343 | 344 | await t.test( 345 | 'should not escape colons in link (reference) labels (label)', 346 | async function () { 347 | assert.deepEqual( 348 | toMarkdown( 349 | { 350 | type: 'paragraph', 351 | children: [ 352 | { 353 | type: 'linkReference', 354 | label: 'http://a', 355 | identifier: '', 356 | referenceType: 'full', 357 | children: [{type: 'text', value: 'a'}] 358 | } 359 | ] 360 | }, 361 | {extensions: [gfmAutolinkLiteralToMarkdown()]} 362 | ), 363 | '[a][http://a]\n' 364 | ) 365 | } 366 | ) 367 | 368 | await t.test( 369 | 'should not escape colons in link (resource) labels', 370 | async function () { 371 | assert.deepEqual( 372 | toMarkdown( 373 | { 374 | type: 'paragraph', 375 | children: [ 376 | { 377 | type: 'link', 378 | url: 'http://a', 379 | children: [{type: 'text', value: 'a'}] 380 | } 381 | ] 382 | }, 383 | {extensions: [gfmAutolinkLiteralToMarkdown()]} 384 | ), 385 | '[a](http://a)\n' 386 | ) 387 | } 388 | ) 389 | 390 | await t.test( 391 | 'should not escape colons in image (reference) labels (label)', 392 | async function () { 393 | assert.deepEqual( 394 | toMarkdown( 395 | { 396 | type: 'paragraph', 397 | children: [ 398 | { 399 | type: 'imageReference', 400 | label: 'http://a', 401 | identifier: '', 402 | referenceType: 'full', 403 | alt: 'a' 404 | } 405 | ] 406 | }, 407 | {extensions: [gfmAutolinkLiteralToMarkdown()]} 408 | ), 409 | '![a][http://a]\n' 410 | ) 411 | } 412 | ) 413 | 414 | await t.test( 415 | 'should not escape colons in image (reference) labels (alt)', 416 | async function () { 417 | assert.deepEqual( 418 | toMarkdown( 419 | { 420 | type: 'paragraph', 421 | children: [ 422 | { 423 | type: 'imageReference', 424 | label: 'a', 425 | identifier: '', 426 | referenceType: 'full', 427 | alt: 'http://a' 428 | } 429 | ] 430 | }, 431 | {extensions: [gfmAutolinkLiteralToMarkdown()]} 432 | ), 433 | '![http://a][a]\n' 434 | ) 435 | } 436 | ) 437 | 438 | await t.test( 439 | 'should not escape colons in image (resource) labels', 440 | async function () { 441 | assert.deepEqual( 442 | toMarkdown( 443 | { 444 | type: 'paragraph', 445 | children: [{type: 'image', url: 'http://a', alt: 'a'}] 446 | }, 447 | {extensions: [gfmAutolinkLiteralToMarkdown()]} 448 | ), 449 | '![a](http://a)\n' 450 | ) 451 | } 452 | ) 453 | }) 454 | 455 | test('fixtures', async function (t) { 456 | const root = new URL('fixture/', import.meta.url) 457 | 458 | const files = await fs.readdir(root) 459 | let index = -1 460 | 461 | while (++index < files.length) { 462 | const file = files[index] 463 | 464 | if (!/\.md$/.test(file)) continue 465 | 466 | const stem = file.split('.').slice(0, -1).join('.') 467 | 468 | await t.test('should work on `' + stem + '`', async function () { 469 | const inputUrl = new URL(file, root) 470 | const expectedUrl = new URL(stem + '.html', root) 471 | 472 | const input = await fs.readFile(inputUrl) 473 | const expected = String(await fs.readFile(expectedUrl)) 474 | 475 | const mdast = fromMarkdown(input, { 476 | extensions: [gfmAutolinkLiteral()], 477 | mdastExtensions: [gfmAutolinkLiteralFromMarkdown()] 478 | }) 479 | 480 | const hast = toHast(mdast, {allowDangerousHtml: true}) 481 | assert(hast && hast.type === 'root', 'expected root') 482 | 483 | let actual = toHtml(hast, { 484 | allowDangerousHtml: true, 485 | characterReferences: {useNamedReferences: true} 486 | }) 487 | 488 | if (actual.charCodeAt(actual.length - 1) !== 10) { 489 | actual += '\n' 490 | } 491 | 492 | assert.deepEqual(actual, expected) 493 | }) 494 | } 495 | }) 496 | --------------------------------------------------------------------------------