Skip to content

Commit

Permalink
Add astro/no-set-text-directive rule
Browse files Browse the repository at this point in the history
  • Loading branch information
ota-meshi committed May 23, 2022
1 parent 6ec7b69 commit faea735
Show file tree
Hide file tree
Showing 29 changed files with 285 additions and 1 deletion.
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,14 @@ These rules relate to security vulnerabilities in Astro component code:
|:--------|:------------|:---|
| [astro/no-set-html-directive](https://ota-meshi.github.io/eslint-plugin-astro/rules/no-set-html-directive/) | disallow use of `set:html` to prevent XSS attack | |

## Best Practices

These rules relate to better ways of doing things to help you avoid problems:

| Rule ID | Description | |
|:--------|:------------|:---|
| [astro/no-set-text-directive](https://ota-meshi.github.io/eslint-plugin-astro/rules/no-set-text-directive/) | disallow use of `set:text` | :wrench: |

<!--RULES_TABLE_END-->
<!--RULES_SECTION_END-->

Expand Down
8 changes: 8 additions & 0 deletions docs/rules.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,11 @@ These rules relate to security vulnerabilities in Astro component code:
| Rule ID | Description | |
|:--------|:------------|:---|
| [astro/no-set-html-directive](./rules/no-set-html-directive.md) | disallow use of `set:html` to prevent XSS attack | |

## Best Practices

These rules relate to better ways of doing things to help you avoid problems:

| Rule ID | Description | |
|:--------|:------------|:---|
| [astro/no-set-text-directive](./rules/no-set-text-directive.md) | disallow use of `set:text` | :wrench: |
1 change: 1 addition & 0 deletions docs/rules/no-set-html-directive.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ This rule reports all uses of `set:html` in order to reduce the risk of injectin
---
<!-- ✓ GOOD -->
<p>{foo}</p>
<p set:text={foo} />
<!-- ✗ BAD -->
Expand Down
54 changes: 54 additions & 0 deletions docs/rules/no-set-text-directive.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
---
title: "astro/no-set-text-directive"
description: "disallow use of `set:text`"
setup: "import ESLintCodeBlock from '../docs-build/src/components/ESLintCodeBlockWrap.astro'"
---

# astro/no-set-text-directive

> disallow use of `set:text`
- :exclamation: <badge text="This rule has not been released yet." vertical="middle" type="error"> **_This rule has not been released yet._** </badge>
- :wrench: The `--fix` option on the [command line](https://eslint.org/docs/user-guide/command-line-interface#fixing-problems) can automatically fix some of the problems reported by this rule.

## :book: Rule Details

This rule reports all uses of `set:text` directive.

The documentation says about the `set:text` directive as follows:

> This is equivalent to just passing a variable into a template expression directly (ex: `<div>{someText}</div>`) and therefore this directive is not commonly used.
See [Astro Documentation | Template Directives Reference > set:text](https://docs.astro.build/en/reference/directives-reference/#settext).

<ESLintCodeBlock fix>

<!--eslint-skip-->

```astro
---
/* eslint astro/no-set-text-directive: "error" */
---
<!-- ✓ GOOD -->
<p>{foo}</p>
<!-- ✗ BAD -->
<p set:text={foo} />
```

</ESLintCodeBlock>

## :wrench: Options

Nothing.

## :books: Further Reading

- [Astro Documentation | Template Directives Reference > set:text](https://docs.astro.build/en/reference/directives-reference/#settext)

## :mag: Implementation

- [Rule source](https://github.com/ota-meshi/eslint-plugin-astro/blob/main/src/rules/no-set-text-directive.ts)
- [Test source](https://github.com/ota-meshi/eslint-plugin-astro/blob/main/tests/src/rules/no-set-text-directive.ts)
90 changes: 90 additions & 0 deletions src/rules/no-set-text-directive.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import type { AST } from "astro-eslint-parser"
import { createRule } from "../utils"

export default createRule("no-set-text-directive", {
meta: {
docs: {
description: "disallow use of `set:text`",
category: "Best Practices",
recommended: false,
},
schema: [],
messages: {
disallow: "Don't use `set:text`.",
},
type: "suggestion",
fixable: "code",
},
create(context) {
if (!context.parserServices.isAstro) {
return {}
}

/** Verify */
function verifyName(
attr: AST.JSXAttribute | AST.AstroTemplateLiteralAttribute,
) {
const { name: node } = attr
if (
node.type !== "JSXNamespacedName" ||
node.namespace.name !== "set" ||
node.name.name !== "text"
) {
return
}
context.report({
node,
messageId: "disallow",
*fix(fixer) {
const element = attr.parent!.parent!
if (!attr.value || !element || element.type !== "JSXElement") {
return
}
if (
element.children.some(
(child) => child.type !== "JSXText" || child.value.trim(),
)
) {
return
}
const sourceCode = context.getSourceCode()

const valueText =
attr.type === "AstroTemplateLiteralAttribute"
? `{${sourceCode.getText(attr.value)}}`
: sourceCode.getText(attr.value)
if (element.openingElement.selfClosing) {
if (
sourceCode.text.slice(
element.openingElement.range[1] - 2,
element.openingElement.range[1],
) !== "/>"
) {
return
}
yield fixer.remove(attr)
yield fixer.removeRange([
element.openingElement.range[1] - 2,
element.openingElement.range[1] - 1,
])
yield fixer.insertTextAfter(
element.openingElement,
`${valueText}</${sourceCode.getText(
element.openingElement.name,
)}>`,
)
} else {
yield fixer.remove(attr)
yield* element.children.map((child) => fixer.remove(child))
yield fixer.insertTextAfter(element.openingElement, valueText)
}
},
})
}

return {
JSXAttribute: verifyName,
AstroTemplateLiteralAttribute: verifyName,
}
},
})
3 changes: 2 additions & 1 deletion src/utils/rules.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { RuleModule } from "../types"
import noSetHtmlDirective from "../rules/no-set-html-directive"
import noSetTextDirective from "../rules/no-set-text-directive"

export const rules = [noSetHtmlDirective] as RuleModule[]
export const rules = [noSetHtmlDirective, noSetTextDirective] as RuleModule[]
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
[
{
"message": "Don't use `set:text`.",
"line": 1,
"column": 4
}
]
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<p set:text ></p>
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<p set:text ></p>
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
[
{
"message": "Don't use `set:text`.",
"line": 5,
"column": 8
}
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
let string = `text`
---

<input set:text={string}>
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
let string = `text`
---

<input set:text={string}>
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
[
{
"message": "Don't use `set:text`.",
"line": 5,
"column": 4
}
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
let string = `text`
---

<p set:text={string} />
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
let string = `text`
---

<p >{string}</p>
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
[
{
"message": "Don't use `set:text`.",
"line": 5,
"column": 4
}
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
let string = `text`
---

<p set:text={string}></p>
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
let string = `text`
---

<p >{string}</p>
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
[
{
"message": "Don't use `set:text`.",
"line": 5,
"column": 6
},
{
"message": "Don't use `set:text`.",
"line": 6,
"column": 6
}
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
let string = `text`
---

<div set:text={string}>Foo</div>
<div set:text={string}>
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
let string = `text`
---

<div set:text={string}>Foo</div>
<div >{string}</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
[
{
"message": "Don't use `set:text`.",
"line": 1,
"column": 4
}
]
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<p set:text=`text`></p>
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<p >{`text`}</p>
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
let string = `text`
---

<p set:html={string}></p>
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<p set:html=`text`></p>
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
let string = `text`
---

<p>{string}</p>
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<p>{`text`}</p>
16 changes: 16 additions & 0 deletions tests/src/rules/no-set-text-directive.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { RuleTester } from "eslint"
import rule from "../../../src/rules/no-set-text-directive"
import { loadTestCases } from "../../utils/utils"

const tester = new RuleTester({
parserOptions: {
ecmaVersion: 2020,
sourceType: "module",
},
})

tester.run(
"no-set-text-directive",
rule as any,
loadTestCases("no-set-text-directive"),
)

0 comments on commit faea735

Please sign in to comment.