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'));