Skip to content

Commit 47248d0

Browse files
authored
Merge branch 'main' into eslint-compat-utils
2 parents c24bc8d + ff28fd3 commit 47248d0

23 files changed

+226
-8
lines changed

.changeset/happy-monkeys-grin.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'eslint-plugin-svelte': minor
3+
---
4+
5+
feat: added the no-inline-styles rule

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# eslint-plugin-svelte
22

3+
## 2.34.1
4+
5+
### Patch Changes
6+
7+
- [#604](https://github.com/sveltejs/eslint-plugin-svelte/pull/604) [`796c0ad`](https://github.com/sveltejs/eslint-plugin-svelte/commit/796c0ad5f71dd927989caea109027e1735202c3b) Thanks [@ota-meshi](https://github.com/ota-meshi)! - fix: false positives for custom-element with svelte v3 in `svelte/valid-compile`
8+
39
## 2.34.0
410

511
### Minor Changes

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -342,6 +342,7 @@ These rules relate to better ways of doing things to help you avoid problems:
342342
| [svelte/no-at-debug-tags](https://sveltejs.github.io/eslint-plugin-svelte/rules/no-at-debug-tags/) | disallow the use of `{@debug}` | :star: |
343343
| [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. | |
344344
| [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. | |
345+
| [svelte/no-inline-styles](https://sveltejs.github.io/eslint-plugin-svelte/rules/no-inline-styles/) | disallow attributes and directives that produce inline styles | |
345346
| [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: |
346347
| [svelte/no-reactive-literals](https://sveltejs.github.io/eslint-plugin-svelte/rules/no-reactive-literals/) | don't assign literal values in reactive statements | :bulb: |
347348
| [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 | |

docs/rules.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ These rules relate to better ways of doing things to help you avoid problems:
5555
| [svelte/no-at-debug-tags](./rules/no-at-debug-tags.md) | disallow the use of `{@debug}` | :star: |
5656
| [svelte/no-ignored-unsubscribe](./rules/no-ignored-unsubscribe.md) | disallow ignoring the unsubscribe method returned by the `subscribe()` on Svelte stores. | |
5757
| [svelte/no-immutable-reactive-statements](./rules/no-immutable-reactive-statements.md) | disallow reactive statements that don't reference reactive values. | |
58+
| [svelte/no-inline-styles](./rules/no-inline-styles.md) | disallow attributes and directives that produce inline styles | |
5859
| [svelte/no-reactive-functions](./rules/no-reactive-functions.md) | it's not necessary to define functions in reactive statements | :bulb: |
5960
| [svelte/no-reactive-literals](./rules/no-reactive-literals.md) | don't assign literal values in reactive statements | :bulb: |
6061
| [svelte/no-unused-class-name](./rules/no-unused-class-name.md) | disallow the use of a class in the template without a corresponding style | |

docs/rules/no-inline-styles.md

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
---
2+
pageClass: 'rule-details'
3+
sidebarDepth: 0
4+
title: 'svelte/no-inline-styles'
5+
description: 'disallow attributes and directives that produce inline styles'
6+
---
7+
8+
# svelte/no-inline-styles
9+
10+
> disallow attributes and directives that produce inline styles
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+
14+
## :book: Rule Details
15+
16+
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.
17+
18+
<ESLintCodeBlock>
19+
20+
<!--eslint-skip-->
21+
22+
```svelte
23+
<script>
24+
/* eslint svelte/no-inline-styles: "error" */
25+
26+
import { fade } from 'svelte/transition';
27+
28+
export let classTwo;
29+
export let blockDisplay;
30+
</script>
31+
32+
<!-- ✓ GOOD -->
33+
<span class="one">Hello World!</span>
34+
35+
<span class:two={classTwo}>Hello World!</span>
36+
37+
<!-- ✗ BAD -->
38+
<span style="display: block;">Hello World!</span>
39+
40+
<span style:display={blockDisplay ? 'block' : 'inline'}>Hello World!</span>
41+
42+
<span transition:fade>Hello World!</span>
43+
```
44+
45+
</ESLintCodeBlock>
46+
47+
## :wrench: Options
48+
49+
```json
50+
{
51+
"svelte/no-inline-styles": [
52+
"error",
53+
{
54+
"allowTransitions": false
55+
}
56+
]
57+
}
58+
```
59+
60+
- `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`.
61+
62+
## :books: Further Reading
63+
64+
- [CSP documentation](https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP)
65+
66+
## :mag: Implementation
67+
68+
- [Rule source](https://github.com/sveltejs/eslint-plugin-svelte/blob/main/src/rules/no-inline-styles.ts)
69+
- [Test source](https://github.com/sveltejs/eslint-plugin-svelte/blob/main/tests/src/rules/no-inline-styles.ts)

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "eslint-plugin-svelte",
3-
"version": "2.34.0",
3+
"version": "2.34.1",
44
"description": "ESLint plugin for Svelte using AST",
55
"repository": "git+https://github.com/sveltejs/eslint-plugin-svelte.git",
66
"homepage": "https://sveltejs.github.io/eslint-plugin-svelte",
@@ -163,7 +163,7 @@
163163
"source-map-js": "^1.0.2",
164164
"stylelint": "^15.0.0",
165165
"stylelint-config-standard": "^34.0.0",
166-
"stylus": "^0.60.0",
166+
"stylus": "^0.61.0",
167167
"svelte": "^4.0.0",
168168
"svelte-adapter-ghpages": "0.1.0",
169169
"svelte-i18n": "^4.0.0",

src/meta.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,4 @@
22
// This file has been automatically generated,
33
// in order to update its content execute "pnpm run update"
44
export const name = 'eslint-plugin-svelte' as const;
5-
export const version = '2.34.0' as const;
5+
export const version = '2.34.1' as const;

src/rules/no-inline-styles.ts

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import { createRule } from '../utils';
2+
3+
export default createRule('no-inline-styles', {
4+
meta: {
5+
docs: {
6+
description: 'disallow attributes and directives that produce inline styles',
7+
category: 'Best Practices',
8+
recommended: false
9+
},
10+
schema: [
11+
{
12+
type: 'object',
13+
properties: {
14+
allowTransitions: {
15+
type: 'boolean'
16+
}
17+
},
18+
additionalProperties: false
19+
}
20+
],
21+
messages: {
22+
hasStyleAttribute: 'Found disallowed style attribute.',
23+
hasStyleDirective: 'Found disallowed style directive.',
24+
hasTransition: 'Found disallowed transition.'
25+
},
26+
type: 'suggestion'
27+
},
28+
create(context) {
29+
const allowTransitions: boolean = context.options[0]?.allowTransitions ?? false;
30+
return {
31+
SvelteElement(node) {
32+
if (node.kind !== 'html') {
33+
return;
34+
}
35+
for (const attribute of node.startTag.attributes) {
36+
if (attribute.type === 'SvelteStyleDirective') {
37+
context.report({ loc: attribute.loc, messageId: 'hasStyleDirective' });
38+
}
39+
if (attribute.type === 'SvelteAttribute' && attribute.key.name === 'style') {
40+
context.report({ loc: attribute.loc, messageId: 'hasStyleAttribute' });
41+
}
42+
if (
43+
!allowTransitions &&
44+
attribute.type === 'SvelteDirective' &&
45+
attribute.kind === 'Transition'
46+
) {
47+
context.report({ loc: attribute.loc, messageId: 'hasTransition' });
48+
}
49+
}
50+
}
51+
};
52+
}
53+
});

src/shared/svelte-compile-warns/index.ts

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import { transform as transformWithStylus } from './transform/stylus';
1515
import type { IgnoreItem } from './ignore-comment';
1616
import { getSvelteIgnoreItems } from './ignore-comment';
1717
import { extractLeadingComments } from './extract-leading-comments';
18-
import { getLangValue } from '../../utils/ast-utils';
18+
import { findAttribute, getLangValue } from '../../utils/ast-utils';
1919
import path from 'path';
2020
import fs from 'fs';
2121
import semver from 'semver';
@@ -116,7 +116,7 @@ function getSvelteCompileWarningsWithoutCache(context: RuleContext): SvelteCompi
116116
transformResults.push(...transformScripts(context, text));
117117

118118
if (!transformResults.length) {
119-
const warnings = getWarningsFromCode(text);
119+
const warnings = getWarningsFromCode(text, context);
120120
return {
121121
...processIgnore(
122122
warnings.warnings,
@@ -297,7 +297,7 @@ function getSvelteCompileWarningsWithoutCache(context: RuleContext): SvelteCompi
297297
}
298298

299299
const code = remapContext.postprocess();
300-
const baseWarnings = getWarningsFromCode(code);
300+
const baseWarnings = getWarningsFromCode(code, context);
301301

302302
const warnings: Warning[] = [];
303303
for (const warn of baseWarnings.warnings) {
@@ -402,17 +402,37 @@ function* transformScripts(context: RuleContext, text: string) {
402402
}
403403
}
404404

405+
function hasTagOption(program: AST.SvelteProgram) {
406+
return program.body.some((body) => {
407+
if (body.type !== 'SvelteElement' || body.kind !== 'special') {
408+
return false;
409+
}
410+
if (body.name.name !== 'svelte:options') {
411+
return false;
412+
}
413+
414+
return Boolean(findAttribute(body, 'tag'));
415+
});
416+
}
417+
405418
/**
406419
* Get compile warnings
407420
*/
408-
function getWarningsFromCode(code: string): {
421+
function getWarningsFromCode(
422+
code: string,
423+
context: RuleContext
424+
): {
409425
warnings: Warning[];
410426
kind: 'warn' | 'error';
411427
} {
412428
try {
413429
const result = compiler.compile(code, {
414430
generate: false,
415-
...(semver.satisfies(compiler.VERSION, '>=4.0.0-0') ? { customElement: true } : {})
431+
...(semver.satisfies(compiler.VERSION, '>=4.0.0-0')
432+
? { customElement: true }
433+
: hasTagOption(context.getSourceCode().ast)
434+
? { customElement: true }
435+
: {})
416436
});
417437

418438
return { warnings: result.warnings as Warning[], kind: 'warn' };

src/utils/rules.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import noExportLoadInSvelteModuleInKitPages from '../rules/no-export-load-in-sve
2929
import noExtraReactiveCurlies from '../rules/no-extra-reactive-curlies';
3030
import noIgnoredUnsubscribe from '../rules/no-ignored-unsubscribe';
3131
import noImmutableReactiveStatements from '../rules/no-immutable-reactive-statements';
32+
import noInlineStyles from '../rules/no-inline-styles';
3233
import noInnerDeclarations from '../rules/no-inner-declarations';
3334
import noNotFunctionHandler from '../rules/no-not-function-handler';
3435
import noObjectInTextMustaches from '../rules/no-object-in-text-mustaches';
@@ -91,6 +92,7 @@ export const rules = [
9192
noExtraReactiveCurlies,
9293
noIgnoredUnsubscribe,
9394
noImmutableReactiveStatements,
95+
noInlineStyles,
9496
noInnerDeclarations,
9597
noNotFunctionHandler,
9698
noObjectInTextMustaches,
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
- message: Found disallowed style attribute.
2+
line: 1
3+
column: 7
4+
suggestions: null
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
<span style="display: block;">Hello World!</span>
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
- message: Found disallowed style directive.
2+
line: 5
3+
column: 7
4+
suggestions: null
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<script>
2+
export let block;
3+
</script>
4+
5+
<span style:display={block ? 'block' : 'inline-block'}>Hello World!</span>
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
- message: Found disallowed transition.
2+
line: 5
3+
column: 7
4+
suggestions: null
5+
- message: Found disallowed transition.
6+
line: 7
7+
column: 7
8+
suggestions: null
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<script>
2+
import { fade, fly } from 'svelte/transition';
3+
</script>
4+
5+
<span transition:fade>Hello World!</span>
6+
7+
<span transition:fly={{ y: 200, duration: 2000 }}>Hello World!</span>
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"options": [
3+
{
4+
"allowTransitions": true
5+
}
6+
]
7+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<script>
2+
import { fade, fly } from 'svelte/transition';
3+
</script>
4+
5+
<span transition:fade>Hello World!</span>
6+
7+
<span transition:fly={{ y: 200, duration: 2000 }}>Hello World!</span>
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
<span class="my-class">Hello World!</span>
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
<span class:one={true}>Hello World!</span>
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
<svelte:options tag="my-component" />
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"svelte": "^3.0.0"
3+
}

tests/src/rules/no-inline-styles.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { RuleTester } from 'eslint';
2+
import rule from '../../../src/rules/no-inline-styles';
3+
import { loadTestCases } from '../../utils/utils';
4+
5+
const tester = new RuleTester({
6+
parserOptions: {
7+
ecmaVersion: 2020,
8+
sourceType: 'module'
9+
}
10+
});
11+
12+
tester.run('no-inline-styles', rule as any, loadTestCases('no-inline-styles'));

0 commit comments

Comments
 (0)