Skip to content

Commit

Permalink
Use official pug-lint to lint pug files (#65)
Browse files Browse the repository at this point in the history
* WIP: Make sure to pass items to puglint

* Group test cases instead of defining just valid/invalid cases

* Make more complicated tests for pug-lint

* Make sure pug-lint works well with indentation

* Improve integration with Attribute Brackets rule

* Make sure we work properly with one-line statements

* Make sure we describe the rule for others

* Make sure we mention pug-lint in main configuration
  • Loading branch information
ezhlobo authored Jun 14, 2019
1 parent 5cd3cdd commit 4d7600e
Show file tree
Hide file tree
Showing 10 changed files with 930 additions and 355 deletions.
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
31 changes: 31 additions & 0 deletions docs/rules/pug-lint.md
Original file line number Diff line number Diff line change
@@ -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()`
```
2 changes: 2 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ 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'),
Expand All @@ -29,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,
Expand Down
118 changes: 118 additions & 0 deletions lib/rules/pug-lint.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
/**
* @fileoverview Inherit pug-lint to validate pug
* @author Eugene Zhlobo
*/

const Linter = require('pug-lint')
const common = require('common-prefix')

const { isReactPugReference, buildLocation, docsUrl } = require('../util/eslint')
const getTemplate = require('../util/getTemplate')

//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------

const buildMessage = actual => (
`Invalid indentation, found "${actual}" spaces`
)

module.exports = {
meta: {
docs: {
description: 'Inherit pug-lint to validate pug (experimental)',
category: 'Stylistic Issues',
recommended: false,
url: docsUrl('pug-lint'),
},
schema: [
{
type: 'object',
},
],
},

create: function (context) {
return {
TaggedTemplateExpression: function (node) {
if (isReactPugReference(node)) {
const template = getTemplate(node)
const lines = template.split('\n')

const linter = new Linter()

linter.configure(context.options[0])

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

const desiredIndent = lines.length > 1
? minimalIndent + 2
: 0

const amountOfUselessSpaces = common(lines.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 = lines
.map(item => item.slice(desiredIndent))
.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
: desiredIndent - 1

let columnStart = error.column + delta
let columnEnd = error.column + delta
let message = error.msg

if (error.msg === 'Invalid indentation') {
columnStart = 0
columnEnd = preparedTemplate.split('\n')[error.line - 1].replace(/^(\s*).*/, '$1').length + desiredIndent
message = buildMessage(columnEnd)
}

context.report({
node,
message,
loc: buildLocation(
[(node.loc.start.line + error.line) - 1, columnStart],
[(node.loc.start.line + error.line) - 1, columnEnd],
),
})
})
}
}

return null
},
}
},
}
19 changes: 19 additions & 0 deletions lib/util/testBuildCases.js
Original file line number Diff line number Diff line change
@@ -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),
}
}
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,9 @@
"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"
},
"devDependencies": {
Expand Down
2 changes: 1 addition & 1 deletion tests/each-rule.js
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
19 changes: 2 additions & 17 deletions tests/lib/rules/eslint.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

const eslint = require('eslint')
const buildError = require('../../../lib/util/testBuildError')
const buildCases = require('../../../lib/util/testBuildCases')

const { RuleTester } = eslint

Expand Down Expand Up @@ -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))
Loading

0 comments on commit 4d7600e

Please sign in to comment.