Skip to content

Commit dba4023

Browse files
petebacondarwinAndrewKushnir
authored andcommitted
fix(compiler-cli): ensure file_system handles mixed Windows drives (angular#38030)
The `fs.relative()` method assumed that the file-system is a single tree, which is not the case in Windows, where you can have multiple drives, e.g. `C:`, `D:` etc. This commit changes `fs.relative()` so that it no longer forces the result to be a `PathSegment` and then flows that refactoring through the rest of the compiler-cli (and ngcc). The main difference is that now, in some cases, we needed to check whether the result is "rooted", i.e an `AbsoluteFsPath`, rather than a `PathSegment`, before using it. Fixes angular#36777 PR Close angular#38030
1 parent 13d1763 commit dba4023

File tree

22 files changed

+99
-75
lines changed

22 files changed

+99
-75
lines changed

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,12 @@
55
* Use of this source code is governed by an MIT-style license that can be
66
* found in the LICENSE file at https://angular.io/license
77
*/
8-
import {AbsoluteFsPath, relative} from '../../../src/ngtsc/file_system';
8+
import {AbsoluteFsPath, isLocalRelativePath, relative} from '../../../src/ngtsc/file_system';
99
import {DependencyTracker} from '../../../src/ngtsc/incremental/api';
1010

1111
export function isWithinPackage(packagePath: AbsoluteFsPath, filePath: AbsoluteFsPath): boolean {
1212
const relativePath = relative(packagePath, filePath);
13-
return !relativePath.startsWith('..') && !relativePath.startsWith('node_modules/');
13+
return isLocalRelativePath(relativePath) && !relativePath.startsWith('node_modules/');
1414
}
1515

1616
class NoopDependencyTracker implements DependencyTracker {

packages/compiler-cli/ngcc/src/entry_point_finder/tracing_entry_point_finder.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -193,7 +193,7 @@ export abstract class TracingEntryPointFinder implements EntryPointFinder {
193193
* Split the given `path` into path segments using an FS independent algorithm.
194194
* @param path The path to split.
195195
*/
196-
private splitPath(path: PathSegment) {
196+
private splitPath(path: PathSegment|AbsoluteFsPath) {
197197
const segments = [];
198198
while (path !== '.') {
199199
segments.unshift(this.fs.basename(path));

packages/compiler-cli/ngcc/src/rendering/esm_rendering_formatter.ts

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import {Statement} from '@angular/compiler';
99
import MagicString from 'magic-string';
1010
import * as ts from 'typescript';
1111

12-
import {absoluteFromSourceFile, AbsoluteFsPath, dirname, relative} from '../../../src/ngtsc/file_system';
12+
import {absoluteFromSourceFile, AbsoluteFsPath, dirname, relative, toRelativeImport} from '../../../src/ngtsc/file_system';
1313
import {NOOP_DEFAULT_IMPORT_RECORDER, Reexport} from '../../../src/ngtsc/imports';
1414
import {Import, ImportManager, translateStatement} from '../../../src/ngtsc/translator';
1515
import {isDtsPath} from '../../../src/ngtsc/util/src/typescript';
@@ -57,8 +57,9 @@ export class EsmRenderingFormatter implements RenderingFormatter {
5757

5858
if (from) {
5959
const basePath = stripExtension(from);
60-
const relativePath = './' + relative(dirname(entryPointBasePath), basePath);
61-
exportFrom = entryPointBasePath !== basePath ? ` from '${relativePath}'` : '';
60+
const relativePath = relative(dirname(entryPointBasePath), basePath);
61+
const relativeImport = toRelativeImport(relativePath);
62+
exportFrom = entryPointBasePath !== basePath ? ` from '${relativeImport}'` : '';
6263
}
6364

6465
const exportStr = `\nexport {${e.identifier}}${exportFrom};`;
@@ -197,10 +198,10 @@ export class EsmRenderingFormatter implements RenderingFormatter {
197198
const ngModuleName = info.ngModule.node.name.text;
198199
const declarationFile = absoluteFromSourceFile(info.declaration.getSourceFile());
199200
const ngModuleFile = absoluteFromSourceFile(info.ngModule.node.getSourceFile());
201+
const relativePath = relative(dirname(declarationFile), ngModuleFile);
202+
const relativeImport = toRelativeImport(relativePath);
200203
const importPath = info.ngModule.ownedByModuleGuess ||
201-
(declarationFile !== ngModuleFile ?
202-
stripExtension(`./${relative(dirname(declarationFile), ngModuleFile)}`) :
203-
null);
204+
(declarationFile !== ngModuleFile ? stripExtension(relativeImport) : null);
204205
const ngModule = generateImportString(importManager, importPath, ngModuleName);
205206

206207
if (info.declaration.type) {

packages/compiler-cli/ngcc/src/writing/new_entry_point_file_writer.ts

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
* Use of this source code is governed by an MIT-style license that can be
77
* found in the LICENSE file at https://angular.io/license
88
*/
9-
import {absoluteFromSourceFile, AbsoluteFsPath, dirname, FileSystem, join, relative} from '../../../src/ngtsc/file_system';
9+
import {absoluteFromSourceFile, AbsoluteFsPath, dirname, FileSystem, isLocalRelativePath, join, relative, resolve} from '../../../src/ngtsc/file_system';
1010
import {isDtsPath} from '../../../src/ngtsc/util/src/typescript';
1111
import {Logger} from '../logging/logger';
1212
import {EntryPoint, EntryPointJsonProperty} from '../packages/entry_point';
@@ -70,9 +70,9 @@ export class NewEntryPointFileWriter extends InPlaceFileWriter {
7070
bundle: EntryPointBundle, packagePath: AbsoluteFsPath, ngccFolder: AbsoluteFsPath) {
7171
bundle.src.program.getSourceFiles().forEach(sourceFile => {
7272
const relativePath = relative(packagePath, absoluteFromSourceFile(sourceFile));
73-
const isOutsidePackage = relativePath.startsWith('..');
74-
if (!sourceFile.isDeclarationFile && !isOutsidePackage) {
75-
const newFilePath = join(ngccFolder, relativePath);
73+
const isInsidePackage = isLocalRelativePath(relativePath);
74+
if (!sourceFile.isDeclarationFile && isInsidePackage) {
75+
const newFilePath = resolve(ngccFolder, relativePath);
7676
this.fs.ensureDir(dirname(newFilePath));
7777
this.fs.copyFile(absoluteFromSourceFile(sourceFile), newFilePath);
7878
}
@@ -86,7 +86,7 @@ export class NewEntryPointFileWriter extends InPlaceFileWriter {
8686
super.writeFileAndBackup(file);
8787
} else {
8888
const relativePath = relative(packagePath, file.path);
89-
const newFilePath = join(ngccFolder, relativePath);
89+
const newFilePath = resolve(ngccFolder, relativePath);
9090
this.fs.ensureDir(dirname(newFilePath));
9191
this.fs.writeFile(newFilePath, file.contents);
9292
}
@@ -98,7 +98,7 @@ export class NewEntryPointFileWriter extends InPlaceFileWriter {
9898
super.revertFileAndBackup(filePath);
9999
} else if (this.fs.exists(filePath)) {
100100
const relativePath = relative(packagePath, filePath);
101-
const newFilePath = join(packagePath, NGCC_DIRECTORY, relativePath);
101+
const newFilePath = resolve(packagePath, NGCC_DIRECTORY, relativePath);
102102
this.fs.removeFile(newFilePath);
103103
}
104104
}
@@ -117,8 +117,9 @@ export class NewEntryPointFileWriter extends InPlaceFileWriter {
117117
// All format properties point to the same format-path.
118118
const oldFormatProp = formatProperties[0]!;
119119
const oldFormatPath = packageJson[oldFormatProp]!;
120-
const oldAbsFormatPath = join(entryPoint.path, oldFormatPath);
121-
const newAbsFormatPath = join(ngccFolder, relative(entryPoint.packagePath, oldAbsFormatPath));
120+
const oldAbsFormatPath = resolve(entryPoint.path, oldFormatPath);
121+
const newAbsFormatPath =
122+
resolve(ngccFolder, relative(entryPoint.packagePath, oldAbsFormatPath));
122123
const newFormatPath = relative(entryPoint.path, newAbsFormatPath);
123124

124125
// Update all properties in `package.json` (both in memory and on disk).

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
* found in the LICENSE file at https://angular.io/license
77
*/
88
export {NgtscCompilerHost} from './src/compiler_host';
9-
export {absoluteFrom, absoluteFromSourceFile, basename, dirname, getFileSystem, isRoot, isRooted, join, relative, relativeFrom, resolve, setFileSystem} from './src/helpers';
9+
export {absoluteFrom, absoluteFromSourceFile, basename, dirname, getFileSystem, isLocalRelativePath, isRoot, isRooted, join, relative, relativeFrom, resolve, setFileSystem, toRelativeImport} from './src/helpers';
1010
export {LogicalFileSystem, LogicalProjectPath} from './src/logical';
1111
export {NodeJSFileSystem} from './src/node_js_file_system';
1212
export {AbsoluteFsPath, FileStats, FileSystem, PathSegment, PathString} from './src/types';

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

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ export function isRooted(path: string): boolean {
8383
/**
8484
* Static access to `relative`.
8585
*/
86-
export function relative<T extends PathString>(from: T, to: T): PathSegment {
86+
export function relative<T extends PathString>(from: T, to: T): PathSegment|AbsoluteFsPath {
8787
return fs.relative(from, to);
8888
}
8989

@@ -93,3 +93,23 @@ export function relative<T extends PathString>(from: T, to: T): PathSegment {
9393
export function basename(filePath: PathString, extension?: string): PathSegment {
9494
return fs.basename(filePath, extension) as PathSegment;
9595
}
96+
97+
/**
98+
* Returns true if the given path is locally relative.
99+
*
100+
* This is used to work out if the given path is relative (i.e. not absolute) but also is not
101+
* escaping the current directory.
102+
*/
103+
export function isLocalRelativePath(relativePath: string): boolean {
104+
return !isRooted(relativePath) && !relativePath.startsWith('..');
105+
}
106+
107+
/**
108+
* Converts a path to a form suitable for use as a relative module import specifier.
109+
*
110+
* In other words it adds the `./` to the path if it is locally relative.
111+
*/
112+
export function toRelativeImport(relativePath: PathSegment|AbsoluteFsPath): PathSegment|
113+
AbsoluteFsPath {
114+
return isLocalRelativePath(relativePath) ? `./${relativePath}` as PathSegment : relativePath;
115+
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ export class InvalidFileSystem implements FileSystem {
8282
isRooted(path: string): boolean {
8383
throw makeError();
8484
}
85-
relative<T extends PathString>(from: T, to: T): PathSegment {
85+
relative<T extends PathString>(from: T, to: T): PathSegment|AbsoluteFsPath {
8686
throw makeError();
8787
}
8888
basename(filePath: string, extension?: string): PathSegment {

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

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

10-
import {absoluteFrom, dirname, relative, resolve} from './helpers';
10+
import {absoluteFrom, dirname, isLocalRelativePath, relative, resolve, toRelativeImport} from './helpers';
1111
import {AbsoluteFsPath, BrandedPath, PathSegment} from './types';
1212
import {stripExtension} from './util';
1313

@@ -29,11 +29,8 @@ export const LogicalProjectPath = {
2929
* importing from `to`.
3030
*/
3131
relativePathBetween: function(from: LogicalProjectPath, to: LogicalProjectPath): PathSegment {
32-
let relativePath = relative(dirname(resolve(from)), resolve(to));
33-
if (!relativePath.startsWith('../')) {
34-
relativePath = ('./' + relativePath) as PathSegment;
35-
}
36-
return relativePath as PathSegment;
32+
const relativePath = relative(dirname(resolve(from)), resolve(to));
33+
return toRelativeImport(relativePath) as PathSegment;
3734
},
3835
};
3936

@@ -122,5 +119,5 @@ export class LogicalFileSystem {
122119
* E.g. `foo/bar/zee` is within `foo/bar` but not within `foo/car`.
123120
*/
124121
function isWithinBasePath(base: AbsoluteFsPath, path: AbsoluteFsPath): boolean {
125-
return !relative(base, path).startsWith('..');
122+
return isLocalRelativePath(relative(base, path));
126123
}

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
import * as fs from 'fs';
1010
import * as fsExtra from 'fs-extra';
1111
import * as p from 'path';
12-
import {absoluteFrom, relativeFrom} from './helpers';
12+
import {absoluteFrom} from './helpers';
1313
import {AbsoluteFsPath, FileStats, FileSystem, PathSegment, PathString} from './types';
1414

1515
/**
@@ -93,8 +93,8 @@ export class NodeJSFileSystem implements FileSystem {
9393
isRooted(path: string): boolean {
9494
return p.isAbsolute(path);
9595
}
96-
relative<T extends PathString>(from: T, to: T): PathSegment {
97-
return relativeFrom(this.normalize(p.relative(from, to)));
96+
relative<T extends PathString>(from: T, to: T): PathSegment|AbsoluteFsPath {
97+
return this.normalize(p.relative(from, to)) as PathSegment | AbsoluteFsPath;
9898
}
9999
basename(filePath: string, extension?: string): PathSegment {
100100
return p.basename(filePath, extension) as PathSegment;

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

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,14 @@ export interface FileSystem {
5757
resolve(...paths: string[]): AbsoluteFsPath;
5858
dirname<T extends PathString>(file: T): T;
5959
join<T extends PathString>(basePath: T, ...paths: string[]): T;
60-
relative<T extends PathString>(from: T, to: T): PathSegment;
60+
/**
61+
* Compute the relative path between `from` and `to`.
62+
*
63+
* In file-systems that can have multiple file trees the returned path may not actually be
64+
* "relative" (i.e. `PathSegment`). For example, Windows can have multiple drives :
65+
* `relative('c:/a/b', 'd:/a/c')` would be `d:/a/c'.
66+
*/
67+
relative<T extends PathString>(from: T, to: T): PathSegment|AbsoluteFsPath;
6168
basename(filePath: string, extension?: string): PathSegment;
6269
realpath(filePath: AbsoluteFsPath): AbsoluteFsPath;
6370
getDefaultLibLocation(): AbsoluteFsPath;

0 commit comments

Comments
 (0)