From 067e90c3885f5359e59b67ff2d2b27715ee20c02 Mon Sep 17 00:00:00 2001 From: Dongwon Lee Date: Thu, 27 Jun 2024 19:00:53 +0900 Subject: [PATCH] Add rules, test codes --- .node-version | 1 + README.md | 1 + docs/rules.md | 1 + docs/rules/no-undefined-print.md | 56 +++++++++++++++ .../src/configs/flat/recommended.ts | 1 + .../src/configs/recommended.ts | 1 + .../eslint-plugin-svelte/src/rule-types.ts | 5 ++ .../src/rules/no-undefined-print.ts | 68 +++++++++++++++++++ .../eslint-plugin-svelte/src/utils/rules.ts | 2 + .../invalid/html-errors.yaml | 4 ++ .../invalid/html-input.svelte | 5 ++ .../valid/debug01-input.svelte | 13 ++++ .../valid/debug02-input.svelte | 13 ++++ .../valid/html-input.svelte | 5 ++ .../valid/text-mustash-input.svelte | 5 ++ .../tests/src/rules/no-undefined-print.ts | 12 ++++ 16 files changed, 193 insertions(+) create mode 100644 .node-version create mode 100644 docs/rules/no-undefined-print.md create mode 100644 packages/eslint-plugin-svelte/src/rules/no-undefined-print.ts create mode 100644 packages/eslint-plugin-svelte/tests/fixtures/rules/no-undefined-print/invalid/html-errors.yaml create mode 100644 packages/eslint-plugin-svelte/tests/fixtures/rules/no-undefined-print/invalid/html-input.svelte create mode 100644 packages/eslint-plugin-svelte/tests/fixtures/rules/no-undefined-print/valid/debug01-input.svelte create mode 100644 packages/eslint-plugin-svelte/tests/fixtures/rules/no-undefined-print/valid/debug02-input.svelte create mode 100644 packages/eslint-plugin-svelte/tests/fixtures/rules/no-undefined-print/valid/html-input.svelte create mode 100644 packages/eslint-plugin-svelte/tests/fixtures/rules/no-undefined-print/valid/text-mustash-input.svelte create mode 100644 packages/eslint-plugin-svelte/tests/src/rules/no-undefined-print.ts diff --git a/.node-version b/.node-version new file mode 100644 index 000000000..3876fd498 --- /dev/null +++ b/.node-version @@ -0,0 +1 @@ +18.16.1 diff --git a/README.md b/README.md index 9a6c25600..f18b8f27e 100644 --- a/README.md +++ b/README.md @@ -392,6 +392,7 @@ These rules relate to possible syntax or logic errors in Svelte code: | [svelte/no-reactive-reassign](https://sveltejs.github.io/eslint-plugin-svelte/rules/no-reactive-reassign/) | disallow reassigning reactive values | | | [svelte/no-shorthand-style-property-overrides](https://sveltejs.github.io/eslint-plugin-svelte/rules/no-shorthand-style-property-overrides/) | disallow shorthand style properties that override related longhand properties | :star: | | [svelte/no-store-async](https://sveltejs.github.io/eslint-plugin-svelte/rules/no-store-async/) | disallow using async/await inside svelte stores because it causes issues with the auto-unsubscribing features | | +| [svelte/no-undefined-print](https://sveltejs.github.io/eslint-plugin-svelte/rules/no-undefined-print/) | Disallow from printing `undefined` | :star: | | [svelte/no-unknown-style-directive-property](https://sveltejs.github.io/eslint-plugin-svelte/rules/no-unknown-style-directive-property/) | disallow unknown `style:property` | :star: | | [svelte/require-store-callbacks-use-set-param](https://sveltejs.github.io/eslint-plugin-svelte/rules/require-store-callbacks-use-set-param/) | store callbacks must use `set` param | | | [svelte/require-store-reactive-access](https://sveltejs.github.io/eslint-plugin-svelte/rules/require-store-reactive-access/) | disallow to use of the store itself as an operand. Need to use $ prefix or get function. | :wrench: | diff --git a/docs/rules.md b/docs/rules.md index 7f115da5c..4ae104785 100644 --- a/docs/rules.md +++ b/docs/rules.md @@ -29,6 +29,7 @@ These rules relate to possible syntax or logic errors in Svelte code: | [svelte/no-reactive-reassign](./rules/no-reactive-reassign.md) | disallow reassigning reactive values | | | [svelte/no-shorthand-style-property-overrides](./rules/no-shorthand-style-property-overrides.md) | disallow shorthand style properties that override related longhand properties | :star: | | [svelte/no-store-async](./rules/no-store-async.md) | disallow using async/await inside svelte stores because it causes issues with the auto-unsubscribing features | | +| [svelte/no-undefined-print](./rules/no-undefined-print.md) | Disallow from printing `undefined` | :star: | | [svelte/no-unknown-style-directive-property](./rules/no-unknown-style-directive-property.md) | disallow unknown `style:property` | :star: | | [svelte/require-store-callbacks-use-set-param](./rules/require-store-callbacks-use-set-param.md) | store callbacks must use `set` param | | | [svelte/require-store-reactive-access](./rules/require-store-reactive-access.md) | disallow to use of the store itself as an operand. Need to use $ prefix or get function. | :wrench: | diff --git a/docs/rules/no-undefined-print.md b/docs/rules/no-undefined-print.md new file mode 100644 index 000000000..07e946470 --- /dev/null +++ b/docs/rules/no-undefined-print.md @@ -0,0 +1,56 @@ +--- +pageClass: 'rule-details' +sidebarDepth: 0 +title: 'svelte/no-undefined-print' +description: 'Disallow from printing `undefined`' +since: 'v0.0.1' +--- + +# svelte/no-undefined-print + +> Disallow from printing `undefined` + +- :gear: This rule is included in `"plugin:svelte/recommended"`. + +## :book: Rule Details + +This rule reports all uses of `{@html}` in order to reduce the risk of injecting potentially unsafe / unescaped html into the browser leading to Cross-Site Scripting (XSS) attacks. + + + + + +```svelte + + + +{foo} + + +{@html foo} +``` + + + +## :wrench: Options + +Nothing. + +## :mute: When Not To Use It + +If you are certain the content passed to `{@html}` is sanitized HTML you can disable this rule. + +## :books: Further Reading + +- [Svelte - Tutorial > 1. Introduction / HTML tags](https://svelte.dev/tutorial/html-tags) + +## :rocket: Version + +This rule was introduced in eslint-plugin-svelte v0.0.1 + +## :mag: Implementation + +- [Rule source](https://github.com/sveltejs/eslint-plugin-svelte/blob/main/src/rules/no-undefined-print.ts) +- [Test source](https://github.com/sveltejs/eslint-plugin-svelte/blob/main/tests/src/rules/no-undefined-print.ts) diff --git a/packages/eslint-plugin-svelte/src/configs/flat/recommended.ts b/packages/eslint-plugin-svelte/src/configs/flat/recommended.ts index eff2aa8d4..1fb137f0f 100644 --- a/packages/eslint-plugin-svelte/src/configs/flat/recommended.ts +++ b/packages/eslint-plugin-svelte/src/configs/flat/recommended.ts @@ -19,6 +19,7 @@ const config: Linter.FlatConfig[] = [ 'svelte/no-not-function-handler': 'error', 'svelte/no-object-in-text-mustaches': 'error', 'svelte/no-shorthand-style-property-overrides': 'error', + 'svelte/no-undefined-print': 'error', 'svelte/no-unknown-style-directive-property': 'error', 'svelte/no-unused-svelte-ignore': 'error', 'svelte/system': 'error', diff --git a/packages/eslint-plugin-svelte/src/configs/recommended.ts b/packages/eslint-plugin-svelte/src/configs/recommended.ts index 5e53547be..5eecdcca6 100644 --- a/packages/eslint-plugin-svelte/src/configs/recommended.ts +++ b/packages/eslint-plugin-svelte/src/configs/recommended.ts @@ -19,6 +19,7 @@ const config: Linter.Config = { 'svelte/no-not-function-handler': 'error', 'svelte/no-object-in-text-mustaches': 'error', 'svelte/no-shorthand-style-property-overrides': 'error', + 'svelte/no-undefined-print': 'error', 'svelte/no-unknown-style-directive-property': 'error', 'svelte/no-unused-svelte-ignore': 'error', 'svelte/system': 'error', diff --git a/packages/eslint-plugin-svelte/src/rule-types.ts b/packages/eslint-plugin-svelte/src/rule-types.ts index 77d3c4ca4..0bb49ca87 100644 --- a/packages/eslint-plugin-svelte/src/rule-types.ts +++ b/packages/eslint-plugin-svelte/src/rule-types.ts @@ -224,6 +224,11 @@ export interface RuleOptions { * @see https://sveltejs.github.io/eslint-plugin-svelte/rules/no-trailing-spaces/ */ 'svelte/no-trailing-spaces'?: Linter.RuleEntry + /** + * Disallow from printing `undefined` + * @see https://sveltejs.github.io/eslint-plugin-svelte/rules/no-undefined-print/ + */ + 'svelte/no-undefined-print'?: Linter.RuleEntry<[]> /** * disallow unknown `style:property` * @see https://sveltejs.github.io/eslint-plugin-svelte/rules/no-unknown-style-directive-property/ diff --git a/packages/eslint-plugin-svelte/src/rules/no-undefined-print.ts b/packages/eslint-plugin-svelte/src/rules/no-undefined-print.ts new file mode 100644 index 000000000..0c4ad282a --- /dev/null +++ b/packages/eslint-plugin-svelte/src/rules/no-undefined-print.ts @@ -0,0 +1,68 @@ +import type { AST } from 'svelte-eslint-parser'; +import { createRule } from '../utils'; + +export default createRule('no-undefined-print', { + meta: { + docs: { + description: 'Disallow from printing `undefined`', + category: 'Possible Errors', + recommended: true + }, + schema: [], + messages: { + unexpected: 'Unexpected `undefined`.' + }, + type: 'problem' + }, + create(context) { + return { + 'SvelteMustacheTag[kind=text]'(node: AST.SvelteMustacheTag) { + if (node.expression.type === 'Identifier' && node.expression.name === 'undefined') { + context.report({ + node, + messageId: 'unexpected' + }); + } + + if (node.expression.type === 'LogicalExpression' && node.expression.operator === '||') { + const left = node.expression.left; + const right = node.expression.right; + + if (left.type === 'Identifier' && right.type === 'Literal' && right.value === undefined) { + context.report({ + node, + messageId: 'unexpected' + }); + } + } + + if (node.expression.type === 'LogicalExpression' && node.expression.operator === '??') { + const left = node.expression.left; + const right = node.expression.right; + + if (left.type === 'Identifier' && right.type === 'Literal' && right.value === undefined) { + context.report({ + node, + messageId: 'unexpected' + }); + } + } + + if (node.expression.type === 'ConditionalExpression') { + const consequent = node.expression.consequent; + const alternate = node.expression.alternate; + + if ( + (consequent.type === 'Literal' && consequent.value === undefined) || + (alternate.type === 'Literal' && alternate.value === undefined) + ) { + context.report({ + node, + messageId: 'unexpected' + }); + } + } + } + }; + } +}); diff --git a/packages/eslint-plugin-svelte/src/utils/rules.ts b/packages/eslint-plugin-svelte/src/utils/rules.ts index af0dd20e6..8afa581c1 100644 --- a/packages/eslint-plugin-svelte/src/utils/rules.ts +++ b/packages/eslint-plugin-svelte/src/utils/rules.ts @@ -44,6 +44,7 @@ import noStoreAsync from '../rules/no-store-async'; import noSvelteInternal from '../rules/no-svelte-internal'; import noTargetBlank from '../rules/no-target-blank'; import noTrailingSpaces from '../rules/no-trailing-spaces'; +import noUndefinedPrint from '../rules/no-undefined-print'; import noUnknownStyleDirectiveProperty from '../rules/no-unknown-style-directive-property'; import noUnusedClassName from '../rules/no-unused-class-name'; import noUnusedSvelteIgnore from '../rules/no-unused-svelte-ignore'; @@ -109,6 +110,7 @@ export const rules = [ noSvelteInternal, noTargetBlank, noTrailingSpaces, + noUndefinedPrint, noUnknownStyleDirectiveProperty, noUnusedClassName, noUnusedSvelteIgnore, diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-undefined-print/invalid/html-errors.yaml b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-undefined-print/invalid/html-errors.yaml new file mode 100644 index 000000000..94c681200 --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-undefined-print/invalid/html-errors.yaml @@ -0,0 +1,4 @@ +- message: 'Unexpected `undefined`aa.' + line: 52 + column: 44 + suggestions: null diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-undefined-print/invalid/html-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-undefined-print/invalid/html-input.svelte new file mode 100644 index 000000000..463eb8250 --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-undefined-print/invalid/html-input.svelte @@ -0,0 +1,5 @@ + + +

{@html string}

diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-undefined-print/valid/debug01-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-undefined-print/valid/debug01-input.svelte new file mode 100644 index 000000000..be49869cf --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-undefined-print/valid/debug01-input.svelte @@ -0,0 +1,13 @@ + + + + + +{@debug} + +

Hello {user.firstname}!

diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-undefined-print/valid/debug02-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-undefined-print/valid/debug02-input.svelte new file mode 100644 index 000000000..30bffc896 --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-undefined-print/valid/debug02-input.svelte @@ -0,0 +1,13 @@ + + + + + +{@debug user} + +

Hello {user.firstname}!

diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-undefined-print/valid/html-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-undefined-print/valid/html-input.svelte new file mode 100644 index 000000000..fe8941f03 --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-undefined-print/valid/html-input.svelte @@ -0,0 +1,5 @@ + + +

{string}

diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-undefined-print/valid/text-mustash-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-undefined-print/valid/text-mustash-input.svelte new file mode 100644 index 000000000..b9f65d36b --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-undefined-print/valid/text-mustash-input.svelte @@ -0,0 +1,5 @@ + + +

{string}

diff --git a/packages/eslint-plugin-svelte/tests/src/rules/no-undefined-print.ts b/packages/eslint-plugin-svelte/tests/src/rules/no-undefined-print.ts new file mode 100644 index 000000000..43d5504f3 --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/src/rules/no-undefined-print.ts @@ -0,0 +1,12 @@ +import { RuleTester } from '../../utils/eslint-compat'; +import rule from '../../../src/rules/no-undefined-print'; +import { loadTestCases } from '../../utils/utils'; + +const tester = new RuleTester({ + languageOptions: { + ecmaVersion: 2020, + sourceType: 'module' + } +}); + +tester.run('no-undefined-print', rule as any, loadTestCases('no-undefined-print'));