Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add svelte/restrict-mustache-expressions rule #840

Closed
wants to merge 12 commits into from
5 changes: 5 additions & 0 deletions .changeset/unlucky-ducks-explain.md
Original file line number Diff line number Diff line change
@@ -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
13 changes: 7 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
@@ -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._**
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -367,8 +367,8 @@ Example **.vscode/settings.json**:
<!-- prettier-ignore-start -->
<!--RULES_SECTION_START-->

: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.

<!--RULES_TABLE_START-->
Expand All @@ -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. | |

Expand Down
6 changes: 3 additions & 3 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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._**
Expand All @@ -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
Expand Down
1 change: 1 addition & 0 deletions docs/rules.md
Original file line number Diff line number Diff line change
Expand Up @@ -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. | |

Expand Down
204 changes: 204 additions & 0 deletions docs/rules/restrict-mustache-expressions.md
Original file line number Diff line number Diff line change
@@ -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: <badge text="This rule has not been released yet." vertical="middle" type="error"> **_This rule has not been released yet._** </badge>
- :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: `<a href="foo/{bar}">foo</a>`), and not on ``<a href={`/foo/${bar}`}>foo</a>``.

<ESLintCodeBlock>

<!--eslint-skip-->

```svelte
<script lang="ts">
/* eslint svelte/restrict-mustache-expressions: "error" */

let str: string = 'foo';
let not_stringifiable = { foo: 'bar' };
</script>

<!-- ✓ GOOD -->
<a href="foo/{123}">foo</a>
<a href="foo/{str}">foo</a>
<a href="foo/{true}">foo</a>

{123}
{str}
{true}

<!-- ✗ BAD -->
<a href="foo/{null}">foo</a>
<a href="foo/{undefined}">foo</a>
<a href="foo/{[1, 2, 3]}">foo</a>
<a href="foo/{not_stringifiable}">foo</a>

{null}
{undefined}
{[1, 2, 3]}
{not_stringifiable}
```

</ESLintCodeBlock>

## :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: <foo>{bar}</foo>
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: <a href="foo/{bar}">foo</a>
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

<ESLintCodeBlock>

### Disallowing numbers

<!--eslint-skip-->

```svelte
<script lang="ts">
/* eslint svelte/restrict-mustache-expressions: ["error", { "allowNumbers": false }] */
</script>

<!-- ✓ GOOD -->
<a href="foo/{str}">foo</a>
{str}

<!-- ✗ BAD -->
<a href="foo/{123}">foo</a>
{123}
```

</ESLintCodeBlock>

### Disallowing booleans

<ESLintCodeBlock>

<!--eslint-skip-->

```svelte
<script lang="ts">
/* eslint svelte/restrict-mustache-expressions: ["error", { "allowBooleans": false }] */
</script>

<!-- ✓ GOOD -->
{str}

<!-- ✗ BAD -->
<a href="foo/{true}">foo</a>
```

</ESLintCodeBlock>

### Disallowing numbers specifically for text expressions

<ESLintCodeBlock>

<!--eslint-skip-->

```svelte
<script lang="ts">
/* eslint svelte/restrict-mustache-expressions: ["error", { "textExpressions": { "allowNumbers": false } }] */
</script>

<!-- ✓ GOOD -->
<a href="foo/{123}">foo</a>

<!-- ✗ BAD -->
{123}
```

</ESLintCodeBlock>

### Disallowing booleans specifically for string template expressions

<ESLintCodeBlock>

<!--eslint-skip-->

```svelte
<script lang="ts">
/* eslint svelte/restrict-mustache-expressions: ["error", { "stringTemplateExpressions": { "allowBooleans": false } }] */
</script>

<!-- ✓ GOOD -->
{true}

<!-- ✗ BAD -->
<a href="foo/{true}">foo</a>
```

</ESLintCodeBlock>

## :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)
2 changes: 1 addition & 1 deletion docs/user-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
36 changes: 18 additions & 18 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
Loading