diff --git a/docs/rules/mustache-interpolation-spacing.md b/docs/rules/mustache-interpolation-spacing.md new file mode 100644 index 000000000..4844d15ab --- /dev/null +++ b/docs/rules/mustache-interpolation-spacing.md @@ -0,0 +1,67 @@ +# enforce unified spacing in mustache interpolations. (mustache-interpolation-spacing) + +- :wrench: The `--fix` option on the [command line](http://eslint.org/docs/user-guide/command-line-interface#fix) can automatically fix some of the problems reported by this rule. + +## :book: Rule Details + +This rule aims to enforce unified spacing in mustache interpolations. + +:-1: Examples of **incorrect** code for this rule: + +```html + +``` + +:+1: Examples of **correct** code for this rule: + +```html + +``` + +## :wrench: Options + +Default spacing is set to `always` + +``` +'vue/mustache-interpolation-spacing': [2, 'always'|'never'] +``` + +### `"always"` - Expect one space between expression and curly brackets. + +:+1: Examples of **correct** code`: + +```html + +``` + +:-1: Examples of **incorrect** code`: + +```html + +``` + +### `"never"` - Expect no spaces between expression and curly brackets. + +:+1: Examples of **correct** code`: + +```html + +``` + +:-1: Examples of **incorrect** code`: + +```html + +``` diff --git a/lib/rules/mustache-interpolation-spacing.js b/lib/rules/mustache-interpolation-spacing.js new file mode 100644 index 000000000..4ea93e96a --- /dev/null +++ b/lib/rules/mustache-interpolation-spacing.js @@ -0,0 +1,91 @@ +/** + * @fileoverview enforce unified spacing in mustache interpolations. + * @author Armano + */ +'use strict' + +// ------------------------------------------------------------------------------ +// Requirements +// ------------------------------------------------------------------------------ + +const utils = require('../utils') + +// ------------------------------------------------------------------------------ +// Rule Definition +// ------------------------------------------------------------------------------ + +module.exports = { + meta: { + docs: { + description: 'enforce unified spacing in mustache interpolations.', + category: 'Stylistic Issues', + recommended: false + }, + fixable: 'whitespace', + schema: [ + { + enum: ['always', 'never'] + } + ] + }, + + create (context) { + const options = context.options[0] + const optSpaces = options !== 'never' + const template = context.parserServices.getTemplateBodyTokenStore && context.parserServices.getTemplateBodyTokenStore() + + // ---------------------------------------------------------------------- + // Helpers + // ---------------------------------------------------------------------- + + function checkTokens (leftToken, rightToken) { + if (leftToken.loc.end.line === rightToken.loc.start.line) { + const spaces = rightToken.loc.start.column - leftToken.loc.end.column + const noSpacesFound = spaces === 0 + + if (optSpaces === noSpacesFound) { + context.report({ + node: rightToken, + loc: { + start: leftToken.loc.end, + end: rightToken.loc.start + }, + message: 'Found {{spaces}} whitespaces, {{type}} expected.', + data: { + spaces: spaces === 0 ? 'none' : spaces, + type: optSpaces ? '1' : 'none' + }, + fix: (fixer) => fixer.replaceTextRange([leftToken.range[1], rightToken.range[0]], optSpaces ? ' ' : '') + }) + } + } + } + + // ---------------------------------------------------------------------- + // Public + // ---------------------------------------------------------------------- + + utils.registerTemplateBodyVisitor(context, { + 'VExpressionContainer[expression!=null]' (node) { + const tokens = template.getTokens(node, { + includeComments: true, + filter: token => token.type !== 'HTMLWhitespace' // When there is only whitespace between ignore it + }) + + const startToken = tokens.shift() + if (!startToken || startToken.type !== 'VExpressionStart') return + const endToken = tokens.pop() + if (!endToken || endToken.type !== 'VExpressionEnd') return + + if (tokens.length > 0) { + checkTokens(startToken, tokens[0]) + checkTokens(tokens[tokens.length - 1], endToken) + } else { + checkTokens(startToken, endToken) + } + } + }) + + return { } + } +} diff --git a/tests/lib/rules/mustache-interpolation-spacing.js b/tests/lib/rules/mustache-interpolation-spacing.js new file mode 100644 index 000000000..84c8ef971 --- /dev/null +++ b/tests/lib/rules/mustache-interpolation-spacing.js @@ -0,0 +1,168 @@ +/** + * @fileoverview enforce unified spacing in mustache interpolations. + * @author Armano + */ +'use strict' + +// ------------------------------------------------------------------------------ +// Requirements +// ------------------------------------------------------------------------------ + +const rule = require('../../../lib/rules/mustache-interpolation-spacing') +const RuleTester = require('eslint').RuleTester + +// ------------------------------------------------------------------------------ +// Tests +// ------------------------------------------------------------------------------ + +const ruleTester = new RuleTester({ + parser: 'vue-eslint-parser', + parserOptions: { ecmaVersion: 2015 } +}) + +ruleTester.run('mustache-interpolation-spacing', rule, { + + valid: [ + { + filename: 'test.vue', + code: '' + }, + { + filename: 'test.vue', + code: '' + }, + { + filename: 'test.vue', + code: '' + }, + { + filename: 'test.vue', + code: '' + }, + { + filename: 'test.vue', + code: '' + }, + { + filename: 'test.vue', + code: '' + }, + { + filename: 'test.vue', + code: '', + options: ['always'] + }, + { + filename: 'test.vue', + code: '', + options: ['never'] + }, + { + filename: 'test.vue', + code: '', + options: ['never'] + }, + { + filename: 'test.vue', + code: '', + options: ['always'] + }, + { + filename: 'test.vue', + code: '', + options: ['always'] + }, + { + filename: 'test.vue', + code: '', + options: ['never'] + }, + { + filename: 'test.vue', + code: '', + options: ['always'] + } + ], + + invalid: [ + { + filename: 'test.vue', + code: '', + output: '', + options: ['always'], + errors: [{ + message: 'Found none whitespaces, 1 expected.', + type: 'VExpressionEnd' + }] + }, + { + filename: 'test.vue', + code: '', + output: '', + options: ['always'], + errors: [{ + message: 'Found none whitespaces, 1 expected.', + type: 'Identifier' + }] + }, + { + filename: 'test.vue', + code: '', + output: '', + options: ['never'], + errors: [{ + message: 'Found 1 whitespaces, none expected.', + type: 'Identifier' + }] + }, + { + filename: 'test.vue', + code: '', + output: '', + options: ['never'], + errors: [{ + message: 'Found 1 whitespaces, none expected.', + type: 'VExpressionEnd' + }] + }, + { + filename: 'test.vue', + code: '', + output: '', + options: ['always'], + errors: [{ + message: 'Found none whitespaces, 1 expected.', + type: 'Identifier' + }, { + message: 'Found none whitespaces, 1 expected.', + type: 'VExpressionEnd' + }] + }, + { + filename: 'test.vue', + code: '', + output: '', + options: ['never'], + errors: [{ + message: 'Found 1 whitespaces, none expected.', + type: 'Identifier' + }, { + message: 'Found 1 whitespaces, none expected.', + type: 'VExpressionEnd' + }] + }, + { + filename: 'test.vue', + code: '', + output: '', + options: ['never'], + errors: [{ + message: 'Found 3 whitespaces, none expected.', + type: 'Identifier' + }, { + message: 'Found 3 whitespaces, none expected.', + type: 'VExpressionEnd' + }] + } + ] +})