From 6fd875886d8d74d6a6843234b84d6e82f5c9a3c9 Mon Sep 17 00:00:00 2001 From: Jackson Gross Date: Fri, 5 Feb 2021 10:53:39 +1100 Subject: [PATCH] Added: rule to enforce component opening tags with a single attribute to be on a single line Signed-off-by: Jackson Gross --- docs/rules/single-attribute-single-line.md | 67 ++++++++ lib/rules/single-attribute-single-line.js | 78 +++++++++ .../lib/rules/single-attribute-single-line.js | 155 ++++++++++++++++++ 3 files changed, 300 insertions(+) create mode 100644 docs/rules/single-attribute-single-line.md create mode 100644 lib/rules/single-attribute-single-line.js create mode 100644 tests/lib/rules/single-attribute-single-line.js diff --git a/docs/rules/single-attribute-single-line.md b/docs/rules/single-attribute-single-line.md new file mode 100644 index 000000000..eec091980 --- /dev/null +++ b/docs/rules/single-attribute-single-line.md @@ -0,0 +1,67 @@ +--- +pageClass: rule-details +sidebarDepth: 0 +title: vue/single-attribute-single-line +description: enforce component opening tags with a single attribute to be on a single line +since: v7.5.0 +--- +# vue/single-attribute-single-line + +> enforce component opening tags with a single attribute to be on a single line + +- :wrench: The `--fix` option on the [command line](https://eslint.org/docs/user-guide/command-line-interface#fixing-problems) can automatically fix some of the problems reported by this rule. + +Ensures that component opening tags that only have one attribute are inline to improve readability. + +## :book: Rule Details + +This rule aims to enforce component opening tags with a single attribute to be place on a single line. +It checks all the elements in a template and verifies that when the number of attributes for a component is 1, that it is inlined on the opening tag. + + + +```vue + +``` + + + +## :wrench: Options + +```json +{ + "vue/single-attribute-single-line": ["error"] +} +``` + + + +## :rocket: Version + +This rule was introduced in eslint-plugin-vue v7.5.0 + +## :mag: Implementation + +- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/single-attribute-single-line.js) +- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/single-attribute-single-line.js) diff --git a/lib/rules/single-attribute-single-line.js b/lib/rules/single-attribute-single-line.js new file mode 100644 index 000000000..dcffe006d --- /dev/null +++ b/lib/rules/single-attribute-single-line.js @@ -0,0 +1,78 @@ +/** + * @fileoverview Enforce component opening tags with a single attribute to be on a single line + * @author Jackson Gross + */ +'use strict' + +// ------------------------------------------------------------------------------ +// Rule Definition +// ------------------------------------------------------------------------------ +const utils = require('../utils') + +module.exports = { + meta: { + type: 'layout', + docs: { + description: + 'enforce component opening tags with a single attribute to be on a single line', + categories: undefined, + url: 'https://eslint.vuejs.org/rules/single-attribute-single-line.html' + }, + fixable: 'whitespace', // or "code" or "whitespace" + schema: [] + }, + /** @param {RuleContext} context */ + create(context) { + const sourceCode = context.getSourceCode() + const template = + context.parserServices.getTemplateBodyTokenStore && + context.parserServices.getTemplateBodyTokenStore() + + return utils.defineTemplateBodyVisitor(context, { + VStartTag(node) { + const closingTag = node.range[1] - 1 + const numberOfAttributes = node.attributes.length + + if (numberOfAttributes !== 1) return + + if (!utils.isSingleLine(node)) { + // Find the closest token before the current prop + // that is not a white space + const prevToken = /** @type {Token} */ (template.getTokenBefore( + node.attributes[0], + { + filter: (token) => token.type !== 'HTMLWhitespace' + } + )) + + const startOfAttribute = node.attributes[0].range[0] + const endOfAttribute = node.attributes[0].range[1] + + /** @type {Range} */ + const rangeBetweenAttributeAndStartTag = [ + prevToken.range[1], + startOfAttribute + ] + + /** @type {Range} */ + const rangeBetweenAttributeAndClosingTag = [ + endOfAttribute, + closingTag + ] + + context.report({ + node, + message: "'{{name}}' should be on a single line.", + data: { name: sourceCode.getText(node.attributes[0].key) }, + fix(fixer) { + return [ + fixer.replaceTextRange(rangeBetweenAttributeAndClosingTag, ''), + fixer.replaceTextRange(rangeBetweenAttributeAndStartTag, ' ') + ] + } + }) + } + } + }) + } +} diff --git a/tests/lib/rules/single-attribute-single-line.js b/tests/lib/rules/single-attribute-single-line.js new file mode 100644 index 000000000..f9bb56d8c --- /dev/null +++ b/tests/lib/rules/single-attribute-single-line.js @@ -0,0 +1,155 @@ +/** + * @fileoverview Enforce component opening tags with a single attribute to be on a single line + * @author Jackson Gross + */ +'use strict' + +// ------------------------------------------------------------------------------ +// Requirements +// ------------------------------------------------------------------------------ + +const RuleTester = require('eslint').RuleTester +const rule = require('../../../lib/rules/single-attribute-single-line') + +// ------------------------------------------------------------------------------ +// Tests +// ------------------------------------------------------------------------------ + +const ruleTester = new RuleTester({ + parser: require.resolve('vue-eslint-parser'), + parserOptions: { ecmaVersion: 2015 } +}) + +ruleTester.run('single-attribute-single-line', rule, { + valid: [ + { + code: `` + }, + { + code: `` + }, + { + code: `` + }, + { + code: `` + }, + { + code: `` + }, + { + code: `` + }, + { + code: `` + }, + { + code: `` + }, + { + code: `` + }, + { + code: `` + } + ], + + invalid: [ + { + code: ``, + output: ``, + errors: ["'name' should be on a single line."] + }, + { + code: ``, + output: ``, + errors: ["':name' should be on a single line."] + }, + { + code: ``, + output: ``, + errors: ["'v-bind' should be on a single line."] + }, + { + code: ``, + output: ``, + errors: ["'@buy' should be on a single line."] + }, + { + code: ``, + output: ``, + errors: ["'@click.stop' should be on a single line."] + }, + { + code: ``, + output: ``, + errors: ["'v-if' should be on a single line."] + }, + { + code: ``, + output: ``, + errors: ["'v-bind:name' should be on a single line."] + } + ] +})