mirror of
https://github.com/oxc-project/oxc.git
synced 2026-11-04 10:29:58 +00:00
fix(parser): report import type alias to a non-external reference in the parser (#23032)
## What
Moves the "An import alias cannot use 'import type'" check (TS1392) out of the semantic checker (`check_ts_import_equals_declaration`) into the parser's `parse_ts_import_equals_declaration`.
`import type Foo = Bar.Baz` is rejected; `import type Foo = require('./foo')` is allowed.
## Why it's a clean move
- **Zero state:** the check is `import_kind.is_type() && !module_reference.is_external()` — both already in hand at the parse site.
- **Node-final:** a `TSImportEqualsDeclaration` is never reinterpreted by cover grammar.
- **Negative-only:** confirmed by `cargo coverage` — zero snapshot/count changes in any suite.
## Notes
A parser unit test parsed `import type foo = bar` (the now-rejected form) and asserted no errors. Updated it to assert the AST is still built (the error is recoverable) alongside the diagnostic.
## Verification
- `import type Foo = Bar.Baz` → error; `import type Foo = require('./foo')` and `import Foo = Bar.Baz` → clean.
- Parser/semantic/linter tests pass; clippy clean.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
This commit is contained in:
6 files changed
+25
-21
No files matched your search
@@ -1209,6 +1209,11 @@ pub fn abstract_with_private_identifier(span: Span) -> OxcDiagnostic {
|
||||
.with_label(span)
|
||||
}
|
||||
|
||||
#[cold]
|
||||
pub fn import_alias_cannot_use_import_type(span: Span) -> OxcDiagnostic {
|
||||
ts_error("1392", "An import alias cannot use 'import type'").with_label(span)
|
||||
}
|
||||
|
||||
#[cold]
|
||||
pub fn abstract_accessor_cannot_have_implementation(name: &str, span: Span) -> OxcDiagnostic {
|
||||
ts_error(
|
||||
|
||||
@@ -1298,8 +1298,10 @@ mod test {
|
||||
}
|
||||
});
|
||||
|
||||
// `import type foo = bar` builds the AST but is a semantic error (TS1392:
|
||||
// an import alias cannot use `import type`), reported by the parser.
|
||||
let src = "import type foo = bar";
|
||||
parse_and_assert_statements(src, |statements| {
|
||||
parse_and_assert_statements_with_error(src, |statements| {
|
||||
if let Statement::TSImportEqualsDeclaration(decl) = statements[0] {
|
||||
assert_eq!(decl.import_kind, ImportOrExportKind::Type);
|
||||
assert_eq!(decl.id.name, "foo");
|
||||
@@ -1360,6 +1362,19 @@ mod test {
|
||||
f(ret.program.body.iter().collect::<Vec<_>>());
|
||||
}
|
||||
|
||||
/// Like [`parse_and_assert_statements`] but for a recoverable error: the parser
|
||||
/// reports a diagnostic yet still builds the AST, which `f` asserts on.
|
||||
fn parse_and_assert_statements_with_error(
|
||||
src: &'static str,
|
||||
f: fn(Vec<&oxc_ast::ast::Statement<'_>>) -> (),
|
||||
) {
|
||||
let source_type = SourceType::default().with_typescript(true);
|
||||
let allocator = Allocator::default();
|
||||
let ret = Parser::new(&allocator, src, source_type).parse();
|
||||
assert!(!ret.errors.is_empty(), "Expected a parse error for source: {src:?}");
|
||||
f(ret.program.body.iter().collect::<Vec<_>>());
|
||||
}
|
||||
|
||||
fn parse_and_assert_import_declarations(
|
||||
src: &'static str,
|
||||
// takes a function which accepts the list of statements
|
||||
|
||||
@@ -651,6 +651,10 @@ impl<'a, C: Config> ParserImpl<'a, C> {
|
||||
if !self.is_ts {
|
||||
self.error(diagnostics::import_equals_can_only_be_used_in_typescript_files(span));
|
||||
}
|
||||
// `import type Foo = Bar.Baz` is not allowed; `import type Foo = require('./foo')` is.
|
||||
if import_kind.is_type() && !module_reference.is_external() {
|
||||
self.error(diagnostics::import_alias_cannot_use_import_type(span));
|
||||
}
|
||||
|
||||
self.ast.declaration_ts_import_equals(span, identifier, module_reference, import_kind)
|
||||
}
|
||||
|
||||
@@ -114,9 +114,6 @@ pub fn check<'a>(kind: AstKind<'a>, ctx: &SemanticBuilder<'a>) {
|
||||
AstKind::TSEnumDeclaration(decl) => ts::check_ts_enum_declaration(decl, ctx),
|
||||
AstKind::TSTypeAliasDeclaration(decl) => ts::check_ts_type_alias_declaration(decl, ctx),
|
||||
AstKind::TSInferType(infer_type) => ts::check_ts_infer_type(infer_type, ctx),
|
||||
AstKind::TSImportEqualsDeclaration(decl) => {
|
||||
ts::check_ts_import_equals_declaration(decl, ctx);
|
||||
}
|
||||
AstKind::JSXExpressionContainer(container) => {
|
||||
ts::check_jsx_expression_container(container, ctx);
|
||||
}
|
||||
|
||||
@@ -173,17 +173,6 @@ pub fn check_ts_enum_declaration<'a>(decl: &TSEnumDeclaration<'a>, ctx: &Semanti
|
||||
check_type_name_is_reserved(&decl.id, ctx, "Enum");
|
||||
}
|
||||
|
||||
pub fn check_ts_import_equals_declaration<'a>(
|
||||
decl: &TSImportEqualsDeclaration<'a>,
|
||||
ctx: &SemanticBuilder<'a>,
|
||||
) {
|
||||
// `import type Foo = require('./foo')` is allowed
|
||||
// `import { Foo } from './foo'; import type Bar = Foo.Bar` is not allowed
|
||||
if decl.import_kind.is_type() && !decl.module_reference.is_external() {
|
||||
ctx.error(diagnostics::import_alias_cannot_use_import_type(decl.span));
|
||||
}
|
||||
}
|
||||
|
||||
pub fn check_class<'a>(class: &Class<'a>, ctx: &SemanticBuilder<'a>) {
|
||||
if !class.r#abstract {
|
||||
for elem in &class.body.body {
|
||||
|
||||
@@ -334,12 +334,6 @@ pub fn enum_member_must_have_initializer(span: Span) -> OxcDiagnostic {
|
||||
OxcDiagnostic::error("Enum member must have initializer.").with_label(span)
|
||||
}
|
||||
|
||||
/// TS(1392)
|
||||
#[cold]
|
||||
pub fn import_alias_cannot_use_import_type(span: Span) -> OxcDiagnostic {
|
||||
ts_error("1392", "An import alias cannot use 'import type'").with_label(span)
|
||||
}
|
||||
|
||||
/// 'infer' declarations are only permitted in the 'extends' clause of a conditional type. (1338)
|
||||
#[cold]
|
||||
pub fn infer_declaration_only_permitted_in_extends_clause(span: Span) -> OxcDiagnostic {
|
||||
|
||||
Reference in new issue
Block a user