From d94b19a92fe884e1699fd3b1e2add63efc193875 Mon Sep 17 00:00:00 2001 From: JoostK Date: Sun, 18 Jan 2026 19:49:50 +0100 Subject: [PATCH] fix(compiler-cli): drop .tsx extension for generated relative imports When synthesizing an import corresponding with a .tsx file, the extension would not be removed unlike regular .ts files. Adjust the import generators to also drop any .tsx extension from module specifiers. Closes #66262 --- .../src/ngtsc/file_system/src/util.ts | 6 ++--- .../src/ngtsc/imports/test/emitter_spec.ts | 26 +++++++++++++++++++ 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/packages/compiler-cli/src/ngtsc/file_system/src/util.ts b/packages/compiler-cli/src/ngtsc/file_system/src/util.ts index 032211ba449..4b1e96f1f6d 100644 --- a/packages/compiler-cli/src/ngtsc/file_system/src/util.ts +++ b/packages/compiler-cli/src/ngtsc/file_system/src/util.ts @@ -8,7 +8,7 @@ import ts from 'typescript'; import {AbsoluteFsPath, PathString} from './types'; -const TS_DTS_JS_EXTENSION = /(?:\.d)?\.ts$|\.js$/; +const TS_DTS_TSX_JS_EXTENSION = /(?:\.d)?\.ts$|\.tsx$|\.js$/; /** * Convert Windows-style separators to POSIX separators. @@ -19,10 +19,10 @@ export function normalizeSeparators(path: string): string { } /** - * Remove a .ts, .d.ts, or .js extension from a file name. + * Remove a .ts, .d.ts, .tsx, or .js extension from a file name. */ export function stripExtension(path: T): T { - return path.replace(TS_DTS_JS_EXTENSION, '') as T; + return path.replace(TS_DTS_TSX_JS_EXTENSION, '') as T; } export function getSourceFileOrError(program: ts.Program, fileName: AbsoluteFsPath): ts.SourceFile { diff --git a/packages/compiler-cli/src/ngtsc/imports/test/emitter_spec.ts b/packages/compiler-cli/src/ngtsc/imports/test/emitter_spec.ts index 415e03abbe6..79f576e5bf7 100644 --- a/packages/compiler-cli/src/ngtsc/imports/test/emitter_spec.ts +++ b/packages/compiler-cli/src/ngtsc/imports/test/emitter_spec.ts @@ -404,6 +404,32 @@ runInEachFileSystem(() => { expect(emitted.expression.value.name).toEqual('Foo'); expect(emitted.expression.value.moduleName).toEqual('./index'); }); + + it('should drop .tsx extension', () => { + const {program} = makeProgram([ + { + name: _('/index.tsx'), + contents: `export class Foo {}`, + }, + { + name: _('/context.ts'), + contents: `export class Context {}`, + }, + ]); + const checker = program.getTypeChecker(); + const strategy = new RelativePathStrategy(new TypeScriptReflectionHost(checker)); + const decl = getDeclaration(program, _('/index.tsx'), 'Foo', ts.isClassDeclaration); + const context = program.getSourceFile(_('/context.ts'))!; + const emitted = strategy.emit(new Reference(decl), context); + if (emitted === null || emitted.kind !== ReferenceEmitKind.Success) { + return fail('Reference should be emitted'); + } + if (!(emitted.expression instanceof ExternalExpr)) { + return fail('Reference should be emitted as ExternalExpr'); + } + expect(emitted.expression.value.name).toEqual('Foo'); + expect(emitted.expression.value.moduleName).toEqual('./index'); + }); }); describe('UnifiedModulesStrategy', () => {