diff --git a/.changeset/unlucky-ducks-explain.md b/.changeset/unlucky-ducks-explain.md
new file mode 100644
index 000000000..1bf2da5ee
--- /dev/null
+++ b/.changeset/unlucky-ducks-explain.md
@@ -0,0 +1,5 @@
+---
+'eslint-plugin-svelte': minor
+---
+
+Added restrict-mustache-expressions rule to prevent surface area for bugs where non-stringifiable objects or arrays get turned into `[object Object]` which is almost never wanted behavior
diff --git a/README.md b/README.md
index 610a270d6..23e942ea2 100644
--- a/README.md
+++ b/README.md
@@ -1,7 +1,7 @@
# Introduction
-`eslint-plugin-svelte` is the official [ESLint] plugin for [Svelte].
-It provides many unique check rules by using the template AST.
+`eslint-plugin-svelte` is the official [ESLint] plugin for [Svelte].
+It provides many unique check rules by using the template AST.
You can check on the [Online DEMO](https://eslint-online-playground.netlify.app/#eslint-plugin-svelte%20with%20typescript).
**_We are working on experimental support for Svelte v5, but may break with new versions of Svelte v5._**
@@ -22,7 +22,7 @@ You can check on the [Online DEMO](https://eslint-online-playground.netlify.app/
## :name_badge: What is this plugin?
-[ESLint] plugin for [Svelte].
+[ESLint] plugin for [Svelte].
It provides many unique check rules using the AST generated by [svelte-eslint-parser].
### ❗ Attention
@@ -292,7 +292,7 @@ module.exports = {
#### settings.svelte.ignoreWarnings
-Specifies an array of rules that ignore reports in the template.
+Specifies an array of rules that ignore reports in the template.
For example, set rules on the template that cannot avoid false positives.
#### settings.svelte.compileOptions
@@ -367,8 +367,8 @@ Example **.vscode/settings.json**:
-:wrench: Indicates that the rule is fixable, and using `--fix` option on the [command line](https://eslint.org/docs/user-guide/command-line-interface#fixing-problems) can automatically fix some of the reported problems.
-:bulb: Indicates that some problems reported by the rule are manually fixable by editor [suggestions](https://eslint.org/docs/developer-guide/working-with-rules#providing-suggestions).
+:wrench: Indicates that the rule is fixable, and using `--fix` option on the [command line](https://eslint.org/docs/user-guide/command-line-interface#fixing-problems) can automatically fix some of the reported problems.
+:bulb: Indicates that some problems reported by the rule are manually fixable by editor [suggestions](https://eslint.org/docs/developer-guide/working-with-rules#providing-suggestions).
:star: Indicates that the rule is included in the `plugin:svelte/recommended` config.
@@ -395,6 +395,7 @@ These rules relate to possible syntax or logic errors in Svelte code:
| [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: |
+| [svelte/restrict-mustache-expressions](https://sveltejs.github.io/eslint-plugin-svelte/rules/restrict-mustache-expressions/) | disallow non-string values in string contexts | :star: |
| [svelte/valid-compile](https://sveltejs.github.io/eslint-plugin-svelte/rules/valid-compile/) | disallow warnings when compiling. | :star: |
| [svelte/valid-prop-names-in-kit-pages](https://sveltejs.github.io/eslint-plugin-svelte/rules/valid-prop-names-in-kit-pages/) | disallow props other than data or errors in SvelteKit page components. | |
diff --git a/docs/README.md b/docs/README.md
index 6bfbba28b..03cc74e99 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -4,8 +4,8 @@ title: 'eslint-plugin-svelte'
# Introduction
-`eslint-plugin-svelte` is the official [ESLint] plugin for [Svelte].
-It provides many unique check rules by using the template AST.
+`eslint-plugin-svelte` is the official [ESLint] plugin for [Svelte].
+It provides many unique check rules by using the template AST.
You can check on the [Online DEMO](https://eslint-online-playground.netlify.app/#eslint-plugin-svelte%20with%20typescript).
**_We are working on experimental support for Svelte v5, but may break with new versions of Svelte v5._**
@@ -26,7 +26,7 @@ You can check on the [Online DEMO](https://eslint-online-playground.netlify.app/
## :name_badge: What is this plugin?
-[ESLint] plugin for [Svelte].
+[ESLint] plugin for [Svelte].
It provides many unique check rules using the AST generated by [svelte-eslint-parser].
### ❗ Attention
diff --git a/docs/rules.md b/docs/rules.md
index 7f115da5c..397d03336 100644
--- a/docs/rules.md
+++ b/docs/rules.md
@@ -32,6 +32,7 @@ These rules relate to possible syntax or logic errors in Svelte code:
| [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: |
+| [svelte/restrict-mustache-expressions](./rules/restrict-mustache-expressions.md) | disallow non-string values in string contexts | :star: |
| [svelte/valid-compile](./rules/valid-compile.md) | disallow warnings when compiling. | :star: |
| [svelte/valid-prop-names-in-kit-pages](./rules/valid-prop-names-in-kit-pages.md) | disallow props other than data or errors in SvelteKit page components. | |
diff --git a/docs/rules/restrict-mustache-expressions.md b/docs/rules/restrict-mustache-expressions.md
new file mode 100644
index 000000000..d1d2f0ee3
--- /dev/null
+++ b/docs/rules/restrict-mustache-expressions.md
@@ -0,0 +1,204 @@
+---
+pageClass: 'rule-details'
+sidebarDepth: 0
+title: 'svelte/restrict-mustache-expressions'
+description: 'disallow non-string values in string contexts'
+---
+
+# svelte/restrict-mustache-expressions
+
+> disallow non-string values in string contexts
+
+- :exclamation: **_This rule has not been released yet._**
+- :gear: This rule is included in `"plugin:svelte/recommended"`.
+
+## :book: Rule Details
+
+JavaScript automatically converts an [object to a string](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String#string_coercion)
+in a string context, such as when concatenating strings or using them in a template string. The default toString() method of objects returns
+`[object Object]`. This is typically incorrect behavior.
+
+This rule prevents non-stringifiable values from being used in contexts where a string is expected.
+
+This rule is based off the [restrict-template-expressions](https://typescript-eslint.io/rules/restrict-template-expressions) rule, and it is recommended to be used
+with that rule, as this only performs checks on svelte template strings (eg: `foo`), and not on ``foo``.
+
+
+
+
+
+```svelte
+
+
+
+foo
+foo
+foo
+
+{123}
+{str}
+{true}
+
+
+foo
+foo
+foo
+foo
+
+{null}
+{undefined}
+{[1, 2, 3]}
+{not_stringifiable}
+```
+
+
+
+## :wrench: Options
+
+```json
+{
+ "svelte/restrict-mustache-expressions": ["error", {}]
+}
+```
+
+```ts
+type Options = {
+ // allows numbers in both svelte template literals and text expressions
+ allowNumbers?: boolean;
+ // allows booleans in both svelte template literals and text expressions
+ allowBooleans?: boolean;
+ // allows null in both svelte template literals and text expressions
+ allowNull?: boolean;
+ // allows undefined in both svelte template literals and text expressions
+ allowUndefined?: boolean;
+ // eg: {bar}
+ textExpressions?: {
+ // allows numbers in text expressions
+ allowNumbers?: boolean;
+ // allows booleans in text expressions
+ allowBooleans?: boolean;
+ // allows null in text expressions
+ allowNull?: boolean;
+ // allows undefined in text expressions
+ allowUndefined?: boolean;
+ };
+ // eg: foo
+ stringTemplateExpressions?: {
+ // allows numbers in string template expressions
+ allowNumbers?: boolean;
+ // allows booleans in string template expressions
+ allowBooleans?: boolean;
+ // allows null in string template expressions
+ allowNull?: boolean;
+ // allows undefined in string template expressions
+ allowUndefined?: boolean;
+ };
+};
+
+type DefaultOptions = {
+ allowNumbers: true;
+ allowBooleans: true;
+ allowNull: false;
+ allowUndefined: false;
+};
+```
+
+## More examples
+
+
+
+### Disallowing numbers
+
+
+
+```svelte
+
+
+
+foo
+{str}
+
+
+foo
+{123}
+```
+
+
+
+### Disallowing booleans
+
+
+
+
+
+```svelte
+
+
+
+{str}
+
+
+foo
+```
+
+
+
+### Disallowing numbers specifically for text expressions
+
+
+
+
+
+```svelte
+
+
+
+foo
+
+
+{123}
+```
+
+
+
+### Disallowing booleans specifically for string template expressions
+
+
+
+
+
+```svelte
+
+
+
+{true}
+
+
+foo
+```
+
+
+
+## :books: Further Reading
+
+- [no-base-to-string](https://typescript-eslint.io/rules/no-base-to-string)
+- [restrict-plus-operands](https://typescript-eslint.io/rules/restrict-plus-operands)
+- [restrict-template-expressions](https://typescript-eslint.io/rules/restrict-template-expressions)
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/sveltejs/eslint-plugin-svelte/blob/main/packages/eslint-plugin-svelte/src/rules/restrict-mustache-expressions.ts)
+- [Test source](https://github.com/sveltejs/eslint-plugin-svelte/blob/main/packages/eslint-plugin-svelte/tests/src/rules/restrict-mustache-expressions.ts)
diff --git a/docs/user-guide.md b/docs/user-guide.md
index 0818976de..7a3871dfc 100644
--- a/docs/user-guide.md
+++ b/docs/user-guide.md
@@ -243,7 +243,7 @@ module.exports = {
#### settings.svelte.ignoreWarnings
-Specifies an array of rules that ignore reports in the template.
+Specifies an array of rules that ignore reports in the template.
For example, set rules on the template that cannot avoid false positives.
#### settings.svelte.compileOptions
diff --git a/package.json b/package.json
index 342ab661f..e6eaee16d 100644
--- a/package.json
+++ b/package.json
@@ -15,34 +15,34 @@
},
"devDependencies": {
"@changesets/changelog-github": "^0.5.0",
- "@changesets/cli": "^2.27.5",
- "@changesets/get-release-plan": "^4.0.2",
+ "@changesets/cli": "^2.27.7",
+ "@changesets/get-release-plan": "^4.0.3",
"@eslint-community/eslint-plugin-eslint-comments": "^4.3.0",
- "@ota-meshi/eslint-plugin": "^0.17.1",
- "@types/eslint": "^8.56.10",
- "@typescript-eslint/eslint-plugin": "^7.13.0",
- "@typescript-eslint/parser": "^7.13.0",
+ "@ota-meshi/eslint-plugin": "^0.17.5",
+ "@types/eslint": "^8.56.11",
+ "@typescript-eslint/eslint-plugin": "^7.18.0",
+ "@typescript-eslint/parser": "^7.18.0",
"env-cmd": "^10.1.0",
- "eslint": "^9.4.0",
+ "eslint": "^9.9.0",
"eslint-config-prettier": "^9.1.0",
"eslint-formatter-friendly": "^7.0.0",
- "eslint-plugin-eslint-plugin": "^6.1.0",
+ "eslint-plugin-eslint-plugin": "^6.2.0",
"eslint-plugin-jsdoc": "^50.0.0",
- "eslint-plugin-json-schema-validator": "^5.1.0",
+ "eslint-plugin-json-schema-validator": "^5.1.2",
"eslint-plugin-jsonc": "^2.16.0",
- "eslint-plugin-markdown": "^5.0.0",
+ "eslint-plugin-markdown": "^5.1.0",
"eslint-plugin-mdx": "^3.1.5",
- "eslint-plugin-n": "^17.9.0",
+ "eslint-plugin-n": "^17.10.2",
"eslint-plugin-node-dependencies": "^0.12.0",
- "eslint-plugin-prettier": "^5.1.3",
+ "eslint-plugin-prettier": "^5.2.1",
"eslint-plugin-regexp": "^2.6.0",
"eslint-plugin-yml": "^1.14.0",
- "npm-run-all2": "^6.2.0",
- "prettier": "^3.3.2",
- "prettier-plugin-svelte": "^3.2.4",
- "rimraf": "^6.0.0",
- "typescript": "~5.5.0",
- "typescript-eslint": "^7.13.0"
+ "npm-run-all2": "^6.2.2",
+ "prettier": "^3.3.3",
+ "prettier-plugin-svelte": "^3.2.6",
+ "rimraf": "^6.0.1",
+ "typescript": "~5.5.4",
+ "typescript-eslint": "^7.18.0"
},
"publishConfig": {
"access": "public"
diff --git a/packages/eslint-plugin-svelte/package.json b/packages/eslint-plugin-svelte/package.json
index a341ec478..f4915e0ab 100644
--- a/packages/eslint-plugin-svelte/package.json
+++ b/packages/eslint-plugin-svelte/package.json
@@ -1,6 +1,6 @@
{
"name": "eslint-plugin-svelte",
- "version": "2.43.0",
+ "version": "2.50.0",
"description": "ESLint plugin for Svelte using AST",
"repository": "git+https://github.com/sveltejs/eslint-plugin-svelte.git",
"homepage": "https://sveltejs.github.io/eslint-plugin-svelte",
@@ -57,20 +57,20 @@
},
"dependencies": {
"@eslint-community/eslint-utils": "^4.4.0",
- "@jridgewell/sourcemap-codec": "^1.4.15",
+ "@jridgewell/sourcemap-codec": "^1.5.0",
"eslint-compat-utils": "^0.5.1",
"esutils": "^2.0.3",
"known-css-properties": "^0.34.0",
- "postcss": "^8.4.38",
+ "postcss": "^8.4.41",
"postcss-load-config": "^3.1.4",
"postcss-safe-parser": "^6.0.0",
- "postcss-selector-parser": "^6.1.0",
- "semver": "^7.6.2",
+ "postcss-selector-parser": "^6.1.1",
+ "semver": "^7.6.3",
"svelte-eslint-parser": "^0.41.0"
},
"devDependencies": {
- "@babel/core": "^7.24.7",
- "@babel/eslint-parser": "^7.24.7",
+ "@babel/core": "^7.25.2",
+ "@babel/eslint-parser": "^7.25.1",
"@babel/plugin-proposal-function-bind": "^7.24.7",
"@eslint-community/eslint-plugin-eslint-comments": "^4.3.0",
"@types/babel__core": "^7.20.5",
@@ -78,30 +78,30 @@
"@types/esutils": "^2.0.2",
"@types/json-schema": "^7.0.15",
"@types/less": "^3.0.6",
- "@types/mocha": "^10.0.6",
- "@types/node": "^20.14.2",
+ "@types/mocha": "^10.0.7",
+ "@types/node": "^20.14.14",
"@types/postcss-safe-parser": "^5.0.4",
"@types/semver": "^7.5.8",
"@types/stylus": "^0.48.42",
- "acorn": "^8.12.0",
+ "acorn": "^8.12.1",
"assert": "^2.1.0",
"esbuild": "^0.23.0",
- "esbuild-register": "^3.5.0",
- "eslint-scope": "^8.0.1",
+ "esbuild-register": "^3.6.0",
+ "eslint-scope": "^8.0.2",
"eslint-typegen": "^0.3.0",
"eslint-visitor-keys": "^4.0.0",
- "espree": "^10.0.1",
+ "espree": "^10.1.0",
"less": "^4.2.0",
- "mocha": "^10.4.0",
+ "mocha": "^10.7.3",
"nyc": "^17.0.0",
- "postcss-nested": "^6.0.1",
- "sass": "^1.77.5",
+ "postcss-nested": "^6.2.0",
+ "sass": "^1.77.8",
"source-map-js": "^1.2.0",
"stylus": "^0.63.0",
- "svelte": "^5.0.0-next.191",
+ "svelte": "5.0.0-next.210",
"svelte-i18n": "^4.0.0",
- "type-coverage": "^2.29.0",
- "yaml": "^2.4.5"
+ "type-coverage": "^2.29.1",
+ "yaml": "^2.5.0"
},
"publishConfig": {
"access": "public"
diff --git a/packages/eslint-plugin-svelte/src/configs/flat/recommended.ts b/packages/eslint-plugin-svelte/src/configs/flat/recommended.ts
index eff2aa8d4..04323ddd6 100644
--- a/packages/eslint-plugin-svelte/src/configs/flat/recommended.ts
+++ b/packages/eslint-plugin-svelte/src/configs/flat/recommended.ts
@@ -21,6 +21,7 @@ const config: Linter.FlatConfig[] = [
'svelte/no-shorthand-style-property-overrides': 'error',
'svelte/no-unknown-style-directive-property': 'error',
'svelte/no-unused-svelte-ignore': 'error',
+ 'svelte/restrict-mustache-expressions': 'error',
'svelte/system': 'error',
'svelte/valid-compile': 'error'
}
diff --git a/packages/eslint-plugin-svelte/src/configs/recommended.ts b/packages/eslint-plugin-svelte/src/configs/recommended.ts
index 5e53547be..c97b9b35d 100644
--- a/packages/eslint-plugin-svelte/src/configs/recommended.ts
+++ b/packages/eslint-plugin-svelte/src/configs/recommended.ts
@@ -21,6 +21,7 @@ const config: Linter.Config = {
'svelte/no-shorthand-style-property-overrides': 'error',
'svelte/no-unknown-style-directive-property': 'error',
'svelte/no-unused-svelte-ignore': 'error',
+ 'svelte/restrict-mustache-expressions': 'error',
'svelte/system': 'error',
'svelte/valid-compile': 'error'
}
diff --git a/packages/eslint-plugin-svelte/src/meta.ts b/packages/eslint-plugin-svelte/src/meta.ts
index 4619d5534..01ea183f2 100644
--- a/packages/eslint-plugin-svelte/src/meta.ts
+++ b/packages/eslint-plugin-svelte/src/meta.ts
@@ -2,4 +2,4 @@
// This file has been automatically generated,
// in order to update its content execute "pnpm run update"
export const name = 'eslint-plugin-svelte';
-export const version = '2.43.0';
+export const version = '2.50.0';
diff --git a/packages/eslint-plugin-svelte/src/rule-types.ts b/packages/eslint-plugin-svelte/src/rule-types.ts
index 77d3c4ca4..0911d0daa 100644
--- a/packages/eslint-plugin-svelte/src/rule-types.ts
+++ b/packages/eslint-plugin-svelte/src/rule-types.ts
@@ -289,6 +289,11 @@ export interface RuleOptions {
* @see https://sveltejs.github.io/eslint-plugin-svelte/rules/require-stores-init/
*/
'svelte/require-stores-init'?: Linter.RuleEntry<[]>
+ /**
+ * disallow non-string values in string contexts
+ * @see https://sveltejs.github.io/eslint-plugin-svelte/rules/restrict-mustache-expressions/
+ */
+ 'svelte/restrict-mustache-expressions'?: Linter.RuleEntry
/**
* enforce use of shorthand syntax in attribute
* @see https://sveltejs.github.io/eslint-plugin-svelte/rules/shorthand-attribute/
@@ -460,6 +465,19 @@ type SvelteNoUselessMustaches = []|[{
type SveltePreferClassDirective = []|[{
prefer?: ("always" | "empty")
}]
+// ----- svelte/restrict-mustache-expressions -----
+type SvelteRestrictMustacheExpressions = []|[{
+ allowBoolean?: boolean
+ allowNull?: boolean
+ allowUndefined?: boolean
+ allowNumber?: boolean
+ textExpressions?: {
+ [k: string]: unknown | undefined
+ }
+ stringTemplateExpressions?: {
+ [k: string]: unknown | undefined
+ }
+}]
// ----- svelte/shorthand-attribute -----
type SvelteShorthandAttribute = []|[{
prefer?: ("always" | "never")
diff --git a/packages/eslint-plugin-svelte/src/rules/restrict-mustache-expressions.ts b/packages/eslint-plugin-svelte/src/rules/restrict-mustache-expressions.ts
new file mode 100644
index 000000000..e230a78ca
--- /dev/null
+++ b/packages/eslint-plugin-svelte/src/rules/restrict-mustache-expressions.ts
@@ -0,0 +1,398 @@
+/* eslint-disable @eslint-community/eslint-comments/require-description */
+import type { AST } from 'svelte-eslint-parser';
+import { createRule } from '../utils';
+import ts, { TypeFlags } from 'typescript';
+import { getScope } from '../utils/ast-utils';
+import type { RuleContext } from '../types';
+import type { TSESTree } from '@typescript-eslint/utils';
+// eslint-disable-next-line @typescript-eslint/no-restricted-imports -- ignore
+import { ESLintUtils } from '@typescript-eslint/utils';
+import type { ParserServicesWithTypeInformation } from '@typescript-eslint/parser';
+import type { TS } from '../utils/ts-utils';
+import { getInnermostScope } from '@eslint-community/eslint-utils';
+
+const props = {
+ allowBoolean: {
+ type: 'boolean'
+ },
+ allowNull: {
+ type: 'boolean'
+ },
+ allowUndefined: {
+ type: 'boolean'
+ },
+ allowNumber: {
+ type: 'boolean'
+ }
+};
+
+function getDefaultOptions() {
+ return {
+ allowBoolean: true,
+ allowNumber: true,
+ allowNull: false,
+ allowUndefined: false
+ };
+}
+
+type Props = {
+ allowBoolean: boolean;
+ allowNumber: boolean;
+ allowNull: boolean;
+ allowUndefined: boolean;
+};
+
+type Config = {
+ stringTemplateExpressions?: Props;
+ textExpressions?: Props;
+} & Props;
+
+function findVariable(context: RuleContext, node: TSESTree.Identifier): Variable | null {
+ const initialScope = getInnermostScope(context, node)
+ const variable = eslintUtils.findVariable(initialScope, node);
+ if (variable) {
+ return variable;
+ }
+ if (!node.name.startsWith('$')) {
+ return variable;
+ }
+ // Remove the $ and search for the variable again, as it may be a store access variable.
+ return eslintUtils.findVariable(initialScope, node.name.slice(1));
+}
+
+function print_node(node: TSESTree.Node, services: ParserServicesWithTypeInformation) {
+ const checker = services.program.getTypeChecker();
+ const type = services.getTypeAtLocation(node);
+ console.log(checker.typeToString(type));
+}
+
+export default createRule('restrict-mustache-expressions', {
+ meta: {
+ docs: {
+ description: 'disallow non-string values in string contexts',
+ category: 'Possible Errors',
+ recommended: true
+ },
+ schema: [
+ {
+ type: 'object',
+ properties: {
+ ...props,
+ textExpressions: {
+ ...props
+ },
+ stringTemplateExpressions: {
+ ...props
+ }
+ },
+ additionalProperties: false
+ }
+ ],
+ messages: {
+ expectedStringifyableType:
+ 'Expected `{{disallowed}}` to be one of the following: {{types}}. You must cast or convert the expression to one of the allowed types.'
+ },
+ type: 'problem'
+ },
+ create(context) {
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ const services = ESLintUtils.getParserServices(context as any);
+ const checker = services.program.getTypeChecker();
+
+ const config: Config = Object.assign(getDefaultOptions(), context.options[0] || {});
+
+ function checkMustacheExpression(node: AST.SvelteMustacheTag) {
+ const allowed_types: Set = new Set(['string']);
+ let opts: Props;
+ if (node.kind === 'raw') return;
+ if (node.parent.type === 'SvelteAttribute') {
+ if (!node.parent.value.find((n) => n.type === 'SvelteLiteral')) {
+ // we are rendering a non-literal attribute (eg: class:disabled={disabled}, so we allow any type
+ // (todo): maybe we could maybe check the expected type of the attribute here, but I think the language server already does that?
+ return;
+ }
+ // we are rendering an template string attribute (eg: href="/page/{page.id}"), so we only allow stringifiable types
+ opts = config?.stringTemplateExpressions
+ ? Object.assign(getDefaultOptions(), config.stringTemplateExpressions)
+ : config;
+ } else if (node.parent.type !== 'SvelteStyleDirective') {
+ // we are rendering a text expression, so we only allow stringifiable types
+ opts = config?.textExpressions
+ ? Object.assign(getDefaultOptions(), config.textExpressions)
+ : config;
+ } else {
+ return;
+ }
+
+ const { allowBoolean, allowNull, allowUndefined, allowNumber } = opts;
+ if (allowBoolean === true) allowed_types.add('boolean');
+ if (allowNumber === true) allowed_types.add('number');
+ if (allowNull) allowed_types.add('null');
+ if (allowUndefined) allowed_types.add('undefined');
+
+ print_node(node.expression as any, services);
+
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ const type = compute_expression_type(node.expression as any, context, services);
+
+ if (type !== null && is_allowed_type(type, allowed_types, context, services)) return;
+
+ context.report({
+ node,
+ messageId: 'expectedStringifyableType',
+ data: {
+ disallowed: type === null ? 'unknown' : checker.typeToString(type),
+ types: [...allowed_types].map((t) => `\`${t}\``).join(', ')
+ }
+ });
+ }
+
+ return {
+ SvelteMustacheTag: checkMustacheExpression
+ };
+ }
+});
+
+function get_node_type(
+ node: TSESTree.Node,
+ services: ParserServicesWithTypeInformation
+): TS.Type | null {
+ const checker = services.program.getTypeChecker();
+ const type = services.getTypeAtLocation(node);
+ return checker.getBaseConstraintOfType(type) ?? type;
+}
+
+function compute_identifier_type(
+ expression: TSESTree.Identifier,
+ context: RuleContext,
+ services: ParserServicesWithTypeInformation
+): TS.Type | null {
+ return get_variable_type(expression, context, services);
+}
+
+function get_variable_type(
+ identifier: TSESTree.Identifier,
+ context: RuleContext,
+ services: ParserServicesWithTypeInformation
+): TS.Type | null {
+ const variable = findVariable(context, identifier);
+ const checker = services.program.getTypeChecker();
+
+ if (variable) {
+ const identifierNode = variable.identifiers[0];
+ const type_node = services.getTypeAtLocation(identifier);
+ const constrained = checker.getBaseConstraintOfType(type_node);
+ // const type = checker.getTypeFromTypeNode(;
+ // console.log(variable.name);
+ // console.log('Type from annotation', checker.typeToString(type_node));
+ // console.log('Constrained type', constrained ? checker.typeToString(constrained) : null);
+ // console.log('-------');
+ return type_node;
+ }
+
+ const type = get_node_type(identifier, services);
+ // console.log('Type from node:', type ? checker.typeToString(type) : null);
+ return type;
+
+ // if (variable?.name === 'side') {
+ // console.log('Variable', variable);
+
+ // // checker.
+ // // const def = variable.defs[0];
+ // // console.log(variable.identifiers[0].typeAnnotation?.typeAnnotation);
+ // const type = get_node_type(variable.identifiers[0], services;
+ // if (!type) return null;
+ // console.log(checker.getApparentType(type));
+ // // console.log('NODE TYPE', type);
+ // // if (!type) return null;
+ // // console.log('TYPE STRING:', checker.typeToString(type));
+ // }
+ // const identifiers = variable?.identifiers[0];
+
+ // if (!identifiers) return get_node_type(identifier, services;
+
+ // const type = get_node_type(variable.identifiers[0], services;
+
+ // if (type === null) return null;
+
+ // return narrow_variable_type(identifier, type, services;
+}
+
+function narrow_variable_type(
+ identifier: TSESTree.Identifier,
+ type: TS.Type,
+ services: ParserServicesWithTypeInformation
+): TS.Type {
+ const checker = services.program.getTypeChecker();
+ let currentNode: TSESTree.Node | AST.SvelteNode | undefined = identifier as TSESTree.Node;
+
+ while (currentNode) {
+ if (currentNode.type === 'SvelteIfBlock') {
+ const condition = currentNode.expression;
+ // TODO: other cases of conditionals
+ if (condition.type === 'Identifier' && condition.name === identifier.name) {
+ return checker.getNonNullableType(type);
+ }
+ }
+ currentNode = currentNode.parent as TSESTree.Node | AST.SvelteNode;
+ }
+
+ return type;
+}
+
+function is_allowed_type(
+ type: TS.Type,
+ allowed_types: Set,
+ context: RuleContext,
+ services: ParserServicesWithTypeInformation
+): boolean {
+ if (type.flags & TypeFlags.StringLike) return true;
+ if (type.flags & TypeFlags.BooleanLike) {
+ return allowed_types.has('boolean');
+ }
+ if (type.flags & TypeFlags.NumberLike) {
+ return allowed_types.has('number');
+ }
+ if (type.flags & TypeFlags.Null) {
+ return allowed_types.has('null');
+ }
+ if (type.flags & TypeFlags.Undefined) {
+ return allowed_types.has('undefined');
+ }
+ if (type.isUnion()) {
+ for (const sub_type of type.types) {
+ if (!is_allowed_type(sub_type, allowed_types, context, services)) return false;
+ }
+ return true;
+ }
+
+ return false;
+}
+
+function compute_literal_type(
+ expression: TSESTree.Literal,
+ services: ParserServicesWithTypeInformation
+): TS.Type | null {
+ return get_node_type(expression, services);
+}
+
+function compute_logical_expression_type(
+ expression: TSESTree.LogicalExpression,
+ services: ParserServicesWithTypeInformation
+): TS.Type | null {
+ // TODO: Do we need to check the type of the left and right expressions more?
+ return get_node_type(expression, services);
+}
+
+function compute_member_expression_type(
+ expression: TSESTree.MemberExpression,
+ context: RuleContext,
+ services: ParserServicesWithTypeInformation
+): TS.Type | null {
+ const object_type = compute_expression_type(expression.object, context, services);
+ if (!object_type) return null;
+
+ if (expression.computed) {
+ const property_type = compute_expression_type(expression.property, context, services);
+ if (property_type === null) return null;
+ return compute_property_return_type(object_type, property_type, services);
+ }
+
+ return compute_property(object_type, expression.property.name, services);
+}
+
+function compute_property(
+ object_type: TS.Type,
+ property_name: string,
+ services: ParserServicesWithTypeInformation
+): TS.Type | null {
+ const checker = services.program.getTypeChecker();
+ const symbol = checker.getPropertyOfType(object_type, property_name);
+ return symbol ? checker.getTypeOfSymbol(symbol) : null;
+}
+
+function compute_expression_type(
+ expression: TSESTree.Expression,
+ context: RuleContext,
+ services: ParserServicesWithTypeInformation
+): TS.Type | null {
+ switch (expression.type) {
+ case 'Identifier':
+ return compute_identifier_type(expression, context, services);
+ case 'Literal':
+ return compute_literal_type(expression, services);
+ case 'MemberExpression':
+ return compute_member_expression_type(expression, context, services);
+ case 'ArrayExpression':
+ return compute_array_expression_type(expression, context, services);
+ case 'LogicalExpression':
+ return compute_logical_expression_type(expression, services);
+ case 'ConditionalExpression':
+ return compute_conditional_expression_type(expression, context, services);
+ default:
+ return null;
+ }
+}
+
+function create_union_type(
+ types: TS.Type[],
+ services: ParserServicesWithTypeInformation
+): TS.Type | null {
+ const checker = services.program.getTypeChecker();
+
+ const new_types: TS.TypeNode[] = [];
+
+ for (const type of types) {
+ const node = checker.typeToTypeNode(type, undefined, ts.NodeBuilderFlags.NoTruncation);
+ if (!node) return null;
+ new_types.push(node);
+ }
+
+ const union_type = ts.factory.createUnionTypeNode(new_types);
+
+ return checker.getTypeFromTypeNode(union_type);
+}
+
+function compute_property_return_type(
+ object_type: TS.Type,
+ property_type: TS.Type,
+ services: ParserServicesWithTypeInformation
+): TS.Type | null {
+ // const checker = tools.service.program.getTypeChecker();
+ // // print the raw source code of the property type
+ // console.log('Property type', checker.typeToString(property_type));
+
+ if (property_type.isStringLiteral()) {
+ return compute_property(object_type, property_type.value, services);
+ } else if (property_type.isNumberLiteral()) {
+ const number_index_type = property_type.getNumberIndexType();
+ if (number_index_type === undefined) return null;
+ console.log('Number index type', number_index_type);
+ return compute_property_return_type(object_type, number_index_type, services);
+ } else if (property_type.isUnion()) {
+ const types: TS.Type[] = [];
+ for (const type of property_type.types) {
+ const subtype = compute_property_return_type(object_type, type, services);
+ if (subtype === null) return null;
+ types.push(subtype);
+ }
+ return create_union_type([...types], services);
+ }
+ return null;
+}
+
+function compute_array_expression_type(
+ expression: TSESTree.ArrayExpression,
+ context: RuleContext,
+ services: ParserServicesWithTypeInformation
+): TS.Type | null {
+ return get_node_type(expression, services);
+}
+
+function compute_conditional_expression_type(
+ expression: TSESTree.ConditionalExpression,
+ context: RuleContext,
+ services: ParserServicesWithTypeInformation
+): TS.Type | null {
+ return get_node_type(expression, services);
+}
diff --git a/packages/eslint-plugin-svelte/src/types-for-node.ts b/packages/eslint-plugin-svelte/src/types-for-node.ts
index 866ea6dab..953fb7457 100644
--- a/packages/eslint-plugin-svelte/src/types-for-node.ts
+++ b/packages/eslint-plugin-svelte/src/types-for-node.ts
@@ -136,6 +136,7 @@ export type ASTNodeListener = {
node: TSESTree.TSEmptyBodyFunctionExpression & ASTNodeWithParent
) => void;
TSEnumDeclaration?: (node: TSESTree.TSEnumDeclaration & ASTNodeWithParent) => void;
+ TSEnumBody?: (node: TSESTree.TSEnumBody & ASTNodeWithParent) => void;
TSEnumMember?: (node: TSESTree.TSEnumMember & ASTNodeWithParent) => void;
TSExportAssignment?: (node: TSESTree.TSExportAssignment & ASTNodeWithParent) => void;
TSExportKeyword?: (node: TSESTree.TSExportKeyword & ASTNodeWithParent) => void;
@@ -355,6 +356,7 @@ export type TSNodeListener = {
node: TSESTree.TSEmptyBodyFunctionExpression & ASTNodeWithParent
) => void;
TSEnumDeclaration?: (node: TSESTree.TSEnumDeclaration & ASTNodeWithParent) => void;
+ TSEnumBody?: (node: TSESTree.TSEnumBody & ASTNodeWithParent) => void;
TSEnumMember?: (node: TSESTree.TSEnumMember & ASTNodeWithParent) => void;
TSExportAssignment?: (node: TSESTree.TSExportAssignment & ASTNodeWithParent) => void;
TSExportKeyword?: (node: TSESTree.TSExportKeyword & ASTNodeWithParent) => void;
diff --git a/packages/eslint-plugin-svelte/src/utils/rules.ts b/packages/eslint-plugin-svelte/src/utils/rules.ts
index af0dd20e6..0049e3c35 100644
--- a/packages/eslint-plugin-svelte/src/utils/rules.ts
+++ b/packages/eslint-plugin-svelte/src/utils/rules.ts
@@ -57,6 +57,7 @@ import requireOptimizedStyleAttribute from '../rules/require-optimized-style-att
import requireStoreCallbacksUseSetParam from '../rules/require-store-callbacks-use-set-param';
import requireStoreReactiveAccess from '../rules/require-store-reactive-access';
import requireStoresInit from '../rules/require-stores-init';
+import restrictMustacheExpressions from '../rules/restrict-mustache-expressions';
import shorthandAttribute from '../rules/shorthand-attribute';
import shorthandDirective from '../rules/shorthand-directive';
import sortAttributes from '../rules/sort-attributes';
@@ -122,6 +123,7 @@ export const rules = [
requireStoreCallbacksUseSetParam,
requireStoreReactiveAccess,
requireStoresInit,
+ restrictMustacheExpressions,
shorthandAttribute,
shorthandDirective,
sortAttributes,
diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/restrict-mustache-expressions/invalid/conditional/conditional-errors.yaml b/packages/eslint-plugin-svelte/tests/fixtures/rules/restrict-mustache-expressions/invalid/conditional/conditional-errors.yaml
new file mode 100644
index 000000000..eaf640ef5
--- /dev/null
+++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/restrict-mustache-expressions/invalid/conditional/conditional-errors.yaml
@@ -0,0 +1,6 @@
+- message: 'Expected `null` to be one of the following: `string`, `boolean`,
+ `number`. You must cast or convert the expression to one of the allowed
+ types.'
+ line: 6
+ column: 5
+ suggestions: null
diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/restrict-mustache-expressions/invalid/conditional/conditional-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/restrict-mustache-expressions/invalid/conditional/conditional-input.svelte
new file mode 100644
index 000000000..93c35c8d2
--- /dev/null
+++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/restrict-mustache-expressions/invalid/conditional/conditional-input.svelte
@@ -0,0 +1,7 @@
+
+{#if foo === null}
+ {foo }
+{/if}
\ No newline at end of file
diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/restrict-mustache-expressions/invalid/object-access/_config.json b/packages/eslint-plugin-svelte/tests/fixtures/rules/restrict-mustache-expressions/invalid/object-access/_config.json
new file mode 100644
index 000000000..0e0dcd235
--- /dev/null
+++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/restrict-mustache-expressions/invalid/object-access/_config.json
@@ -0,0 +1,3 @@
+{
+
+}
\ No newline at end of file
diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/restrict-mustache-expressions/invalid/object-access/object-access-errors.yaml b/packages/eslint-plugin-svelte/tests/fixtures/rules/restrict-mustache-expressions/invalid/object-access/object-access-errors.yaml
new file mode 100644
index 000000000..f5205e1bc
--- /dev/null
+++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/restrict-mustache-expressions/invalid/object-access/object-access-errors.yaml
@@ -0,0 +1,6 @@
+- message: 'Expected `null` to be one of the following: `string`, `boolean`,
+ `number`. You must cast or convert the expression to one of the allowed
+ types.'
+ line: 10
+ column: 1
+ suggestions: null
diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/restrict-mustache-expressions/invalid/object-access/object-access-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/restrict-mustache-expressions/invalid/object-access/object-access-input.svelte
new file mode 100644
index 000000000..33575e161
--- /dev/null
+++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/restrict-mustache-expressions/invalid/object-access/object-access-input.svelte
@@ -0,0 +1,10 @@
+
+{foo.a.b.c}
\ No newline at end of file
diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/restrict-mustache-expressions/invalid/stringTemplateExpression/array/array-errors.yaml b/packages/eslint-plugin-svelte/tests/fixtures/rules/restrict-mustache-expressions/invalid/stringTemplateExpression/array/array-errors.yaml
new file mode 100644
index 000000000..d5575f817
--- /dev/null
+++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/restrict-mustache-expressions/invalid/stringTemplateExpression/array/array-errors.yaml
@@ -0,0 +1,12 @@
+- message: 'Expected `string[]` to be one of the following: `string`, `boolean`,
+ `number`. You must cast or convert the expression to one of the allowed
+ types.'
+ line: 4
+ column: 14
+ suggestions: null
+- message: 'Expected `string[]` to be one of the following: `string`, `boolean`,
+ `number`. You must cast or convert the expression to one of the allowed
+ types.'
+ line: 5
+ column: 14
+ suggestions: null
diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/restrict-mustache-expressions/invalid/stringTemplateExpression/array/array-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/restrict-mustache-expressions/invalid/stringTemplateExpression/array/array-input.svelte
new file mode 100644
index 000000000..01437f86e
--- /dev/null
+++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/restrict-mustache-expressions/invalid/stringTemplateExpression/array/array-input.svelte
@@ -0,0 +1,5 @@
+
+foo
+foo
\ No newline at end of file
diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/restrict-mustache-expressions/invalid/stringTemplateExpression/boolean/_config.json b/packages/eslint-plugin-svelte/tests/fixtures/rules/restrict-mustache-expressions/invalid/stringTemplateExpression/boolean/_config.json
new file mode 100644
index 000000000..758734e43
--- /dev/null
+++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/restrict-mustache-expressions/invalid/stringTemplateExpression/boolean/_config.json
@@ -0,0 +1,9 @@
+{
+ "options": [
+ {
+ "stringTemplateExpressions": {
+ "allowBoolean": false
+ }
+ }
+ ]
+}
\ No newline at end of file
diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/restrict-mustache-expressions/invalid/stringTemplateExpression/boolean/boolean-errors.yaml b/packages/eslint-plugin-svelte/tests/fixtures/rules/restrict-mustache-expressions/invalid/stringTemplateExpression/boolean/boolean-errors.yaml
new file mode 100644
index 000000000..c4bec53b2
--- /dev/null
+++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/restrict-mustache-expressions/invalid/stringTemplateExpression/boolean/boolean-errors.yaml
@@ -0,0 +1,10 @@
+- message: 'Expected `true` to be one of the following: `string`, `number`. You
+ must cast or convert the expression to one of the allowed types.'
+ line: 4
+ column: 14
+ suggestions: null
+- message: 'Expected `boolean` to be one of the following: `string`, `number`. You
+ must cast or convert the expression to one of the allowed types.'
+ line: 5
+ column: 14
+ suggestions: null
diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/restrict-mustache-expressions/invalid/stringTemplateExpression/boolean/boolean-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/restrict-mustache-expressions/invalid/stringTemplateExpression/boolean/boolean-input.svelte
new file mode 100644
index 000000000..1e5535d0f
--- /dev/null
+++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/restrict-mustache-expressions/invalid/stringTemplateExpression/boolean/boolean-input.svelte
@@ -0,0 +1,5 @@
+
+foo
+foo
\ No newline at end of file
diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/restrict-mustache-expressions/invalid/stringTemplateExpression/number/_config.json b/packages/eslint-plugin-svelte/tests/fixtures/rules/restrict-mustache-expressions/invalid/stringTemplateExpression/number/_config.json
new file mode 100644
index 000000000..9770285cc
--- /dev/null
+++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/restrict-mustache-expressions/invalid/stringTemplateExpression/number/_config.json
@@ -0,0 +1,9 @@
+{
+ "options": [
+ {
+ "stringTemplateExpressions": {
+ "allowNumber": false
+ }
+ }
+ ]
+}
\ No newline at end of file
diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/restrict-mustache-expressions/invalid/stringTemplateExpression/number/number-errors.yaml b/packages/eslint-plugin-svelte/tests/fixtures/rules/restrict-mustache-expressions/invalid/stringTemplateExpression/number/number-errors.yaml
new file mode 100644
index 000000000..e8ef0168e
--- /dev/null
+++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/restrict-mustache-expressions/invalid/stringTemplateExpression/number/number-errors.yaml
@@ -0,0 +1,10 @@
+- message: 'Expected `123` to be one of the following: `string`, `boolean`. You
+ must cast or convert the expression to one of the allowed types.'
+ line: 4
+ column: 14
+ suggestions: null
+- message: 'Expected `number` to be one of the following: `string`, `boolean`. You
+ must cast or convert the expression to one of the allowed types.'
+ line: 5
+ column: 14
+ suggestions: null
diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/restrict-mustache-expressions/invalid/stringTemplateExpression/number/number-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/restrict-mustache-expressions/invalid/stringTemplateExpression/number/number-input.svelte
new file mode 100644
index 000000000..bb86448df
--- /dev/null
+++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/restrict-mustache-expressions/invalid/stringTemplateExpression/number/number-input.svelte
@@ -0,0 +1,5 @@
+
+foo
+foo
\ No newline at end of file
diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/restrict-mustache-expressions/invalid/stringTemplateExpression/object/object-errors.yaml b/packages/eslint-plugin-svelte/tests/fixtures/rules/restrict-mustache-expressions/invalid/stringTemplateExpression/object/object-errors.yaml
new file mode 100644
index 000000000..127c94e2a
--- /dev/null
+++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/restrict-mustache-expressions/invalid/stringTemplateExpression/object/object-errors.yaml
@@ -0,0 +1,12 @@
+- message: 'Expected `{ invalid: string; }` to be one of the following: `string`,
+ `boolean`, `number`. You must cast or convert the expression to one of the
+ allowed types.'
+ line: 6
+ column: 14
+ suggestions: null
+- message: 'Expected `{ bar: string; }` to be one of the following: `string`,
+ `boolean`, `number`. You must cast or convert the expression to one of the
+ allowed types.'
+ line: 7
+ column: 14
+ suggestions: null
diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/restrict-mustache-expressions/invalid/stringTemplateExpression/object/object-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/restrict-mustache-expressions/invalid/stringTemplateExpression/object/object-input.svelte
new file mode 100644
index 000000000..de771cf5c
--- /dev/null
+++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/restrict-mustache-expressions/invalid/stringTemplateExpression/object/object-input.svelte
@@ -0,0 +1,7 @@
+
+foo
+foo
\ No newline at end of file
diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/restrict-mustache-expressions/invalid/stringTemplateExpression/undefined/undefined-errors.yaml b/packages/eslint-plugin-svelte/tests/fixtures/rules/restrict-mustache-expressions/invalid/stringTemplateExpression/undefined/undefined-errors.yaml
new file mode 100644
index 000000000..21fde401b
--- /dev/null
+++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/restrict-mustache-expressions/invalid/stringTemplateExpression/undefined/undefined-errors.yaml
@@ -0,0 +1,12 @@
+- message: 'Expected `undefined` to be one of the following: `string`, `boolean`,
+ `number`. You must cast or convert the expression to one of the allowed
+ types.'
+ line: 4
+ column: 14
+ suggestions: null
+- message: 'Expected `undefined` to be one of the following: `string`, `boolean`,
+ `number`. You must cast or convert the expression to one of the allowed
+ types.'
+ line: 5
+ column: 14
+ suggestions: null
diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/restrict-mustache-expressions/invalid/stringTemplateExpression/undefined/undefined-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/restrict-mustache-expressions/invalid/stringTemplateExpression/undefined/undefined-input.svelte
new file mode 100644
index 000000000..4333a26fb
--- /dev/null
+++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/restrict-mustache-expressions/invalid/stringTemplateExpression/undefined/undefined-input.svelte
@@ -0,0 +1,5 @@
+
+foo
+foo
\ No newline at end of file
diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/restrict-mustache-expressions/invalid/textExpression/array/array-errors.yaml b/packages/eslint-plugin-svelte/tests/fixtures/rules/restrict-mustache-expressions/invalid/textExpression/array/array-errors.yaml
new file mode 100644
index 000000000..a83d973a9
--- /dev/null
+++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/restrict-mustache-expressions/invalid/textExpression/array/array-errors.yaml
@@ -0,0 +1,12 @@
+- message: 'Expected `number[]` to be one of the following: `string`, `boolean`,
+ `number`. You must cast or convert the expression to one of the allowed
+ types.'
+ line: 4
+ column: 1
+ suggestions: null
+- message: 'Expected `number[]` to be one of the following: `string`, `boolean`,
+ `number`. You must cast or convert the expression to one of the allowed
+ types.'
+ line: 5
+ column: 1
+ suggestions: null
diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/restrict-mustache-expressions/invalid/textExpression/array/array-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/restrict-mustache-expressions/invalid/textExpression/array/array-input.svelte
new file mode 100644
index 000000000..cfb3c1456
--- /dev/null
+++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/restrict-mustache-expressions/invalid/textExpression/array/array-input.svelte
@@ -0,0 +1,5 @@
+
+{ [1, 2, 3] }
+{ invalid_array }
\ No newline at end of file
diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/restrict-mustache-expressions/invalid/textExpression/boolean/_config.json b/packages/eslint-plugin-svelte/tests/fixtures/rules/restrict-mustache-expressions/invalid/textExpression/boolean/_config.json
new file mode 100644
index 000000000..e79470454
--- /dev/null
+++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/restrict-mustache-expressions/invalid/textExpression/boolean/_config.json
@@ -0,0 +1,9 @@
+{
+ "options": [
+ {
+ "textExpressions": {
+ "allowBoolean": false
+ }
+ }
+ ]
+}
\ No newline at end of file
diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/restrict-mustache-expressions/invalid/textExpression/boolean/boolean-errors.yaml b/packages/eslint-plugin-svelte/tests/fixtures/rules/restrict-mustache-expressions/invalid/textExpression/boolean/boolean-errors.yaml
new file mode 100644
index 000000000..8f2fe4a22
--- /dev/null
+++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/restrict-mustache-expressions/invalid/textExpression/boolean/boolean-errors.yaml
@@ -0,0 +1,10 @@
+- message: 'Expected `true` to be one of the following: `string`, `number`. You
+ must cast or convert the expression to one of the allowed types.'
+ line: 4
+ column: 1
+ suggestions: null
+- message: 'Expected `boolean` to be one of the following: `string`, `number`. You
+ must cast or convert the expression to one of the allowed types.'
+ line: 5
+ column: 1
+ suggestions: null
diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/restrict-mustache-expressions/invalid/textExpression/boolean/boolean-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/restrict-mustache-expressions/invalid/textExpression/boolean/boolean-input.svelte
new file mode 100644
index 000000000..5acb3f9bb
--- /dev/null
+++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/restrict-mustache-expressions/invalid/textExpression/boolean/boolean-input.svelte
@@ -0,0 +1,5 @@
+
+{ true }
+{ invalid_boolean }
\ No newline at end of file
diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/restrict-mustache-expressions/invalid/textExpression/null/null-errors.yaml b/packages/eslint-plugin-svelte/tests/fixtures/rules/restrict-mustache-expressions/invalid/textExpression/null/null-errors.yaml
new file mode 100644
index 000000000..cbe7dd441
--- /dev/null
+++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/restrict-mustache-expressions/invalid/textExpression/null/null-errors.yaml
@@ -0,0 +1,12 @@
+- message: 'Expected `null` to be one of the following: `string`, `boolean`,
+ `number`. You must cast or convert the expression to one of the allowed
+ types.'
+ line: 4
+ column: 1
+ suggestions: null
+- message: 'Expected `null` to be one of the following: `string`, `boolean`,
+ `number`. You must cast or convert the expression to one of the allowed
+ types.'
+ line: 5
+ column: 1
+ suggestions: null
diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/restrict-mustache-expressions/invalid/textExpression/null/null-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/restrict-mustache-expressions/invalid/textExpression/null/null-input.svelte
new file mode 100644
index 000000000..bc32e2f7f
--- /dev/null
+++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/restrict-mustache-expressions/invalid/textExpression/null/null-input.svelte
@@ -0,0 +1,5 @@
+
+{ null }
+{ invalid_null }
\ No newline at end of file
diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/restrict-mustache-expressions/invalid/textExpression/number/_config.json b/packages/eslint-plugin-svelte/tests/fixtures/rules/restrict-mustache-expressions/invalid/textExpression/number/_config.json
new file mode 100644
index 000000000..ee72b6545
--- /dev/null
+++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/restrict-mustache-expressions/invalid/textExpression/number/_config.json
@@ -0,0 +1,9 @@
+{
+ "options": [
+ {
+ "textExpressions": {
+ "allowNumber": false
+ }
+ }
+ ]
+}
\ No newline at end of file
diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/restrict-mustache-expressions/invalid/textExpression/number/number-errors.yaml b/packages/eslint-plugin-svelte/tests/fixtures/rules/restrict-mustache-expressions/invalid/textExpression/number/number-errors.yaml
new file mode 100644
index 000000000..b390fda51
--- /dev/null
+++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/restrict-mustache-expressions/invalid/textExpression/number/number-errors.yaml
@@ -0,0 +1,10 @@
+- message: 'Expected `123` to be one of the following: `string`, `boolean`. You
+ must cast or convert the expression to one of the allowed types.'
+ line: 4
+ column: 1
+ suggestions: null
+- message: 'Expected `number` to be one of the following: `string`, `boolean`. You
+ must cast or convert the expression to one of the allowed types.'
+ line: 5
+ column: 1
+ suggestions: null
diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/restrict-mustache-expressions/invalid/textExpression/number/number-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/restrict-mustache-expressions/invalid/textExpression/number/number-input.svelte
new file mode 100644
index 000000000..7c88e7929
--- /dev/null
+++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/restrict-mustache-expressions/invalid/textExpression/number/number-input.svelte
@@ -0,0 +1,5 @@
+
+{ 123 }
+{ invalid_number }
\ No newline at end of file
diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/restrict-mustache-expressions/invalid/textExpression/object/object-errors.yaml b/packages/eslint-plugin-svelte/tests/fixtures/rules/restrict-mustache-expressions/invalid/textExpression/object/object-errors.yaml
new file mode 100644
index 000000000..2c284b1d0
--- /dev/null
+++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/restrict-mustache-expressions/invalid/textExpression/object/object-errors.yaml
@@ -0,0 +1,12 @@
+- message: 'Expected `{ foo: string; }` to be one of the following: `string`,
+ `boolean`, `number`. You must cast or convert the expression to one of the
+ allowed types.'
+ line: 4
+ column: 1
+ suggestions: null
+- message: 'Expected `{ foo: string; }` to be one of the following: `string`,
+ `boolean`, `number`. You must cast or convert the expression to one of the
+ allowed types.'
+ line: 5
+ column: 1
+ suggestions: null
diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/restrict-mustache-expressions/invalid/textExpression/object/object-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/restrict-mustache-expressions/invalid/textExpression/object/object-input.svelte
new file mode 100644
index 000000000..5975884ca
--- /dev/null
+++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/restrict-mustache-expressions/invalid/textExpression/object/object-input.svelte
@@ -0,0 +1,5 @@
+
+{ { foo: 'bar' } }
+{ invalid_object }
\ No newline at end of file
diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/restrict-mustache-expressions/invalid/textExpression/undefined/undefined-errors.yaml b/packages/eslint-plugin-svelte/tests/fixtures/rules/restrict-mustache-expressions/invalid/textExpression/undefined/undefined-errors.yaml
new file mode 100644
index 000000000..9a205ee8a
--- /dev/null
+++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/restrict-mustache-expressions/invalid/textExpression/undefined/undefined-errors.yaml
@@ -0,0 +1,12 @@
+- message: 'Expected `undefined` to be one of the following: `string`, `boolean`,
+ `number`. You must cast or convert the expression to one of the allowed
+ types.'
+ line: 4
+ column: 1
+ suggestions: null
+- message: 'Expected `undefined` to be one of the following: `string`, `boolean`,
+ `number`. You must cast or convert the expression to one of the allowed
+ types.'
+ line: 5
+ column: 1
+ suggestions: null
diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/restrict-mustache-expressions/invalid/textExpression/undefined/undefined-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/restrict-mustache-expressions/invalid/textExpression/undefined/undefined-input.svelte
new file mode 100644
index 000000000..004540f2d
--- /dev/null
+++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/restrict-mustache-expressions/invalid/textExpression/undefined/undefined-input.svelte
@@ -0,0 +1,5 @@
+
+{ undefined }
+{ invalid_undefined }
\ No newline at end of file
diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/restrict-mustache-expressions/valid/conditional/conditional-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/restrict-mustache-expressions/valid/conditional/conditional-input.svelte
new file mode 100644
index 000000000..b7b0f971c
--- /dev/null
+++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/restrict-mustache-expressions/valid/conditional/conditional-input.svelte
@@ -0,0 +1,6 @@
+
+{#if foo}
+ { foo }
+{/if}
\ No newline at end of file
diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/restrict-mustache-expressions/valid/ignore-style-directive/ignore-style-directive.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/restrict-mustache-expressions/valid/ignore-style-directive/ignore-style-directive.svelte
new file mode 100644
index 000000000..eae0fad1a
--- /dev/null
+++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/restrict-mustache-expressions/valid/ignore-style-directive/ignore-style-directive.svelte
@@ -0,0 +1,4 @@
+
+
\ No newline at end of file
diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/restrict-mustache-expressions/valid/index-access/index-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/restrict-mustache-expressions/valid/index-access/index-input.svelte
new file mode 100644
index 000000000..885799ef4
--- /dev/null
+++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/restrict-mustache-expressions/valid/index-access/index-input.svelte
@@ -0,0 +1,13 @@
+
+{ side }
+{foo[side]}
+{foo["left"]}
+{foo.left}
\ No newline at end of file
diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/restrict-mustache-expressions/valid/object-access/object-access-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/restrict-mustache-expressions/valid/object-access/object-access-input.svelte
new file mode 100644
index 000000000..a48d67f15
--- /dev/null
+++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/restrict-mustache-expressions/valid/object-access/object-access-input.svelte
@@ -0,0 +1,12 @@
+
+{foo.bar}
+{foo.a.b.c}
\ No newline at end of file
diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/restrict-mustache-expressions/valid/stringTemplateExpression/boolean/_config.json b/packages/eslint-plugin-svelte/tests/fixtures/rules/restrict-mustache-expressions/valid/stringTemplateExpression/boolean/_config.json
new file mode 100644
index 000000000..9002d0e7f
--- /dev/null
+++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/restrict-mustache-expressions/valid/stringTemplateExpression/boolean/_config.json
@@ -0,0 +1,9 @@
+{
+ "options": [
+ {
+ "stringTemplateExpressions": {
+
+ }
+ }
+ ]
+}
\ No newline at end of file
diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/restrict-mustache-expressions/valid/stringTemplateExpression/boolean/boolean-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/restrict-mustache-expressions/valid/stringTemplateExpression/boolean/boolean-input.svelte
new file mode 100644
index 000000000..e5d59b75d
--- /dev/null
+++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/restrict-mustache-expressions/valid/stringTemplateExpression/boolean/boolean-input.svelte
@@ -0,0 +1,5 @@
+
+foo
+foo
\ No newline at end of file
diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/restrict-mustache-expressions/valid/stringTemplateExpression/direct/direct-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/restrict-mustache-expressions/valid/stringTemplateExpression/direct/direct-input.svelte
new file mode 100644
index 000000000..076cf606a
--- /dev/null
+++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/restrict-mustache-expressions/valid/stringTemplateExpression/direct/direct-input.svelte
@@ -0,0 +1,8 @@
+
+foo
+foo
+foo
+foo
+foo
+foo
+foo
\ No newline at end of file
diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/restrict-mustache-expressions/valid/stringTemplateExpression/null/_config.json b/packages/eslint-plugin-svelte/tests/fixtures/rules/restrict-mustache-expressions/valid/stringTemplateExpression/null/_config.json
new file mode 100644
index 000000000..861309fc9
--- /dev/null
+++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/restrict-mustache-expressions/valid/stringTemplateExpression/null/_config.json
@@ -0,0 +1,9 @@
+{
+ "options": [
+ {
+ "stringTemplateExpressions": {
+ "allowNull": true
+ }
+ }
+ ]
+}
\ No newline at end of file
diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/restrict-mustache-expressions/valid/stringTemplateExpression/null/null-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/restrict-mustache-expressions/valid/stringTemplateExpression/null/null-input.svelte
new file mode 100644
index 000000000..f12723e69
--- /dev/null
+++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/restrict-mustache-expressions/valid/stringTemplateExpression/null/null-input.svelte
@@ -0,0 +1,5 @@
+
+foo
+foo
\ No newline at end of file
diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/restrict-mustache-expressions/valid/stringTemplateExpression/number/_config.json b/packages/eslint-plugin-svelte/tests/fixtures/rules/restrict-mustache-expressions/valid/stringTemplateExpression/number/_config.json
new file mode 100644
index 000000000..9002d0e7f
--- /dev/null
+++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/restrict-mustache-expressions/valid/stringTemplateExpression/number/_config.json
@@ -0,0 +1,9 @@
+{
+ "options": [
+ {
+ "stringTemplateExpressions": {
+
+ }
+ }
+ ]
+}
\ No newline at end of file
diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/restrict-mustache-expressions/valid/stringTemplateExpression/number/number-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/restrict-mustache-expressions/valid/stringTemplateExpression/number/number-input.svelte
new file mode 100644
index 000000000..19a64aef5
--- /dev/null
+++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/restrict-mustache-expressions/valid/stringTemplateExpression/number/number-input.svelte
@@ -0,0 +1,5 @@
+
+foo
+foo
\ No newline at end of file
diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/restrict-mustache-expressions/valid/stringTemplateExpression/template-string/attribute-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/restrict-mustache-expressions/valid/stringTemplateExpression/template-string/attribute-input.svelte
new file mode 100644
index 000000000..39740448e
--- /dev/null
+++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/restrict-mustache-expressions/valid/stringTemplateExpression/template-string/attribute-input.svelte
@@ -0,0 +1,2 @@
+
+foo
\ No newline at end of file
diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/restrict-mustache-expressions/valid/stringTemplateExpression/undefined/_config.json b/packages/eslint-plugin-svelte/tests/fixtures/rules/restrict-mustache-expressions/valid/stringTemplateExpression/undefined/_config.json
new file mode 100644
index 000000000..9eddd9b40
--- /dev/null
+++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/restrict-mustache-expressions/valid/stringTemplateExpression/undefined/_config.json
@@ -0,0 +1,9 @@
+{
+ "options": [
+ {
+ "stringTemplateExpressions": {
+ "allowUndefined": true
+ }
+ }
+ ]
+}
\ No newline at end of file
diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/restrict-mustache-expressions/valid/stringTemplateExpression/undefined/undefined-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/restrict-mustache-expressions/valid/stringTemplateExpression/undefined/undefined-input.svelte
new file mode 100644
index 000000000..c0fc90bf7
--- /dev/null
+++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/restrict-mustache-expressions/valid/stringTemplateExpression/undefined/undefined-input.svelte
@@ -0,0 +1,5 @@
+
+foo
+foo
\ No newline at end of file
diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/restrict-mustache-expressions/valid/textExpression/boolean/boolean-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/restrict-mustache-expressions/valid/textExpression/boolean/boolean-input.svelte
new file mode 100644
index 000000000..2ae6476d0
--- /dev/null
+++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/restrict-mustache-expressions/valid/textExpression/boolean/boolean-input.svelte
@@ -0,0 +1,5 @@
+
+{ true }
+{ valid_boolean }
\ No newline at end of file
diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/restrict-mustache-expressions/valid/textExpression/null/_config.json b/packages/eslint-plugin-svelte/tests/fixtures/rules/restrict-mustache-expressions/valid/textExpression/null/_config.json
new file mode 100644
index 000000000..5b1b41c61
--- /dev/null
+++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/restrict-mustache-expressions/valid/textExpression/null/_config.json
@@ -0,0 +1,9 @@
+{
+ "options": [
+ {
+ "textExpressions": {
+ "allowNull": true
+ }
+ }
+ ]
+}
\ No newline at end of file
diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/restrict-mustache-expressions/valid/textExpression/null/null-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/restrict-mustache-expressions/valid/textExpression/null/null-input.svelte
new file mode 100644
index 000000000..b2f4256ff
--- /dev/null
+++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/restrict-mustache-expressions/valid/textExpression/null/null-input.svelte
@@ -0,0 +1,5 @@
+
+{ null }
+{ valid_null }
\ No newline at end of file
diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/restrict-mustache-expressions/valid/textExpression/number/number-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/restrict-mustache-expressions/valid/textExpression/number/number-input.svelte
new file mode 100644
index 000000000..06c55b28c
--- /dev/null
+++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/restrict-mustache-expressions/valid/textExpression/number/number-input.svelte
@@ -0,0 +1,5 @@
+
+{ 123 }
+{ valid_number }
\ No newline at end of file
diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/restrict-mustache-expressions/valid/textExpression/undefined/_config.json b/packages/eslint-plugin-svelte/tests/fixtures/rules/restrict-mustache-expressions/valid/textExpression/undefined/_config.json
new file mode 100644
index 000000000..a6a81bcfc
--- /dev/null
+++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/restrict-mustache-expressions/valid/textExpression/undefined/_config.json
@@ -0,0 +1,9 @@
+{
+ "options": [
+ {
+ "textExpressions": {
+ "allowUndefined": true
+ }
+ }
+ ]
+}
\ No newline at end of file
diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/restrict-mustache-expressions/valid/textExpression/undefined/undefined-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/restrict-mustache-expressions/valid/textExpression/undefined/undefined-input.svelte
new file mode 100644
index 000000000..982d1b1b2
--- /dev/null
+++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/restrict-mustache-expressions/valid/textExpression/undefined/undefined-input.svelte
@@ -0,0 +1,5 @@
+
+{ undefined }
+{ valid_undefined }
\ No newline at end of file
diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/restrict-mustache-expressions/valid/union/union-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/restrict-mustache-expressions/valid/union/union-input.svelte
new file mode 100644
index 000000000..88b7b1f41
--- /dev/null
+++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/restrict-mustache-expressions/valid/union/union-input.svelte
@@ -0,0 +1,5 @@
+
+{ num || "" || bool}
\ No newline at end of file
diff --git a/packages/eslint-plugin-svelte/tests/src/rules/restrict-mustache-expressions.ts b/packages/eslint-plugin-svelte/tests/src/rules/restrict-mustache-expressions.ts
new file mode 100644
index 000000000..1b29b11ef
--- /dev/null
+++ b/packages/eslint-plugin-svelte/tests/src/rules/restrict-mustache-expressions.ts
@@ -0,0 +1,18 @@
+import { RuleTester } from '../../utils/eslint-compat';
+import rule from '../../../src/rules/restrict-mustache-expressions';
+import { loadTestCases, RULES_PROJECT } from '../../utils/utils';
+
+const tester = new RuleTester({
+ languageOptions: {
+ parser: "@typescript-eslint/parser",
+ ecmaVersion: 2020,
+ sourceType: 'module',
+ parserOptions: {
+ // parser: '@typescript-eslint/parser',
+ projectService: true,
+ project: RULES_PROJECT,
+ }
+ },
+});
+
+tester.run('restrict-mustache-expressions', rule as any, loadTestCases('restrict-mustache-expressions'));