From 9332ebe44f0a8b5a76a731e1aa6a3979164ef3d3 Mon Sep 17 00:00:00 2001 From: Eugene Zhlobo Date: Sun, 10 Feb 2019 11:38:17 +0300 Subject: [PATCH 1/8] WIP: Make sure to pass items to puglint --- index.js | 1 + lib/rules/pug-lint.js | 69 ++++++++++++++++++++++ package.json | 1 + tests/lib/rules/pug-lint.js | 98 +++++++++++++++++++++++++++++++ yarn.lock | 111 ++++++++++++++++++++++++++++++++++-- 5 files changed, 275 insertions(+), 5 deletions(-) create mode 100644 lib/rules/pug-lint.js create mode 100644 tests/lib/rules/pug-lint.js diff --git a/index.js b/index.js index a9823e3..4af32e9 100644 --- a/index.js +++ b/index.js @@ -12,6 +12,7 @@ const allRules = { quotes: require('./lib/rules/quotes'), 'uses-react': require('./lib/rules/uses-react'), 'uses-vars': require('./lib/rules/uses-vars'), + 'pug-lint': require('./lib/rules/pug-lint'), } /* eslint-enable */ diff --git a/lib/rules/pug-lint.js b/lib/rules/pug-lint.js new file mode 100644 index 0000000..830556f --- /dev/null +++ b/lib/rules/pug-lint.js @@ -0,0 +1,69 @@ +/** + * @fileoverview + * @author Eugene Zhlobo + */ + +const Linter = require('pug-lint') + +const { isReactPugReference, buildLocation, docsUrl } = require('../util/eslint') +const getTemplate = require('../util/getTemplate') + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + docs: { + description: '', + category: '', + recommended: true, + url: docsUrl('pug-lint'), + }, + schema: [ + { + type: 'object', + properties: { + disallowIdAttributeWithStaticValue: { type: 'boolean' }, + }, + }, + ], + }, + + create: function (context) { + return { + TaggedTemplateExpression: function (node) { + if (isReactPugReference(node)) { + const template = getTemplate(node) + + const linter = new Linter() + + linter.configure(context.options[0]) + + const result = linter.checkString(template, 'testfile') + + if (result.length) { + result.forEach((error) => { + const delta = error.line === 1 + // When template starts plus backtick + ? node.quasi.quasis[0].loc.start.column + 1 + : -1 + + const columnStart = error.column + delta + const columnEnd = error.column + delta + + context.report({ + node, + loc: buildLocation( + [(node.loc.start.line + error.line) - 1, columnStart], + [(node.loc.start.line + error.line) - 1, columnEnd], + ), + message: error.msg, + }) + }) + } + } + }, + } + }, +} diff --git a/package.json b/package.json index 78ea8d4..a818f13 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,7 @@ "@babel/parser": "^7.0.0-rc.2", "@babel/traverse": "^7.0.0-rc.2", "pug-lexer": "^4.0.0", + "pug-lint": "^2.5.0", "pug-uses-variables": "^2.0.2" }, "devDependencies": { diff --git a/tests/lib/rules/pug-lint.js b/tests/lib/rules/pug-lint.js new file mode 100644 index 0000000..eee6cd1 --- /dev/null +++ b/tests/lib/rules/pug-lint.js @@ -0,0 +1,98 @@ +/** + * @fileoverview Tests for pug-lint + * @author Eugene Zhlobo + */ + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const eslint = require('eslint') +const buildError = require('../../../lib/util/testBuildError') + +const { RuleTester } = eslint + +const rule = require('../../../lib/rules/pug-lint') + +const parserOptions = { + ecmaVersion: 8, + sourceType: 'module', +} + +//------------------------------------------------------------------------------ +// Tests +//------------------------------------------------------------------------------ + +const ruleTester = new RuleTester({ parserOptions }) + +ruleTester.run('rule "pug-lint"', rule, { + valid: [ + { + code: ` + pug\`p(id="hello")\`; + `, + }, + { + options: [{ + disallowIdAttributeWithStaticValue: true, + }], + code: ` + pug\`p#hello\`; + `, + }, + ], + invalid: [ + { + options: [{ + disallowIdAttributeWithStaticValue: true, + }], + code: ` + pug\` + p(id="hello") + \`; + `, + errors: [ + buildError([3, 13], [3, 13], 'Static attribute "id" must be written as ID literal'), + ], + }, + { + options: [{ + disallowIdAttributeWithStaticValue: true, + }], + code: ` + pug\` + p(id="hello") + p(id="hello") + \`; + `, + errors: [ + buildError([3, 13], [3, 13], 'Static attribute "id" must be written as ID literal'), + buildError([4, 13], [4, 13], 'Static attribute "id" must be written as ID literal'), + ], + }, + { + options: [{ + disallowIdAttributeWithStaticValue: true, + }], + code: ` + pug\`p(id="hello")\`; + `, + errors: [ + buildError([2, 16], [2, 16], 'Static attribute "id" must be written as ID literal'), + ], + }, + { + options: [{ + disallowIdAttributeWithStaticValue: true, + }], + code: ` + pug\`p(id="hello")\`; + pug\`p(id="hello")\`; + `, + errors: [ + buildError([2, 16], [2, 16], 'Static attribute "id" must be written as ID literal'), + buildError([3, 16], [3, 16], 'Static attribute "id" must be written as ID literal'), + ], + }, + ], +}) diff --git a/yarn.lock b/yarn.lock index 814e3da..4a058f9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -80,20 +80,32 @@ lodash "^4.17.10" to-fast-properties "^2.0.0" +"@types/babel-types@*", "@types/babel-types@^7.0.0": + version "7.0.4" + resolved "https://registry.yarnpkg.com/@types/babel-types/-/babel-types-7.0.4.tgz#bfd5b0d0d1ba13e351dff65b6e52783b816826c8" + integrity sha512-WiZhq3SVJHFRgRYLXvpf65XnV6ipVHhnNaNvE8yCimejrGglkg38kEj0JcizqwSHxmPSjcTlig/6JouxLGEhGw== + +"@types/babylon@^6.16.2": + version "6.16.4" + resolved "https://registry.yarnpkg.com/@types/babylon/-/babylon-6.16.4.tgz#d3df72518b34a6a015d0dc58745cd238b5bb8ad2" + integrity sha512-8dZMcGPno3g7pJ/d0AyJERo+lXh9i1JhDuCUs+4lNIN9eUe5Yh6UCLrpgSEi05Ve2JMLauL2aozdvKwNL0px1Q== + dependencies: + "@types/babel-types" "*" + acorn-jsx@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-4.1.1.tgz#e8e41e48ea2fe0c896740610ab6a4ffd8add225e" dependencies: acorn "^5.0.3" +acorn@^4.0.1, acorn@~4.0.2: + version "4.0.13" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-4.0.13.tgz#105495ae5361d697bd195c825192e1ad7f253787" + acorn@^5.0.3, acorn@^5.6.0: version "5.7.1" resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.7.1.tgz#f095829297706a7c9776958c0afc8930a9b9d9d8" -acorn@~4.0.2: - version "4.0.13" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-4.0.13.tgz#105495ae5361d697bd195c825192e1ad7f253787" - ajv-keywords@^3.0.0: version "3.2.0" resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.2.0.tgz#e86b819c602cf8821ad637413698f1dec021847a" @@ -282,10 +294,25 @@ commander@2.15.1: version "2.15.1" resolved "https://registry.yarnpkg.com/commander/-/commander-2.15.1.tgz#df46e867d0fc2aec66a34662b406a9ccafff5b0f" +commander@^2.9.0: + version "2.19.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-2.19.0.tgz#f6198aa84e5b83c46054b94ddedbfed5ee9ff12a" + integrity sha512-6tvAOO+D6OENvRAh524Dh9jcfKTYDQAqvqezbCW82xj5X0pSrcpxtvRKHLG0yBY6SD7PSDrJaj+0AiOcKVd1Xg== + concat-map@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" +constantinople@^3.0.1: + version "3.1.2" + resolved "https://registry.yarnpkg.com/constantinople/-/constantinople-3.1.2.tgz#d45ed724f57d3d10500017a7d3a889c1381ae647" + integrity sha512-yePcBqEFhLOqSBtwYOGGS1exHo/s1xjekXiinh4itpNQGCu4KA1euPh1fg07N2wMITZXQkBz75Ntdt1ctGZouw== + dependencies: + "@types/babel-types" "^7.0.0" + "@types/babylon" "^6.16.2" + babel-types "^6.26.0" + babylon "^6.18.0" + contains-path@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/contains-path/-/contains-path-0.1.0.tgz#fe8cf184ff6670b6baef01a9d4861a5cbec4120a" @@ -304,6 +331,11 @@ cross-spawn@^6.0.5: shebang-command "^1.2.0" which "^1.2.9" +css-selector-parser@^1.1.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/css-selector-parser/-/css-selector-parser-1.3.0.tgz#5f1ad43e2d8eefbfdc304fcd39a521664943e3eb" + integrity sha1-XxrUPi2O77/cME/NOaUhZklD4+s= + debug@3.1.0, debug@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261" @@ -547,6 +579,11 @@ file-entry-cache@^2.0.0: flat-cache "^1.2.1" object-assign "^4.0.1" +find-line-column@^0.5.2: + version "0.5.2" + resolved "https://registry.yarnpkg.com/find-line-column/-/find-line-column-0.5.2.tgz#db00238ff868551a182e74a103416d295a98c8ca" + integrity sha1-2wAjj/hoVRoYLnShA0FtKVqYyMo= + find-up@^1.0.0: version "1.1.2" resolved "https://registry.yarnpkg.com/find-up/-/find-up-1.1.2.tgz#6b2e9822b1a2ce0a60ab64d610eccad53cb24d0f" @@ -769,6 +806,11 @@ isexe@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" +js-stringify@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/js-stringify/-/js-stringify-1.0.2.tgz#1736fddfd9724f28a3682adc6230ae7e4e9679db" + integrity sha1-Fzb939lyTyijaCrcYjCufk6Weds= + js-tokens@^3.0.0, js-tokens@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b" @@ -989,6 +1031,11 @@ path-parse@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.5.tgz#3c1adf871ea9cd6c9431b6ea2bd74a0ff055c4c1" +path-parse@^1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c" + integrity sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw== + path-type@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/path-type/-/path-type-2.0.0.tgz#f012ccb8415b7096fc2daa1054c3d72389594c73" @@ -1027,10 +1074,28 @@ progress@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.0.tgz#8a1be366bf8fc23db2bd23f10c6fe920b4389d1f" -pug-error@^1.3.2: +pug-attrs@^2.0.1: + version "2.0.3" + resolved "https://registry.yarnpkg.com/pug-attrs/-/pug-attrs-2.0.3.tgz#a3095f970e64151f7bdad957eef55fb5d7905d15" + integrity sha1-owlflw5kFR972tlX7vVftdeQXRU= + dependencies: + constantinople "^3.0.1" + js-stringify "^1.0.1" + pug-runtime "^2.0.4" + +pug-error@^1.3.0, pug-error@^1.3.2: version "1.3.2" resolved "https://registry.yarnpkg.com/pug-error/-/pug-error-1.3.2.tgz#53ae7d9d29bb03cf564493a026109f54c47f5f26" +pug-lexer@^2.0.1: + version "2.3.2" + resolved "https://registry.yarnpkg.com/pug-lexer/-/pug-lexer-2.3.2.tgz#68b19d96ea5dc0e4a86148b01cb966c17815a614" + integrity sha1-aLGdlupdwOSoYUiwHLlmwXgVphQ= + dependencies: + character-parser "^2.1.1" + is-expression "^3.0.0" + pug-error "^1.3.2" + pug-lexer@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/pug-lexer/-/pug-lexer-4.0.0.tgz#210c18457ef2e1760242740c5e647bd794cec278" @@ -1039,6 +1104,30 @@ pug-lexer@^4.0.0: is-expression "^3.0.0" pug-error "^1.3.2" +pug-lint@^2.5.0: + version "2.5.0" + resolved "https://registry.yarnpkg.com/pug-lint/-/pug-lint-2.5.0.tgz#4419ee301aeca45f54061b0eca4a9a471f3aa9a9" + integrity sha1-RBnuMBrspF9UBhsOykqaRx86qak= + dependencies: + acorn "^4.0.1" + commander "^2.9.0" + css-selector-parser "^1.1.0" + find-line-column "^0.5.2" + glob "^7.0.3" + minimatch "^3.0.3" + path-is-absolute "^1.0.0" + pug-attrs "^2.0.1" + pug-error "^1.3.0" + pug-lexer "^2.0.1" + resolve "^1.1.7" + strip-json-comments "^2.0.1" + void-elements "^2.0.1" + +pug-runtime@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/pug-runtime/-/pug-runtime-2.0.4.tgz#e178e1bda68ab2e8c0acfc9bced2c54fd88ceb58" + integrity sha1-4XjhvaaKsujArPybztLFT9iM61g= + pug-uses-variables@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/pug-uses-variables/-/pug-uses-variables-2.0.2.tgz#f92b6f8a229c4f16b894f03966f547ced6c066fc" @@ -1085,6 +1174,13 @@ resolve-from@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-1.0.1.tgz#26cbfe935d1aeeeabb29bc3fe5aeb01e93d44226" +resolve@^1.1.7: + version "1.10.0" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.10.0.tgz#3bdaaeaf45cc07f375656dfd2e54ed0810b101ba" + integrity sha512-3sUr9aq5OfSg2S9pNtPA9hL1FVEAjvfOC4leW0SNf/mpnaakz2a9femSd6LqAww2RaFctwyf1lCqnTHuF1rxDg== + dependencies: + path-parse "^1.0.6" + resolve@^1.2.0: version "1.4.0" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.4.0.tgz#a75be01c53da25d934a98ebd0e4c4a7312f92a86" @@ -1275,6 +1371,11 @@ validate-npm-package-license@^3.0.1: spdx-correct "~1.0.0" spdx-expression-parse "~1.0.0" +void-elements@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/void-elements/-/void-elements-2.0.1.tgz#c066afb582bb1cb4128d60ea92392e94d5e9dbec" + integrity sha1-wGavtYK7HLQSjWDqkjkulNXp2+w= + which@^1.2.9: version "1.3.0" resolved "https://registry.yarnpkg.com/which/-/which-1.3.0.tgz#ff04bdfc010ee547d780bec38e1ac1c2777d253a" From 11c34b70ec29c6b936174a628e782fd2fb7cf212 Mon Sep 17 00:00:00 2001 From: Eugene Zhlobo Date: Thu, 30 May 2019 10:33:49 +0300 Subject: [PATCH 2/8] Group test cases instead of defining just valid/invalid cases --- lib/util/testBuildCases.js | 19 +++++++++++++++++++ tests/lib/rules/eslint.js | 19 ++----------------- 2 files changed, 21 insertions(+), 17 deletions(-) create mode 100644 lib/util/testBuildCases.js diff --git a/lib/util/testBuildCases.js b/lib/util/testBuildCases.js new file mode 100644 index 0000000..bbecb1f --- /dev/null +++ b/lib/util/testBuildCases.js @@ -0,0 +1,19 @@ +const extractCase = type => item => ({ + options: item.options || [], + ...item[type], +}) + +const extractCases = type => (items) => { + if (items.some(item => item.only)) { + return items.filter(item => item.only).map(extractCase(type)) + } + + return items.map(extractCase(type)) +} + +module.exports = function (cases) { + return { + valid: extractCases('valid')(cases), + invalid: extractCases('invalid')(cases), + } +} diff --git a/tests/lib/rules/eslint.js b/tests/lib/rules/eslint.js index dfbfa89..fbaa61e 100644 --- a/tests/lib/rules/eslint.js +++ b/tests/lib/rules/eslint.js @@ -9,6 +9,7 @@ const eslint = require('eslint') const buildError = require('../../../lib/util/testBuildError') +const buildCases = require('../../../lib/util/testBuildCases') const { RuleTester } = eslint @@ -216,20 +217,4 @@ const cases = [ }, ] -const extractCase = type => item => ({ - options: item.options || [], - ...item[type], -}) - -const extractCases = type => (items) => { - if (items.some(item => item.only)) { - return items.filter(item => item.only).map(extractCase(type)) - } - - return items.map(extractCase(type)) -} - -ruleTester.run('rule "eslint"', rule, { - valid: extractCases('valid')(cases), - invalid: extractCases('invalid')(cases), -}) +ruleTester.run('rule "eslint"', rule, buildCases(cases)) From 38a1031c00f56b210fe2b10a134d97241263f508 Mon Sep 17 00:00:00 2001 From: Eugene Zhlobo Date: Thu, 30 May 2019 10:34:09 +0300 Subject: [PATCH 3/8] Make more complicated tests for pug-lint --- lib/rules/pug-lint.js | 3 - tests/lib/rules/pug-lint.js | 130 +++++++++++++++++++++--------------- 2 files changed, 77 insertions(+), 56 deletions(-) diff --git a/lib/rules/pug-lint.js b/lib/rules/pug-lint.js index 830556f..1680f6b 100644 --- a/lib/rules/pug-lint.js +++ b/lib/rules/pug-lint.js @@ -23,9 +23,6 @@ module.exports = { schema: [ { type: 'object', - properties: { - disallowIdAttributeWithStaticValue: { type: 'boolean' }, - }, }, ], }, diff --git a/tests/lib/rules/pug-lint.js b/tests/lib/rules/pug-lint.js index eee6cd1..c45ff01 100644 --- a/tests/lib/rules/pug-lint.js +++ b/tests/lib/rules/pug-lint.js @@ -9,6 +9,7 @@ const eslint = require('eslint') const buildError = require('../../../lib/util/testBuildError') +const buildCases = require('../../../lib/util/testBuildCases') const { RuleTester } = eslint @@ -25,74 +26,97 @@ const parserOptions = { const ruleTester = new RuleTester({ parserOptions }) -ruleTester.run('rule "pug-lint"', rule, { - valid: [ - { - code: ` - pug\`p(id="hello")\`; - `, - }, - { - options: [{ - disallowIdAttributeWithStaticValue: true, - }], - code: ` - pug\`p#hello\`; - `, - }, - ], - invalid: [ - { - options: [{ - disallowIdAttributeWithStaticValue: true, - }], +const cases = [ + { + name: 'Attribute separator, single line', + options: [{ + validateAttributeSeparator: { + separator: ' ', + multiLineSeparator: '\n ', + }, + }], + valid: { code: ` + const ref = 'hello' pug\` - p(id="hello") - \`; + div(...spread bool variable=ref number=0 string="string" object={a:true} array=[1, 'second']) + div( + ...spread bool variable=ref number=0 string="string" object={a:true} array=[1, 'second'] + ) + \` `, - errors: [ - buildError([3, 13], [3, 13], 'Static attribute "id" must be written as ID literal'), - ], }, - { - options: [{ - disallowIdAttributeWithStaticValue: true, - }], + invalid: { code: ` + const ref = 'hello' pug\` - p(id="hello") - p(id="hello") - \`; + div(...spread bool variable=ref number=0 string="string" object={a:true} array=[1, 'second']) + div( + ...spread bool variable=ref number=0 string="string" object={a:true} array=[1, 'second'] + ) + \` `, errors: [ - buildError([3, 13], [3, 13], 'Static attribute "id" must be written as ID literal'), - buildError([4, 13], [4, 13], 'Static attribute "id" must be written as ID literal'), + buildError([4, 24], [4, 24], 'Invalid attribute separator found'), + buildError([4, 30], [4, 30], 'Invalid attribute separator found'), + buildError([4, 44], [4, 44], 'Invalid attribute separator found'), + buildError([4, 54], [4, 54], 'Invalid attribute separator found'), + buildError([4, 71], [4, 71], 'Invalid attribute separator found'), + buildError([4, 88], [4, 88], 'Invalid attribute separator found'), + buildError([6, 22], [6, 22], 'Invalid attribute separator found'), + buildError([6, 28], [6, 28], 'Invalid attribute separator found'), + buildError([6, 42], [6, 42], 'Invalid attribute separator found'), + buildError([6, 52], [6, 52], 'Invalid attribute separator found'), + buildError([6, 69], [6, 69], 'Invalid attribute separator found'), + buildError([6, 86], [6, 86], 'Invalid attribute separator found'), ], }, - { - options: [{ - disallowIdAttributeWithStaticValue: true, - }], + }, + + { + only: true, + name: 'Attribute separator, multiline', + options: [{ + validateAttributeSeparator: { + separator: ' ', + multiLineSeparator: '\n ', + }, + }], + valid: { code: ` - pug\`p(id="hello")\`; + pug\` + div( + bool + variable=ref + number=0 + string="string" + object={a:true} + array=[1, 'second'] + ) + \` `, - errors: [ - buildError([2, 16], [2, 16], 'Static attribute "id" must be written as ID literal'), - ], }, - { - options: [{ - disallowIdAttributeWithStaticValue: true, - }], + invalid: { code: ` - pug\`p(id="hello")\`; - pug\`p(id="hello")\`; + pug\` + div( + ...props, + bool + variable=ref + , number=0 + string="string" + object={a:true} + array=[1, 'second'] + ) + \` `, errors: [ - buildError([2, 16], [2, 16], 'Static attribute "id" must be written as ID literal'), - buildError([3, 16], [3, 16], 'Static attribute "id" must be written as ID literal'), + buildError([5, 9], [5, 9], 'Invalid attribute separator found'), + buildError([6, 10], [6, 10], 'Invalid attribute separator found'), + buildError([7, 10], [7, 10], 'Invalid attribute separator found'), ], }, - ], -}) + }, +] + +ruleTester.run('rule "pug-lint"', rule, buildCases(cases)) From 05a509269fc3bd12d1bc22786302ea523c3d7fad Mon Sep 17 00:00:00 2001 From: Eugene Zhlobo Date: Fri, 14 Jun 2019 16:27:51 +0300 Subject: [PATCH 4/8] Make sure pug-lint works well with indentation --- lib/rules/pug-lint.js | 34 +++++++++++++++++++++---- tests/lib/rules/pug-lint.js | 49 ++++++++++++++++++++++++++++++++++++- 2 files changed, 77 insertions(+), 6 deletions(-) diff --git a/lib/rules/pug-lint.js b/lib/rules/pug-lint.js index 1680f6b..2fb4a02 100644 --- a/lib/rules/pug-lint.js +++ b/lib/rules/pug-lint.js @@ -37,25 +37,49 @@ module.exports = { linter.configure(context.options[0]) - const result = linter.checkString(template, 'testfile') + const firstTokenInLine = context + .getSourceCode() + .getTokensBefore(node, { + filter: token => token.loc.end.line === node.loc.start.line, + })[0] + + const minimalIndent = firstTokenInLine + ? firstTokenInLine.loc.start.column + : node.loc.start.column + + // We need to pass the template without not valuable spaces in the + // beginning of each line + const preparedTemplate = template + .split('\n') + .map(item => item.slice(minimalIndent)) + .join('\n') + + const result = linter.checkString(preparedTemplate, 'testfile') if (result.length) { result.forEach((error) => { const delta = error.line === 1 // When template starts plus backtick ? node.quasi.quasis[0].loc.start.column + 1 - : -1 + : minimalIndent - 1 + + let columnStart = error.column + delta + let columnEnd = error.column + delta + let message = error.msg - const columnStart = error.column + delta - const columnEnd = error.column + delta + if (error.msg === 'Invalid indentation') { + columnStart = 0 + columnEnd = preparedTemplate.split('\n')[error.line - 1].replace(/^(\s*).*/, '$1').length + minimalIndent + message = `Invalid indentation, found "${columnEnd}" spaces` + } context.report({ node, + message, loc: buildLocation( [(node.loc.start.line + error.line) - 1, columnStart], [(node.loc.start.line + error.line) - 1, columnEnd], ), - message: error.msg, }) }) } diff --git a/tests/lib/rules/pug-lint.js b/tests/lib/rules/pug-lint.js index c45ff01..939f4a3 100644 --- a/tests/lib/rules/pug-lint.js +++ b/tests/lib/rules/pug-lint.js @@ -26,6 +26,10 @@ const parserOptions = { const ruleTester = new RuleTester({ parserOptions }) +const buildIndentationMessage = actual => ( + `Invalid indentation, found "${actual}" spaces` +) + const cases = [ { name: 'Attribute separator, single line', @@ -74,7 +78,6 @@ const cases = [ }, { - only: true, name: 'Attribute separator, multiline', options: [{ validateAttributeSeparator: { @@ -109,6 +112,8 @@ const cases = [ array=[1, 'second'] ) \` + + pug\`div Hello\` `, errors: [ buildError([5, 9], [5, 9], 'Invalid attribute separator found'), @@ -117,6 +122,48 @@ const cases = [ ], }, }, + + { + name: 'Indentation', + options: [{ + validateIndentation: 2, + }], + valid: { + code: ` + export default () => pug\` + div + div: div + \` + `, + }, + invalid: { + code: ` + export default () => pug\` + div + div + \` + + const test = pug\` + div + \` + + pug\` + div + p + \` + + pug\` + div + \` + `, + errors: [ + buildError([4, 1], [4, 12], buildIndentationMessage(11)), + buildError([8, 1], [8, 10], buildIndentationMessage(9)), + buildError([12, 1], [12, 12], buildIndentationMessage(11)), + buildError([17, 1], [17, 14], buildIndentationMessage(13)), + ], + }, + }, ] ruleTester.run('rule "pug-lint"', rule, buildCases(cases)) From 56d6714b89081628be797cf1053e48c6d3b57aea Mon Sep 17 00:00:00 2001 From: Eugene Zhlobo Date: Fri, 14 Jun 2019 18:01:17 +0300 Subject: [PATCH 5/8] Improve integration with Attribute Brackets rule --- lib/rules/pug-lint.js | 34 ++++++++++++++++++++++++++---- package.json | 1 + tests/lib/rules/pug-lint.js | 42 ++++++++++++++++++++++++++++++++++--- yarn.lock | 5 +++++ 4 files changed, 75 insertions(+), 7 deletions(-) diff --git a/lib/rules/pug-lint.js b/lib/rules/pug-lint.js index 2fb4a02..fea09e9 100644 --- a/lib/rules/pug-lint.js +++ b/lib/rules/pug-lint.js @@ -4,6 +4,7 @@ */ const Linter = require('pug-lint') +const common = require('common-prefix') const { isReactPugReference, buildLocation, docsUrl } = require('../util/eslint') const getTemplate = require('../util/getTemplate') @@ -12,6 +13,10 @@ const getTemplate = require('../util/getTemplate') // Rule Definition //------------------------------------------------------------------------------ +const buildMessage = actual => ( + `Invalid indentation, found "${actual}" spaces` +) + module.exports = { meta: { docs: { @@ -47,11 +52,30 @@ module.exports = { ? firstTokenInLine.loc.start.column : node.loc.start.column + const desiredIndent = minimalIndent + 2 + + const amountOfUselessSpaces = common( + template.split('\n').filter(item => item.trim() !== ''), + ).replace(/^(\s*).*/, '$1').length + + if (amountOfUselessSpaces > 0 && amountOfUselessSpaces < desiredIndent) { + context.report({ + node, + message: buildMessage(amountOfUselessSpaces), + loc: buildLocation( + [(node.loc.start.line + 1), 0], + [(node.loc.start.line + 1), amountOfUselessSpaces], + ), + }) + + return null + } + // We need to pass the template without not valuable spaces in the // beginning of each line const preparedTemplate = template .split('\n') - .map(item => item.slice(minimalIndent)) + .map(item => item.slice(desiredIndent)) .join('\n') const result = linter.checkString(preparedTemplate, 'testfile') @@ -61,7 +85,7 @@ module.exports = { const delta = error.line === 1 // When template starts plus backtick ? node.quasi.quasis[0].loc.start.column + 1 - : minimalIndent - 1 + : desiredIndent - 1 let columnStart = error.column + delta let columnEnd = error.column + delta @@ -69,8 +93,8 @@ module.exports = { if (error.msg === 'Invalid indentation') { columnStart = 0 - columnEnd = preparedTemplate.split('\n')[error.line - 1].replace(/^(\s*).*/, '$1').length + minimalIndent - message = `Invalid indentation, found "${columnEnd}" spaces` + columnEnd = preparedTemplate.split('\n')[error.line - 1].replace(/^(\s*).*/, '$1').length + desiredIndent + message = buildMessage(columnEnd) } context.report({ @@ -84,6 +108,8 @@ module.exports = { }) } } + + return null }, } }, diff --git a/package.json b/package.json index 628daea..ab96e3e 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,7 @@ "dependencies": { "@babel/parser": "^7.3.2", "@babel/traverse": "^7.2.3", + "common-prefix": "^1.1.0", "pug-lexer": "^4.0.0", "pug-lint": "^2.5.0", "pug-uses-variables": "^3.0.0" diff --git a/tests/lib/rules/pug-lint.js b/tests/lib/rules/pug-lint.js index 939f4a3..cbaafd8 100644 --- a/tests/lib/rules/pug-lint.js +++ b/tests/lib/rules/pug-lint.js @@ -116,9 +116,9 @@ const cases = [ pug\`div Hello\` `, errors: [ - buildError([5, 9], [5, 9], 'Invalid attribute separator found'), - buildError([6, 10], [6, 10], 'Invalid attribute separator found'), - buildError([7, 10], [7, 10], 'Invalid attribute separator found'), + buildError([5, 11], [5, 11], 'Invalid attribute separator found'), + buildError([6, 11], [6, 11], 'Invalid attribute separator found'), + buildError([7, 11], [7, 11], 'Invalid attribute separator found'), ], }, }, @@ -134,6 +134,8 @@ const cases = [ div div: div \` + + pug\`p Hello\` `, }, invalid: { @@ -160,10 +162,44 @@ const cases = [ buildError([4, 1], [4, 12], buildIndentationMessage(11)), buildError([8, 1], [8, 10], buildIndentationMessage(9)), buildError([12, 1], [12, 12], buildIndentationMessage(11)), + buildError([13, 1], [13, 13], buildIndentationMessage(12)), buildError([17, 1], [17, 14], buildIndentationMessage(13)), ], }, }, + + { + name: 'Spaces inside attribute brackets', + options: [{ + disallowSpacesInsideAttributeBrackets: true, + }], + valid: { + code: ` + export default () => pug\` + div(test) + div( + test + ) + \` + `, + }, + invalid: { + code: ` + export default () => pug\` + div( test) + div(test ) + div( + test + ) + \` + `, + errors: [ + buildError([3, 15], [3, 15], 'Illegal space after opening bracket'), + buildError([4, 20], [4, 20], 'Illegal space before closing bracket'), + buildError([7, 12], [7, 12], 'Illegal space before closing bracket'), + ], + }, + }, ] ruleTester.run('rule "pug-lint"', rule, buildCases(cases)) diff --git a/yarn.lock b/yarn.lock index 2c0181f..dcd5d51 100644 --- a/yarn.lock +++ b/yarn.lock @@ -270,6 +270,11 @@ commander@^2.9.0: resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.0.tgz#d58bb2b5c1ee8f87b0d340027e9e94e222c5a422" integrity sha512-7j2y+40w61zy6YC2iRNpUe/NwhNyoXrYpHMrSunaMG64nRnaf96zO/KMQR4OyN/UnE5KLyEBnKHd4aG3rskjpQ== +common-prefix@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/common-prefix/-/common-prefix-1.1.0.tgz#e3a5ea7fafaefc7eb84e760523e1afb985f90f00" + integrity sha1-46Xqf6+u/H64TnYFI+GvuYX5DwA= + concat-map@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" From 38a1bdd6d0535e8abfa30a18b59f0d29e8f4ba85 Mon Sep 17 00:00:00 2001 From: Eugene Zhlobo Date: Fri, 14 Jun 2019 18:42:05 +0300 Subject: [PATCH 6/8] Make sure we work properly with one-line statements --- lib/rules/pug-lint.js | 14 ++++++++------ tests/lib/rules/pug-lint.js | 14 ++++++++++++++ 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/lib/rules/pug-lint.js b/lib/rules/pug-lint.js index fea09e9..5e4616a 100644 --- a/lib/rules/pug-lint.js +++ b/lib/rules/pug-lint.js @@ -37,6 +37,7 @@ module.exports = { TaggedTemplateExpression: function (node) { if (isReactPugReference(node)) { const template = getTemplate(node) + const lines = template.split('\n') const linter = new Linter() @@ -52,11 +53,13 @@ module.exports = { ? firstTokenInLine.loc.start.column : node.loc.start.column - const desiredIndent = minimalIndent + 2 + const desiredIndent = lines.length > 1 + ? minimalIndent + 2 + : 0 - const amountOfUselessSpaces = common( - template.split('\n').filter(item => item.trim() !== ''), - ).replace(/^(\s*).*/, '$1').length + const amountOfUselessSpaces = common(lines.filter(item => item.trim() !== '')) + .replace(/^(\s*).*/, '$1') + .length if (amountOfUselessSpaces > 0 && amountOfUselessSpaces < desiredIndent) { context.report({ @@ -73,8 +76,7 @@ module.exports = { // We need to pass the template without not valuable spaces in the // beginning of each line - const preparedTemplate = template - .split('\n') + const preparedTemplate = lines .map(item => item.slice(desiredIndent)) .join('\n') diff --git a/tests/lib/rules/pug-lint.js b/tests/lib/rules/pug-lint.js index cbaafd8..dd98394 100644 --- a/tests/lib/rules/pug-lint.js +++ b/tests/lib/rules/pug-lint.js @@ -200,6 +200,20 @@ const cases = [ ], }, }, + + { + name: 'Pug-Lint can analyse invalid syntax', + options: [], + valid: { + code: ` + const a = pug\`div(...props)\` + `, + }, + invalid: { + code: '', + errors: [], + }, + }, ] ruleTester.run('rule "pug-lint"', rule, buildCases(cases)) From 5fe173dadc598d632241c16c85b5652ef690339e Mon Sep 17 00:00:00 2001 From: Eugene Zhlobo Date: Fri, 14 Jun 2019 19:01:00 +0300 Subject: [PATCH 7/8] Make sure we describe the rule for others --- README.md | 4 ++++ docs/rules/pug-lint.md | 31 +++++++++++++++++++++++++++++++ lib/rules/pug-lint.js | 8 ++++---- tests/each-rule.js | 2 +- 4 files changed, 40 insertions(+), 5 deletions(-) create mode 100644 docs/rules/pug-lint.md diff --git a/README.md b/README.md index a493d11..7007d1e 100644 --- a/README.md +++ b/README.md @@ -61,3 +61,7 @@ Then configure the rules you want to use under the rules section. * [`react-pug/quotes`](./docs/rules/quotes.md): Manage quotes in Pug * [`react-pug/uses-react`](./docs/rules/uses-react.md): Prevent React to be marked as unused * [`react-pug/uses-vars`](./docs/rules/uses-vars.md): Prevent variables used in Pug to be marked as unused + +Experimental: + +* [`react-pug/pug-lint`](./docs/rules/pug-lint.md): Inherit pug-lint to validate pug diff --git a/docs/rules/pug-lint.md b/docs/rules/pug-lint.md new file mode 100644 index 0000000..dc02d3f --- /dev/null +++ b/docs/rules/pug-lint.md @@ -0,0 +1,31 @@ +# Inherit pug-lint to validate pug (experimental) + +This rule applies pug-lint to pug templates in js. + +[See pug-lint project](https://github.com/pugjs/pug-lint) + +## Rule Details + +The following patterns are considered warnings: + +```jsx +/*eslint react-pug/pug-lint: ["error", { "requireSpaceAfterCodeOperator": true }]*/ +pug`div=greeting` +``` + +```jsx +/*eslint react-pug/pug-lint: ["error", { "disallowTrailingSpaces": true }]*/ +pug`div: img() ` +``` + +The following patterns are **not** considered warnings: + +```jsx +/*eslint react-pug/pug-lint: ["error", { "requireSpaceAfterCodeOperator": true }]*/ +pug`div= greeting` +``` + +```jsx +/*eslint react-pug/pug-lint: ["error", { "disallowTrailingSpaces": true }]*/ +pug`div: img()` +``` diff --git a/lib/rules/pug-lint.js b/lib/rules/pug-lint.js index 5e4616a..a4f11a1 100644 --- a/lib/rules/pug-lint.js +++ b/lib/rules/pug-lint.js @@ -1,5 +1,5 @@ /** - * @fileoverview + * @fileoverview Inherit pug-lint to validate pug * @author Eugene Zhlobo */ @@ -20,9 +20,9 @@ const buildMessage = actual => ( module.exports = { meta: { docs: { - description: '', - category: '', - recommended: true, + description: 'Inherit pug-lint to validate pug (experimental)', + category: 'Stylistic Issues', + recommended: false, url: docsUrl('pug-lint'), }, schema: [ diff --git a/tests/each-rule.js b/tests/each-rule.js index dc4f51a..4004233 100644 --- a/tests/each-rule.js +++ b/tests/each-rule.js @@ -34,7 +34,7 @@ describe('each rule', () => { describe('mentioned in README', () => { const listOfRulesInReadme = fs .readFileSync(path.resolve(__dirname, '..', 'README.md'), 'utf-8') - .match(/## List of supported rules\n+((\*.+\n+)+)/m)[1] + .match(/## List of supported rules\n+((\*.+\n+|^[^#]+)+)/m)[1] .split('\n') .filter(Boolean) From ae21946160266871d8b8be4359c97851fe48125f Mon Sep 17 00:00:00 2001 From: Eugene Zhlobo Date: Fri, 14 Jun 2019 19:03:21 +0300 Subject: [PATCH 8/8] Make sure we mention pug-lint in main configuration --- index.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/index.js b/index.js index bc8deb0..2cb0b95 100644 --- a/index.js +++ b/index.js @@ -10,10 +10,10 @@ const allRules = { 'no-broken-template': require('./lib/rules/no-broken-template'), 'no-interpolation': require('./lib/rules/no-interpolation'), 'no-undef': require('./lib/rules/no-undef'), + 'pug-lint': require('./lib/rules/pug-lint'), quotes: require('./lib/rules/quotes'), 'uses-react': require('./lib/rules/uses-react'), 'uses-vars': require('./lib/rules/uses-vars'), - 'pug-lint': require('./lib/rules/pug-lint'), } /* eslint-enable */ @@ -30,6 +30,7 @@ module.exports = { 'react-pug/indent': 2, 'react-pug/no-broken-template': 2, 'react-pug/no-undef': 2, + 'react-pug/pug-lint': 2, 'react-pug/no-interpolation': 2, 'react-pug/quotes': 2, 'react-pug/uses-react': 2,