Skip to content

Commit fb44323

Browse files
crisbetodevversion
authored andcommitted
fix(compiler-cli): incorrectly generating relative file paths on case-insensitive platforms (#58150)
We're using `path.relative` to compute a relative path between a `SourceFile` and the one of the `rootDirs`. The problem is that the `rootDirs` get passed through `getCanonicalFileName` which lowercases the path in some platforms, while `SourceFile.fileName` is always case-insensitive. This yields a path outside of the project which we were ignoring. This change passes the `SourceFile.fileName` before passing it through `path.relative` to ensure that we get a valid result. PR Close #58150
1 parent 852c042 commit fb44323

7 files changed

Lines changed: 49 additions & 36 deletions

File tree

packages/compiler-cli/src/ngtsc/annotations/common/src/debug_info.ts

Lines changed: 4 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,15 @@
77
*/
88

99
import {literal, R3ClassDebugInfo, WrappedNodeExpr} from '@angular/compiler';
10-
import * as path from 'path';
10+
import ts from 'typescript';
1111

1212
import {DeclarationNode, ReflectionHost} from '../../../reflection';
13+
import {getProjectRelativePath} from './util';
1314

1415
export function extractClassDebugInfo(
1516
clazz: DeclarationNode,
1617
reflection: ReflectionHost,
18+
compilerHost: Pick<ts.CompilerHost, 'getCanonicalFileName'>,
1719
rootDirs: ReadonlyArray<string>,
1820
forbidOrphanRendering: boolean,
1921
): R3ClassDebugInfo | null {
@@ -22,7 +24,7 @@ export function extractClassDebugInfo(
2224
}
2325

2426
const srcFile = clazz.getSourceFile();
25-
const srcFileMaybeRelativePath = computeRelativePathIfPossible(srcFile.fileName, rootDirs);
27+
const srcFileMaybeRelativePath = getProjectRelativePath(srcFile, rootDirs, compilerHost);
2628

2729
return {
2830
type: new WrappedNodeExpr(clazz.name),
@@ -32,21 +34,3 @@ export function extractClassDebugInfo(
3234
forbidOrphanRendering,
3335
};
3436
}
35-
36-
/**
37-
* Computes a source file path relative to the project root folder if possible, otherwise returns
38-
* null.
39-
*/
40-
function computeRelativePathIfPossible(
41-
filePath: string,
42-
rootDirs: ReadonlyArray<string>,
43-
): string | null {
44-
for (const rootDir of rootDirs) {
45-
const rel = path.relative(rootDir, filePath);
46-
if (!rel.startsWith('..')) {
47-
return rel;
48-
}
49-
}
50-
51-
return null;
52-
}

packages/compiler-cli/src/ngtsc/annotations/common/src/util.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import {
2020
Statement,
2121
WrappedNodeExpr,
2222
} from '@angular/compiler';
23+
import {relative} from 'path';
2324
import ts from 'typescript';
2425

2526
import {
@@ -509,3 +510,31 @@ export function isAbstractClassDeclaration(clazz: ClassDeclaration): boolean {
509510
? clazz.modifiers.some((mod) => mod.kind === ts.SyntaxKind.AbstractKeyword)
510511
: false;
511512
}
513+
514+
/**
515+
* Attempts to generate a project-relative path
516+
* @param sourceFile
517+
* @param rootDirs
518+
* @param compilerHost
519+
* @returns
520+
*/
521+
export function getProjectRelativePath(
522+
sourceFile: ts.SourceFile,
523+
rootDirs: readonly string[],
524+
compilerHost: Pick<ts.CompilerHost, 'getCanonicalFileName'>,
525+
): string | null {
526+
// Note: we need to pass both the file name and the root directories through getCanonicalFileName,
527+
// because the root directories might've been passed through it already while the source files
528+
// definitely have not. This can break the relative return value, because in some platforms
529+
// getCanonicalFileName lowercases the path.
530+
const filePath = compilerHost.getCanonicalFileName(sourceFile.fileName);
531+
532+
for (const rootDir of rootDirs) {
533+
const rel = relative(compilerHost.getCanonicalFileName(rootDir), filePath);
534+
if (!rel.startsWith('..')) {
535+
return rel;
536+
}
537+
}
538+
539+
return null;
540+
}

packages/compiler-cli/src/ngtsc/annotations/component/src/handler.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -221,7 +221,7 @@ export class ComponentDecoratorHandler
221221
private metaRegistry: MetadataRegistry,
222222
private metaReader: MetadataReader,
223223
private scopeReader: ComponentScopeReader,
224-
private dtsScopeReader: DtsModuleScopeResolver,
224+
private compilerHost: Pick<ts.CompilerHost, 'getCanonicalFileName'>,
225225
private scopeRegistry: LocalModuleScopeRegistry,
226226
private typeCheckScopeRegistry: TypeCheckScopeRegistry,
227227
private resourceRegistry: ResourceRegistry,
@@ -872,11 +872,12 @@ export class ComponentDecoratorHandler
872872
classDebugInfo: extractClassDebugInfo(
873873
node,
874874
this.reflector,
875+
this.compilerHost,
875876
this.rootDirs,
876877
/* forbidOrphanRenderering */ this.forbidOrphanRendering,
877878
),
878879
hmrInitializerMeta: this.enableHmr
879-
? extractHmrInitializerMeta(node, this.reflector, this.rootDirs)
880+
? extractHmrInitializerMeta(node, this.reflector, this.compilerHost, this.rootDirs)
880881
: null,
881882
template,
882883
providersRequiringFactory,

packages/compiler-cli/src/ngtsc/annotations/component/src/hmr.ts

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,29 +8,26 @@
88

99
import {R3HmrInitializerMetadata, WrappedNodeExpr} from '@angular/compiler';
1010
import {DeclarationNode, ReflectionHost} from '../../../reflection';
11-
import {relative} from 'path';
11+
import {getProjectRelativePath} from '../../common';
12+
import ts from 'typescript';
1213

1314
/**
1415
* Extracts the metadata necessary to generate an HMR initializer.
1516
*/
1617
export function extractHmrInitializerMeta(
1718
clazz: DeclarationNode,
1819
reflection: ReflectionHost,
20+
compilerHost: Pick<ts.CompilerHost, 'getCanonicalFileName'>,
1921
rootDirs: readonly string[],
2022
): R3HmrInitializerMetadata | null {
2123
if (!reflection.isClass(clazz)) {
2224
return null;
2325
}
2426

25-
// Attempt to generate a project-relative path before falling back to the full path.
26-
let filePath = clazz.getSourceFile().fileName;
27-
for (const rootDir of rootDirs) {
28-
const relativePath = relative(rootDir, filePath);
29-
if (!relativePath.startsWith('..')) {
30-
filePath = relativePath;
31-
break;
32-
}
33-
}
27+
const sourceFile = clazz.getSourceFile();
28+
const filePath =
29+
getProjectRelativePath(sourceFile, rootDirs, compilerHost) ||
30+
compilerHost.getCanonicalFileName(sourceFile.fileName);
3431

3532
const meta: R3HmrInitializerMetadata = {
3633
type: new WrappedNodeExpr(clazz.name),

packages/compiler-cli/src/ngtsc/annotations/component/test/component_spec.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,9 @@ function setup(
120120
metaRegistry,
121121
metaReader,
122122
scopeRegistry,
123-
dtsResolver,
123+
{
124+
getCanonicalFileName: (fileName) => fileName,
125+
},
124126
scopeRegistry,
125127
typeCheckScopeRegistry,
126128
resourceRegistry,

packages/compiler-cli/src/ngtsc/core/src/compiler.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1429,7 +1429,7 @@ export class NgCompiler {
14291429
metaRegistry,
14301430
metaReader,
14311431
scopeReader,
1432-
depScopeReader,
1432+
this.adapter,
14331433
ngModuleScopeRegistry,
14341434
typeCheckScopeRegistry,
14351435
resourceRegistry,

packages/compiler-cli/test/ngtsc/ngtsc_spec.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10574,10 +10574,10 @@ runInEachFileSystem((os: string) => {
1057410574
// We need a regex match here, because the file path changes based on
1057510575
// the file system and the timestamp will be different for each test run.
1057610576
expect(jsContents).toMatch(
10577-
/import\.meta\.hot && import\.meta\.hot\.on\("angular:component-update", d => { if \(d\.id == ".*test\.ts%40Cmp"\) {/,
10577+
/import\.meta\.hot && import\.meta\.hot\.on\("angular:component-update", d => { if \(d\.id == "test\.ts%40Cmp"\) {/,
1057810578
);
1057910579
expect(jsContents).toMatch(
10580-
/import\("\/@ng\/component\?c=.*test\.ts%40Cmp&t=\d+"\).then\(m => i0\.ɵɵreplaceMetadata\(Cmp, m\.default\)\);/,
10580+
/import\("\/@ng\/component\?c=test\.ts%40Cmp&t=\d+"\).then\(m => i0\.ɵɵreplaceMetadata\(Cmp, m\.default\)\);/,
1058110581
);
1058210582
});
1058310583
});

0 commit comments

Comments
 (0)