Skip to content

Commit aa041a9

Browse files
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 <[email protected]>
1 parent 92cdead commit aa041a9

File tree

11 files changed

+153
-3
lines changed

11 files changed

+153
-3
lines changed

.changeset/rare-crews-collect.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'svelte': patch
3+
---
4+
5+
fix: warn on bidirectional control characters

documentation/docs/98-reference/.generated/compile-warnings.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -586,6 +586,14 @@ Attributes should not contain ':' characters to prevent ambiguity with Svelte di
586586
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
587587
```
588588

589+
### bidirectional_control_characters
590+
591+
```
592+
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
593+
```
594+
595+
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.
596+
589597
### bind_invalid_each_rest
590598

591599
```

packages/svelte/messages/compile-warnings/misc.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
## bidirectional_control_characters
2+
3+
> 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
4+
5+
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.
6+
17
## legacy_code
28

39
> `%code%` is no longer valid — please use `%suggestion%` instead

packages/svelte/src/compiler/phases/2-analyze/index.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ import { ImportDeclaration } from './visitors/ImportDeclaration.js';
4343
import { KeyBlock } from './visitors/KeyBlock.js';
4444
import { LabeledStatement } from './visitors/LabeledStatement.js';
4545
import { LetDirective } from './visitors/LetDirective.js';
46+
import { Literal } from './visitors/Literal.js';
4647
import { MemberExpression } from './visitors/MemberExpression.js';
4748
import { NewExpression } from './visitors/NewExpression.js';
4849
import { OnDirective } from './visitors/OnDirective.js';
@@ -63,6 +64,7 @@ import { SvelteSelf } from './visitors/SvelteSelf.js';
6364
import { SvelteWindow } from './visitors/SvelteWindow.js';
6465
import { SvelteBoundary } from './visitors/SvelteBoundary.js';
6566
import { TaggedTemplateExpression } from './visitors/TaggedTemplateExpression.js';
67+
import { TemplateElement } from './visitors/TemplateElement.js';
6668
import { Text } from './visitors/Text.js';
6769
import { TitleElement } from './visitors/TitleElement.js';
6870
import { TransitionDirective } from './visitors/TransitionDirective.js';
@@ -156,6 +158,7 @@ const visitors = {
156158
KeyBlock,
157159
LabeledStatement,
158160
LetDirective,
161+
Literal,
159162
MemberExpression,
160163
NewExpression,
161164
OnDirective,
@@ -176,6 +179,7 @@ const visitors = {
176179
SvelteWindow,
177180
SvelteBoundary,
178181
TaggedTemplateExpression,
182+
TemplateElement,
179183
Text,
180184
TransitionDirective,
181185
TitleElement,
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
/** @import { Literal } from 'estree' */
2+
import * as w from '../../../warnings.js';
3+
import { regex_bidirectional_control_characters } from '../../patterns.js';
4+
5+
/**
6+
* @param {Literal} node
7+
*/
8+
export function Literal(node) {
9+
if (typeof node.value === 'string') {
10+
if (regex_bidirectional_control_characters.test(node.value)) {
11+
w.bidirectional_control_characters(node);
12+
}
13+
}
14+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
/** @import { TemplateElement } from 'estree' */
2+
import * as w from '../../../warnings.js';
3+
import { regex_bidirectional_control_characters } from '../../patterns.js';
4+
5+
/**
6+
* @param {TemplateElement} node
7+
*/
8+
export function TemplateElement(node) {
9+
if (regex_bidirectional_control_characters.test(node.value.cooked ?? '')) {
10+
w.bidirectional_control_characters(node);
11+
}
12+
}
Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,52 @@
11
/** @import { AST } from '#compiler' */
22
/** @import { Context } from '../types' */
33
import { is_tag_valid_with_parent } from '../../../../html-tree-validation.js';
4-
import { regex_not_whitespace } from '../../patterns.js';
4+
import { regex_bidirectional_control_characters, regex_not_whitespace } from '../../patterns.js';
55
import * as e from '../../../errors.js';
6+
import * as w from '../../../warnings.js';
7+
import { extract_svelte_ignore } from '../../../utils/extract_svelte_ignore.js';
68

79
/**
810
* @param {AST.Text} node
911
* @param {Context} context
1012
*/
1113
export function Text(node, context) {
12-
const in_template = context.path.at(-1)?.type === 'Fragment';
14+
const parent = /** @type {AST.SvelteNode} */ (context.path.at(-1));
1315

14-
if (in_template && context.state.parent_element && regex_not_whitespace.test(node.data)) {
16+
if (
17+
parent.type === 'Fragment' &&
18+
context.state.parent_element &&
19+
regex_not_whitespace.test(node.data)
20+
) {
1521
const message = is_tag_valid_with_parent('#text', context.state.parent_element);
1622
if (message) {
1723
e.node_invalid_placement(node, message);
1824
}
1925
}
26+
27+
regex_bidirectional_control_characters.lastIndex = 0;
28+
for (const match of node.data.matchAll(regex_bidirectional_control_characters)) {
29+
let is_ignored = false;
30+
31+
// if we have a svelte-ignore comment earlier in the text, bail
32+
// (otherwise we can only use svelte-ignore on parent elements/blocks)
33+
if (parent.type === 'Fragment') {
34+
for (const child of parent.nodes) {
35+
if (child === node) break;
36+
37+
if (child.type === 'Comment') {
38+
is_ignored ||= extract_svelte_ignore(
39+
child.start + 4,
40+
child.data,
41+
context.state.analysis.runes
42+
).includes('bidirectional_control_characters');
43+
}
44+
}
45+
}
46+
47+
if (!is_ignored) {
48+
let start = match.index + node.start;
49+
w.bidirectional_control_characters({ start, end: start + match[0].length });
50+
}
51+
}
2052
}

packages/svelte/src/compiler/phases/patterns.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,3 +21,5 @@ export const regex_invalid_identifier_chars = /(^[^a-zA-Z_$]|[^a-zA-Z0-9_$])/g;
2121
export const regex_starts_with_vowel = /^[aeiou]/;
2222
export const regex_heading_tags = /^h[1-6]$/;
2323
export const regex_illegal_attribute_character = /(^[0-9-.])|[\^$@%&#?!|()[\]{}^*+~;]/;
24+
export const regex_bidirectional_control_characters =
25+
/[\u202a\u202b\u202c\u202d\u202e\u2066\u2067\u2068\u2069]+/g;

packages/svelte/src/compiler/warnings.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ export const codes = [
8686
'a11y_role_supports_aria_props_implicit',
8787
'a11y_unknown_aria_attribute',
8888
'a11y_unknown_role',
89+
'bidirectional_control_characters',
8990
'legacy_code',
9091
'unknown_code',
9192
'options_deprecated_accessors',
@@ -506,6 +507,14 @@ export function a11y_unknown_role(node, role, suggestion) {
506507
w(node, 'a11y_unknown_role', `${suggestion ? `Unknown role '${role}'. Did you mean '${suggestion}'?` : `Unknown role '${role}'`}\nhttps://svelte.dev/e/a11y_unknown_role`);
507508
}
508509

510+
/**
511+
* 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
512+
* @param {null | NodeLike} node
513+
*/
514+
export function bidirectional_control_characters(node) {
515+
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`);
516+
}
517+
509518
/**
510519
* `%code%` is no longer valid — please use `%suggestion%` instead
511520
* @param {null | NodeLike} node
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<script>
2+
let name = '\u2067\u2066rld\u2069\u2066wo\u2069\u2069';
3+
</script>
4+
⁧⁦def⁩⁦abc⁩⁩
5+
<h1>Hello, {name}!</h1>
6+
7+
<!-- svelte-ignore bidirectional_control_characters -->
8+
⁧⁦def⁩⁦abc⁩⁩
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
[
2+
{
3+
"code": "bidirectional_control_characters",
4+
"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",
5+
"start": {
6+
"line": 2,
7+
"column": 15
8+
},
9+
"end": {
10+
"line": 2,
11+
"column": 58
12+
}
13+
},
14+
{
15+
"code": "bidirectional_control_characters",
16+
"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",
17+
"start": {
18+
"line": 4,
19+
"column": 0
20+
},
21+
"end": {
22+
"line": 4,
23+
"column": 2
24+
}
25+
},
26+
{
27+
"code": "bidirectional_control_characters",
28+
"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",
29+
"start": {
30+
"line": 4,
31+
"column": 5
32+
},
33+
"end": {
34+
"line": 4,
35+
"column": 7
36+
}
37+
},
38+
{
39+
"code": "bidirectional_control_characters",
40+
"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",
41+
"start": {
42+
"line": 4,
43+
"column": 10
44+
},
45+
"end": {
46+
"line": 4,
47+
"column": 12
48+
}
49+
}
50+
]

0 commit comments

Comments
 (0)