Skip to content

Commit 612b815

Browse files
authored
Add first-attribute-linebreak rule (#38)
1 parent 09eaf59 commit 612b815

23 files changed

+402
-1
lines changed

README.md

+1
Original file line numberDiff line numberDiff line change
@@ -276,6 +276,7 @@ These rules relate to style guidelines, and are therefore quite subjective:
276276

277277
| Rule ID | Description | |
278278
|:--------|:------------|:---|
279+
| [@ota-meshi/svelte/first-attribute-linebreak](https://ota-meshi.github.io/eslint-plugin-svelte/rules/first-attribute-linebreak.html) | enforce the location of first attribute | :wrench: |
279280
| [@ota-meshi/svelte/html-quotes](https://ota-meshi.github.io/eslint-plugin-svelte/rules/html-quotes.html) | enforce quotes style of HTML attributes | :wrench: |
280281
| [@ota-meshi/svelte/indent](https://ota-meshi.github.io/eslint-plugin-svelte/rules/indent.html) | enforce consistent indentation | :wrench: |
281282
| [@ota-meshi/svelte/max-attributes-per-line](https://ota-meshi.github.io/eslint-plugin-svelte/rules/max-attributes-per-line.html) | enforce the maximum number of attributes per line | :wrench: |

docs/rules/README.md

+1
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ These rules relate to style guidelines, and are therefore quite subjective:
4444

4545
| Rule ID | Description | |
4646
|:--------|:------------|:---|
47+
| [@ota-meshi/svelte/first-attribute-linebreak](./first-attribute-linebreak.md) | enforce the location of first attribute | :wrench: |
4748
| [@ota-meshi/svelte/html-quotes](./html-quotes.md) | enforce quotes style of HTML attributes | :wrench: |
4849
| [@ota-meshi/svelte/indent](./indent.md) | enforce consistent indentation | :wrench: |
4950
| [@ota-meshi/svelte/max-attributes-per-line](./max-attributes-per-line.md) | enforce the maximum number of attributes per line | :wrench: |
+79
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
---
2+
pageClass: "rule-details"
3+
sidebarDepth: 0
4+
title: "@ota-meshi/svelte/first-attribute-linebreak"
5+
description: "enforce the location of first attribute"
6+
---
7+
8+
# @ota-meshi/svelte/first-attribute-linebreak
9+
10+
> enforce the location of first attribute
11+
12+
- :exclamation: <badge text="This rule has not been released yet." vertical="middle" type="error"> **_This rule has not been released yet._** </badge>
13+
- :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.
14+
15+
## :book: Rule Details
16+
17+
This rule aims to enforce a consistent location for the first attribute.
18+
19+
<eslint-code-block fix>
20+
21+
<!-- prettier-ignore-start -->
22+
<!--eslint-skip-->
23+
24+
```svelte
25+
<script>
26+
/* eslint @ota-meshi/svelte/first-attribute-linebreak: "error" */
27+
</script>
28+
29+
<!-- ✓ GOOD -->
30+
<input type="checkbox" />
31+
<button
32+
type="button"
33+
on:click={click} />
34+
<button type="button" on:click={click} />
35+
36+
<!-- ✗ BAD -->
37+
<input
38+
type="checkbox" />
39+
<button type="button"
40+
on:click={click} />
41+
<button
42+
type="button" on:click={click} />
43+
```
44+
45+
<!-- prettier-ignore-end -->
46+
47+
</eslint-code-block>
48+
49+
## :wrench: Options
50+
51+
```json
52+
{
53+
"@ota-meshi/svelte/first-attribute-linebreak": [
54+
"error",
55+
{
56+
"multiline": "below", // or "beside"
57+
"singleline": "beside" // "below"
58+
}
59+
]
60+
}
61+
```
62+
63+
- `multiline` ... The location of the first attribute when the attributes span multiple lines. Default is `"below"`.
64+
- `"below"` ... Requires a newline before the first attribute.
65+
- `"beside"` ... Disallows a newline before the first attribute.
66+
- `singleline` ... The location of the first attribute when the attributes on single line. Default is `"beside"`.
67+
- `"below"` ... Requires a newline before the first attribute.
68+
- `"beside"` ... Disallows a newline before the first attribute.
69+
70+
## :couple: Related Rules
71+
72+
- [@ota-meshi/svelte/max-attributes-per-line]
73+
74+
[@ota-meshi/svelte/max-attributes-per-line]: ./max-attributes-per-line.md
75+
76+
## :mag: Implementation
77+
78+
- [Rule source](https://github.com/ota-meshi/eslint-plugin-svelte/blob/main/src/rules/first-attribute-linebreak.ts)
79+
- [Test source](https://github.com/ota-meshi/eslint-plugin-svelte/blob/main/tests/src/rules/first-attribute-linebreak.ts)

docs/rules/max-attributes-per-line.md

+7-1
Original file line numberDiff line numberDiff line change
@@ -74,8 +74,14 @@ There is a configurable number of attributes that are acceptable in one-line cas
7474
}
7575
```
7676

77-
- `singleline` ... The number of maximum attributes per line when the opening tag is in a single line. Default is `1`.
7877
- `multiline` ... The number of maximum attributes per line when the opening tag is in multiple lines. Default is `1`.
78+
- `singleline` ... The number of maximum attributes per line when the opening tag is in a single line. Default is `1`.
79+
80+
## :couple: Related Rules
81+
82+
- [@ota-meshi/svelte/first-attribute-linebreak]
83+
84+
[@ota-meshi/svelte/first-attribute-linebreak]: ./first-attribute-linebreak.md
7985

8086
## :rocket: Version
8187

+84
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
import type { AST } from "svelte-eslint-parser"
2+
import { createRule } from "../utils"
3+
4+
export default createRule("first-attribute-linebreak", {
5+
meta: {
6+
docs: {
7+
description: "enforce the location of first attribute",
8+
category: "Stylistic Issues",
9+
recommended: false,
10+
},
11+
fixable: "whitespace",
12+
schema: [
13+
{
14+
type: "object",
15+
properties: {
16+
multiline: { enum: ["below", "beside"] },
17+
singleline: { enum: ["below", "beside"] },
18+
},
19+
additionalProperties: false,
20+
},
21+
],
22+
messages: {
23+
expected: "Expected a linebreak before this attribute.",
24+
unexpected: "Expected no linebreak before this attribute.",
25+
},
26+
type: "layout",
27+
},
28+
create(context) {
29+
const multiline: "below" | "beside" =
30+
context.options[0]?.multiline || "below"
31+
const singleline: "below" | "beside" =
32+
context.options[0]?.singleline || "beside"
33+
const sourceCode = context.getSourceCode()
34+
35+
/**
36+
* Report attribute
37+
*/
38+
function report(
39+
firstAttribute: AST.SvelteStartTag["attributes"][number],
40+
location: "below" | "beside",
41+
) {
42+
context.report({
43+
node: firstAttribute,
44+
messageId: location === "beside" ? "unexpected" : "expected",
45+
fix(fixer) {
46+
const prevToken = sourceCode.getTokenBefore(firstAttribute, {
47+
includeComments: true,
48+
})!
49+
return fixer.replaceTextRange(
50+
[prevToken.range[1], firstAttribute.range[0]],
51+
location === "beside" ? " " : "\n",
52+
)
53+
},
54+
})
55+
}
56+
57+
return {
58+
SvelteStartTag(node) {
59+
const firstAttribute = node.attributes[0]
60+
if (!firstAttribute) return
61+
62+
const lastAttribute = node.attributes[node.attributes.length - 1]
63+
64+
const location =
65+
firstAttribute.loc.start.line === lastAttribute.loc.end.line
66+
? singleline
67+
: multiline
68+
69+
if (location === "beside") {
70+
if (
71+
node.parent.name.loc!.end.line === firstAttribute.loc.start.line
72+
) {
73+
return
74+
}
75+
} else {
76+
if (node.parent.name.loc!.end.line < firstAttribute.loc.start.line) {
77+
return
78+
}
79+
}
80+
report(firstAttribute, location)
81+
},
82+
}
83+
},
84+
})

src/utils/rules.ts

+2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import type { RuleModule } from "../types"
22
import buttonHasType from "../rules/button-has-type"
33
import commentDirective from "../rules/comment-directive"
4+
import firstAttributeLinebreak from "../rules/first-attribute-linebreak"
45
import htmlQuotes from "../rules/html-quotes"
56
import indent from "../rules/indent"
67
import maxAttributesPerLine from "../rules/max-attributes-per-line"
@@ -20,6 +21,7 @@ import system from "../rules/system"
2021
export const rules = [
2122
buttonHasType,
2223
commentDirective,
24+
firstAttributeLinebreak,
2325
htmlQuotes,
2426
indent,
2527
maxAttributesPerLine,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"options": [{ "multiline": "below", "singleline": "below" }]
3+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
[
2+
{
3+
"message": "Expected a linebreak before this attribute.",
4+
"line": 9,
5+
"column": 9
6+
},
7+
{
8+
"message": "Expected a linebreak before this attribute.",
9+
"line": 13,
10+
"column": 8
11+
}
12+
]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<script>
2+
function click() {}
3+
</script>
4+
5+
<!-- prettier-ignore -->
6+
<input
7+
type="checkbox">
8+
<!-- prettier-ignore -->
9+
<button type="button"
10+
on:click={click} />
11+
12+
<!-- prettier-ignore -->
13+
<input type="checkbox">
14+
<!-- prettier-ignore -->
15+
<button
16+
on:click={
17+
click
18+
} />
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<script>
2+
function click() {}
3+
</script>
4+
5+
<!-- prettier-ignore -->
6+
<input
7+
type="checkbox">
8+
<!-- prettier-ignore -->
9+
<button
10+
type="button"
11+
on:click={click} />
12+
13+
<!-- prettier-ignore -->
14+
<input
15+
type="checkbox">
16+
<!-- prettier-ignore -->
17+
<button
18+
on:click={
19+
click
20+
} />
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"options": [{ "multiline": "beside", "singleline": "beside" }]
3+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
[
2+
{
3+
"message": "Expected no linebreak before this attribute.",
4+
"line": 7,
5+
"column": 3
6+
},
7+
{
8+
"message": "Expected no linebreak before this attribute.",
9+
"line": 16,
10+
"column": 3
11+
}
12+
]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<script>
2+
function click() {}
3+
</script>
4+
5+
<!-- prettier-ignore -->
6+
<input
7+
type="checkbox">
8+
<!-- prettier-ignore -->
9+
<button type="button"
10+
on:click={click} />
11+
12+
<!-- prettier-ignore -->
13+
<input type="checkbox">
14+
<!-- prettier-ignore -->
15+
<button
16+
on:click={
17+
click
18+
} />
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<script>
2+
function click() {}
3+
</script>
4+
5+
<!-- prettier-ignore -->
6+
<input type="checkbox">
7+
<!-- prettier-ignore -->
8+
<button type="button"
9+
on:click={click} />
10+
11+
<!-- prettier-ignore -->
12+
<input type="checkbox">
13+
<!-- prettier-ignore -->
14+
<button on:click={
15+
click
16+
} />
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
[
2+
{
3+
"message": "Expected no linebreak before this attribute.",
4+
"line": 7,
5+
"column": 3
6+
},
7+
{
8+
"message": "Expected a linebreak before this attribute.",
9+
"line": 9,
10+
"column": 9
11+
}
12+
]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<script>
2+
function click() {}
3+
</script>
4+
5+
<!-- prettier-ignore -->
6+
<input
7+
type="checkbox">
8+
<!-- prettier-ignore -->
9+
<button type="button"
10+
on:click={click} />
11+
12+
<!-- prettier-ignore -->
13+
<input type="checkbox">
14+
<!-- prettier-ignore -->
15+
<button
16+
on:click={
17+
click
18+
} />
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<script>
2+
function click() {}
3+
</script>
4+
5+
<!-- prettier-ignore -->
6+
<input type="checkbox">
7+
<!-- prettier-ignore -->
8+
<button
9+
type="button"
10+
on:click={click} />
11+
12+
<!-- prettier-ignore -->
13+
<input type="checkbox">
14+
<!-- prettier-ignore -->
15+
<button
16+
on:click={
17+
click
18+
} />
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"options": [{ "multiline": "below", "singleline": "below" }]
3+
}

0 commit comments

Comments
 (0)