Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add
vue/first-attribute-linebreak
rule (#1587)
- Loading branch information
Showing
11 changed files
with
553 additions
and
255 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,164 @@ | ||
--- | ||
pageClass: rule-details | ||
sidebarDepth: 0 | ||
title: vue/first-attribute-linebreak | ||
description: enforce the location of first attribute | ||
--- | ||
# vue/first-attribute-linebreak | ||
|
||
> enforce the location of first attribute | ||
- :exclamation: <badge text="This rule has not been released yet." vertical="middle" type="error"> ***This rule has not been released yet.*** </badge> | ||
- :gear: This rule is included in all of `"plugin:vue/vue3-strongly-recommended"`, `"plugin:vue/strongly-recommended"`, `"plugin:vue/vue3-recommended"` and `"plugin:vue/recommended"`. | ||
- :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. | ||
|
||
## :book: Rule Details | ||
|
||
This rule aims to enforce a consistent location for the first attribute. | ||
|
||
<eslint-code-block fix :rules="{'vue/first-attribute-linebreak': ['error']}"> | ||
|
||
```vue | ||
<template> | ||
<!-- ✓ GOOD --> | ||
<MyComponent lorem="1"/> | ||
<MyComponent lorem="1" ipsum="2"/> | ||
<MyComponent | ||
lorem="1" | ||
ipsum="2" | ||
/> | ||
<!-- ✗ BAD --> | ||
<MyComponent lorem="1" | ||
ipsum="2"/> | ||
</template> | ||
``` | ||
|
||
</eslint-code-block> | ||
|
||
## :wrench: Options | ||
|
||
```json | ||
{ | ||
"vue/first-attribute-linebreak": ["error", { | ||
"singleline": "ignore", | ||
"multiline": "below" | ||
}] | ||
} | ||
``` | ||
|
||
- `singleline` ... The location of the first attribute when the attributes on single line. Default is `"ignore"`. | ||
- `"below"` ... Requires a newline before the first attribute. | ||
- `"beside"` ... Disallows a newline before the first attribute. | ||
- `"ignore"` ... Ignores attribute checking. | ||
- `multiline` ... The location of the first attribute when the attributes span multiple lines. Default is `"below"`. | ||
- `"below"` ... Requires a newline before the first attribute. | ||
- `"beside"` ... Disallows a newline before the first attribute. | ||
- `"ignore"` ... Ignores attribute checking. | ||
|
||
### `"singleline": "beside"` | ||
|
||
<eslint-code-block fix :rules="{'vue/first-attribute-linebreak': ['error', {singleline: 'beside'}]}"> | ||
|
||
```vue | ||
<template> | ||
<!-- ✓ GOOD --> | ||
<MyComponent lorem="1"/> | ||
<MyComponent lorem="1" ipsum="2"/> | ||
<!-- ✗ BAD --> | ||
<MyComponent | ||
lorem="1"/> | ||
<MyComponent | ||
lorem="1" ipsum="2" | ||
/> | ||
</template> | ||
``` | ||
|
||
</eslint-code-block> | ||
|
||
### `"singleline": "below"` | ||
|
||
<eslint-code-block fix :rules="{'vue/first-attribute-linebreak': ['error', {singleline: 'below'}]}"> | ||
|
||
```vue | ||
<template> | ||
<!-- ✓ GOOD --> | ||
<MyComponent | ||
lorem="1"/> | ||
<MyComponent | ||
lorem="1" ipsum="2" | ||
/> | ||
<!-- ✗ BAD --> | ||
<MyComponent lorem="1"/> | ||
<MyComponent lorem="1" ipsum="2"/> | ||
</template> | ||
``` | ||
|
||
</eslint-code-block> | ||
|
||
### `"multiline": "beside"` | ||
|
||
<eslint-code-block fix :rules="{'vue/first-attribute-linebreak': ['error', {multiline: 'beside'}]}"> | ||
|
||
```vue | ||
<template> | ||
<!-- ✓ GOOD --> | ||
<MyComponent lorem="1" | ||
ipsum="2"/> | ||
<MyComponent :lorem="{ | ||
a: 1 | ||
}"/> | ||
<!-- ✗ BAD --> | ||
<MyComponent | ||
lorem="1" | ||
ipsum="2"/> | ||
<MyComponent | ||
:lorem="{ | ||
a: 1 | ||
}"/> | ||
</template> | ||
``` | ||
|
||
</eslint-code-block> | ||
|
||
### `"multiline": "below"` | ||
|
||
<eslint-code-block fix :rules="{'vue/first-attribute-linebreak': ['error', {multiline: 'below'}]}"> | ||
|
||
```vue | ||
<template> | ||
<!-- ✓ GOOD --> | ||
<MyComponent | ||
lorem="1" | ||
ipsum="2"/> | ||
<MyComponent | ||
:lorem="{ | ||
a: 1 | ||
}"/> | ||
<!-- ✗ BAD --> | ||
<MyComponent lorem="1" | ||
ipsum="2"/> | ||
<MyComponent :lorem="{ | ||
a: 1 | ||
}"/> | ||
</template> | ||
``` | ||
|
||
</eslint-code-block> | ||
|
||
## :couple: Related Rules | ||
|
||
- [vue/max-attributes-per-line](./max-attributes-per-line.md) | ||
|
||
## :books: Further Reading | ||
|
||
- [Style guide - Multi attribute elements](https://v3.vuejs.org/style-guide/#multi-attribute-elements-strongly-recommended) | ||
|
||
## :mag: Implementation | ||
|
||
- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/first-attribute-linebreak.js) | ||
- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/first-attribute-linebreak.js) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
/** | ||
* @fileoverview Enforce the location of first attribute | ||
* @author Yosuke Ota | ||
*/ | ||
'use strict' | ||
|
||
// ------------------------------------------------------------------------------ | ||
// Rule Definition | ||
// ------------------------------------------------------------------------------ | ||
const utils = require('../utils') | ||
|
||
module.exports = { | ||
meta: { | ||
type: 'layout', | ||
docs: { | ||
description: 'enforce the location of first attribute', | ||
categories: ['vue3-strongly-recommended', 'strongly-recommended'], | ||
url: 'https://eslint.vuejs.org/rules/first-attribute-linebreak.html' | ||
}, | ||
fixable: 'whitespace', // or "code" or "whitespace" | ||
schema: [ | ||
{ | ||
type: 'object', | ||
properties: { | ||
multiline: { enum: ['below', 'beside', 'ignore'] }, | ||
singleline: { enum: ['below', 'beside', 'ignore'] } | ||
}, | ||
additionalProperties: false | ||
} | ||
], | ||
messages: { | ||
expected: 'Expected a linebreak before this attribute.', | ||
unexpected: 'Expected no linebreak before this attribute.' | ||
} | ||
}, | ||
/** @param {RuleContext} context */ | ||
create(context) { | ||
/** @type {"below" | "beside" | "ignore"} */ | ||
const singleline = | ||
(context.options[0] && context.options[0].singleline) || 'ignore' | ||
/** @type {"below" | "beside" | "ignore"} */ | ||
const multiline = | ||
(context.options[0] && context.options[0].multiline) || 'below' | ||
|
||
const template = | ||
context.parserServices.getTemplateBodyTokenStore && | ||
context.parserServices.getTemplateBodyTokenStore() | ||
|
||
/** | ||
* Report attribute | ||
* @param {VAttribute | VDirective} firstAttribute | ||
* @param { "below" | "beside"} location | ||
*/ | ||
function report(firstAttribute, location) { | ||
context.report({ | ||
node: firstAttribute, | ||
messageId: location === 'beside' ? 'unexpected' : 'expected', | ||
fix(fixer) { | ||
const prevToken = template.getTokenBefore(firstAttribute, { | ||
includeComments: true | ||
}) | ||
return fixer.replaceTextRange( | ||
[prevToken.range[1], firstAttribute.range[0]], | ||
location === 'beside' ? ' ' : '\n' | ||
) | ||
} | ||
}) | ||
} | ||
|
||
return utils.defineTemplateBodyVisitor(context, { | ||
VStartTag(node) { | ||
const firstAttribute = node.attributes[0] | ||
if (!firstAttribute) return | ||
|
||
const lastAttribute = node.attributes[node.attributes.length - 1] | ||
|
||
const location = | ||
firstAttribute.loc.start.line === lastAttribute.loc.end.line | ||
? singleline | ||
: multiline | ||
if (location === 'ignore') { | ||
return | ||
} | ||
|
||
if (location === 'beside') { | ||
if (node.loc.start.line === firstAttribute.loc.start.line) { | ||
return | ||
} | ||
} else { | ||
if (node.loc.start.line < firstAttribute.loc.start.line) { | ||
return | ||
} | ||
} | ||
report(firstAttribute, location) | ||
} | ||
}) | ||
} | ||
} |
Oops, something went wrong.