From 6039703ff614c84f7846034695054e82f67ec209 Mon Sep 17 00:00:00 2001 From: Titus Wormer Date: Sun, 13 Sep 2020 14:23:21 +0200 Subject: [PATCH] . --- .editorconfig | 9 +++ .gitignore | 6 ++ .npmrc | 1 + .prettierignore | 4 ++ .remarkignore | 1 + .travis.yml | 5 ++ html.js | 1 + index.js | 1 + lib/html.js | 10 +++ lib/syntax.js | 156 +++++++++++++++++++++++++++++++++++++++++++++++ license | 22 +++++++ package.json | 75 +++++++++++++++++++++++ readme.md | 126 ++++++++++++++++++++++++++++++++++++++ test/index.js | 82 +++++++++++++++++++++++++ test/input.md | 137 +++++++++++++++++++++++++++++++++++++++++ test/output.html | 68 +++++++++++++++++++++ 16 files changed, 704 insertions(+) create mode 100644 .editorconfig create mode 100644 .gitignore create mode 100644 .npmrc create mode 100644 .prettierignore create mode 100644 .remarkignore create mode 100644 .travis.yml create mode 100644 html.js create mode 100644 index.js create mode 100644 lib/html.js create mode 100644 lib/syntax.js create mode 100644 license create mode 100644 package.json create mode 100644 readme.md create mode 100644 test/index.js create mode 100644 test/input.md create mode 100644 test/output.html diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..c6c8b36 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,9 @@ +root = true + +[*] +indent_style = space +indent_size = 2 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..fdefc8c --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +.DS_Store +*.log +.nyc_output/ +coverage/ +node_modules/ +yarn.lock diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000..43c97e7 --- /dev/null +++ b/.npmrc @@ -0,0 +1 @@ +package-lock=false diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..2ac20d2 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,4 @@ +coverage/ +*.html +*.json +*.md diff --git a/.remarkignore b/.remarkignore new file mode 100644 index 0000000..65e3ba2 --- /dev/null +++ b/.remarkignore @@ -0,0 +1 @@ +test/ diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..e108609 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,5 @@ +language: node_js +node_js: + - lts/dubnium + - node +after_script: bash <(curl -s https://codecov.io/bash) diff --git a/html.js b/html.js new file mode 100644 index 0000000..227dd9a --- /dev/null +++ b/html.js @@ -0,0 +1 @@ +module.exports = require('./lib/html') diff --git a/index.js b/index.js new file mode 100644 index 0000000..624255d --- /dev/null +++ b/index.js @@ -0,0 +1 @@ +module.exports = require('./lib/syntax') diff --git a/lib/html.js b/lib/html.js new file mode 100644 index 0000000..7f55b28 --- /dev/null +++ b/lib/html.js @@ -0,0 +1,10 @@ +exports.enter = {strikethrough: onenterstrikethrough} +exports.exit = {strikethrough: onexitstrikethrough} + +function onenterstrikethrough() { + this.tag('') +} + +function onexitstrikethrough() { + this.tag('') +} diff --git a/lib/syntax.js b/lib/syntax.js new file mode 100644 index 0000000..cab2027 --- /dev/null +++ b/lib/syntax.js @@ -0,0 +1,156 @@ +var classifyCharacter = require('micromark/dist/util/classify-character') +var shallow = require('micromark/dist/util/shallow') +var resolveAll = require('micromark/dist/util/resolve-all') + +var characterGroupPunctuation = 2 + +var tokenizer = { + tokenize: tokenizeStrikethrough, + resolveAll: resolveAllStrikethrough +} + +exports.text = {126: tokenizer} +exports.insideSpan = {null: tokenizer} + +// Take events and resolve strikethrough. +function resolveAllStrikethrough(events, context) { + var length = events.length + var index = -1 + var strikethrough + var opening + var closing + var text + var indexOpen + var eventsUpTo + + // Walk through all events. + while (++index < length) { + closing = events[index][1] + + // Find a token that can close. + if (closing.type === 'strikethroughSequenceTemporary' && closing._close) { + indexOpen = index + + // Now walk back to find an opener. + while (indexOpen--) { + opening = events[indexOpen][1] + + // Find a token that can open the closer. + if ( + opening.type === 'strikethroughSequenceTemporary' && + opening._open && + closing.end.offset - closing.start.offset === + opening.end.offset - opening.start.offset && + !(closing._open || opening._close) + ) { + closing.type = 'strikethroughSequence' + opening.type = 'strikethroughSequence' + + strikethrough = { + type: 'strikethrough', + start: shallow(opening.start), + end: shallow(closing.end) + } + + text = { + type: 'strikethroughText', + start: shallow(opening.end), + end: shallow(closing.start) + } + + eventsUpTo = [].concat( + // Before. + events.slice(0, indexOpen - 1), + // Opening. + [ + ['enter', strikethrough, context], + ['enter', opening, context], + ['exit', opening, context], + ['enter', text, context] + ], + // Between. + resolveAll( + context.parser.constructs.insideSpan.null, + events.slice(indexOpen + 1, index), + context + ), + // Closing. + [ + ['exit', text, context], + ['enter', closing, context], + ['exit', closing, context], + ['exit', strikethrough, context] + ] + ) + + // After. + events = eventsUpTo.concat(events.slice(index + 2)) + length = events.length + index = eventsUpTo.length - 1 + break + } + } + } + } + + return removeRemainingSequences(events) +} + +function removeRemainingSequences(events) { + var index = -1 + var length = events.length + + while (++index < length) { + if (events[index][1].type === 'strikethroughSequenceTemporary') { + events[index][1].type = 'data' + } + } + + return events +} + +function tokenizeStrikethrough(effects, ok, nok) { + var previous = this.previous + var events = this.events + var size = 0 + + return start + + function start(code) { + if ( + code !== 126 || + (previous === 126 && + events[events.length - 1][1].type !== 'characterEscape') + ) { + return nok(code) + } + + effects.enter('strikethroughSequenceTemporary') + return more(code) + } + + function more(code) { + var token + var before + var after + + if (code === 126) { + // If this is the third marker, exit. + if (size > 1) { + return nok(code) + } + + effects.consume(code) + size++ + return more + } + + before = classifyCharacter(previous) + after = classifyCharacter(code) + token = effects.exit('strikethroughSequenceTemporary') + token._open = !after || (before && after === characterGroupPunctuation) + token._close = !before || (after && before === characterGroupPunctuation) + + return ok(code) + } +} diff --git a/license b/license new file mode 100644 index 0000000..3937235 --- /dev/null +++ b/license @@ -0,0 +1,22 @@ +(The MIT License) + +Copyright (c) 2020 Titus Wormer + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +'Software'), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/package.json b/package.json new file mode 100644 index 0000000..3f26b23 --- /dev/null +++ b/package.json @@ -0,0 +1,75 @@ +{ + "name": "micromark-extension-gfm-strikethrough", + "version": "0.0.0", + "description": "micromark extension to support GFM strikethrough", + "license": "MIT", + "keywords": [ + "micromark", + "micromark-extension", + "strikethrough", + "strike", + "through", + "del", + "delete", + "deletion", + "gfm", + "markdown", + "unified" + ], + "repository": "micromark/micromark-extension-gfm-strikethrough", + "bugs": "https://github.com/micromark/micromark-extension-gfm-strikethrough/issues", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + }, + "author": "Titus Wormer (https://wooorm.com)", + "contributors": [ + "Titus Wormer (https://wooorm.com)" + ], + "files": [ + "lib/", + "index.js", + "html.js" + ], + "dependencies": { + "fault": "^1.0.0", + "micromark": "^2.3.0" + }, + "devDependencies": { + "nyc": "^15.0.0", + "prettier": "^2.0.0", + "remark-cli": "^8.0.0", + "remark-preset-wooorm": "^7.0.0", + "tape": "^5.0.0", + "xo": "^0.33.0" + }, + "scripts": { + "format": "remark . -qfo && prettier . -w --loglevel warn && xo --fix", + "test-api": "node test", + "test-coverage": "nyc --reporter lcov tape test/index.js", + "test": "npm run format && npm run test-coverage" + }, + "nyc": { + "check-coverage": true, + "lines": 100, + "functions": 100, + "branches": 100 + }, + "prettier": { + "tabWidth": 2, + "useTabs": false, + "singleQuote": true, + "bracketSpacing": false, + "semi": false, + "trailingComma": "none" + }, + "xo": { + "prettier": true, + "esnext": false + }, + "remarkConfig": { + "plugins": [ + "preset-wooorm" + ] + } +} diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..ade081f --- /dev/null +++ b/readme.md @@ -0,0 +1,126 @@ +# micromark-extension-gfm-strikethrough + +[![Build][build-badge]][build] +[![Coverage][coverage-badge]][coverage] +[![Downloads][downloads-badge]][downloads] +[![Size][size-badge]][size] +[![Sponsors][sponsors-badge]][collective] +[![Backers][backers-badge]][collective] +[![Chat][chat-badge]][chat] + +**[micromark][]** extension to support GitHub flavored markdown strikethrough +(~~like this~~). + +This package provides the low-level modules for integrating with the micromark +tokenizer and the micromark HTML compiler. + +While GFM defines [strikethrough][], GitHub does not adhere to their +specification. +This syntax extension matches the GitHub parser, not the specification. + +You probably shouldn’t use this package directly, but instead use +[`mdast-util-gfm-strikethrough`][mdast-util-gfm-strikethrough] with +**[mdast][]**. + +## Install + +[npm][]: + +```sh +npm install micromark-extension-gfm-strikethrough +``` + +## API + +### `syntax` + +### `html` + +> Note: `syntax` is the default export of this module, html is available at +> `micromark-extension-gfm-strikethrough/html`. + +Support strikethrough (~~like this~~) +The exports are extensions, respectively for the micromark parser (to tokenize +strikethrough, can be passed in `extensions`) and the default HTML compiler +(to compile as a `` element, can be passed in `htmlExtensions`). + +## Related + +* [`remarkjs/remark`][remark] + — markdown processor powered by plugins +* [`micromark/micromark`][micromark] + — the smallest commonmark-compliant markdown parser that exists +* [`syntax-tree/mdast-util-gfm-strikethrough`][mdast-util-gfm-strikethrough] + — mdast utility to support strikethrough +* [`syntax-tree/mdast-util-from-markdown`][from-markdown] + — mdast parser using `micromark` to create mdast from markdown +* [`syntax-tree/mdast-util-to-markdown`][to-markdown] + — mdast serializer to create markdown from mdast + +## Contribute + +See [`contributing.md` in `micromark/.github`][contributing] for ways to get +started. +See [`support.md`][support] for ways to get help. + +This project has a [code of conduct][coc]. +By interacting with this repository, organization, or community you agree to +abide by its terms. + +## License + +[MIT][license] © [Titus Wormer][author] + + + +[build-badge]: https://img.shields.io/travis/micromark/micromark-extension-gfm-strikethrough.svg + +[build]: https://travis-ci.org/micromark/micromark-extension-gfm-strikethrough + +[coverage-badge]: https://img.shields.io/codecov/c/github/micromark/micromark-extension-gfm-strikethrough.svg + +[coverage]: https://codecov.io/github/micromark/micromark-extension-gfm-strikethrough + +[downloads-badge]: https://img.shields.io/npm/dm/micromark-extension-gfm-strikethrough.svg + +[downloads]: https://www.npmjs.com/package/micromark-extension-gfm-strikethrough + +[size-badge]: https://img.shields.io/bundlephobia/minzip/micromark-extension-gfm-strikethrough.svg + +[size]: https://bundlephobia.com/result?p=micromark-extension-gfm-strikethrough + +[sponsors-badge]: https://opencollective.com/unified/sponsors/badge.svg + +[backers-badge]: https://opencollective.com/unified/backers/badge.svg + +[collective]: https://opencollective.com/unified + +[chat-badge]: https://img.shields.io/badge/chat-discussions-success.svg + +[chat]: https://github.com/micromark/unist/discussions + +[npm]: https://docs.npmjs.com/cli/install + +[license]: license + +[author]: https://wooorm.com + +[contributing]: https://github.com/micromark/.github/blob/HEAD/contributing.md + +[support]: https://github.com/micromark/.github/blob/HEAD/support.md + +[coc]: https://github.com/micromark/.github/blob/HEAD/code-of-conduct.md + +[micromark]: https://github.com/micromark/micromark + +[from-markdown]: https://github.com/syntax-tree/mdast-util-from-markdown + +[to-markdown]: https://github.com/syntax-tree/mdast-util-to-markdown + +[remark]: https://github.com/remarkjs/remark + +[mdast]: https://github.com/syntax-tree/mdast + +[mdast-util-gfm-strikethrough]: https://github.com/syntax-tree/mdast-util-gfm-strikethrough + +[strikethrough]: https://github.github.com/gfm/#strikethrough-extension- diff --git a/test/index.js b/test/index.js new file mode 100644 index 0000000..7a0bbad --- /dev/null +++ b/test/index.js @@ -0,0 +1,82 @@ +var fs = require('fs') +var path = require('path') +var test = require('tape') +var micromark = require('micromark') +var syntax = require('..') +var html = require('../html') + +var input = fs.readFileSync(path.join(__dirname, 'input.md')) +var output = fs.readFileSync(path.join(__dirname, 'output.html'), 'utf8') + +test('markdown -> html (micromark)', function (t) { + t.deepEqual( + micromark('a ~b~', { + extensions: [syntax], + htmlExtensions: [html] + }), + '

a b

', + 'should support strikethrough w/ one tilde' + ) + + t.deepEqual( + micromark('a ~~b~~', { + extensions: [syntax], + htmlExtensions: [html] + }), + '

a b

', + 'should support strikethrough w/ two tildes' + ) + + t.deepEqual( + micromark('a ~~~b~~~', { + extensions: [syntax], + htmlExtensions: [html] + }), + '

a ~~~b~~~

', + 'should not support strikethrough w/ three tildes' + ) + + t.deepEqual( + micromark(input, {extensions: [syntax], htmlExtensions: [html]}), + output, + 'should support strikethrough matching how GH does it' + ) + + t.deepEqual( + micromark('a \\~~~b~~ c', { + extensions: [syntax], + htmlExtensions: [html] + }), + '

a ~b c

', + 'should support strikethrough w/ after an escaped tilde' + ) + + t.deepEqual( + micromark('a ~~b ~~c~~ d~~ e', { + extensions: [syntax], + htmlExtensions: [html] + }), + '

a b c d e

', + 'should support nested strikethrough' + ) + + t.deepEqual( + micromark('a ~-1~ b', { + extensions: [syntax], + htmlExtensions: [html] + }), + '

a -1 b

', + 'should open if preceded by whitespace and followed by punctuation' + ) + + t.deepEqual( + micromark('a ~b.~ c', { + extensions: [syntax], + htmlExtensions: [html] + }), + '

a b. c

', + 'should close if preceded by punctuation and followed by whitespace' + ) + + t.end() +}) diff --git a/test/input.md b/test/input.md new file mode 100644 index 0000000..1f01832 --- /dev/null +++ b/test/input.md @@ -0,0 +1,137 @@ +# Strike through / delete + +## Balanced + +a ~one~ b + +a ~~two~~ b + +a ~~~three~~~ b + +a ~~~~four~~~~ b + +## Unbalanced + +a ~one/two~~ b + +a ~one/three~~~ b + +a ~one/four~~~~ b + +*** + +a ~~two/one~ b + +a ~~two/three~~~ b + +a ~~two/four~~~~ b + +*** + +a ~~~three/one~ b + +a ~~~three/two~~ b + +a ~~~three/four~~~~ b + +*** + +a ~~~~four/one~ b + +a ~~~~four/two~~ b + +a ~~~~four/three~~~ b + +## Multiple + +a ~one b one~ c one~ d + +a ~one b two~~ c one~ d + +a ~one b one~ c two~~ d + +a ~~two b two~~ c two~~ d + +a ~~two b one~ c two~~ d + +a ~~two b two~~ c one~ d + +## Flanking + +a oneRight~ b oneRight~ c oneRight~ d + +a oneRight~ b oneRight~ c ~oneLeft d + +a oneRight~ b ~oneLeft c oneRight~ d + +a ~oneLeft b oneRight~ c oneRight~ d + +a ~oneLeft b oneRight~ c ~oneLeft d + +a ~oneLeft b ~oneLeft c oneRight~ d + +a ~oneLeft b ~oneLeft c ~oneLeft d + +*** + +a twoRight~~ b twoRight~~ c twoRight~~ d + +a twoRight~~ b twoRight~~ c ~~twoLeft d + +a twoRight~~ b ~~twoLeft c twoRight~~ d + +a ~~twoLeft b twoRight~~ c twoRight~~ d + +a ~~twoLeft b twoRight~~ c ~~twoLeft d + +a ~~twoLeft b ~~twoLeft c twoRight~~ d + +a ~~twoLeft b ~~twoLeft c ~~twoLeft d + +## Interleave with attention + +a ~~two *emphasis* two~~ b + +a ~~two **strong** two~~ b + +a *marker ~~two marker* two~~ b + +a ~~two *marker two~~ marker* b + +## Interleave with links + +a ~~two [resource](#) two~~ b + +a ~~two [reference][#] two~~ b + +a [label start ~~two label end](#) two~~ b + +a ~~two [label start two~~ label end](#) b + +a ~~two [label start ~one one~ label end](#) two~~ b + +a ~one [label start ~~two two~~ label end](#) one~ b + +a ~one [label start ~one one~ label end](#) one~ b + +a ~~two [label start ~~two two~~ label end](#) two~~ b + +[#]: # + +## Interleave with code (text) + +a ~~two `code` two~~ b + +a ~~two `code two~~` b + +a `code start ~~two code end` two~~ b + +a ~~two `code start two~~ code end` b + +a ~~two `code start ~one one~ code end` two~~ b + +a ~one `code start ~~two two~~ code end` one~ b + +a ~one `code start ~one one~ code end` one~ b + +a ~~two `code start ~~two two~~ code end` two~~ b diff --git a/test/output.html b/test/output.html new file mode 100644 index 0000000..33d849d --- /dev/null +++ b/test/output.html @@ -0,0 +1,68 @@ +

Strike through / delete

+

Balanced

+

a one b

+

a two b

+

a ~~~three~~~ b

+

a ~~~~four~~~~ b

+

Unbalanced

+

a ~one/two~~ b

+

a ~one/three~~~ b

+

a ~one/four~~~~ b

+
+

a ~~two/one~ b

+

a ~~two/three~~~ b

+

a ~~two/four~~~~ b

+
+

a ~~~three/one~ b

+

a ~~~three/two~~ b

+

a ~~~three/four~~~~ b

+
+

a ~~~~four/one~ b

+

a ~~~~four/two~~ b

+

a ~~~~four/three~~~ b

+

Multiple

+

a one b one c one~ d

+

a one b two~~ c one d

+

a one b one c two~~ d

+

a two b two c two~~ d

+

a two b one~ c two d

+

a two b two c one~ d

+

Flanking

+

a oneRight~ b oneRight~ c oneRight~ d

+

a oneRight~ b oneRight~ c ~oneLeft d

+

a oneRight~ b oneLeft c oneRight d

+

a oneLeft b oneRight c oneRight~ d

+

a oneLeft b oneRight c ~oneLeft d

+

a ~oneLeft b oneLeft c oneRight d

+

a ~oneLeft b ~oneLeft c ~oneLeft d

+
+

a twoRight~~ b twoRight~~ c twoRight~~ d

+

a twoRight~~ b twoRight~~ c ~~twoLeft d

+

a twoRight~~ b twoLeft c twoRight d

+

a twoLeft b twoRight c twoRight~~ d

+

a twoLeft b twoRight c ~~twoLeft d

+

a ~~twoLeft b twoLeft c twoRight d

+

a ~~twoLeft b ~~twoLeft c ~~twoLeft d

+

Interleave with attention

+

a two emphasis two b

+

a two strong two b

+

a marker ~~two marker two~~ b

+

a two *marker two marker* b

+

Interleave with links

+

a two resource two b

+

a two reference two b

+

a label start ~~two label end two~~ b

+

a ~~two label start two~~ label end b

+

a two label start one one label end two b

+

a one label start two two label end one b

+

a one label start one one label end one b

+

a two label start two two label end two b

+

Interleave with code (text)

+

a two code two b

+

a ~~two code two~~ b

+

a code start ~~two code end two~~ b

+

a ~~two code start two~~ code end b

+

a two code start ~one one~ code end two b

+

a one code start ~~two two~~ code end one b

+

a one code start ~one one~ code end one b

+

a two code start ~~two two~~ code end two b