From 80d0d1f0c47fabe4bd2f0b3bde2d66166f82fb53 Mon Sep 17 00:00:00 2001 From: DonIsaac <22823424+DonIsaac@users.noreply.github.com> Date: Fri, 16 Aug 2024 02:30:09 +0000 Subject: [PATCH] feat(semantic): check for invalid interface heritage clauses (#4928) --- crates/oxc_semantic/src/checker/mod.rs | 1 + crates/oxc_semantic/src/checker/typescript.rs | 25 +++++++++++++++++++ .../oxc_semantic/tests/integration/symbols.rs | 3 +++ tasks/coverage/parser_typescript.snap | 19 +++++++++++--- 4 files changed, 45 insertions(+), 3 deletions(-) diff --git a/crates/oxc_semantic/src/checker/mod.rs b/crates/oxc_semantic/src/checker/mod.rs index 809ead5da..9b8120b19 100644 --- a/crates/oxc_semantic/src/checker/mod.rs +++ b/crates/oxc_semantic/src/checker/mod.rs @@ -99,6 +99,7 @@ pub fn check<'a>(node: &AstNode<'a>, ctx: &SemanticBuilder<'a>) { AstKind::YieldExpression(expr) => js::check_yield_expression(expr, node, ctx), AstKind::VariableDeclarator(decl) => ts::check_variable_declarator(decl, ctx), AstKind::SimpleAssignmentTarget(target) => ts::check_simple_assignment_target(target, ctx), + AstKind::TSInterfaceDeclaration(decl) => ts::check_ts_interface_declaration(decl, ctx), AstKind::TSTypeParameterDeclaration(declaration) => { ts::check_ts_type_parameter_declaration(declaration, ctx); } diff --git a/crates/oxc_semantic/src/checker/typescript.rs b/crates/oxc_semantic/src/checker/typescript.rs index 28c263a01..ff6de0b05 100644 --- a/crates/oxc_semantic/src/checker/typescript.rs +++ b/crates/oxc_semantic/src/checker/typescript.rs @@ -120,6 +120,31 @@ pub fn check_array_pattern<'a>(pattern: &ArrayPattern<'a>, ctx: &SemanticBuilder } } +/// An interface can only extend an identifier/qualified-name with optional type arguments.(2499) +fn invalid_interface_extend(span0: Span) -> OxcDiagnostic { + ts_error( + "2499", + "An interface can only extend an identifier/qualified-name with optional type arguments.", + ) + .with_label(span0) +} + +pub fn check_ts_interface_declaration<'a>( + decl: &TSInterfaceDeclaration<'a>, + ctx: &SemanticBuilder<'a>, +) { + if let Some(extends) = &decl.extends { + for extend in extends { + if !matches!( + &extend.expression, + Expression::Identifier(_) | Expression::StaticMemberExpression(_), + ) { + ctx.error(invalid_interface_extend(extend.span)); + } + } + } +} + fn not_allowed_namespace_declaration(span0: Span) -> OxcDiagnostic { OxcDiagnostic::error( "A namespace declaration is only allowed at the top level of a namespace or module.", diff --git a/crates/oxc_semantic/tests/integration/symbols.rs b/crates/oxc_semantic/tests/integration/symbols.rs index 2ac2613cf..64c638223 100644 --- a/crates/oxc_semantic/tests/integration/symbols.rs +++ b/crates/oxc_semantic/tests/integration/symbols.rs @@ -342,12 +342,15 @@ fn test_type_query() { #[test] fn test_ts_interface_heritage() { + // NOTE: interface heritage clauses can only be identifiers or qualified + // names, but we handle references on invalid heritage clauses anyways. SemanticTester::ts( " type Heritage = { x: number; y: string; }; interface A extends (Heritage.x) {} ", ) + .expect_errors(true) .has_some_symbol("Heritage") .has_number_of_references(1) .test(); diff --git a/tasks/coverage/parser_typescript.snap b/tasks/coverage/parser_typescript.snap index e8a77e2f8..b78c0baac 100644 --- a/tasks/coverage/parser_typescript.snap +++ b/tasks/coverage/parser_typescript.snap @@ -3,7 +3,7 @@ commit: d8086f14 parser_typescript Summary: AST Parsed : 6446/6456 (99.85%) Positive Passed: 6423/6456 (99.49%) -Negative Passed: 1167/5653 (20.64%) +Negative Passed: 1169/5653 (20.68%) Expect Syntax Error: "compiler/ClassDeclaration10.ts" Expect Syntax Error: "compiler/ClassDeclaration11.ts" Expect Syntax Error: "compiler/ClassDeclaration13.ts" @@ -1158,7 +1158,6 @@ Expect Syntax Error: "compiler/interfaceImplementation6.ts" Expect Syntax Error: "compiler/interfaceImplementation7.ts" Expect Syntax Error: "compiler/interfaceImplementation8.ts" Expect Syntax Error: "compiler/interfaceInheritance.ts" -Expect Syntax Error: "compiler/interfaceMayNotBeExtendedWitACall.ts" Expect Syntax Error: "compiler/interfaceMemberValidation.ts" Expect Syntax Error: "compiler/interfaceNameAsIdentifier.ts" Expect Syntax Error: "compiler/interfacePropertiesWithSameName2.ts" @@ -3317,7 +3316,6 @@ Expect Syntax Error: "conformance/interfaces/declarationMerging/twoGenericInterf Expect Syntax Error: "conformance/interfaces/declarationMerging/twoInterfacesDifferentRootModule.ts" Expect Syntax Error: "conformance/interfaces/declarationMerging/twoInterfacesDifferentRootModule2.ts" Expect Syntax Error: "conformance/interfaces/interfaceDeclarations/derivedInterfaceIncompatibleWithBaseIndexer.ts" -Expect Syntax Error: "conformance/interfaces/interfaceDeclarations/interfaceExtendingOptionalChain.ts" Expect Syntax Error: "conformance/interfaces/interfaceDeclarations/interfaceExtendsObjectIntersectionErrors.ts" Expect Syntax Error: "conformance/interfaces/interfaceDeclarations/interfaceThatHidesBaseProperty2.ts" Expect Syntax Error: "conformance/interfaces/interfaceDeclarations/interfaceThatIndirectlyInheritsFromItself.ts" @@ -8119,6 +8117,14 @@ Expect to Parse: "conformance/salsa/typeFromPropertyAssignmentWithExport.ts" · ╰── `{` expected ╰──── + × TS(2499): An interface can only extend an identifier/qualified-name with optional type arguments. + ╭─[compiler/interfaceMayNotBeExtendedWitACall.ts:3:24] + 2 │ + 3 │ interface blue extends color() { // error + · ─────── + 4 │ + ╰──── + × Expected a semicolon or an implicit semicolon after a statement, but found none ╭─[compiler/interfaceNaming1.ts:1:10] 1 │ interface { } @@ -17990,6 +17996,13 @@ Expect to Parse: "conformance/salsa/typeFromPropertyAssignmentWithExport.ts" 11 │ I // This should be the identifier 'I' ╰──── + × TS(2499): An interface can only extend an identifier/qualified-name with optional type arguments. + ╭─[conformance/interfaces/interfaceDeclarations/interfaceExtendingOptionalChain.ts:5:22] + 4 │ + 5 │ interface C1 extends Foo?.Bar {} + · ──────── + ╰──── + × Expected a semicolon or an implicit semicolon after a statement, but found none ╭─[conformance/interfaces/interfaceDeclarations/interfacesWithPredefinedTypesAsNames.ts:5:10] 4 │ interface boolean { }