Skip to content

Commit d0be845

Browse files
authored
Merge pull request #1106 from sveltejs/gh-1083
Enforce valid names for computed properties
2 parents 24ea1af + 80c55b1 commit d0be845

File tree

10 files changed

+153
-1
lines changed

10 files changed

+153
-1
lines changed

src/parse/index.ts

+18-1
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
1+
import { isIdentifierStart, isIdentifierChar } from 'acorn';
12
import { locate, Location } from 'locate-character';
23
import fragment from './state/fragment';
34
import { whitespace } from '../utils/patterns';
45
import { trimStart, trimEnd } from '../utils/trim';
56
import getCodeFrame from '../utils/getCodeFrame';
67
import reservedNames from '../utils/reservedNames';
8+
import fullCharCodeAt from '../utils/fullCharCodeAt';
79
import hash from './utils/hash';
810
import { Node, Parsed } from '../interfaces';
911
import CompileError from '../utils/CompileError';
@@ -147,7 +149,22 @@ export class Parser {
147149

148150
readIdentifier() {
149151
const start = this.index;
150-
const identifier = this.read(/[a-zA-Z_$][a-zA-Z0-9_$]*/);
152+
153+
let i = this.index;
154+
155+
const code = fullCharCodeAt(this.template, i);
156+
if (!isIdentifierStart(code, true)) return null;
157+
158+
i += code <= 0xffff ? 1 : 2;
159+
160+
while (i < this.template.length) {
161+
const code = fullCharCodeAt(this.template, i);
162+
163+
if (!isIdentifierChar(code, true)) break;
164+
i += code <= 0xffff ? 1 : 2;
165+
}
166+
167+
const identifier = this.template.slice(this.index, this.index = i);
151168

152169
if (reservedNames.has(identifier)) {
153170
this.error(`'${identifier}' is a reserved word in JavaScript and cannot be used here`, start);

src/utils/fullCharCodeAt.ts

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
// Adapted from https://github.com/acornjs/acorn/blob/6584815dca7440e00de841d1dad152302fdd7ca5/src/tokenize.js
2+
// Reproduced under MIT License https://github.com/acornjs/acorn/blob/master/LICENSE
3+
4+
export default function fullCharCodeAt(str: string, i: number): number {
5+
let code = str.charCodeAt(i)
6+
if (code <= 0xd7ff || code >= 0xe000) return code;
7+
8+
let next = str.charCodeAt(i + 1);
9+
return (code << 10) + next - 0x35fdc00;
10+
}

src/utils/isValidIdentifier.ts

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { isIdentifierStart, isIdentifierChar } from 'acorn';
2+
import fullCharCodeAt from './fullCharCodeAt';
3+
4+
export default function isValidIdentifier(str: string): boolean {
5+
let i = 0;
6+
7+
while (i < str.length) {
8+
const code = fullCharCodeAt(str, i);
9+
if (!(i === 0 ? isIdentifierStart : isIdentifierChar)(code, true)) return false;
10+
11+
i += code <= 0xffff ? 1 : 2;
12+
}
13+
14+
return true;
15+
}

src/validate/js/propValidators/computed.ts

+20
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
import checkForDupes from '../utils/checkForDupes';
22
import checkForComputedKeys from '../utils/checkForComputedKeys';
3+
import getName from '../../../utils/getName';
4+
import isValidIdentifier from '../../../utils/isValidIdentifier';
5+
import reservedNames from '../../../utils/reservedNames';
36
import { Validator } from '../../';
47
import { Node } from '../../../interfaces';
58
import walkThroughTopFunctionScope from '../../../utils/walkThroughTopFunctionScope';
@@ -22,6 +25,23 @@ export default function computed(validator: Validator, prop: Node) {
2225
checkForComputedKeys(validator, prop.value.properties);
2326

2427
prop.value.properties.forEach((computation: Node) => {
28+
const name = getName(computation.key);
29+
30+
if (!isValidIdentifier(name)) {
31+
const suggestion = name.replace(/[^_$a-z0-9]/ig, '_').replace(/^\d/, '_$&');
32+
validator.error(
33+
`Computed property name '${name}' is invalid — must be a valid identifier such as ${suggestion}`,
34+
computation.start
35+
);
36+
}
37+
38+
if (reservedNames.has(name)) {
39+
validator.error(
40+
`Computed property name '${name}' is invalid — cannot be a JavaScript reserved word`,
41+
computation.start
42+
);
43+
}
44+
2545
if (!isFunctionExpression.has(computation.value.type)) {
2646
validator.error(
2747
`Computed properties can be function expressions or arrow function expressions`,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{{#each things as 𐊧}}
2+
<p>{{𐊧}}</p>
3+
{{/each}}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
{
2+
"hash": 795130236,
3+
"html": {
4+
"start": 0,
5+
"end": 47,
6+
"type": "Fragment",
7+
"children": [
8+
{
9+
"start": 0,
10+
"end": 47,
11+
"type": "EachBlock",
12+
"expression": {
13+
"type": "Identifier",
14+
"start": 8,
15+
"end": 14,
16+
"name": "things"
17+
},
18+
"children": [
19+
{
20+
"start": 24,
21+
"end": 37,
22+
"type": "Element",
23+
"name": "p",
24+
"attributes": [],
25+
"children": [
26+
{
27+
"start": 27,
28+
"end": 33,
29+
"type": "MustacheTag",
30+
"expression": {
31+
"type": "Identifier",
32+
"start": 29,
33+
"end": 31,
34+
"name": "𐊧"
35+
}
36+
}
37+
]
38+
}
39+
],
40+
"context": "𐊧"
41+
}
42+
]
43+
},
44+
"css": null,
45+
"js": null
46+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
[{
2+
"message":
3+
"Computed property name 'new' is invalid — cannot be a JavaScript reserved word",
4+
"loc": {
5+
"line": 9,
6+
"column": 3
7+
},
8+
"pos": 87
9+
}]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<script>
2+
export default {
3+
data() {
4+
return {
5+
a: 1
6+
};
7+
},
8+
computed: {
9+
new: a => a * 2
10+
}
11+
};
12+
</script>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
[{
2+
"message": "Computed property name 'with-hyphen' is invalid — must be a valid identifier such as with_hyphen",
3+
"loc": {
4+
"line": 9,
5+
"column": 3
6+
},
7+
"pos": 87
8+
}]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<script>
2+
export default {
3+
data() {
4+
return {
5+
a: 1
6+
};
7+
},
8+
computed: {
9+
"with-hyphen": a => a * 2
10+
}
11+
};
12+
</script>

0 commit comments

Comments
 (0)