From a16b9f73b0737a46e852f9c55a17a612f17a9587 Mon Sep 17 00:00:00 2001 From: Nikolay Kostyurin Date: Sun, 12 Apr 2020 21:14:52 +0200 Subject: [PATCH] fix(parser): don't eat not allowed tags with params (#58) fixes #54 * feat(parser): write test for only allowed tags parsing * chore(parser): rename only allowed test * fix(parser): only allowed tag rendering * fix(plugin-helper): add new TagNode toString tests --- packages/bbob-parser/src/parse.js | 53 ++++++++++++------- packages/bbob-parser/test/parse.test.js | 26 +++++++++ packages/bbob-plugin-helper/src/TagNode.js | 36 +++++++++++-- .../bbob-plugin-helper/test/TagNode.test.js | 34 ++++++++++-- 4 files changed, 122 insertions(+), 27 deletions(-) diff --git a/packages/bbob-parser/src/parse.js b/packages/bbob-parser/src/parse.js index 2bb697f0..4526c97e 100644 --- a/packages/bbob-parser/src/parse.js +++ b/packages/bbob-parser/src/parse.js @@ -1,4 +1,5 @@ import TagNode from '@bbob/plugin-helper/lib/TagNode'; +import { isTagNode } from '@bbob/plugin-helper'; import { createLexer } from './lexer'; import { createList } from './utils'; @@ -57,8 +58,25 @@ const parse = (input, opts = {}) => { return nestedTagsMap[token.getValue()]; }; + /** + * @param tagName + * @returns {boolean} + */ const isTagNested = (tagName) => !!nestedTagsMap[tagName]; + /** + * @private + * @param {String} value + * @return {boolean} + */ + const isAllowedTag = (value) => { + if (options.onlyAllowTags && options.onlyAllowTags.length) { + return options.onlyAllowTags.indexOf(value) >= 0; + } + + return true; + }; + /** * Flushes temp tag nodes and its attributes buffers * @private @@ -86,27 +104,22 @@ const parse = (input, opts = {}) => { /** * @private - * @param {TagNode} tag + * @param {string|TagNode} node */ - const appendNodes = (tag) => { + const appendNodes = (node) => { const items = getNodes(); if (Array.isArray(items)) { - items.push(tag); - } - }; - - /** - * @private - * @param {String} value - * @return {boolean} - */ - const isAllowedTag = (value) => { - if (options.onlyAllowTags && options.onlyAllowTags.length) { - return options.onlyAllowTags.indexOf(value) >= 0; + if (isTagNode(node)) { + if (isAllowedTag(node.tag)) { + items.push(node.toTagNode()); + } else { + items.push(node.toString()); + } + } else { + items.push(node); + } } - - return true; }; /** @@ -124,7 +137,7 @@ const parse = (input, opts = {}) => { if (isNested) { nestedNodes.push(tagNode); } else { - appendNodes(tagNode); + appendNodes(tagNode, token); } }; @@ -138,8 +151,8 @@ const parse = (input, opts = {}) => { const lastNestedNode = nestedNodes.flushLast(); if (lastNestedNode) { - appendNodes(lastNestedNode); - } else if (options.onError) { + appendNodes(lastNestedNode, token); + } else if (typeof options.onError === 'function') { const tag = token.getValue(); const line = token.getLine(); const column = token.getColumn(); @@ -217,7 +230,7 @@ const parse = (input, opts = {}) => { * @param {Token} token */ const onToken = (token) => { - if (token.isTag() && isAllowedTag(token.getName())) { + if (token.isTag()) { handleTag(token); } else { handleNode(token); diff --git a/packages/bbob-parser/test/parse.test.js b/packages/bbob-parser/test/parse.test.js index a7d3ce64..8d7df2b9 100644 --- a/packages/bbob-parser/test/parse.test.js +++ b/packages/bbob-parser/test/parse.test.js @@ -119,6 +119,32 @@ describe('Parser', () => { expect(onError).toHaveBeenCalled(); }); + test('parse only allowed tags with params', () => { + const options = { + onlyAllowTags: ['b', 'i', 'u'] + }; + const ast = parse('hello [blah foo="bar"]world[/blah]', options); + + expectOutput(ast, [ + 'hello', + ' ', + '[blah foo="bar"]world[/blah]', + ]) + }); + + test('parse only allowed tags with named param', () => { + const options = { + onlyAllowTags: ['b', 'i', 'u'] + }; + const ast = parse('hello [blah="bar"]world[/blah]', options); + + expectOutput(ast, [ + 'hello', + ' ', + '[blah="bar"]world[/blah]', + ]) + }); + describe('html', () => { const parseHTML = input => parse(input, { openTag: '<', closeTag: '>' }); diff --git a/packages/bbob-plugin-helper/src/TagNode.js b/packages/bbob-plugin-helper/src/TagNode.js index a479bed4..c2d6e252 100644 --- a/packages/bbob-plugin-helper/src/TagNode.js +++ b/packages/bbob-plugin-helper/src/TagNode.js @@ -1,9 +1,28 @@ import { OPEN_BRAKET, CLOSE_BRAKET, SLASH } from './char'; -import { getNodeLength, appendToNode } from './index'; +import { + getNodeLength, appendToNode, attrsToString, attrValue, getUniqAttr, +} from './index'; + +const getTagAttrs = (tag, params) => { + const uniqAattr = getUniqAttr(params); + + if (uniqAattr) { + const tagAttr = attrValue(tag, uniqAattr); + const attrs = { ...params }; + + delete attrs[uniqAattr]; + + const attrsStr = attrsToString(attrs); + + return `${tagAttr}${attrsStr}`; + } + + return `${tag}${attrsToString(params)}`; +}; class TagNode { constructor(tag, attrs, content) { - this.tag = tag.toLowerCase(); + this.tag = tag; this.attrs = attrs; this.content = [].concat(content); } @@ -24,11 +43,22 @@ class TagNode { return getNodeLength(this); } + toTagNode() { + return new TagNode(this.tag.toLowerCase(), this.attrs, this.content); + } + toString() { const OB = OPEN_BRAKET; const CB = CLOSE_BRAKET; + const isEmpty = this.content.length === 0; + const content = this.content.reduce((r, node) => r + node.toString(), ''); + const tagAttrs = getTagAttrs(this.tag, this.attrs); + + if (isEmpty) { + return `${OB}${tagAttrs}${CB}`; + } - return OB + this.tag + CB + this.content.reduce((r, node) => r + node.toString(), '') + OB + SLASH + this.tag + CB; + return `${OB}${tagAttrs}${CB}${content}${OB}${SLASH}${this.tag}${CB}`; } } diff --git a/packages/bbob-plugin-helper/test/TagNode.test.js b/packages/bbob-plugin-helper/test/TagNode.test.js index 9ebcb26e..52f8d568 100644 --- a/packages/bbob-plugin-helper/test/TagNode.test.js +++ b/packages/bbob-plugin-helper/test/TagNode.test.js @@ -13,9 +13,35 @@ describe('@bbob/plugin-helper/lib/TagNode', () => { expect(TagNode.isOf(tagNode, 'test')).toBe(true); }); - test('toString', () => { - const tagNode = TagNode.create('test', {test: 1}, ['Hello']); + describe('toString', () => { + test('tag with content and params', () => { + const tagNode = TagNode.create('test', {test: 1}, ['Hello']); - expect(String(tagNode)).toBe('[test]Hello[/test]'); - }); + expect(String(tagNode)).toBe('[test test="1"]Hello[/test]'); + }); + + test('tag with content and uniq attr', () => { + const tagNode = TagNode.create('test', {test: 'test'}, ['Hello']); + + expect(String(tagNode)).toBe('[test="test"]Hello[/test]'); + }); + + test('tag without content', () => { + const tagNode = TagNode.create('test'); + + expect(String(tagNode)).toBe('[test]'); + }); + + test('tag without content', () => { + const tagNode = TagNode.create('test', {}, 'Content'); + + expect(String(tagNode)).toBe('[test]Content[/test]'); + }); + + test('tag with snakeCase', () => { + const tagNode = TagNode.create('snakeCaseTag'); + + expect(String(tagNode)).toBe('[snakeCaseTag]'); + }); + }) });