From c9a1c1bc7b98e826b7532f53cea2b446354b0939 Mon Sep 17 00:00:00 2001 From: Trisha Le Date: Tue, 12 Mar 2024 10:48:43 -0700 Subject: [PATCH] refactor: reusable content parser (#838) * refactor: reusable content parser * chore: prettier * fix: export type * update regex Co-authored-by: Kelly Joseph Price --------- Co-authored-by: Kelly Joseph Price --- __tests__/flavored-compilers.test.js | 81 ++++++++----------- .../reusable-content.test.js.snap | 32 ++++++++ .../reusable-content.test.js | 0 processor/compile/reusable-content.js | 2 +- processor/parse/index.js | 1 + processor/parse/reusable-content-parser.js | 38 +++++++++ processor/transform/index.js | 3 +- processor/transform/reusable-content.js | 28 ------- 8 files changed, 105 insertions(+), 80 deletions(-) rename __tests__/{transformers => flavored-parsers}/__snapshots__/reusable-content.test.js.snap (90%) rename __tests__/{transformers => flavored-parsers}/reusable-content.test.js (100%) create mode 100644 processor/parse/reusable-content-parser.js delete mode 100644 processor/transform/reusable-content.js diff --git a/__tests__/flavored-compilers.test.js b/__tests__/flavored-compilers.test.js index a7723a0ee..c2d55a725 100644 --- a/__tests__/flavored-compilers.test.js +++ b/__tests__/flavored-compilers.test.js @@ -1,46 +1,29 @@ -const { defaultSchema: sanitize } = require('hast-util-sanitize/lib/schema'); -const rehypeSanitize = require('rehype-sanitize'); -const remarkParse = require('remark-parse'); -const remarkStringify = require('remark-stringify'); -const unified = require('unified'); - -const parsers = Object.values(require('../processor/parse')).map(parser => parser.sanitize?.(sanitize) || parser); -const compilers = Object.values(require('../processor/compile')); -const options = require('../options').options.markdownOptions; - -const processor = unified() - .use(remarkParse, options) - .data('settings', { position: false }) - .use(parsers) - .use(rehypeSanitize); - -const parse = text => text && processor().parse(text); -const compile = tree => tree && processor().use(remarkStringify, options).use(compilers).stringify(tree); +import { mdast, md } from '../index'; describe('ReadMe Flavored Blocks', () => { it('Embed', () => { const txt = '[Embedded meta links.](https://nyti.me/s/gzoa2xb2v3 "@nyt")'; - const ast = parse(txt); - const out = compile(ast); + const ast = mdast(txt); + const out = md(ast); expect(out).toMatchSnapshot(); }); it('Variables', () => { - expect(compile(parse('<>'))).toMatchInlineSnapshot(` + expect(md(mdast('<>'))).toMatchInlineSnapshot(` "<> " `); }); it('Glossary Items', () => { - expect(compile(parse('<>'))).toMatchInlineSnapshot(` + expect(md(mdast('<>'))).toMatchInlineSnapshot(` "<> " `); }); it('Emojis', () => { - expect(compile(parse(':smiley:'))).toMatchInlineSnapshot(` + expect(md(mdast(':smiley:'))).toMatchInlineSnapshot(` ":smiley: " `); @@ -56,9 +39,9 @@ describe('ReadMe Flavored Blocks', () => { } [/block] `; - const ast = parse(text); + const ast = mdast(text); - expect(compile(ast)).toMatchInlineSnapshot(` + expect(md(ast)).toMatchInlineSnapshot(` "[block:html] { \\"html\\": \\"\\" @@ -79,8 +62,8 @@ describe('ReadMe Magic Blocks', () => { "favicon": "https://youtu.be/favicon.ico" } [/block]`; - const ast = parse(txt); - const out = compile(ast); + const ast = mdast(txt); + const out = md(ast); expect(out).toMatchSnapshot(); }); @@ -101,8 +84,8 @@ describe('ReadMe Magic Blocks', () => { } [/block] `; - const ast = parse(txt); - const out = compile(ast); + const ast = mdast(txt); + const out = md(ast); expect(out).toMatchSnapshot(); }); @@ -117,8 +100,8 @@ describe('ReadMe Magic Blocks', () => { And this is a paragraph! `; - const ast = parse(txt); - const out = compile(ast); + const ast = mdast(txt); + const out = md(ast); expect(out).toMatchSnapshot(); }); @@ -137,8 +120,8 @@ describe('ReadMe Magic Blocks', () => { ] } [/block]`; - const ast = parse(txt); - const out = compile(ast); + const ast = mdast(txt); + const out = md(ast); expect(out).toMatchSnapshot(); }); @@ -158,8 +141,8 @@ describe('ReadMe Magic Blocks', () => { ] } [/block]`; - const ast = parse(txt); - const out = compile(ast); + const ast = mdast(txt); + const out = md(ast); expect(out).toMatchSnapshot(); }); @@ -174,8 +157,8 @@ describe('ReadMe Magic Blocks', () => { }] } [/block]`; - const ast = parse(txt); - const out = compile(ast); + const ast = mdast(txt); + const out = md(ast); expect(out).toMatchSnapshot(); }); @@ -193,8 +176,8 @@ describe('ReadMe Magic Blocks', () => { }] } [/block]`; - const ast = parse(txt); - const out = compile(ast); + const ast = mdast(txt); + const out = md(ast); expect(out).toMatchSnapshot(); }); @@ -212,8 +195,8 @@ describe('ReadMe Magic Blocks', () => { }] } [/block]`; - const ast = parse(txt); - const out = compile(ast); + const ast = mdast(txt); + const out = md(ast); expect(out).toMatchSnapshot(); }); @@ -235,16 +218,16 @@ ${JSON.stringify( `; - const ast = parse(txt); - const out = compile(ast); + const ast = mdast(txt); + const out = md(ast); expect(out).toMatchSnapshot(); }); it('font-awesome emojis', () => { const txt = ':fa-rss-square:'; - const ast = parse(txt); - const out = compile(ast); + const ast = mdast(txt); + const out = md(ast); expect(out).toMatchInlineSnapshot(` ":fa-rss-square: " @@ -252,7 +235,7 @@ ${JSON.stringify( }); it('Tables', () => { - const md = ` + const text = ` [block:parameters] ${JSON.stringify({ data: { @@ -268,11 +251,11 @@ ${JSON.stringify({ [/block] `; - expect(compile(parse(md))).toMatchSnapshot(); + expect(md(mdast(text))).toMatchSnapshot(); }); it('Tables with breaks', () => { - const md = ` + const text = ` [block:parameters] ${JSON.stringify({ data: { @@ -288,6 +271,6 @@ ${JSON.stringify({ [/block] `; - expect(compile(parse(md))).toMatchSnapshot(); + expect(md(mdast(text))).toMatchSnapshot(); }); }); diff --git a/__tests__/transformers/__snapshots__/reusable-content.test.js.snap b/__tests__/flavored-parsers/__snapshots__/reusable-content.test.js.snap similarity index 90% rename from __tests__/transformers/__snapshots__/reusable-content.test.js.snap rename to __tests__/flavored-parsers/__snapshots__/reusable-content.test.js.snap index 712663c7b..43317f5f2 100644 --- a/__tests__/transformers/__snapshots__/reusable-content.test.js.snap +++ b/__tests__/flavored-parsers/__snapshots__/reusable-content.test.js.snap @@ -101,6 +101,22 @@ Object { "type": "paragraph", }, ], + "position": Position { + "end": Object { + "column": 1, + "line": 6, + "offset": 19, + }, + "indent": Array [ + 1, + 1, + ], + "start": Object { + "column": 1, + "line": 4, + "offset": 9, + }, + }, "tag": "Test", "type": "reusable-content", } @@ -207,6 +223,22 @@ Object { "type": "paragraph", }, ], + "position": Position { + "end": Object { + "column": 1, + "line": 3, + "offset": 23, + }, + "indent": Array [ + 1, + 1, + ], + "start": Object { + "column": 1, + "line": 1, + "offset": 0, + }, + }, "tag": "MyCustomComponent", "type": "reusable-content", } diff --git a/__tests__/transformers/reusable-content.test.js b/__tests__/flavored-parsers/reusable-content.test.js similarity index 100% rename from __tests__/transformers/reusable-content.test.js rename to __tests__/flavored-parsers/reusable-content.test.js diff --git a/processor/compile/reusable-content.js b/processor/compile/reusable-content.js index cf8f610e6..8e8134bc7 100644 --- a/processor/compile/reusable-content.js +++ b/processor/compile/reusable-content.js @@ -1,4 +1,4 @@ -import { type } from '../transform/reusable-content'; +import { type } from '../parse/reusable-content-parser'; export default function ReusableContentCompiler() { const { serialize = true } = this.data('reusableContent') || {}; diff --git a/processor/parse/index.js b/processor/parse/index.js index 10205e1b3..a0162f0ac 100644 --- a/processor/parse/index.js +++ b/processor/parse/index.js @@ -6,6 +6,7 @@ export { default as flavorEmbed } from './flavored/embed'; export { default as escape } from './escape'; export { default as compactHeadings } from './compact-headings'; +export { default as reusableContentParser } from './reusable-content-parser'; export { default as variableParser } from './variable-parser'; export { default as gemojiParser } from './gemoji-parser'; export { default as htmlBlockParser } from './html-block'; diff --git a/processor/parse/reusable-content-parser.js b/processor/parse/reusable-content-parser.js new file mode 100644 index 000000000..07fd5b67a --- /dev/null +++ b/processor/parse/reusable-content-parser.js @@ -0,0 +1,38 @@ +const { insertBlockTokenizerBefore } = require('./utils'); + +export const type = 'reusable-content'; + +function tokenizeReusableContent(eat, value, silent) { + const { tags, disabled, wrap = true } = this.data('reusableContent'); + if (disabled) return false; + + // Modifies the regular expression to match from + // the start of the line + const match = /^<(?[A-Z]\S+)\s*\/>\s*\n/.exec(value); + + if (!match || !match.groups.tag) return false; + const { tag } = match.groups; + + /* istanbul ignore if */ + if (silent) return true; + + const node = wrap + ? { + type: 'reusable-content', + tag, + children: tag in tags ? tags[tag] : [], + } + : tags[tag]; + + return eat(match[0])(node); +} + +function parser() { + insertBlockTokenizerBefore.call(this, { + name: 'reusableContent', + before: 'html', + tokenizer: tokenizeReusableContent.bind(this), + }); +} + +export default parser; diff --git a/processor/transform/index.js b/processor/transform/index.js index dd2ea49ec..5b12c9d5c 100644 --- a/processor/transform/index.js +++ b/processor/transform/index.js @@ -1,6 +1,5 @@ -import reusableContent from './reusable-content'; import singleCodeTabs from './single-code-tabs'; import tableCellInlineCode from './table-cell-inline-code'; -export const remarkTransformers = [singleCodeTabs, reusableContent]; +export const remarkTransformers = [singleCodeTabs]; export const rehypeTransformers = [tableCellInlineCode]; diff --git a/processor/transform/reusable-content.js b/processor/transform/reusable-content.js deleted file mode 100644 index 6301ef7ef..000000000 --- a/processor/transform/reusable-content.js +++ /dev/null @@ -1,28 +0,0 @@ -import { visit } from 'unist-util-visit'; - -export const type = 'reusable-content'; - -const regexp = /^\s*<(?[A-Z]\S+)\s*\/>\s*$/; - -const reusableContentTransformer = function () { - const { tags, disabled, wrap = true } = this.data('reusableContent'); - if (disabled) return () => undefined; - - return tree => { - visit(tree, 'html', (node, index, parent) => { - const result = regexp.exec(node.value); - if (!result || !result.groups.tag) return; - const { tag } = result.groups; - - if (wrap) { - parent.children[index] = { type, tag, children: tag in tags ? tags[tag] : [] }; - } else { - parent.children.splice(index, tags[tag].length, ...tags[tag]); - } - }); - - return tree; - }; -}; - -export default reusableContentTransformer;