mirror of
https://github.com/oxc-project/oxc.git
synced 2026-09-14 19:36:11 +08:00
fix(parser): allow escaped type default import bindings (#26409)
TypeScript allows a default import binding named `type` to contain Unicode escapes. Oxc currently reports `Keywords cannot contain escape characters` for these valid imports:
```ts
import t\u0079pe from "m";
import t\u0079pe, { x } from "m";
import t\u0079pe, * as ns from "m";
```
In each example, `t\u0079pe` is an identifier whose decoded name is `type`, and the declaration is a value import.
The parser initially recognizes the decoded `type` token before it has enough context to distinguish a default binding from a type-only import modifier. The escape check ran at that point, so it rejected the binding even when the following tokens established that this was a value import.
Move the escape check into the two branches that interpret `type` as a modifier: before a named or namespace import, and before another binding identifier after ruling out `import type from "m"`. The latter check also runs before the early return for a type-only import-equals declaration.
Escaped modifiers remain invalid, including these forms:
```ts
import t\u0079pe Foo = require("m");
import t\u0079pe { x } from "m";
import t\u0079pe * as ns from "m";
import { t\u0079pe Bar } from "m";
```
The change is limited to when the declaration-level escape diagnostic is emitted; import classification and specifier parsing retain their existing behavior.
This commit is contained in:
@@ -103,14 +103,13 @@ impl<'a, C: Config> ParserImpl<'a, C> {
|
||||
} else if self.is_ts && token_after_import.kind() == Kind::Type {
|
||||
// `import type ...`
|
||||
|
||||
if token_after_import.escaped() {
|
||||
self.error(diagnostics::escaped_keyword(token_after_import.span()));
|
||||
}
|
||||
|
||||
let kind = self.cur_kind();
|
||||
if kind == Kind::LCurly || kind == Kind::Star {
|
||||
// `import type { ...`
|
||||
// `import type * ...`
|
||||
if token_after_import.escaped() {
|
||||
self.error(diagnostics::escaped_keyword(token_after_import.span()));
|
||||
}
|
||||
import_kind = ImportOrExportKind::Type;
|
||||
has_default_specifier = false;
|
||||
} else if kind.is_binding_identifier() {
|
||||
@@ -119,10 +118,16 @@ impl<'a, C: Config> ParserImpl<'a, C> {
|
||||
let identifier_after_type = self.parse_binding_identifier();
|
||||
if token.kind() == Kind::From && self.at(Kind::Str) {
|
||||
// `import type from 'source'`
|
||||
if token.escaped() {
|
||||
self.error(diagnostics::escaped_keyword(token.span()));
|
||||
}
|
||||
has_default_specifier = true;
|
||||
import_kind = ImportOrExportKind::Value;
|
||||
should_parse_specifiers = false;
|
||||
} else {
|
||||
if token_after_import.escaped() {
|
||||
self.error(diagnostics::escaped_keyword(token_after_import.span()));
|
||||
}
|
||||
identifier_after_import = Some(identifier_after_type);
|
||||
import_kind = ImportOrExportKind::Type;
|
||||
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
import t\u0079pe Foo = require("m");
|
||||
import t\u0079pe { x } from "m";
|
||||
import t\u0079pe * as ns from "m";
|
||||
import { t\u0079pe Bar } from "m";
|
||||
import t\u0079pe fr\u006fm "m";
|
||||
@@ -0,0 +1 @@
|
||||
import t\u0079pe, { x } from "m";
|
||||
@@ -0,0 +1 @@
|
||||
import t\u0079pe, * as ns from "m";
|
||||
@@ -0,0 +1 @@
|
||||
import t\u0079pe from "m";
|
||||
@@ -1,3 +1,3 @@
|
||||
codegen_misc Summary:
|
||||
AST Parsed : 86/86 (100.00%)
|
||||
Positive Passed: 86/86 (100.00%)
|
||||
AST Parsed : 89/89 (100.00%)
|
||||
Positive Passed: 89/89 (100.00%)
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
formatter_misc Summary:
|
||||
AST Parsed : 86/86 (100.00%)
|
||||
Positive Passed: 86/86 (100.00%)
|
||||
AST Parsed : 89/89 (100.00%)
|
||||
Positive Passed: 89/89 (100.00%)
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
lexer_misc Summary:
|
||||
AST Parsed : 274/274 (100.00%)
|
||||
Positive Passed: 274/274 (100.00%)
|
||||
AST Parsed : 278/278 (100.00%)
|
||||
Positive Passed: 278/278 (100.00%)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
parser_misc Summary:
|
||||
AST Parsed : 86/86 (100.00%)
|
||||
Positive Passed: 86/86 (100.00%)
|
||||
Negative Passed: 188/188 (100.00%)
|
||||
AST Parsed : 89/89 (100.00%)
|
||||
Positive Passed: 89/89 (100.00%)
|
||||
Negative Passed: 189/189 (100.00%)
|
||||
|
||||
× Cannot assign to 'arguments' in strict mode
|
||||
╭─[misc/fail/arguments-eval-ambient.ts:2:13]
|
||||
@@ -377,6 +377,44 @@ Negative Passed: 188/188 (100.00%)
|
||||
╰────
|
||||
help: for octal literals use the '0o' prefix instead
|
||||
|
||||
× Keywords cannot contain escape characters
|
||||
╭─[misc/fail/escaped-type-import.ts:1:8]
|
||||
1 │ import t\u0079pe Foo = require("m");
|
||||
· ─────────
|
||||
2 │ import t\u0079pe { x } from "m";
|
||||
╰────
|
||||
|
||||
× Keywords cannot contain escape characters
|
||||
╭─[misc/fail/escaped-type-import.ts:2:8]
|
||||
1 │ import t\u0079pe Foo = require("m");
|
||||
2 │ import t\u0079pe { x } from "m";
|
||||
· ─────────
|
||||
3 │ import t\u0079pe * as ns from "m";
|
||||
╰────
|
||||
|
||||
× Keywords cannot contain escape characters
|
||||
╭─[misc/fail/escaped-type-import.ts:3:8]
|
||||
2 │ import t\u0079pe { x } from "m";
|
||||
3 │ import t\u0079pe * as ns from "m";
|
||||
· ─────────
|
||||
4 │ import { t\u0079pe Bar } from "m";
|
||||
╰────
|
||||
|
||||
× Keywords cannot contain escape characters
|
||||
╭─[misc/fail/escaped-type-import.ts:4:10]
|
||||
3 │ import t\u0079pe * as ns from "m";
|
||||
4 │ import { t\u0079pe Bar } from "m";
|
||||
· ─────────
|
||||
5 │ import t\u0079pe fr\u006fm "m";
|
||||
╰────
|
||||
|
||||
× Keywords cannot contain escape characters
|
||||
╭─[misc/fail/escaped-type-import.ts:5:18]
|
||||
4 │ import { t\u0079pe Bar } from "m";
|
||||
5 │ import t\u0079pe fr\u006fm "m";
|
||||
· ─────────
|
||||
╰────
|
||||
|
||||
× A unary expression with the '-' operator cannot be used as the left operand of an exponentiation expression
|
||||
╭─[misc/fail/exponentiation-left-operands.ts:1:1]
|
||||
1 │ -value ** 2;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
semantic_misc Summary:
|
||||
AST Parsed : 86/86 (100.00%)
|
||||
Positive Passed: 82/86 (95.35%)
|
||||
AST Parsed : 89/89 (100.00%)
|
||||
Positive Passed: 85/89 (95.51%)
|
||||
semantic Error: tasks/coverage/misc/pass/declare-let-private.ts
|
||||
Bindings mismatch:
|
||||
after transform: ScopeId(0): ["private"]
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
transformer_misc Summary:
|
||||
AST Parsed : 86/86 (100.00%)
|
||||
Positive Passed: 86/86 (100.00%)
|
||||
AST Parsed : 89/89 (100.00%)
|
||||
Positive Passed: 89/89 (100.00%)
|
||||
|
||||
Reference in New Issue
Block a user