diff --git a/.c8rc b/.c8rc new file mode 100644 index 0000000..f37f305 --- /dev/null +++ b/.c8rc @@ -0,0 +1,3 @@ +{ + "reporter": ["lcov", "text"] +} \ No newline at end of file diff --git a/.editorconfig b/.editorconfig index fbb9538..10a51da 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,22 +1,19 @@ -# This file is for unifying the coding style for different editors and IDEs. -# More information at http://EditorConfig.org - -# No .editorconfig files above the root directory root = true [*] -charset = utf-8 +indent_style = space indent_size = 4 end_of_line = lf -indent_style = space +charset = utf-8 +quote_type = single trim_trailing_whitespace = true insert_final_newline = true -[*.{bemjson.js,deps.js}] -indent_size = 4 - -[{bower,package}.json] +[{package.json,*.yml,*.jade,*.pss,*.css,*.js,*.md,.*,*.ts}] indent_size = 2 +[{changelog.md,.*}] +insert_final_newline = false + [*.md] trim_trailing_whitespace = false diff --git a/.github/funding.yml b/.github/funding.yml new file mode 100644 index 0000000..91bead1 --- /dev/null +++ b/.github/funding.yml @@ -0,0 +1,12 @@ +# These are supported funding model platforms + +github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] +patreon: posthtml +open_collective: posthtml +ko_fi: # Replace with a single Ko-fi username +tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel +community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry +liberapay: # Replace with a single Liberapay username +issuehunt: # Replace with a single IssueHunt username +otechie: # Replace with a single Otechie username +custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] diff --git a/.github/workflows/nodejs.yml b/.github/workflows/nodejs.yml new file mode 100644 index 0000000..26eab00 --- /dev/null +++ b/.github/workflows/nodejs.yml @@ -0,0 +1,39 @@ +name: Actions Status +on: + pull_request: + types: [opened, synchronize] + branches: + - master +env: + CI: true + +jobs: + run: + name: Node ${{ matrix.node }} on ${{ matrix.os }} + runs-on: ${{ matrix.os }} + + strategy: + fail-fast: false + matrix: + node: [10, 12, 14] + os: [ubuntu-latest] + + steps: + - name: Clone repository + uses: actions/checkout@v2 + + - name: Set Node.js version + uses: actions/setup-node@v1 + with: + node-version: ${{ matrix.node }} + + - name: Install npm dependencies + run: npm ci + + - name: Run tests + run: npm run test + + - name: Run Coveralls + uses: coverallsapp/github-action@master + with: + github-token: "${{ secrets.GITHUB_TOKEN }}" diff --git a/.jscsrc b/.jscsrc deleted file mode 100644 index f5cc4fc..0000000 --- a/.jscsrc +++ /dev/null @@ -1,61 +0,0 @@ -{ - "excludeFiles": [ - ".git/**", - "node_modules/**", - "coverage/**" - ], - "fileExtensions": [".js"], - "validateParameterSeparator": ", ", - "disallowSpacesInCallExpression": true, - "disallowSpaceAfterObjectKeys": true, - "disallowSpaceBeforeSemicolon": true, - "disallowSpaceBeforePostfixUnaryOperators": true, - "disallowNewlineBeforeBlockStatements": true, - "disallowMultipleLineBreaks": true, - "disallowTrailingComma": true, - "disallowTrailingWhitespace": true, - "disallowNamedUnassignedFunctions": true, - "disallowSpacesInFunctionDeclaration": { - "beforeOpeningRoundBrace": true - }, - "disallowSpacesInFunctionExpression": { - "beforeOpeningRoundBrace": true - }, - "disallowSpacesInConditionalExpression": { - "afterTest": true, - "afterConsequent": true - }, - "requireSpaceBeforeBlockStatements": 1, - "requireSpaceAfterKeywords": [ - "do", - "for", - "if", - "else", - "switch", - "case", - "try", - "catch", - "void", - "while", - "with", - "return", - "typeof" - ], - "requireSpaceAfterBinaryOperators": true, - "requireSpacesInForStatement": true, - "requireSpaceBetweenArguments": true, - "requireSemicolons": true, - "requireFunctionDeclarations": true, - "requireCommaBeforeLineBreak": true, - "requireSpacesInConditionalExpression": { - "afterTest": true, - "afterConsequent": true - }, - "requireSpacesInFunctionDeclaration": { - "beforeOpeningCurlyBrace": true - }, - "requireSpacesInConditionalExpression": { - "beforeConsequent": true, - "beforeAlternate": true - } -} diff --git a/.jshintignore b/.jshintignore deleted file mode 100644 index ba2a97b..0000000 --- a/.jshintignore +++ /dev/null @@ -1,2 +0,0 @@ -node_modules -coverage diff --git a/.jshintrc b/.jshintrc deleted file mode 100644 index dfd00eb..0000000 --- a/.jshintrc +++ /dev/null @@ -1,8 +0,0 @@ -{ - "eqeqeq": true, - "expr": true, - "maxlen": 120, - "undef": true, - "unused": true, - "node": true -} diff --git a/.lintstagedrc b/.lintstagedrc index 1130c5f..d9d1cdf 100644 --- a/.lintstagedrc +++ b/.lintstagedrc @@ -1,3 +1,3 @@ { - "*.js": "jshint . && jscs ." -} \ No newline at end of file + "*.js": "xo" +} diff --git a/.npmignore b/.npmignore index 0b4cf45..9a63c69 100644 --- a/.npmignore +++ b/.npmignore @@ -1,7 +1,4 @@ -node_modules/ -test/ +node_modules +test npm-debug.log .editorconfig -.jscsrc -.jshintignore -.jshintrc diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index edd95f3..0000000 --- a/.travis.yml +++ /dev/null @@ -1,9 +0,0 @@ -sudo: false -language: node_js -node_js: - - "stable" - - "lts/*" - - 10 - -after_success: - - npm run coverage diff --git a/MAINTAINERS b/MAINTAINERS deleted file mode 100644 index 992aa6a..0000000 --- a/MAINTAINERS +++ /dev/null @@ -1,5 +0,0 @@ -voischev -awinogradov -zxqfox -Yeti-or -jescalan diff --git a/changelog.md b/changelog.md index 1a943fc..e10f708 100644 --- a/changelog.md +++ b/changelog.md @@ -1,11 +1,35 @@ +## 0.5.1 (2020-10-27) + +* style: 2 space, close issue #33 ([cdd7dbd](https://github.com/posthtml/posthtml-parser/commit/cdd7dbd)), closes [#33](https://github.com/posthtml/posthtml-parser/issues/33) +* style: after lint ([23dd2a0](https://github.com/posthtml/posthtml-parser/commit/23dd2a0)) +* build: change eslint to xo ([5233a43](https://github.com/posthtml/posthtml-parser/commit/5233a43)) +* build: change lint to eslint ([f6eef50](https://github.com/posthtml/posthtml-parser/commit/f6eef50)) +* build: update bump version script ([7b7cfbf](https://github.com/posthtml/posthtml-parser/commit/7b7cfbf)) +* build: update dep dev ([8601e40](https://github.com/posthtml/posthtml-parser/commit/8601e40)) +* perf: concate with prev content string ([fe7ddb0](https://github.com/posthtml/posthtml-parser/commit/fe7ddb0)) +* perf: downgrade code for old node ([b160b9d](https://github.com/posthtml/posthtml-parser/commit/b160b9d)) +* perf: migrate to parser v5 ([c2349ad](https://github.com/posthtml/posthtml-parser/commit/c2349ad)) +* ci: change nyc to c8 ([1638a07](https://github.com/posthtml/posthtml-parser/commit/1638a07)) +* ci: forgot config for c8 ([ba45ba6](https://github.com/posthtml/posthtml-parser/commit/ba45ba6)) +* ci: migrate to github action from travis ([05e3768](https://github.com/posthtml/posthtml-parser/commit/05e3768)) +* test: contents are split with '<' in comment, issue #18 ([cbeb319](https://github.com/posthtml/posthtml-parser/commit/cbeb319)), closes [#18](https://github.com/posthtml/posthtml-parser/issues/18) +* test: contents are split with '<' in comment, issue #45 ([74169cd](https://github.com/posthtml/posthtml-parser/commit/74169cd)), closes [#45](https://github.com/posthtml/posthtml-parser/issues/45) +* fix: contents are split with '<' in comment, close #18, close #45 ([8e64082](https://github.com/posthtml/posthtml-parser/commit/8e64082)), closes [#18](https://github.com/posthtml/posthtml-parser/issues/18) [#45](https://github.com/posthtml/posthtml-parser/issues/45) +* refactor: migrate to es-next syntax ([d89bf85](https://github.com/posthtml/posthtml-parser/commit/d89bf85)) +* docs: simple update ([0c10d9d](https://github.com/posthtml/posthtml-parser/commit/0c10d9d)) + + + ## 0.5.0 (2020-08-22) -* revert: incorrect changes ([0601fbd](https://github.com/posthtml/posthtml-parser/commit/0601fbd)) -* style: lintstage lint update ([43398d5](https://github.com/posthtml/posthtml-parser/commit/43398d5)) +* 0.5.0 ([be05a9f](https://github.com/posthtml/posthtml-parser/commit/be05a9f)) * build: forgot conventional-changelog-cli ([8ff437a](https://github.com/posthtml/posthtml-parser/commit/8ff437a)) +* build: update changelog ([d0a07dc](https://github.com/posthtml/posthtml-parser/commit/d0a07dc)) * build: update dep dev ([f8efe55](https://github.com/posthtml/posthtml-parser/commit/f8efe55)) * build: update lint/build system ([54adc49](https://github.com/posthtml/posthtml-parser/commit/54adc49)) * build(deps): bump handlebars from 4.0.11 to 4.5.3 ([8773f14](https://github.com/posthtml/posthtml-parser/commit/8773f14)) +* revert: incorrect changes ([0601fbd](https://github.com/posthtml/posthtml-parser/commit/0601fbd)) +* style: lintstage lint update ([43398d5](https://github.com/posthtml/posthtml-parser/commit/43398d5)) * ci: drop old node support ([cf95d62](https://github.com/posthtml/posthtml-parser/commit/cf95d62)) * ci: drop support old node ([d415fb9](https://github.com/posthtml/posthtml-parser/commit/d415fb9)) * fix: incorrect merge defaultOptions, close #47 ([11ba7fb](https://github.com/posthtml/posthtml-parser/commit/11ba7fb)), closes [#47](https://github.com/posthtml/posthtml-parser/issues/47) diff --git a/index.d.ts b/index.d.ts index f358b3f..3dff13b 100644 --- a/index.d.ts +++ b/index.d.ts @@ -1,96 +1,96 @@ declare namespace parser { type DefaultOptions = { - /** + /** * @default false */ - lowerCaseTags: boolean; + lowerCaseTags: boolean; - /** + /** * @default false */ - lowerCaseAttributeNames: boolean; + lowerCaseAttributeNames: boolean; }; type Directive = { - name: string; - start: string; - end: string; + name: string; + start: string; + end: string; }; type Options = { - /** + /** * Adds processing of custom directives. * Note: The property `name` in custom directives can be `String` or `RegExp` type. * * @default * [{name: '!doctype', start: '<', end: '>'}] */ - directives?: Directive[]; + directives?: Directive[]; - /** + /** * Indicates whether special tags (`', {xmlMode: true})) - .to.eql([{tag: 'script', content: ['console.log(1);']}]); - }); - - it('should be parse tag with escape object in attribute', function() { - var htmlString = ''; - // console.log(htmlString); - var tree = [ - { - tag: 'button', - attrs: { - type: 'submit', - 'data-bem': '{"button":{"checkedView":"extra"}}' - } - } - ]; - - expect(parser(htmlString)).to.eql(tree); - }); - - // it('should be parse tag with object in attribute data witchout escape', function() { - // var htmlString = ''; - // // console.log(htmlString); - // var tree = [ - // { - // tag: 'button', - // attrs: { - // type: 'submit', - // 'data-bem': '{"button":{"checkedView":"extra"}}' - // } - // } - // ]; - - // expect(parser(htmlString)).to.eql(tree); - // }); - - // it('should be parse tag with object in attribute data escape', function() { - // var json = JSON.stringify({button: {checkedView:'extra'}}); - // var htmlString = ''; - // // console.log(htmlString); - // var tree = [ - // { - // tag: 'button', - // attrs: { - // type: 'submit', - // 'data-bem': '{"button":{"checkedView":"extra"}}' - // } - // } - // ]; - - // expect(parser(htmlString)).to.eql(tree); - // }); - - it('should be parse comment in content', function() { - expect(parser('
')).to.eql([{tag: 'div', content: ['']}]); - }); - - it('should be parse doctype', function() { - expect(parser('')).to.eql(['']); - }); - - it('should be parse directive', function() { - var options = { - directives: [ - { name: '?php', start: '<', end: '>' } - ] - }; - - expect(parser('', options)).to.eql(['']); - }); - - it('should be parse regular expression directive', function() { - var options = { - directives: [ - { name: /\?(php|=).*/, start: '<', end: '>' } - ] - }; - - expect(parser('', options)).to.eql(['']); - expect(parser('', options)).to.eql(['']); - }); - - it('should be parse directives and tag', function() { - var options = { - directives: [ - { name: '!doctype', start: '<', end: '>' }, - { name: '?php', start: '<', end: '>' } - ] - }; - - var html = '
{{%njk test %}}'; - var tree = [ - '', - { - content: [''], - tag: 'header' - }, - { - content: ['{{%njk test %}}'], - tag: 'body' - } - ]; - - expect(parser(html, options)).to.eql(tree); - }); - - it('should be parse tag', function() { - expect(parser('')).to.eql([{ tag: 'html' }]); - }); - - it('should be parse doctype and tag', function() { - expect(parser('')).to.eql(['', { tag: 'html' }]); - }); - - it('should be parse tag attrs', function() { - expect(parser('
')).to.eql([{ - tag: 'div', attrs: { id: 'id', class: 'class'} } - ]); - }); - - it('should be parse text', function() { - expect(parser('Text')).to.eql(['Text']); - }); - - it('should be parse text in content', function() { - expect(parser('
Text
')).to.eql([{ tag: 'div', content: ['Text'] }]); - }); - - it('should be parse not a single node in tree', function() { - expect(parser('Text1Text2Text3')).to.eql([ - { tag: 'span', content: ['Text1']}, { tag: 'span', content: ['Text2']}, 'Text3' - ]); - }); - - it('should be parse not a single node in parent content', function() { - expect(parser('
Text1Text2Text3
')).to.eql([ - { tag: 'div', content: [{ tag: 'span', content: ['Text1']}, { tag: 'span', content: ['Text2']}, 'Text3'] } - ]); - }); - - it('should be parse camelCase tag name', function() { - expect(parser('')).to.eql([ - { tag: 'mySuperTag' } - ]); - }); +describe('PostHTML-Parser test', () => { + describe('Call signatures', () => { + const customOptions = {lowerCaseTags: false, lowerCaseAttributeNames: false}; + let MockedHtmlParser2; + let parserSpy; + + beforeEach(() => { + MockedHtmlParser2 = function () { + }; + + MockedHtmlParser2.prototype = { + write() { + }, + end() { + } + }; + + // Create spy on mocked htmlparser2 to collect call stats + parserSpy = sinon.spy(MockedHtmlParser2); + + // Replace real htmlparser2 dependency of posthtml-parser with mocked + parserWithMockedDeps.__set__({ + Parser: parserSpy + }); + }); + + it('should use default options when called with 1 param', () => { + parserWithMockedDeps(''); + expect(parserSpy.firstCall.args[1]).to.eql(customOptions); + }); + + it('should use custom options when called with 2 params', () => { + parserWithMockedDeps('', customOptions); + expect(parserSpy.firstCall.args[1]).to.eql(customOptions); + }); + + it('should use custom params when called as factory function', () => { + const factory = parserWithMockedDeps(customOptions); + expect(factory).to.be.a('function'); + expect(factory('')).to.be.an('array'); + expect(parserSpy.firstCall.args[1]).to.eql(customOptions); + }); + }); + + it('should be parse doctype in uppercase', () => { + expect(parser('')).to.eql(['']); + }); + + it('should be parse comment', () => { + expect(parser('')).to.eql(['']); + }); + + it('should be parse CDATA', () => { + expect(parser('', {xmlMode: true})) + .to.eql([{tag: 'script', content: ['console.log(1);']}]); + }); + + it('should be parse tag with escape object in attribute', () => { + const htmlString = ''; + const tree = [ + { + tag: 'button', + attrs: { + type: 'submit', + 'data-bem': '{"button":{"checkedView":"extra"}}' + } + } + ]; + + expect(parser(htmlString)).to.eql(tree); + }); + + it.skip('should be parse tag with object in attribute data witchout escape', () => { + const htmlString = ''; + // Console.log(htmlString); + const tree = [ + { + tag: 'button', + attrs: { + type: 'submit', + 'data-bem': '{"button":{"checkedView":"extra"}}' + } + } + ]; + + expect(parser(htmlString)).to.eql(tree); + }); + + it.skip('should be parse tag with object in attribute data escape', () => { + const json = JSON.stringify({button: {checkedView: 'extra'}}); + const htmlString = ''; + // Console.log(htmlString); + const tree = [ + { + tag: 'button', + attrs: { + type: 'submit', + 'data-bem': '{"button":{"checkedView":"extra"}}' + } + } + ]; + + expect(parser(htmlString)).to.eql(tree); + }); + + it('should be parse comment in content', () => { + expect(parser('
')).to.eql([{tag: 'div', content: ['']}]); + }); + + it('should be parse doctype', () => { + expect(parser('')).to.eql(['']); + }); + + it('should be parse directive', () => { + const options = { + directives: [ + {name: '?php', start: '<', end: '>'} + ] + }; + + expect(parser('', options)).to.eql(['']); + }); + + it('should be parse regular expression directive', () => { + const options = { + directives: [ + {name: /\?(php|=).*/, start: '<', end: '>'} + ] + }; + + expect(parser('', options)).to.eql(['']); + expect(parser('', options)).to.eql(['']); + }); + + it('should be parse directives and tag', () => { + const options = { + directives: [ + {name: '!doctype', start: '<', end: '>'}, + {name: '?php', start: '<', end: '>'} + ] + }; + + const html = '
{{%njk test %}}'; + const tree = [ + '', + { + content: [''], + tag: 'header' + }, + { + content: ['{{%njk test %}}'], + tag: 'body' + } + ]; + + expect(parser(html, options)).to.eql(tree); + }); + + it('should be parse tag', () => { + expect(parser('')).to.eql([{tag: 'html'}]); + }); + + it('should be parse doctype and tag', () => { + expect(parser('')).to.eql(['', {tag: 'html'}]); + }); + + it('should be parse tag attrs', () => { + expect(parser('
')).to.eql([{ + tag: 'div', attrs: {id: 'id', class: 'class'} + }]); + }); + + it('should be parse text', () => { + expect(parser('Text')).to.eql(['Text']); + }); + + it('should be parse text in content', () => { + expect(parser('
Text
')).to.eql([{tag: 'div', content: ['Text']}]); + }); + + it('should be parse not a single node in tree', () => { + expect(parser('Text1Text2Text3')).to.eql([ + {tag: 'span', content: ['Text1']}, {tag: 'span', content: ['Text2']}, 'Text3' + ]); + }); + + it('should be parse not a single node in parent content', () => { + expect(parser('
Text1Text2Text3
')).to.eql([ + {tag: 'div', content: [{tag: 'span', content: ['Text1']}, {tag: 'span', content: ['Text2']}, 'Text3']} + ]); + }); + + it('should be parse camelCase tag name', () => { + expect(parser('')).to.eql([ + {tag: 'mySuperTag'} + ]); + }); + + it('should be parse simple contents are split with "<" in comment', () => { + const html = ' /* width < 800px */
test
'; + expect(parser(html)).to.eql([ + {tag: 'a', content: [' /* width < 800px */ ', {tag: 'hr'}, ' test']} + ]); + }); + + it('should be parse style contents are split with "<" in comment', () => { + const html = ''; + expect(parser(html)).to.eql([ + {tag: 'style', content: [' /* width < 800px */ @media (max-width: 800px) { /* selectors */} ']} + ]); + }); + + it('should be parse script contents are split with "<" in comment', () => { + const html = ''; + expect(parser(html)).to.eql([ + {tag: 'script', content: [' var str = \'hey