Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(router): add a migration to remove
relativeLinkResolution
usag…
…es (#47604) As of Angular v15, the deprecated `relativeLinkResolution` config option of the Router is removed. This migration cleans up (removes) the `relativeLinkResolution` fields from the Router config objects in applications code. ```ts import { RouterModule } from '@angular/router'; RouterModule.forRoot([], { relativeLinkResolution: 'legacy', enableTracing: false, }); ``` ```ts import { RouterModule } from '@angular/router'; RouterModule.forRoot([], { // the `relativeLinkResolution` is removed enableTracing: false, }); ``` PR Close #47604
- Loading branch information
1 parent
739e689
commit 7bee28d
Showing
11 changed files
with
426 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
31 changes: 31 additions & 0 deletions
31
packages/core/schematics/migrations/google3/relativeLinkResolutionRule.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
/** | ||
* @license | ||
* Copyright Google LLC All Rights Reserved. | ||
* | ||
* Use of this source code is governed by an MIT-style license that can be | ||
* found in the LICENSE file at https://angular.io/license | ||
*/ | ||
|
||
import {Replacement, RuleFailure, Rules} from 'tslint'; | ||
import ts from 'typescript'; | ||
|
||
import {migrateFile} from '../relative-link-resolution/util'; | ||
|
||
/** TSLint rule for the `relativeLinkResolution` migration. */ | ||
export class Rule extends Rules.TypedRule { | ||
override applyWithProgram(sourceFile: ts.SourceFile, program: ts.Program): RuleFailure[] { | ||
const failures: RuleFailure[] = []; | ||
|
||
const rewriter = (startPos: number, origLength: number, text: string) => { | ||
const failure = new RuleFailure( | ||
sourceFile, startPos, startPos + origLength, | ||
'The `relativeLinkResolution` Router config option is removed and should not be used.', | ||
this.ruleName, new Replacement(startPos, origLength, text)); | ||
failures.push(failure); | ||
}; | ||
|
||
migrateFile(sourceFile, rewriter); | ||
|
||
return failures; | ||
} | ||
} |
18 changes: 18 additions & 0 deletions
18
packages/core/schematics/migrations/relative-link-resolution/BUILD.bazel
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
load("//tools:defaults.bzl", "ts_library") | ||
|
||
ts_library( | ||
name = "relative-link-resolution", | ||
srcs = glob(["**/*.ts"]), | ||
tsconfig = "//packages/core/schematics:tsconfig.json", | ||
visibility = [ | ||
"//packages/core/schematics:__pkg__", | ||
"//packages/core/schematics/migrations/google3:__pkg__", | ||
"//packages/core/schematics/test:__pkg__", | ||
], | ||
deps = [ | ||
"//packages/core/schematics/utils", | ||
"@npm//@angular-devkit/schematics", | ||
"@npm//@types/node", | ||
"@npm//typescript", | ||
], | ||
) |
25 changes: 25 additions & 0 deletions
25
packages/core/schematics/migrations/relative-link-resolution/README.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
## Relative link resolution migration | ||
|
||
As of Angular v15, the deprecated `relativeLinkResolution` config option of the Router is removed. | ||
This migration cleans up (removes) the `relativeLinkResolution` fields from the Router config objects | ||
in applications code. | ||
|
||
#### Before | ||
```ts | ||
import { RouterModule } from '@angular/router'; | ||
|
||
RouterModule.forRoot([], { | ||
relativeLinkResolution: 'legacy', | ||
enableTracing: false, | ||
}); | ||
``` | ||
|
||
#### After | ||
```ts | ||
import { RouterModule } from '@angular/router'; | ||
|
||
RouterModule.forRoot([], { | ||
// the `relativeLinkResolution` is removed | ||
enableTracing: false, | ||
}); | ||
``` |
58 changes: 58 additions & 0 deletions
58
packages/core/schematics/migrations/relative-link-resolution/index.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
/** | ||
* @license | ||
* Copyright Google LLC All Rights Reserved. | ||
* | ||
* Use of this source code is governed by an MIT-style license that can be | ||
* found in the LICENSE file at https://angular.io/license | ||
*/ | ||
|
||
import {Rule, SchematicsException, Tree, UpdateRecorder} from '@angular-devkit/schematics'; | ||
import {relative} from 'path'; | ||
|
||
import {getProjectTsConfigPaths} from '../../utils/project_tsconfig_paths'; | ||
import {canMigrateFile, createMigrationProgram} from '../../utils/typescript/compiler_host'; | ||
|
||
import {migrateFile} from './util'; | ||
|
||
export default function(): Rule { | ||
return async (tree: Tree) => { | ||
const {buildPaths, testPaths} = await getProjectTsConfigPaths(tree); | ||
const basePath = process.cwd(); | ||
const allPaths = [...buildPaths, ...testPaths]; | ||
|
||
if (!allPaths.length) { | ||
throw new SchematicsException( | ||
'Could not find any tsconfig file. ' + | ||
'Cannot run a migration to cleanup the deprecated `relativeLinkResolution` config option.'); | ||
} | ||
|
||
for (const tsconfigPath of allPaths) { | ||
runRelativeLinkResolutionMigration(tree, tsconfigPath, basePath); | ||
} | ||
}; | ||
} | ||
|
||
function runRelativeLinkResolutionMigration(tree: Tree, tsconfigPath: string, basePath: string) { | ||
const {program} = createMigrationProgram(tree, tsconfigPath, basePath); | ||
const sourceFiles = | ||
program.getSourceFiles().filter(sourceFile => canMigrateFile(basePath, sourceFile, program)); | ||
|
||
for (const sourceFile of sourceFiles) { | ||
let update: UpdateRecorder|null = null; | ||
|
||
const rewriter = (startPos: number, origLength: number, text: string) => { | ||
if (update === null) { | ||
// Lazily initialize update, because most files will not require migration. | ||
update = tree.beginUpdate(relative(basePath, sourceFile.fileName)); | ||
} | ||
update.remove(startPos, origLength); | ||
update.insertLeft(startPos, text); | ||
}; | ||
|
||
migrateFile(sourceFile, rewriter); | ||
|
||
if (update !== null) { | ||
tree.commitUpdate(update); | ||
} | ||
} | ||
} |
85 changes: 85 additions & 0 deletions
85
packages/core/schematics/migrations/relative-link-resolution/util.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
/** | ||
* @license | ||
* Copyright Google LLC All Rights Reserved. | ||
* | ||
* Use of this source code is governed by an MIT-style license that can be | ||
* found in the LICENSE file at https://angular.io/license | ||
*/ | ||
|
||
import ts from 'typescript'; | ||
|
||
const relativeLinkResolution = 'relativeLinkResolution'; | ||
const knownConfigValues = new Set([`'legacy'`, `'corrected'`]); | ||
|
||
export interface RewriteEntity { | ||
startPos: number; | ||
width: number; | ||
replacement: string; | ||
} | ||
|
||
export interface MigratableNode { | ||
objectLiteral: ts.ObjectLiteralExpression; | ||
property: ts.ObjectLiteralElementLike; | ||
} | ||
|
||
export type RewriteFn = (startPos: number, origLength: number, text: string) => void; | ||
|
||
export function migrateFile(sourceFile: ts.SourceFile, rewriteFn: RewriteFn) { | ||
let rewrites: RewriteEntity[] = []; | ||
const usages = getUsages(sourceFile); | ||
for (const {objectLiteral, property} of usages) { | ||
const replacementNode = ts.factory.updateObjectLiteralExpression( | ||
objectLiteral, objectLiteral.properties.filter(prop => prop !== property)); | ||
const printer = ts.createPrinter(); | ||
const replacementText = printer.printNode(ts.EmitHint.Unspecified, replacementNode, sourceFile); | ||
rewrites.push({ | ||
startPos: objectLiteral.getStart(), | ||
width: objectLiteral.getWidth(), | ||
replacement: replacementText, | ||
}); | ||
} | ||
|
||
// Process rewrites last-to-first (based on start pos) to avoid offset shifts during rewrites. | ||
rewrites = sortByStartPosDescending(rewrites); | ||
for (const rewrite of rewrites) { | ||
rewriteFn(rewrite.startPos, rewrite.width, rewrite.replacement); | ||
} | ||
} | ||
|
||
function getUsages(sourceFile: ts.SourceFile): MigratableNode[] { | ||
const usages: MigratableNode[] = []; | ||
const visitNode = (node: ts.Node) => { | ||
if (ts.isObjectLiteralExpression(node)) { | ||
// Look for patterns like the following: | ||
// ``` | ||
// { ... relativeLinkResolution: 'legacy', ... } | ||
// ``` | ||
// or: | ||
// ``` | ||
// { ... relativeLinkResolution: 'corrected', ... } | ||
// ``` | ||
// If the value is unknown (i.e. not 'legacy' or 'corrected'), | ||
// do not attempt to rewrite (this might be an application-specific | ||
// configuration, not a part of Router). | ||
const property = node.properties.find( | ||
prop => ts.isPropertyAssignment(prop) && ts.isIdentifier(prop.name) && | ||
prop.name.text === relativeLinkResolution && | ||
knownConfigValues.has(prop.initializer.getText())); | ||
if (property) { | ||
usages.push({objectLiteral: node, property}); | ||
} | ||
} | ||
ts.forEachChild(node, visitNode); | ||
}; | ||
ts.forEachChild(sourceFile, visitNode); | ||
return usages; | ||
} | ||
|
||
/** | ||
* Sort all found usages based on their start positions in the source file in descending order (i.e. | ||
* last usage goes first on the list, etc). This is needed to avoid shifting offsets in the source | ||
* file (in case there are multiple usages) as we rewrite symbols. | ||
*/ | ||
function sortByStartPosDescending(rewrites: RewriteEntity[]): RewriteEntity[] { | ||
return rewrites.sort((entityA, entityB) => entityB.startPos - entityA.startPos); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
72 changes: 72 additions & 0 deletions
72
packages/core/schematics/test/google3/relative_link_resolution_spec.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
/** | ||
* @license | ||
* Copyright Google LLC All Rights Reserved. | ||
* | ||
* Use of this source code is governed by an MIT-style license that can be | ||
* found in the LICENSE file at https://angular.io/license | ||
*/ | ||
|
||
import {readFileSync, writeFileSync} from 'fs'; | ||
import {dirname, join} from 'path'; | ||
import * as shx from 'shelljs'; | ||
import {Configuration, Linter} from 'tslint'; | ||
|
||
describe('Google3 relativeLinkResolution TSLint rule', () => { | ||
const rulesDirectory = | ||
dirname(require.resolve('../../migrations/google3/relativeLinkResolutionRule')); | ||
|
||
let tmpDir: string; | ||
|
||
beforeEach(() => { | ||
tmpDir = join(process.env['TEST_TMPDIR']!, 'google3-test'); | ||
shx.mkdir('-p', tmpDir); | ||
|
||
writeFile('tsconfig.json', JSON.stringify({ | ||
compilerOptions: { | ||
module: 'es2015', | ||
baseUrl: './', | ||
}, | ||
})); | ||
}); | ||
|
||
afterEach(() => shx.rm('-r', tmpDir)); | ||
|
||
function runTSLint(fix: boolean) { | ||
const program = Linter.createProgram(join(tmpDir, 'tsconfig.json')); | ||
const linter = new Linter({fix, rulesDirectory: [rulesDirectory]}, program); | ||
const config = Configuration.parseConfigFile({rules: {'relativeLinkResolution': true}}); | ||
|
||
program.getRootFileNames().forEach(fileName => { | ||
linter.lint(fileName, program.getSourceFile(fileName)!.getFullText(), config); | ||
}); | ||
|
||
return linter; | ||
} | ||
|
||
function writeFile(fileName: string, content: string) { | ||
writeFileSync(join(tmpDir, fileName), content); | ||
} | ||
|
||
function getFile(fileName: string) { | ||
return readFileSync(join(tmpDir, fileName), 'utf8'); | ||
} | ||
|
||
// This is just a sanity check for the TSLint configuration; | ||
// see test/relative_link_resolution_spec.ts for the full test suite. | ||
it('should migrate a simple example', () => { | ||
writeFile('/index.ts', ` | ||
import { RouterModule } from '@angular/router'; | ||
let providers = RouterModule.forRoot([], { | ||
onSameUrlNavigation: 'reload', | ||
paramsInheritanceStrategy: 'always', | ||
relativeLinkResolution: 'legacy', | ||
enableTracing: false, | ||
}); | ||
`); | ||
|
||
runTSLint(true); | ||
const content = getFile(`/index.ts`); | ||
expect(content).not.toContain('relativeLinkResolution'); | ||
}); | ||
}); |
Oops, something went wrong.