Skip to content

Commit b70a409

Browse files
authored
refactor: ensure paths are safely passed around in ng-update (#19354)
1 parent 2635f0c commit b70a409

22 files changed

+205
-95
lines changed

src/cdk/schematics/ng-update/devkit-file-system.ts

Lines changed: 35 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -6,32 +6,45 @@
66
* found in the LICENSE file at https://angular.io/license
77
*/
88

9-
import {normalize} from '@angular-devkit/core';
9+
import {normalize, Path, relative} from '@angular-devkit/core';
1010
import {Tree, UpdateRecorder} from '@angular-devkit/schematics';
11-
import {relative} from 'path';
11+
import * as path from 'path';
1212
import {FileSystem} from '../update-tool/file-system';
1313

1414
/**
1515
* File system that leverages the virtual tree from the CLI devkit. This file
1616
* system is commonly used by `ng update` migrations that run as part of the
1717
* Angular CLI.
1818
*/
19-
export class DevkitFileSystem implements FileSystem {
19+
export class DevkitFileSystem extends FileSystem<Path> {
2020
private _updateRecorderCache = new Map<string, UpdateRecorder>();
21+
private _workspaceFsPath: Path;
2122

22-
constructor(private _tree: Tree, private _workspaceFsPath: string) {}
23+
constructor(private _tree: Tree, workspaceFsPath: string) {
24+
super();
25+
this._workspaceFsPath = normalize(workspaceFsPath);
26+
}
2327

24-
resolve(fsFilePath: string) {
25-
return normalize(relative(this._workspaceFsPath, fsFilePath)) as string;
28+
resolve(...segments: string[]): Path {
29+
// Note: We use `posix.resolve` as the devkit paths are using posix separators.
30+
const resolvedPath = normalize(path.posix.resolve(...segments.map(normalize)));
31+
// If the resolved path points to the workspace root, then this is an absolute disk
32+
// path and we need to compute a devkit tree relative path.
33+
if (resolvedPath.startsWith(this._workspaceFsPath)) {
34+
return relative(this._workspaceFsPath, resolvedPath);
35+
}
36+
// Otherwise we know that the path is absolute (due to the resolve), and that it
37+
// refers to an absolute devkit tree path (like `/angular.json`). We keep those
38+
// unmodified as they are already resolved workspace paths.
39+
return resolvedPath;
2640
}
2741

28-
edit(fsFilePath: string) {
29-
const treeFilePath = this.resolve(fsFilePath);
30-
if (this._updateRecorderCache.has(treeFilePath)) {
31-
return this._updateRecorderCache.get(treeFilePath)!;
42+
edit(filePath: Path) {
43+
if (this._updateRecorderCache.has(filePath)) {
44+
return this._updateRecorderCache.get(filePath)!;
3245
}
33-
const recorder = this._tree.beginUpdate(treeFilePath);
34-
this._updateRecorderCache.set(treeFilePath, recorder);
46+
const recorder = this._tree.beginUpdate(filePath);
47+
this._updateRecorderCache.set(filePath, recorder);
3548
return recorder;
3649
}
3750

@@ -40,24 +53,24 @@ export class DevkitFileSystem implements FileSystem {
4053
this._updateRecorderCache.clear();
4154
}
4255

43-
exists(fsFilePath: string) {
44-
return this._tree.exists(this.resolve(fsFilePath));
56+
exists(filePath: Path) {
57+
return this._tree.exists(filePath);
4558
}
4659

47-
overwrite(fsFilePath: string, content: string) {
48-
this._tree.overwrite(this.resolve(fsFilePath), content);
60+
overwrite(filePath: Path, content: string) {
61+
this._tree.overwrite(filePath, content);
4962
}
5063

51-
create(fsFilePath: string, content: string) {
52-
this._tree.create(this.resolve(fsFilePath), content);
64+
create(filePath: Path, content: string) {
65+
this._tree.create(filePath, content);
5366
}
5467

55-
delete(fsFilePath: string) {
56-
this._tree.delete(this.resolve(fsFilePath));
68+
delete(filePath: Path) {
69+
this._tree.delete(filePath);
5770
}
5871

59-
read(fsFilePath: string) {
60-
const buffer = this._tree.read(this.resolve(fsFilePath));
72+
read(filePath: Path) {
73+
const buffer = this._tree.read(filePath);
6174
return buffer !== null ? buffer.toString() : null;
6275
}
6376
}

src/cdk/schematics/ng-update/devkit-migration-rule.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {join} from 'path';
1515
import {UpdateProject} from '../update-tool';
1616
import {MigrationCtor} from '../update-tool/migration';
1717
import {TargetVersion} from '../update-tool/target-version';
18+
import {WorkspacePath} from '../update-tool/file-system';
1819
import {getTargetTsconfigPath, getWorkspaceConfigGracefully} from '../utils/project-tsconfig-paths';
1920

2021
import {DevkitFileSystem} from './devkit-file-system';
@@ -72,7 +73,7 @@ export function createMigrationSchematicRule(
7273
// Keep track of all project source files which have been checked/migrated. This is
7374
// necessary because multiple TypeScript projects can contain the same source file and
7475
// we don't want to check these again, as this would result in duplicated failure messages.
75-
const analyzedFiles = new Set<string>();
76+
const analyzedFiles = new Set<WorkspacePath>();
7677
// The CLI uses the working directory as the base directory for the virtual file system tree.
7778
const workspaceFsPath = process.cwd();
7879
const fileSystem = new DevkitFileSystem(tree, workspaceFsPath);

src/cdk/schematics/ng-update/migrations/attribute-selectors.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
*/
88

99
import * as ts from 'typescript';
10+
import {WorkspacePath} from '../../update-tool/file-system';
1011
import {ResolvedResource} from '../../update-tool/component-resource-collector';
1112
import {Migration} from '../../update-tool/migration';
1213
import {AttributeSelectorUpgradeData} from '../data/attribute-selectors';
@@ -63,11 +64,13 @@ export class AttributeSelectorsMigration extends Migration<UpgradeData> {
6364
this.data.forEach(selector => {
6465
findAllSubstringIndices(literalText, selector.replace)
6566
.map(offset => literal.getStart() + offset)
66-
.forEach(start => this._replaceSelector(filePath, start, selector));
67+
.forEach(start => this._replaceSelector(
68+
this.fileSystem.resolve(filePath), start, selector));
6769
});
6870
}
6971

70-
private _replaceSelector(filePath: string, start: number, data: AttributeSelectorUpgradeData) {
72+
private _replaceSelector(filePath: WorkspacePath, start: number,
73+
data: AttributeSelectorUpgradeData) {
7174
this.fileSystem.edit(filePath)
7275
.remove(start, data.replace.length)
7376
.insertRight(start, data.replaceWith);

src/cdk/schematics/ng-update/migrations/class-names.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,8 +97,9 @@ export class ClassNamesMigration extends Migration<UpgradeData> {
9797
/** Creates a failure and replacement for the specified identifier. */
9898
private _createFailureWithReplacement(identifier: ts.Identifier) {
9999
const classData = this.data.find(data => data.replace === identifier.text)!;
100+
const filePath = this.fileSystem.resolve(identifier.getSourceFile().fileName);
100101

101-
this.fileSystem.edit(identifier.getSourceFile().fileName)
102+
this.fileSystem.edit(filePath)
102103
.remove(identifier.getStart(), identifier.getWidth())
103104
.insertRight(identifier.getStart(), classData.replaceWith);
104105
}

src/cdk/schematics/ng-update/migrations/css-selectors.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
*/
88

99
import * as ts from 'typescript';
10+
import {WorkspacePath} from '../../update-tool/file-system';
1011
import {ResolvedResource} from '../../update-tool/component-resource-collector';
1112
import {Migration} from '../../update-tool/migration';
1213
import {CssSelectorUpgradeData} from '../data/css-selectors';
@@ -69,11 +70,11 @@ export class CssSelectorsMigration extends Migration<UpgradeData> {
6970

7071
findAllSubstringIndices(textContent, data.replace)
7172
.map(offset => node.getStart() + offset)
72-
.forEach(start => this._replaceSelector(filePath, start, data));
73+
.forEach(start => this._replaceSelector(this.fileSystem.resolve(filePath), start, data));
7374
});
7475
}
7576

76-
private _replaceSelector(filePath: string, start: number, data: CssSelectorUpgradeData) {
77+
private _replaceSelector(filePath: WorkspacePath, start: number, data: CssSelectorUpgradeData) {
7778
this.fileSystem.edit(filePath)
7879
.remove(start, data.replace.length)
7980
.insertRight(start, data.replaceWith);

src/cdk/schematics/ng-update/migrations/element-selectors.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
import * as ts from 'typescript';
1010
import {ResolvedResource} from '../../update-tool/component-resource-collector';
11+
import {WorkspacePath} from '../../update-tool/file-system';
1112
import {Migration} from '../../update-tool/migration';
1213
import {ElementSelectorUpgradeData} from '../data/element-selectors';
1314
import {findAllSubstringIndices} from '../typescript/literal';
@@ -57,11 +58,13 @@ export class ElementSelectorsMigration extends Migration<UpgradeData> {
5758
this.data.forEach(selector => {
5859
findAllSubstringIndices(textContent, selector.replace)
5960
.map(offset => node.getStart() + offset)
60-
.forEach(start => this._replaceSelector(filePath, start, selector));
61+
.forEach(start => this._replaceSelector(
62+
this.fileSystem.resolve(filePath), start, selector));
6163
});
6264
}
6365

64-
private _replaceSelector(filePath: string, start: number, data: ElementSelectorUpgradeData) {
66+
private _replaceSelector(filePath: WorkspacePath, start: number,
67+
data: ElementSelectorUpgradeData) {
6568
this.fileSystem.edit(filePath)
6669
.remove(start, data.replace.length)
6770
.insertRight(start, data.replaceWith);

src/cdk/schematics/ng-update/migrations/input-names.ts

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

9+
import {WorkspacePath} from '../../update-tool/file-system';
910
import {findInputsOnElementWithAttr, findInputsOnElementWithTag} from '../html-parsing/angular';
1011
import {ResolvedResource} from '../../update-tool/component-resource-collector';
1112
import {Migration} from '../../update-tool/migration';
@@ -63,7 +64,8 @@ export class InputNamesMigration extends Migration<UpgradeData> {
6364
});
6465
}
6566

66-
private _replaceInputName(filePath: string, start: number, width: number, newName: string) {
67+
private _replaceInputName(filePath: WorkspacePath, start: number, width: number,
68+
newName: string) {
6769
this.fileSystem.edit(filePath)
6870
.remove(start, width)
6971
.insertRight(start, newName);

src/cdk/schematics/ng-update/migrations/output-names.ts

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

9+
import {WorkspacePath} from '../../update-tool/file-system';
910
import {ResolvedResource} from '../../update-tool/component-resource-collector';
1011
import {Migration} from '../../update-tool/migration';
1112

@@ -49,7 +50,8 @@ export class OutputNamesMigration extends Migration<UpgradeData> {
4950
});
5051
}
5152

52-
private _replaceOutputName(filePath: string, start: number, width: number, newName: string) {
53+
private _replaceOutputName(filePath: WorkspacePath, start: number, width: number,
54+
newName: string) {
5355
this.fileSystem.edit(filePath)
5456
.remove(start, width)
5557
.insertRight(start, newName);

src/cdk/schematics/ng-update/migrations/property-names.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ export class PropertyNamesMigration extends Migration<UpgradeData> {
3939
}
4040

4141
if (!data.whitelist || data.whitelist.classes.includes(typeName)) {
42-
this.fileSystem.edit(node.getSourceFile().fileName)
42+
this.fileSystem.edit(this.fileSystem.resolve(node.getSourceFile().fileName))
4343
.remove(node.name.getStart(), node.name.getWidth())
4444
.insertRight(node.name.getStart(), data.replaceWith);
4545
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import {resolveBazelPath} from '@angular/cdk/schematics/testing';
2+
import {MIGRATION_PATH} from '../../../index.spec';
3+
import {createTestCaseSetup} from '../../../testing';
4+
5+
describe('ng-update external resource resolution', () => {
6+
7+
it('should properly resolve referenced resources in components', async () => {
8+
const {runFixers, writeFile, removeTempDir, appTree} = await createTestCaseSetup(
9+
'migration-v6', MIGRATION_PATH,
10+
[resolveBazelPath(__dirname, './external-resource-resolution_input.ts')]);
11+
12+
const testContent = `<div cdk-connected-overlay [origin]="test"></div>`;
13+
const expected = `<div cdk-connected-overlay [cdkConnectedOverlayOrigin]="test"></div>`;
14+
15+
writeFile('/projects/material/test.html', testContent);
16+
writeFile('/projects/cdk-testing/src/some-tmpl.html', testContent);
17+
writeFile('/projects/cdk-testing/src/test-cases/local.html', testContent);
18+
19+
await runFixers();
20+
21+
expect(appTree.readContent('/projects/material/test.html'))
22+
.toBe(expected, 'Expected absolute devkit tree paths to work.');
23+
expect(appTree.readContent('/projects/cdk-testing/src/some-tmpl.html'))
24+
.toBe(expected, 'Expected relative paths with parent segments to work.');
25+
expect(appTree.readContent('/projects/cdk-testing/src/test-cases/local.html'))
26+
.toBe(expected, 'Expected relative paths without explicit dot to work.');
27+
28+
removeTempDir();
29+
});
30+
});

0 commit comments

Comments
 (0)