Skip to content

Add prefer-style-directive rule #95

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jan 13, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
/tests/fixtures/rules/indent/invalid/ts-v5
/tests/fixtures/rules/valid-compile/invalid/ts
/tests/fixtures/rules/valid-compile/valid/ts
/tests/fixtures/rules/prefer-style-directive
/.svelte-kit
/svelte.config-dist.js
/build
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,7 @@ These rules relate to style guidelines, and are therefore quite subjective:
| [@ota-meshi/svelte/max-attributes-per-line](https://ota-meshi.github.io/eslint-plugin-svelte/rules/max-attributes-per-line/) | enforce the maximum number of attributes per line | :wrench: |
| [@ota-meshi/svelte/mustache-spacing](https://ota-meshi.github.io/eslint-plugin-svelte/rules/mustache-spacing/) | enforce unified spacing in mustache | :wrench: |
| [@ota-meshi/svelte/prefer-class-directive](https://ota-meshi.github.io/eslint-plugin-svelte/rules/prefer-class-directive/) | require class directives instead of ternary expressions | :wrench: |
| [@ota-meshi/svelte/prefer-style-directive](https://ota-meshi.github.io/eslint-plugin-svelte/rules/prefer-style-directive/) | require style directives instead of style attribute | :wrench: |
| [@ota-meshi/svelte/shorthand-attribute](https://ota-meshi.github.io/eslint-plugin-svelte/rules/shorthand-attribute/) | enforce use of shorthand syntax in attribute | :wrench: |
| [@ota-meshi/svelte/spaced-html-comment](https://ota-meshi.github.io/eslint-plugin-svelte/rules/spaced-html-comment/) | enforce consistent spacing after the `<!--` and before the `-->` in a HTML comment | :wrench: |

Expand Down
3 changes: 3 additions & 0 deletions docs-svelte-kit/src/lib/components/ESLintPlayground.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
lastname: 'Lovelace'
};
let current = 'foo';
let color = 'red';
<` +
`/script>

Expand Down Expand Up @@ -60,6 +61,8 @@
class={current === 'foo' ? 'selected' : ''}
on:click="{() => current = 'foo'}"
>foo</button>

<div style="color: {color};">...</div>
`

const state = deserializeState(
Expand Down
1 change: 1 addition & 0 deletions docs/rules.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ These rules relate to style guidelines, and are therefore quite subjective:
| [@ota-meshi/svelte/max-attributes-per-line](./rules/max-attributes-per-line.md) | enforce the maximum number of attributes per line | :wrench: |
| [@ota-meshi/svelte/mustache-spacing](./rules/mustache-spacing.md) | enforce unified spacing in mustache | :wrench: |
| [@ota-meshi/svelte/prefer-class-directive](./rules/prefer-class-directive.md) | require class directives instead of ternary expressions | :wrench: |
| [@ota-meshi/svelte/prefer-style-directive](./rules/prefer-style-directive.md) | require style directives instead of style attribute | :wrench: |
| [@ota-meshi/svelte/shorthand-attribute](./rules/shorthand-attribute.md) | enforce use of shorthand syntax in attribute | :wrench: |
| [@ota-meshi/svelte/spaced-html-comment](./rules/spaced-html-comment.md) | enforce consistent spacing after the `<!--` and before the `-->` in a HTML comment | :wrench: |

Expand Down
6 changes: 6 additions & 0 deletions docs/rules/prefer-class-directive.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,12 @@ You cannot enforce this style by using [prettier-plugin-svelte]. That is, this r

Nothing.

## :couple: Related Rules

- [@ota-meshi/svelte/prefer-style-directive]

[@ota-meshi/svelte/prefer-style-directive]: ./prefer-style-directive.md

## :books: Further Reading

- [Svelte - Tutorial > 13. Classes / The class directive](https://svelte.dev/tutorial/classes)
Expand Down
61 changes: 61 additions & 0 deletions docs/rules/prefer-style-directive.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
---
pageClass: "rule-details"
sidebarDepth: 0
title: "@ota-meshi/svelte/prefer-style-directive"
description: "require style directives instead of style attribute"
---

# @ota-meshi/svelte/prefer-style-directive

> require style directives instead of style attribute
- :exclamation: <badge text="This rule has not been released yet." vertical="middle" type="error"> **_This rule has not been released yet._** </badge>
- :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 replace a style attribute with the style directive.

Style directive were added in Svelte v3.46.

<ESLintCodeBlock fix>

<!--eslint-skip-->

```svelte
<script>
/* eslint @ota-meshi/svelte/prefer-style-directive: "error" */
let color = "red"
</script>
<!-- ✓ GOOD -->
<div style:color={color}>...</div>
<!-- ✗ BAD -->
<div style="color: {color};">...</div>
```

</ESLintCodeBlock>

You cannot enforce this style by using [prettier-plugin-svelte]. That is, this rule does not conflict with [prettier-plugin-svelte] and can be used with [prettier-plugin-svelte].

[prettier-plugin-svelte]: https://github.com/sveltejs/prettier-plugin-svelte

## :wrench: Options

Nothing.

## :couple: Related Rules

- [@ota-meshi/svelte/prefer-class-directive]

[@ota-meshi/svelte/prefer-class-directive]: ./prefer-class-directive.md

## :books: Further Reading

- [Svelte - Docs > style:property](https://svelte.dev/docs#template-syntax-element-directives-style-property)

## :mag: Implementation

- [Rule source](https://github.com/ota-meshi/eslint-plugin-svelte/blob/main/src/rules/prefer-style-directive.ts)
- [Test source](https://github.com/ota-meshi/eslint-plugin-svelte/blob/main/tests/src/rules/prefer-style-directive.ts)
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@
"dependencies": {
"debug": "^4.3.1",
"eslint-utils": "^3.0.0",
"postcss": "^8.4.5",
"sourcemap-codec": "^1.4.8",
"svelte-eslint-parser": "^0.11.0"
},
Expand Down
98 changes: 98 additions & 0 deletions src/rules/prefer-style-directive.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import type { AST } from "svelte-eslint-parser"
import { parse as parseCss } from "postcss"
import { createRule } from "../utils"

export default createRule("prefer-style-directive", {
meta: {
docs: {
description: "require style directives instead of style attribute",
category: "Stylistic Issues",
recommended: false,
},
fixable: "code",
schema: [],
messages: {
unexpected: "Can use style directives instead.",
},
type: "suggestion",
},
create(context) {
const sourceCode = context.getSourceCode()
return {
"SvelteStartTag > SvelteAttribute"(
node: AST.SvelteAttribute & {
parent: AST.SvelteStartTag
},
) {
if (node.key.name !== "style") {
return
}
const mustacheTags = node.value.filter(
(v) => v.type === "SvelteMustacheTag",
)
const valueStartIndex = node.value[0].range[0]
const cssCode = node.value
.map((value) => {
if (value.type === "SvelteMustacheTag") {
return "_".repeat(value.range[1] - value.range[0])
}
return sourceCode.getText(value)
})
.join("")
const root = parseCss(cssCode)
root.walkDecls((decl) => {
if (
node.parent.attributes.some(
(attr) =>
attr.type === "SvelteStyleDirective" &&
attr.key.name.name === decl.prop,
)
) {
// has style directive
return
}

const declRange: AST.Range = [
valueStartIndex + decl.source!.start!.offset,
valueStartIndex + decl.source!.end!.offset + 1,
]
if (
mustacheTags.some(
(tag) =>
(tag.range[0] < declRange[0] && declRange[0] < tag.range[1]) ||
(tag.range[0] < declRange[1] && declRange[1] < tag.range[1]),
)
) {
// intersection
return
}
const declValueStartIndex =
declRange[0] + decl.prop.length + (decl.raws.between || "").length
const declValueRange: AST.Range = [
declValueStartIndex,
declValueStartIndex + (decl.raws.value?.value || decl.value).length,
]

context.report({
node,
messageId: "unexpected",
*fix(fixer) {
const styleDirective = `style:${
decl.prop
}="${sourceCode.text.slice(...declValueRange)}"`
if (root.nodes.length === 1 && root.nodes[0] === decl) {
yield fixer.replaceTextRange(node.range, styleDirective)
} else {
yield fixer.removeRange(declRange)
yield fixer.insertTextAfterRange(
node.range,
` ${styleDirective}`,
)
}
},
})
})
},
}
},
})
2 changes: 2 additions & 0 deletions src/utils/rules.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import noTargetBlank from "../rules/no-target-blank"
import noUnusedSvelteIgnore from "../rules/no-unused-svelte-ignore"
import noUselessMustaches from "../rules/no-useless-mustaches"
import preferClassDirective from "../rules/prefer-class-directive"
import preferStyleDirective from "../rules/prefer-style-directive"
import shorthandAttribute from "../rules/shorthand-attribute"
import spacedHtmlComment from "../rules/spaced-html-comment"
import system from "../rules/system"
Expand All @@ -43,6 +44,7 @@ export const rules = [
noUnusedSvelteIgnore,
noUselessMustaches,
preferClassDirective,
preferStyleDirective,
shorthandAttribute,
spacedHtmlComment,
system,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
[
{
"message": "Can use style directives instead.",
"line": 1,
"column": 6
}
]
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<div style="color: {r}e{d}">...</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<div style:color="{r}e{d}">...</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
[
{
"message": "Can use style directives instead.",
"line": 10,
"column": 6
}
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@

<script>
let red = "red"
</script>

<!-- ✓ GOOD -->
<div style:color={red}>...</div>

<!-- ✗ BAD -->
<div style="color: {red};">...</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@

<script>
let red = "red"
</script>

<!-- ✓ GOOD -->
<div style:color={red}>...</div>

<!-- ✗ BAD -->
<div style:color="{red}">...</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
[
{
"message": "Can use style directives instead.",
"line": 1,
"column": 6
},
{
"message": "Can use style directives instead.",
"line": 1,
"column": 6
}
]
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<div style="color: {red}; width: 12px">...</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<div style=" width: 12px" style:color="{red}">...</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
[
{
"message": "Can use style directives instead.",
"line": 1,
"column": 6
}
]
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<div style="color: red;">...</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<div style:color="red">...</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@

<script>
let red = "red"
</script>

<div style:color={red}>...</div>
16 changes: 16 additions & 0 deletions tests/src/rules/prefer-style-directive.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { RuleTester } from "eslint"
import rule from "../../../src/rules/prefer-style-directive"
import { loadTestCases } from "../../utils/utils"

const tester = new RuleTester({
parserOptions: {
ecmaVersion: 2020,
sourceType: "module",
},
})

tester.run(
"prefer-style-directive",
rule as any,
loadTestCases("prefer-style-directive"),
)