Skip to content
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 README.md
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,7 @@ These rules relate to possible syntax or logic errors in Svelte code:
| [@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: |
| [@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: |
| [@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: |
| [@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: |
| [@ota-meshi/svelte/valid-compile](https://ota-meshi.github.io/eslint-plugin-svelte/rules/valid-compile/) | disallow warnings when compiling. | :star: |

## Security Vulnerability
Expand Down
1 change: 1 addition & 0 deletions docs/rules.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ These rules relate to possible syntax or logic errors in Svelte code:
| [@ota-meshi/svelte/no-dynamic-slot-name](./rules/no-dynamic-slot-name.md) | disallow dynamic slot name | :star::wrench: |
| [@ota-meshi/svelte/no-not-function-handler](./rules/no-not-function-handler.md) | disallow use of not function in event handler | :star: |
| [@ota-meshi/svelte/no-object-in-text-mustaches](./rules/no-object-in-text-mustaches.md) | disallow objects in text mustache interpolation | :star: |
| [@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: |
| [@ota-meshi/svelte/valid-compile](./rules/valid-compile.md) | disallow warnings when compiling. | :star: |

## Security Vulnerability
Expand Down
51 changes: 51 additions & 0 deletions docs/rules/no-shorthand-style-property-overrides.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
---
pageClass: "rule-details"
sidebarDepth: 0
title: "@ota-meshi/svelte/no-shorthand-style-property-overrides"
description: "disallow shorthand style properties that override related longhand properties"
---

# @ota-meshi/svelte/no-shorthand-style-property-overrides

> disallow shorthand style properties that override related longhand properties

- :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 `"plugin:@ota-meshi/svelte/recommended"`.

## :book: Rule Details

This rule reports when a shorthand style property overrides a previously defined longhand property.

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/).

<ESLintCodeBlock>

<!--eslint-skip-->

```svelte
<script>
/* eslint @ota-meshi/svelte/no-shorthand-style-property-overrides: "error" */
let red = "red"
</script>

<!-- ✓ GOOD -->
<div style:background-repeat="repeat" style:background-color="green">...</div>
<div style="background-repeat: repeat; background-color: {red};">...</div>
<div style:background-repeat="repeat" style="background-color: {red}">...</div>

<!-- ✗ BAD -->
<div style:background-repeat="repeat" style:background="green">...</div>
<div style="background-repeat: repeat; background: {red};">...</div>
<div style:background-repeat="repeat" style="background: {red}">...</div>
```

</ESLintCodeBlock>

## :wrench: Options

Nothing.

## :mag: Implementation

- [Rule source](https://github.com/ota-meshi/eslint-plugin-svelte/blob/main/src/rules/no-shorthand-style-property-overrides.ts)
- [Test source](https://github.com/ota-meshi/eslint-plugin-svelte/blob/main/tests/src/rules/no-shorthand-style-property-overrides.ts)
1 change: 1 addition & 0 deletions src/configs/recommended.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export = {
"@ota-meshi/svelte/no-inner-declarations": "error",
"@ota-meshi/svelte/no-not-function-handler": "error",
"@ota-meshi/svelte/no-object-in-text-mustaches": "error",
"@ota-meshi/svelte/no-shorthand-style-property-overrides": "error",
"@ota-meshi/svelte/no-unused-svelte-ignore": "error",
"@ota-meshi/svelte/system": "error",
"@ota-meshi/svelte/valid-compile": "error",
Expand Down
121 changes: 121 additions & 0 deletions src/rules/no-shorthand-style-property-overrides.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
import type { AST } from "svelte-eslint-parser"
import { createRule } from "../utils"
import type { SvelteStyleRoot } from "../utils/css-utils"
import {
getVendorPrefix,
stripVendorPrefix,
parseStyleAttributeValue,
SHORTHAND_PROPERTIES,
} from "../utils/css-utils"

export default createRule("no-shorthand-style-property-overrides", {
meta: {
docs: {
description:
"disallow shorthand style properties that override related longhand properties",
category: "Possible Errors",
recommended: true,
},
schema: [],
messages: {
unexpected: "Unexpected shorthand '{{shorthand}}' after '{{original}}'.",
},
type: "problem",
},
create(context) {
type StyleDecl = {
prop: string
loc: AST.SourceLocation
}
type StyleDeclSet = {
decls: StyleDecl[]
}

return {
SvelteStartTag(node: AST.SvelteStartTag) {
const beforeDeclarations = new Set<string>()
for (const { decls } of iterateStyleDeclSetFromAttrs(node.attributes)) {
for (const decl of decls) {
const normalized = stripVendorPrefix(decl.prop)
const prefix = getVendorPrefix(decl.prop)

const longhandProps = SHORTHAND_PROPERTIES.get(normalized)
if (!longhandProps) {
continue
}

for (const longhandProp of longhandProps) {
const longhandPropWithPrefix = prefix + longhandProp
if (!beforeDeclarations.has(longhandPropWithPrefix)) {
continue
}

context.report({
node,
loc: decl.loc,
messageId: "unexpected",
data: {
shorthand: decl.prop,
original: longhandPropWithPrefix,
},
})
}
}
for (const decl of decls) {
beforeDeclarations.add(decl.prop)
}
}
},
}

/** Iterate the style decl set from attrs */
function* iterateStyleDeclSetFromAttrs(
attrs: AST.SvelteStartTag["attributes"],
): Iterable<StyleDeclSet> {
for (const attr of attrs) {
if (attr.type === "SvelteStyleDirective") {
yield {
decls: [{ prop: attr.key.name.name, loc: attr.key.name.loc! }],
}
} else if (attr.type === "SvelteAttribute") {
if (attr.key.name !== "style") {
continue
}
const root = parseStyleAttributeValue(attr, context)
if (!root) {
continue
}
yield* iterateStyleDeclSetFromStyleRoot(root)
}
}
}

/** Iterate the style decl set from style root */
function* iterateStyleDeclSetFromStyleRoot(
root: SvelteStyleRoot,
): Iterable<StyleDeclSet> {
for (const child of root.nodes) {
if (child.type === "decl") {
yield {
decls: [
{
prop: child.prop.name,
get loc() {
return child.prop.loc
},
},
],
}
} else if (child.type === "inline") {
const decls: StyleDecl[] = []
for (const root of child.getAllInlineStyles().values()) {
for (const set of iterateStyleDeclSetFromStyleRoot(root)) {
decls.push(...set.decls)
}
}
yield { decls }
}
}
}
},
})
Loading