Skip to content

Commit

Permalink
feat: added the no-inline-styles rule (#608)
Browse files Browse the repository at this point in the history
  • Loading branch information
marekdedic committed Nov 6, 2023
1 parent 318b9bf commit ff28fd3
Show file tree
Hide file tree
Showing 17 changed files with 188 additions and 0 deletions.
5 changes: 5 additions & 0 deletions .changeset/happy-monkeys-grin.md
@@ -0,0 +1,5 @@
---
'eslint-plugin-svelte': minor
---

feat: added the no-inline-styles rule
1 change: 1 addition & 0 deletions README.md
Expand Up @@ -342,6 +342,7 @@ These rules relate to better ways of doing things to help you avoid problems:
| [svelte/no-at-debug-tags](https://sveltejs.github.io/eslint-plugin-svelte/rules/no-at-debug-tags/) | disallow the use of `{@debug}` | :star: |
| [svelte/no-ignored-unsubscribe](https://sveltejs.github.io/eslint-plugin-svelte/rules/no-ignored-unsubscribe/) | disallow ignoring the unsubscribe method returned by the `subscribe()` on Svelte stores. | |
| [svelte/no-immutable-reactive-statements](https://sveltejs.github.io/eslint-plugin-svelte/rules/no-immutable-reactive-statements/) | disallow reactive statements that don't reference reactive values. | |
| [svelte/no-inline-styles](https://sveltejs.github.io/eslint-plugin-svelte/rules/no-inline-styles/) | disallow attributes and directives that produce inline styles | |
| [svelte/no-reactive-functions](https://sveltejs.github.io/eslint-plugin-svelte/rules/no-reactive-functions/) | it's not necessary to define functions in reactive statements | :bulb: |
| [svelte/no-reactive-literals](https://sveltejs.github.io/eslint-plugin-svelte/rules/no-reactive-literals/) | don't assign literal values in reactive statements | :bulb: |
| [svelte/no-unused-class-name](https://sveltejs.github.io/eslint-plugin-svelte/rules/no-unused-class-name/) | disallow the use of a class in the template without a corresponding style | |
Expand Down
1 change: 1 addition & 0 deletions docs/rules.md
Expand Up @@ -55,6 +55,7 @@ These rules relate to better ways of doing things to help you avoid problems:
| [svelte/no-at-debug-tags](./rules/no-at-debug-tags.md) | disallow the use of `{@debug}` | :star: |
| [svelte/no-ignored-unsubscribe](./rules/no-ignored-unsubscribe.md) | disallow ignoring the unsubscribe method returned by the `subscribe()` on Svelte stores. | |
| [svelte/no-immutable-reactive-statements](./rules/no-immutable-reactive-statements.md) | disallow reactive statements that don't reference reactive values. | |
| [svelte/no-inline-styles](./rules/no-inline-styles.md) | disallow attributes and directives that produce inline styles | |
| [svelte/no-reactive-functions](./rules/no-reactive-functions.md) | it's not necessary to define functions in reactive statements | :bulb: |
| [svelte/no-reactive-literals](./rules/no-reactive-literals.md) | don't assign literal values in reactive statements | :bulb: |
| [svelte/no-unused-class-name](./rules/no-unused-class-name.md) | disallow the use of a class in the template without a corresponding style | |
Expand Down
69 changes: 69 additions & 0 deletions docs/rules/no-inline-styles.md
@@ -0,0 +1,69 @@
---
pageClass: 'rule-details'
sidebarDepth: 0
title: 'svelte/no-inline-styles'
description: 'disallow attributes and directives that produce inline styles'
---

# svelte/no-inline-styles

> disallow attributes and directives that produce inline styles
- :exclamation: <badge text="This rule has not been released yet." vertical="middle" type="error"> **_This rule has not been released yet._** </badge>

## :book: Rule Details

This rule reports all attributes and directives that would compile to inline styles. This is mainly useful when adding Content Security Policy to your app, as having inline styles requires the `style-src: 'unsafe-inline'` directive, which is generally discouraged and unsafe.

<ESLintCodeBlock>

<!--eslint-skip-->

```svelte
<script>
/* eslint svelte/no-inline-styles: "error" */
import { fade } from 'svelte/transition';
export let classTwo;
export let blockDisplay;
</script>
<!-- ✓ GOOD -->
<span class="one">Hello World!</span>
<span class:two={classTwo}>Hello World!</span>
<!-- ✗ BAD -->
<span style="display: block;">Hello World!</span>
<span style:display={blockDisplay ? 'block' : 'inline'}>Hello World!</span>
<span transition:fade>Hello World!</span>
```

</ESLintCodeBlock>

## :wrench: Options

```json
{
"svelte/no-inline-styles": [
"error",
{
"allowTransitions": false
}
]
}
```

- `allowTransitions` ... Most svelte transitions (including the built-in ones) use inline styles. However, it is theoretically possible to only use transitions that don't (see this [issue](https://github.com/sveltejs/svelte/issues/6662) about removing inline styles from built-in transitions). This option allows transitions to be used in such cases. Default `false`.

## :books: Further Reading

- [CSP documentation](https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP)

## :mag: Implementation

- [Rule source](https://github.com/sveltejs/eslint-plugin-svelte/blob/main/src/rules/no-inline-styles.ts)
- [Test source](https://github.com/sveltejs/eslint-plugin-svelte/blob/main/tests/src/rules/no-inline-styles.ts)
53 changes: 53 additions & 0 deletions src/rules/no-inline-styles.ts
@@ -0,0 +1,53 @@
import { createRule } from '../utils';

export default createRule('no-inline-styles', {
meta: {
docs: {
description: 'disallow attributes and directives that produce inline styles',
category: 'Best Practices',
recommended: false
},
schema: [
{
type: 'object',
properties: {
allowTransitions: {
type: 'boolean'
}
},
additionalProperties: false
}
],
messages: {
hasStyleAttribute: 'Found disallowed style attribute.',
hasStyleDirective: 'Found disallowed style directive.',
hasTransition: 'Found disallowed transition.'
},
type: 'suggestion'
},
create(context) {
const allowTransitions: boolean = context.options[0]?.allowTransitions ?? false;
return {
SvelteElement(node) {
if (node.kind !== 'html') {
return;
}
for (const attribute of node.startTag.attributes) {
if (attribute.type === 'SvelteStyleDirective') {
context.report({ loc: attribute.loc, messageId: 'hasStyleDirective' });
}
if (attribute.type === 'SvelteAttribute' && attribute.key.name === 'style') {
context.report({ loc: attribute.loc, messageId: 'hasStyleAttribute' });
}
if (
!allowTransitions &&
attribute.type === 'SvelteDirective' &&
attribute.kind === 'Transition'
) {
context.report({ loc: attribute.loc, messageId: 'hasTransition' });
}
}
}
};
}
});
2 changes: 2 additions & 0 deletions src/utils/rules.ts
Expand Up @@ -29,6 +29,7 @@ import noExportLoadInSvelteModuleInKitPages from '../rules/no-export-load-in-sve
import noExtraReactiveCurlies from '../rules/no-extra-reactive-curlies';
import noIgnoredUnsubscribe from '../rules/no-ignored-unsubscribe';
import noImmutableReactiveStatements from '../rules/no-immutable-reactive-statements';
import noInlineStyles from '../rules/no-inline-styles';
import noInnerDeclarations from '../rules/no-inner-declarations';
import noNotFunctionHandler from '../rules/no-not-function-handler';
import noObjectInTextMustaches from '../rules/no-object-in-text-mustaches';
Expand Down Expand Up @@ -91,6 +92,7 @@ export const rules = [
noExtraReactiveCurlies,
noIgnoredUnsubscribe,
noImmutableReactiveStatements,
noInlineStyles,
noInnerDeclarations,
noNotFunctionHandler,
noObjectInTextMustaches,
Expand Down
@@ -0,0 +1,4 @@
- message: Found disallowed style attribute.
line: 1
column: 7
suggestions: null
@@ -0,0 +1 @@
<span style="display: block;">Hello World!</span>
@@ -0,0 +1,4 @@
- message: Found disallowed style directive.
line: 5
column: 7
suggestions: null
@@ -0,0 +1,5 @@
<script>
export let block;
</script>

<span style:display={block ? 'block' : 'inline-block'}>Hello World!</span>
@@ -0,0 +1,8 @@
- message: Found disallowed transition.
line: 5
column: 7
suggestions: null
- message: Found disallowed transition.
line: 7
column: 7
suggestions: null
@@ -0,0 +1,7 @@
<script>
import { fade, fly } from 'svelte/transition';
</script>

<span transition:fade>Hello World!</span>

<span transition:fly={{ y: 200, duration: 2000 }}>Hello World!</span>
@@ -0,0 +1,7 @@
{
"options": [
{
"allowTransitions": true
}
]
}
@@ -0,0 +1,7 @@
<script>
import { fade, fly } from 'svelte/transition';
</script>

<span transition:fade>Hello World!</span>

<span transition:fly={{ y: 200, duration: 2000 }}>Hello World!</span>
@@ -0,0 +1 @@
<span class="my-class">Hello World!</span>
@@ -0,0 +1 @@
<span class:one={true}>Hello World!</span>
12 changes: 12 additions & 0 deletions tests/src/rules/no-inline-styles.ts
@@ -0,0 +1,12 @@
import { RuleTester } from 'eslint';
import rule from '../../../src/rules/no-inline-styles';
import { loadTestCases } from '../../utils/utils';

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

tester.run('no-inline-styles', rule as any, loadTestCases('no-inline-styles'));

0 comments on commit ff28fd3

Please sign in to comment.