Skip to content

Commit 53a8459

Browse files
petebacondarwinalxhub
authored andcommitted
fix(compiler-cli): ensure LogicalFileSystem handles case-sensitivity (#36859)
The `LogicalFileSystem` was not taking into account the case-sensitivity of the file-system when caching logical file paths. PR Close #36859
1 parent 0ec0ff3 commit 53a8459

File tree

7 files changed

+46
-25
lines changed

7 files changed

+46
-25
lines changed

packages/compiler-cli/ngcc/src/analysis/decoration_analyzer.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,8 @@ export class DecorationAnalyzer {
7575
// TODO(alxhub): there's no reason why ngcc needs the "logical file system" logic here, as ngcc
7676
// projects only ever have one rootDir. Instead, ngcc should just switch its emitted import
7777
// based on whether a bestGuessOwningModule is present in the Reference.
78-
new LogicalProjectStrategy(this.reflectionHost, new LogicalFileSystem(this.rootDirs)),
78+
new LogicalProjectStrategy(
79+
this.reflectionHost, new LogicalFileSystem(this.rootDirs, this.host)),
7980
]);
8081
aliasingHost = this.bundle.entryPoint.generateDeepReexports ?
8182
new PrivateExportAliasingHost(this.reflectionHost) :

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -612,8 +612,8 @@ export class NgCompiler {
612612
(this.options.rootDirs !== undefined && this.options.rootDirs.length > 0)) {
613613
// rootDirs logic is in effect - use the `LogicalProjectStrategy` for in-project relative
614614
// imports.
615-
localImportStrategy =
616-
new LogicalProjectStrategy(reflector, new LogicalFileSystem([...this.host.rootDirs]));
615+
localImportStrategy = new LogicalProjectStrategy(
616+
reflector, new LogicalFileSystem([...this.host.rootDirs], this.host));
617617
} else {
618618
// Plain relative imports are all that's needed.
619619
localImportStrategy = new RelativePathStrategy(reflector);

packages/compiler-cli/src/ngtsc/file_system/src/logical.ts

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -53,10 +53,13 @@ export class LogicalFileSystem {
5353
*/
5454
private cache: Map<AbsoluteFsPath, LogicalProjectPath|null> = new Map();
5555

56-
constructor(rootDirs: AbsoluteFsPath[]) {
56+
constructor(rootDirs: AbsoluteFsPath[], private compilerHost: ts.CompilerHost) {
5757
// Make a copy and sort it by length in reverse order (longest first). This speeds up lookups,
5858
// since there's no need to keep going through the array once a match is found.
59-
this.rootDirs = rootDirs.concat([]).sort((a, b) => b.length - a.length);
59+
this.rootDirs =
60+
rootDirs.map(dir => this.compilerHost.getCanonicalFileName(dir) as AbsoluteFsPath)
61+
.concat([])
62+
.sort((a, b) => b.length - a.length);
6063
}
6164

6265
/**
@@ -76,11 +79,13 @@ export class LogicalFileSystem {
7679
* of the TS project's root directories.
7780
*/
7881
logicalPathOfFile(physicalFile: AbsoluteFsPath): LogicalProjectPath|null {
79-
if (!this.cache.has(physicalFile)) {
82+
const canonicalFilePath =
83+
this.compilerHost.getCanonicalFileName(physicalFile) as AbsoluteFsPath;
84+
if (!this.cache.has(canonicalFilePath)) {
8085
let logicalFile: LogicalProjectPath|null = null;
8186
for (const rootDir of this.rootDirs) {
82-
if (physicalFile.startsWith(rootDir)) {
83-
logicalFile = this.createLogicalProjectPath(physicalFile, rootDir);
87+
if (isWithinBasePath(rootDir, canonicalFilePath)) {
88+
logicalFile = this.createLogicalProjectPath(canonicalFilePath, rootDir);
8489
// The logical project does not include any special "node_modules" nested directories.
8590
if (logicalFile.indexOf('/node_modules/') !== -1) {
8691
logicalFile = null;
@@ -89,9 +94,9 @@ export class LogicalFileSystem {
8994
}
9095
}
9196
}
92-
this.cache.set(physicalFile, logicalFile);
97+
this.cache.set(canonicalFilePath, logicalFile);
9398
}
94-
return this.cache.get(physicalFile)!;
99+
return this.cache.get(canonicalFilePath)!;
95100
}
96101

97102
private createLogicalProjectPath(file: AbsoluteFsPath, rootDir: AbsoluteFsPath):
@@ -100,3 +105,11 @@ export class LogicalFileSystem {
100105
return (logicalPath.startsWith('/') ? logicalPath : '/' + logicalPath) as LogicalProjectPath;
101106
}
102107
}
108+
109+
/**
110+
* Is the `path` a descendant of the `base`?
111+
* E.g. `foo/bar/zee` is within `foo/bar` but not within `foo/car`.
112+
*/
113+
function isWithinBasePath(base: AbsoluteFsPath, path: AbsoluteFsPath): boolean {
114+
return !relative(base, path).startsWith('..');
115+
}

packages/compiler-cli/src/ngtsc/file_system/test/logical_spec.ts

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,18 +6,24 @@
66
* found in the LICENSE file at https://angular.io/license
77
*/
88

9-
import {absoluteFrom} from '../src/helpers';
9+
import {NgtscCompilerHost} from '../src/compiler_host';
10+
import {absoluteFrom, getFileSystem} from '../src/helpers';
1011
import {LogicalFileSystem, LogicalProjectPath} from '../src/logical';
1112
import {runInEachFileSystem} from '../testing';
1213

1314
runInEachFileSystem(() => {
1415
describe('logical paths', () => {
1516
let _: typeof absoluteFrom;
16-
beforeEach(() => _ = absoluteFrom);
17+
let host: NgtscCompilerHost;
18+
19+
beforeEach(() => {
20+
_ = absoluteFrom;
21+
host = new NgtscCompilerHost(getFileSystem());
22+
});
1723

1824
describe('LogicalFileSystem', () => {
1925
it('should determine logical paths in a single root file system', () => {
20-
const fs = new LogicalFileSystem([_('/test')]);
26+
const fs = new LogicalFileSystem([_('/test')], host);
2127
expect(fs.logicalPathOfFile(_('/test/foo/foo.ts')))
2228
.toEqual('/foo/foo' as LogicalProjectPath);
2329
expect(fs.logicalPathOfFile(_('/test/bar/bar.ts')))
@@ -26,23 +32,23 @@ runInEachFileSystem(() => {
2632
});
2733

2834
it('should determine logical paths in a multi-root file system', () => {
29-
const fs = new LogicalFileSystem([_('/test/foo'), _('/test/bar')]);
35+
const fs = new LogicalFileSystem([_('/test/foo'), _('/test/bar')], host);
3036
expect(fs.logicalPathOfFile(_('/test/foo/foo.ts'))).toEqual('/foo' as LogicalProjectPath);
3137
expect(fs.logicalPathOfFile(_('/test/bar/bar.ts'))).toEqual('/bar' as LogicalProjectPath);
3238
});
3339

3440
it('should continue to work when one root is a child of another', () => {
35-
const fs = new LogicalFileSystem([_('/test'), _('/test/dist')]);
41+
const fs = new LogicalFileSystem([_('/test'), _('/test/dist')], host);
3642
expect(fs.logicalPathOfFile(_('/test/foo.ts'))).toEqual('/foo' as LogicalProjectPath);
3743
expect(fs.logicalPathOfFile(_('/test/dist/foo.ts'))).toEqual('/foo' as LogicalProjectPath);
3844
});
3945

4046
it('should always return `/` prefixed logical paths', () => {
41-
const rootFs = new LogicalFileSystem([_('/')]);
47+
const rootFs = new LogicalFileSystem([_('/')], host);
4248
expect(rootFs.logicalPathOfFile(_('/foo/foo.ts')))
4349
.toEqual('/foo/foo' as LogicalProjectPath);
4450

45-
const nonRootFs = new LogicalFileSystem([_('/test/')]);
51+
const nonRootFs = new LogicalFileSystem([_('/test/')], host);
4652
expect(nonRootFs.logicalPathOfFile(_('/test/foo/foo.ts')))
4753
.toEqual('/foo/foo' as LogicalProjectPath);
4854
});

packages/compiler-cli/src/ngtsc/imports/test/emitter_spec.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,7 @@ runInEachFileSystem(() => {
146146
}
147147
}
148148

149-
const {program} = makeProgram([
149+
const {program, host} = makeProgram([
150150
{
151151
name: _('/index.ts'),
152152
contents: `export class Foo {}`,
@@ -157,7 +157,7 @@ runInEachFileSystem(() => {
157157
}
158158
]);
159159
const checker = program.getTypeChecker();
160-
const logicalFs = new LogicalFileSystem([_('/')]);
160+
const logicalFs = new LogicalFileSystem([_('/')], host);
161161
const strategy = new LogicalProjectStrategy(new TestHost(checker), logicalFs);
162162
const decl = getDeclaration(program, _('/index.ts'), 'Foo', ts.isClassDeclaration);
163163
const context = program.getSourceFile(_('/context.ts'))!;

packages/compiler-cli/src/ngtsc/typecheck/test/test_utils.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ export function angularCoreDts(): TestFile {
9595
export declare class EventEmitter<T> {
9696
subscribe(generatorOrNext?: any, error?: any, complete?: any): unknown;
9797
}
98-
98+
9999
export declare type NgIterable<T> = Array<T> | Iterable<T>;
100100
`
101101
};
@@ -254,7 +254,7 @@ export function typecheck(
254254
makeProgram(files, {strictNullChecks: true, noImplicitAny: true, ...opts}, undefined, false);
255255
const sf = program.getSourceFile(absoluteFrom('/main.ts'))!;
256256
const checker = program.getTypeChecker();
257-
const logicalFs = new LogicalFileSystem(getRootDirs(host, options));
257+
const logicalFs = new LogicalFileSystem(getRootDirs(host, options), host);
258258
const reflectionHost = new TypeScriptReflectionHost(checker);
259259
const moduleResolver =
260260
new ModuleResolver(program, options, host, /* moduleResolutionCache */ null);

packages/compiler-cli/src/ngtsc/typecheck/test/type_constructor_spec.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
*/
88
import * as ts from 'typescript';
99

10-
import {absoluteFrom, getSourceFileOrError, LogicalFileSystem} from '../../file_system';
10+
import {absoluteFrom, getFileSystem, getSourceFileOrError, LogicalFileSystem, NgtscCompilerHost} from '../../file_system';
1111
import {runInEachFileSystem, TestFile} from '../../file_system/testing';
1212
import {AbsoluteModuleStrategy, LocalIdentifierStrategy, LogicalProjectStrategy, ModuleResolver, Reference, ReferenceEmitter} from '../../imports';
1313
import {isNamedClassDeclaration, ReflectionHost, TypeScriptReflectionHost} from '../../reflection';
@@ -40,6 +40,7 @@ runInEachFileSystem(() => {
4040
});
4141

4242
it('should not produce an empty SourceFile when there is nothing to typecheck', () => {
43+
const host = new NgtscCompilerHost(getFileSystem());
4344
const file = new TypeCheckFile(
4445
_('/_typecheck_.ts'), ALL_ENABLED_CONFIG, new ReferenceEmitter([]),
4546
/* reflector */ null!);
@@ -64,7 +65,7 @@ TestClass.ngTypeCtor({value: 'test'});
6465
const {program, host, options} = makeProgram(files, undefined, undefined, false);
6566
const checker = program.getTypeChecker();
6667
const reflectionHost = new TypeScriptReflectionHost(checker);
67-
const logicalFs = new LogicalFileSystem(getRootDirs(host, options));
68+
const logicalFs = new LogicalFileSystem(getRootDirs(host, options), host);
6869
const moduleResolver =
6970
new ModuleResolver(program, options, host, /* moduleResolutionCache */ null);
7071
const emitter = new ReferenceEmitter([
@@ -100,7 +101,7 @@ TestClass.ngTypeCtor({value: 'test'});
100101
const {program, host, options} = makeProgram(files, undefined, undefined, false);
101102
const checker = program.getTypeChecker();
102103
const reflectionHost = new TypeScriptReflectionHost(checker);
103-
const logicalFs = new LogicalFileSystem(getRootDirs(host, options));
104+
const logicalFs = new LogicalFileSystem(getRootDirs(host, options), host);
104105
const moduleResolver =
105106
new ModuleResolver(program, options, host, /* moduleResolutionCache */ null);
106107
const emitter = new ReferenceEmitter([
@@ -143,7 +144,7 @@ TestClass.ngTypeCtor({value: 'test'});
143144
const {program, host, options} = makeProgram(files, undefined, undefined, false);
144145
const checker = program.getTypeChecker();
145146
const reflectionHost = new TypeScriptReflectionHost(checker);
146-
const logicalFs = new LogicalFileSystem(getRootDirs(host, options));
147+
const logicalFs = new LogicalFileSystem(getRootDirs(host, options), host);
147148
const moduleResolver =
148149
new ModuleResolver(program, options, host, /* moduleResolutionCache */ null);
149150
const emitter = new ReferenceEmitter([

0 commit comments

Comments
 (0)