Skip to content

Commit 55eda1b

Browse files
committed
[compiler] Emit more specific error when making identifiers with reserved words
This currently throws an invariant which may be misleading. I checked the ecma262 spec and used the same list of reserved words in our check. To err on the side of being conservative, we also error when strict mode reserved words are used.
1 parent bdb4a96 commit 55eda1b

File tree

4 files changed

+148
-6
lines changed

4 files changed

+148
-6
lines changed

compiler/packages/babel-plugin-react-compiler/src/HIR/HIR.ts

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import type {HookKind} from './ObjectShape';
1414
import {Type, makeType} from './Types';
1515
import {z} from 'zod';
1616
import type {AliasingEffect} from '../Inference/AliasingEffects';
17+
import {isReservedWord} from '../Utils/Keyword';
1718

1819
/*
1920
* *******************************************************************************************
@@ -1320,12 +1321,21 @@ export function forkTemporaryIdentifier(
13201321
* original source code.
13211322
*/
13221323
export function makeIdentifierName(name: string): ValidatedIdentifier {
1323-
CompilerError.invariant(t.isValidIdentifier(name), {
1324-
reason: `Expected a valid identifier name`,
1325-
loc: GeneratedSource,
1326-
description: `\`${name}\` is not a valid JavaScript identifier`,
1327-
suggestions: null,
1328-
});
1324+
if (isReservedWord(name)) {
1325+
CompilerError.throwInvalidJS({
1326+
reason: 'Expected a non-reserved identifier name',
1327+
loc: GeneratedSource,
1328+
description: `\`${name}\` is a reserved word in JavaScript and cannot be used as an identifier name`,
1329+
suggestions: null,
1330+
});
1331+
} else {
1332+
CompilerError.invariant(t.isValidIdentifier(name), {
1333+
reason: `Expected a valid identifier name`,
1334+
loc: GeneratedSource,
1335+
description: `\`${name}\` is not a valid JavaScript identifier`,
1336+
suggestions: null,
1337+
});
1338+
}
13291339
return {
13301340
kind: 'named',
13311341
value: name as ValidIdentifierName,
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
/**
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
/**
9+
* https://tc39.es/ecma262/multipage/ecmascript-language-lexical-grammar.html#sec-keywords-and-reserved-words
10+
*/
11+
12+
/**
13+
* Note: `await` and `yield` are contextually allowed as identifiers.
14+
* await: reserved inside async functions and modules
15+
* yield: reserved inside generator functions
16+
*
17+
* Note: `async` is not reserved.
18+
*/
19+
const RESERVED_WORDS = new Set([
20+
'break',
21+
'case',
22+
'catch',
23+
'class',
24+
'const',
25+
'continue',
26+
'debugger',
27+
'default',
28+
'delete',
29+
'do',
30+
'else',
31+
'enum',
32+
'export',
33+
'extends',
34+
'false',
35+
'finally',
36+
'for',
37+
'function',
38+
'if',
39+
'import',
40+
'in',
41+
'instanceof',
42+
'new',
43+
'null',
44+
'return',
45+
'super',
46+
'switch',
47+
'this',
48+
'throw',
49+
'true',
50+
'try',
51+
'typeof',
52+
'var',
53+
'void',
54+
'while',
55+
'with',
56+
]);
57+
58+
/**
59+
* Reserved when a module has a 'use strict' directive.
60+
*/
61+
const STRICT_MODE_RESERVED_WORDS = new Set([
62+
'let',
63+
'static',
64+
'implements',
65+
'interface',
66+
'package',
67+
'private',
68+
'protected',
69+
'public',
70+
]);
71+
/**
72+
* The names arguments and eval are not keywords, but they are subject to some restrictions in
73+
* strict mode code.
74+
*/
75+
const STRICT_MODE_RESTRICTED_WORDS = new Set(['eval', 'arguments']);
76+
77+
/**
78+
* Conservative check for whether an identifer name is reserved or not. We assume that code is
79+
* written with strict mode.
80+
*/
81+
export function isReservedWord(identifierName: string): boolean {
82+
return (
83+
RESERVED_WORDS.has(identifierName) ||
84+
STRICT_MODE_RESERVED_WORDS.has(identifierName) ||
85+
STRICT_MODE_RESTRICTED_WORDS.has(identifierName)
86+
);
87+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
2+
## Input
3+
4+
```javascript
5+
import {useRef} from 'react';
6+
7+
function useThing(fn) {
8+
const fnRef = useRef(fn);
9+
const ref = useRef(null);
10+
11+
if (ref.current === null) {
12+
ref.current = function (this: unknown, ...args) {
13+
return fnRef.current.call(this, ...args);
14+
};
15+
}
16+
return ref.current;
17+
}
18+
19+
```
20+
21+
22+
## Error
23+
24+
```
25+
Found 1 error:
26+
27+
Error: Expected a non-reserved identifier name
28+
29+
`this` is a reserved word in JavaScript and cannot be used as an identifier name.
30+
```
31+
32+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import {useRef} from 'react';
2+
3+
function useThing(fn) {
4+
const fnRef = useRef(fn);
5+
const ref = useRef(null);
6+
7+
if (ref.current === null) {
8+
ref.current = function (this: unknown, ...args) {
9+
return fnRef.current.call(this, ...args);
10+
};
11+
}
12+
return ref.current;
13+
}

0 commit comments

Comments
 (0)