Skip to content

Commit 157081d

Browse files
authored
Add no-shorthand-style-property-overrides rule (#126)
1 parent d15cd6b commit 157081d

28 files changed

+859
-180
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,7 @@ These rules relate to possible syntax or logic errors in Svelte code:
246246
| [@ota-meshi/svelte/no-dynamic-slot-name](https://ota-meshi.github.io/eslint-plugin-svelte/rules/no-dynamic-slot-name/) | disallow dynamic slot name | :star::wrench: |
247247
| [@ota-meshi/svelte/no-not-function-handler](https://ota-meshi.github.io/eslint-plugin-svelte/rules/no-not-function-handler/) | disallow use of not function in event handler | :star: |
248248
| [@ota-meshi/svelte/no-object-in-text-mustaches](https://ota-meshi.github.io/eslint-plugin-svelte/rules/no-object-in-text-mustaches/) | disallow objects in text mustache interpolation | :star: |
249+
| [@ota-meshi/svelte/no-shorthand-style-property-overrides](https://ota-meshi.github.io/eslint-plugin-svelte/rules/no-shorthand-style-property-overrides/) | disallow shorthand style properties that override related longhand properties | :star: |
249250
| [@ota-meshi/svelte/valid-compile](https://ota-meshi.github.io/eslint-plugin-svelte/rules/valid-compile/) | disallow warnings when compiling. | :star: |
250251

251252
## Security Vulnerability

docs/rules.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ These rules relate to possible syntax or logic errors in Svelte code:
1919
| [@ota-meshi/svelte/no-dynamic-slot-name](./rules/no-dynamic-slot-name.md) | disallow dynamic slot name | :star::wrench: |
2020
| [@ota-meshi/svelte/no-not-function-handler](./rules/no-not-function-handler.md) | disallow use of not function in event handler | :star: |
2121
| [@ota-meshi/svelte/no-object-in-text-mustaches](./rules/no-object-in-text-mustaches.md) | disallow objects in text mustache interpolation | :star: |
22+
| [@ota-meshi/svelte/no-shorthand-style-property-overrides](./rules/no-shorthand-style-property-overrides.md) | disallow shorthand style properties that override related longhand properties | :star: |
2223
| [@ota-meshi/svelte/valid-compile](./rules/valid-compile.md) | disallow warnings when compiling. | :star: |
2324

2425
## Security Vulnerability
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
---
2+
pageClass: "rule-details"
3+
sidebarDepth: 0
4+
title: "@ota-meshi/svelte/no-shorthand-style-property-overrides"
5+
description: "disallow shorthand style properties that override related longhand properties"
6+
---
7+
8+
# @ota-meshi/svelte/no-shorthand-style-property-overrides
9+
10+
> disallow shorthand style properties that override related longhand properties
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+
- :gear: This rule is included in `"plugin:@ota-meshi/svelte/recommended"`.
14+
15+
## :book: Rule Details
16+
17+
This rule reports when a shorthand style property overrides a previously defined longhand property.
18+
19+
This rule was inspired by [Stylelint's declaration-block-no-shorthand-property-overrides rule](https://stylelint.io/user-guide/rules/list/declaration-block-no-shorthand-property-overrides/).
20+
21+
<ESLintCodeBlock>
22+
23+
<!--eslint-skip-->
24+
25+
```svelte
26+
<script>
27+
/* eslint @ota-meshi/svelte/no-shorthand-style-property-overrides: "error" */
28+
let red = "red"
29+
</script>
30+
31+
<!-- ✓ GOOD -->
32+
<div style:background-repeat="repeat" style:background-color="green">...</div>
33+
<div style="background-repeat: repeat; background-color: {red};">...</div>
34+
<div style:background-repeat="repeat" style="background-color: {red}">...</div>
35+
36+
<!-- ✗ BAD -->
37+
<div style:background-repeat="repeat" style:background="green">...</div>
38+
<div style="background-repeat: repeat; background: {red};">...</div>
39+
<div style:background-repeat="repeat" style="background: {red}">...</div>
40+
```
41+
42+
</ESLintCodeBlock>
43+
44+
## :wrench: Options
45+
46+
Nothing.
47+
48+
## :mag: Implementation
49+
50+
- [Rule source](https://github.com/ota-meshi/eslint-plugin-svelte/blob/main/src/rules/no-shorthand-style-property-overrides.ts)
51+
- [Test source](https://github.com/ota-meshi/eslint-plugin-svelte/blob/main/tests/src/rules/no-shorthand-style-property-overrides.ts)

src/configs/recommended.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ export = {
1414
"@ota-meshi/svelte/no-inner-declarations": "error",
1515
"@ota-meshi/svelte/no-not-function-handler": "error",
1616
"@ota-meshi/svelte/no-object-in-text-mustaches": "error",
17+
"@ota-meshi/svelte/no-shorthand-style-property-overrides": "error",
1718
"@ota-meshi/svelte/no-unused-svelte-ignore": "error",
1819
"@ota-meshi/svelte/system": "error",
1920
"@ota-meshi/svelte/valid-compile": "error",
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
import type { AST } from "svelte-eslint-parser"
2+
import { createRule } from "../utils"
3+
import type { SvelteStyleRoot } from "../utils/css-utils"
4+
import {
5+
getVendorPrefix,
6+
stripVendorPrefix,
7+
parseStyleAttributeValue,
8+
SHORTHAND_PROPERTIES,
9+
} from "../utils/css-utils"
10+
11+
export default createRule("no-shorthand-style-property-overrides", {
12+
meta: {
13+
docs: {
14+
description:
15+
"disallow shorthand style properties that override related longhand properties",
16+
category: "Possible Errors",
17+
recommended: true,
18+
},
19+
schema: [],
20+
messages: {
21+
unexpected: "Unexpected shorthand '{{shorthand}}' after '{{original}}'.",
22+
},
23+
type: "problem",
24+
},
25+
create(context) {
26+
type StyleDecl = {
27+
prop: string
28+
loc: AST.SourceLocation
29+
}
30+
type StyleDeclSet = {
31+
decls: StyleDecl[]
32+
}
33+
34+
return {
35+
SvelteStartTag(node: AST.SvelteStartTag) {
36+
const beforeDeclarations = new Set<string>()
37+
for (const { decls } of iterateStyleDeclSetFromAttrs(node.attributes)) {
38+
for (const decl of decls) {
39+
const normalized = stripVendorPrefix(decl.prop)
40+
const prefix = getVendorPrefix(decl.prop)
41+
42+
const longhandProps = SHORTHAND_PROPERTIES.get(normalized)
43+
if (!longhandProps) {
44+
continue
45+
}
46+
47+
for (const longhandProp of longhandProps) {
48+
const longhandPropWithPrefix = prefix + longhandProp
49+
if (!beforeDeclarations.has(longhandPropWithPrefix)) {
50+
continue
51+
}
52+
53+
context.report({
54+
node,
55+
loc: decl.loc,
56+
messageId: "unexpected",
57+
data: {
58+
shorthand: decl.prop,
59+
original: longhandPropWithPrefix,
60+
},
61+
})
62+
}
63+
}
64+
for (const decl of decls) {
65+
beforeDeclarations.add(decl.prop)
66+
}
67+
}
68+
},
69+
}
70+
71+
/** Iterate the style decl set from attrs */
72+
function* iterateStyleDeclSetFromAttrs(
73+
attrs: AST.SvelteStartTag["attributes"],
74+
): Iterable<StyleDeclSet> {
75+
for (const attr of attrs) {
76+
if (attr.type === "SvelteStyleDirective") {
77+
yield {
78+
decls: [{ prop: attr.key.name.name, loc: attr.key.name.loc! }],
79+
}
80+
} else if (attr.type === "SvelteAttribute") {
81+
if (attr.key.name !== "style") {
82+
continue
83+
}
84+
const root = parseStyleAttributeValue(attr, context)
85+
if (!root) {
86+
continue
87+
}
88+
yield* iterateStyleDeclSetFromStyleRoot(root)
89+
}
90+
}
91+
}
92+
93+
/** Iterate the style decl set from style root */
94+
function* iterateStyleDeclSetFromStyleRoot(
95+
root: SvelteStyleRoot,
96+
): Iterable<StyleDeclSet> {
97+
for (const child of root.nodes) {
98+
if (child.type === "decl") {
99+
yield {
100+
decls: [
101+
{
102+
prop: child.prop.name,
103+
get loc() {
104+
return child.prop.loc
105+
},
106+
},
107+
],
108+
}
109+
} else if (child.type === "inline") {
110+
const decls: StyleDecl[] = []
111+
for (const root of child.getAllInlineStyles().values()) {
112+
for (const set of iterateStyleDeclSetFromStyleRoot(root)) {
113+
decls.push(...set.decls)
114+
}
115+
}
116+
yield { decls }
117+
}
118+
}
119+
}
120+
},
121+
})

0 commit comments

Comments
 (0)