fix: warn on bidirectional control characters, fix various issues with template expressions (#15893)

* fix: warn on bidirectional control characters

* check evaluated values as well, fix minor issue

* fix failing tests

* lint

* fix

* shrink warning code

* use validator test suite rather than snapshot (which should be used sparingly as it creates more git noise)

* show ranges during parsing, and warn on all occurrences rather than just the first

* fix lint

* move check into Text visitor so it happens in expected order

* unused

* add svelte-ignore test

* ignore control characters following a svelte-ignore comment

* tweak message

* no need to test evaluations, since we are already testing the literals that they are composed of

---------

Co-authored-by: Rich Harris <rich.harris@vercel.com>
pull/15899/head
ComputerGuy 4 months ago committed by GitHub
parent 92cdeadadf
commit aa041a9e65
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -0,0 +1,5 @@
---
'svelte': patch
---
fix: warn on bidirectional control characters

@ -586,6 +586,14 @@ Attributes should not contain ':' characters to prevent ambiguity with Svelte di
Quoted attributes on components and custom elements will be stringified in a future version of Svelte. If this isn't what you want, remove the quotes
```
### bidirectional_control_characters
```
A bidirectional control character was detected in your code. These characters can be used to alter the visual direction of your code and could have unintended consequences
```
Bidirectional control characters can alter the direction in which text appears to be in. For example, via control characters, you can make `defabc` look like `abcdef`. As a result, if you were to unknowingly copy and paste some code that has these control characters, they may alter the behavior of your code in ways you did not intend. See [trojansource.codes](https://trojansource.codes/) for more information.
### bind_invalid_each_rest
```

@ -1,3 +1,9 @@
## bidirectional_control_characters
> A bidirectional control character was detected in your code. These characters can be used to alter the visual direction of your code and could have unintended consequences
Bidirectional control characters can alter the direction in which text appears to be in. For example, via control characters, you can make `defabc` look like `abcdef`. As a result, if you were to unknowingly copy and paste some code that has these control characters, they may alter the behavior of your code in ways you did not intend. See [trojansource.codes](https://trojansource.codes/) for more information.
## legacy_code
> `%code%` is no longer valid — please use `%suggestion%` instead

@ -43,6 +43,7 @@ import { ImportDeclaration } from './visitors/ImportDeclaration.js';
import { KeyBlock } from './visitors/KeyBlock.js';
import { LabeledStatement } from './visitors/LabeledStatement.js';
import { LetDirective } from './visitors/LetDirective.js';
import { Literal } from './visitors/Literal.js';
import { MemberExpression } from './visitors/MemberExpression.js';
import { NewExpression } from './visitors/NewExpression.js';
import { OnDirective } from './visitors/OnDirective.js';
@ -63,6 +64,7 @@ import { SvelteSelf } from './visitors/SvelteSelf.js';
import { SvelteWindow } from './visitors/SvelteWindow.js';
import { SvelteBoundary } from './visitors/SvelteBoundary.js';
import { TaggedTemplateExpression } from './visitors/TaggedTemplateExpression.js';
import { TemplateElement } from './visitors/TemplateElement.js';
import { Text } from './visitors/Text.js';
import { TitleElement } from './visitors/TitleElement.js';
import { TransitionDirective } from './visitors/TransitionDirective.js';
@ -156,6 +158,7 @@ const visitors = {
KeyBlock,
LabeledStatement,
LetDirective,
Literal,
MemberExpression,
NewExpression,
OnDirective,
@ -176,6 +179,7 @@ const visitors = {
SvelteWindow,
SvelteBoundary,
TaggedTemplateExpression,
TemplateElement,
Text,
TransitionDirective,
TitleElement,

@ -0,0 +1,14 @@
/** @import { Literal } from 'estree' */
import * as w from '../../../warnings.js';
import { regex_bidirectional_control_characters } from '../../patterns.js';
/**
* @param {Literal} node
*/
export function Literal(node) {
if (typeof node.value === 'string') {
if (regex_bidirectional_control_characters.test(node.value)) {
w.bidirectional_control_characters(node);
}
}
}

@ -0,0 +1,12 @@
/** @import { TemplateElement } from 'estree' */
import * as w from '../../../warnings.js';
import { regex_bidirectional_control_characters } from '../../patterns.js';
/**
* @param {TemplateElement} node
*/
export function TemplateElement(node) {
if (regex_bidirectional_control_characters.test(node.value.cooked ?? '')) {
w.bidirectional_control_characters(node);
}
}

@ -1,20 +1,52 @@
/** @import { AST } from '#compiler' */
/** @import { Context } from '../types' */
import { is_tag_valid_with_parent } from '../../../../html-tree-validation.js';
import { regex_not_whitespace } from '../../patterns.js';
import { regex_bidirectional_control_characters, regex_not_whitespace } from '../../patterns.js';
import * as e from '../../../errors.js';
import * as w from '../../../warnings.js';
import { extract_svelte_ignore } from '../../../utils/extract_svelte_ignore.js';
/**
* @param {AST.Text} node
* @param {Context} context
*/
export function Text(node, context) {
const in_template = context.path.at(-1)?.type === 'Fragment';
const parent = /** @type {AST.SvelteNode} */ (context.path.at(-1));
if (in_template && context.state.parent_element && regex_not_whitespace.test(node.data)) {
if (
parent.type === 'Fragment' &&
context.state.parent_element &&
regex_not_whitespace.test(node.data)
) {
const message = is_tag_valid_with_parent('#text', context.state.parent_element);
if (message) {
e.node_invalid_placement(node, message);
}
}
regex_bidirectional_control_characters.lastIndex = 0;
for (const match of node.data.matchAll(regex_bidirectional_control_characters)) {
let is_ignored = false;
// if we have a svelte-ignore comment earlier in the text, bail
// (otherwise we can only use svelte-ignore on parent elements/blocks)
if (parent.type === 'Fragment') {
for (const child of parent.nodes) {
if (child === node) break;
if (child.type === 'Comment') {
is_ignored ||= extract_svelte_ignore(
child.start + 4,
child.data,
context.state.analysis.runes
).includes('bidirectional_control_characters');
}
}
}
if (!is_ignored) {
let start = match.index + node.start;
w.bidirectional_control_characters({ start, end: start + match[0].length });
}
}
}

@ -21,3 +21,5 @@ export const regex_invalid_identifier_chars = /(^[^a-zA-Z_$]|[^a-zA-Z0-9_$])/g;
export const regex_starts_with_vowel = /^[aeiou]/;
export const regex_heading_tags = /^h[1-6]$/;
export const regex_illegal_attribute_character = /(^[0-9-.])|[\^$@%&#?!|()[\]{}^*+~;]/;
export const regex_bidirectional_control_characters =
/[\u202a\u202b\u202c\u202d\u202e\u2066\u2067\u2068\u2069]+/g;

@ -86,6 +86,7 @@ export const codes = [
'a11y_role_supports_aria_props_implicit',
'a11y_unknown_aria_attribute',
'a11y_unknown_role',
'bidirectional_control_characters',
'legacy_code',
'unknown_code',
'options_deprecated_accessors',
@ -506,6 +507,14 @@ export function a11y_unknown_role(node, role, suggestion) {
w(node, 'a11y_unknown_role', `${suggestion ? `Unknown role '${role}'. Did you mean '${suggestion}'?` : `Unknown role '${role}'`}\nhttps://svelte.dev/e/a11y_unknown_role`);
}
/**
* A bidirectional control character was detected in your code. These characters can be used to alter the visual direction of your code and could have unintended consequences
* @param {null | NodeLike} node
*/
export function bidirectional_control_characters(node) {
w(node, 'bidirectional_control_characters', `A bidirectional control character was detected in your code. These characters can be used to alter the visual direction of your code and could have unintended consequences\nhttps://svelte.dev/e/bidirectional_control_characters`);
}
/**
* `%code%` is no longer valid please use `%suggestion%` instead
* @param {null | NodeLike} node

@ -0,0 +1,8 @@
<script>
let name = '\u2067\u2066rld\u2069\u2066wo\u2069\u2069';
</script>
defabc
<h1>Hello, {name}!</h1>
<!-- svelte-ignore bidirectional_control_characters -->
defabc

@ -0,0 +1,50 @@
[
{
"code": "bidirectional_control_characters",
"message": "A bidirectional control character was detected in your code. These characters can be used to alter the visual direction of your code and could have unintended consequences",
"start": {
"line": 2,
"column": 15
},
"end": {
"line": 2,
"column": 58
}
},
{
"code": "bidirectional_control_characters",
"message": "A bidirectional control character was detected in your code. These characters can be used to alter the visual direction of your code and could have unintended consequences",
"start": {
"line": 4,
"column": 0
},
"end": {
"line": 4,
"column": 2
}
},
{
"code": "bidirectional_control_characters",
"message": "A bidirectional control character was detected in your code. These characters can be used to alter the visual direction of your code and could have unintended consequences",
"start": {
"line": 4,
"column": 5
},
"end": {
"line": 4,
"column": 7
}
},
{
"code": "bidirectional_control_characters",
"message": "A bidirectional control character was detected in your code. These characters can be used to alter the visual direction of your code and could have unintended consequences",
"start": {
"line": 4,
"column": 10
},
"end": {
"line": 4,
"column": 12
}
}
]
Loading…
Cancel
Save