-
Notifications
You must be signed in to change notification settings - Fork 6.8k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix(google-maps): add schematic to switch to the new clusterer name
Since the clusterer's class and tag were renamed, we need to migrated existing users to the new name.
- Loading branch information
Showing
8 changed files
with
461 additions
and
1 deletion.
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
{ | ||
"schematics": { | ||
"migration-v19": { | ||
"version": "19.0.0-0", | ||
"description": "Updates the Angular Google Maps package to v19", | ||
"factory": "./ng-update/index_bundled#updateToV19" | ||
} | ||
} | ||
} |
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,79 @@ | ||
load("//tools:defaults.bzl", "esbuild", "jasmine_node_test", "spec_bundle", "ts_library") | ||
|
||
## THIS ONE IS ESM | ||
# By default everything is ESM | ||
# ESBUild needs ESM for bundling. Cannot reliably use CJS as input. | ||
ts_library( | ||
name = "ng_update_lib", | ||
srcs = glob( | ||
["**/*.ts"], | ||
exclude = ["**/*.spec.ts"], | ||
), | ||
# Schematics can not yet run in ESM module. For now we continue to use CommonJS. | ||
# TODO(ESM): remove this once the Angular CLI supports ESM schematics. | ||
devmode_module = "commonjs", | ||
deps = [ | ||
"@npm//@angular-devkit/core", | ||
"@npm//@angular-devkit/schematics", | ||
"@npm//@schematics/angular", | ||
"@npm//@types/node", | ||
"@npm//typescript", | ||
], | ||
) | ||
|
||
esbuild( | ||
name = "ng_update_index", | ||
entry_point = ":index.ts", | ||
external = [ | ||
"@schematics/angular", | ||
"@angular-devkit/schematics", | ||
"@angular-devkit/core", | ||
"typescript", | ||
], | ||
# TODO: Switch to ESM when Angular CLI supports it. | ||
format = "cjs", | ||
output = "index_bundled.js", | ||
platform = "node", | ||
target = "es2015", | ||
visibility = ["//src/google-maps/schematics:__pkg__"], | ||
deps = [":ng_update_lib"], | ||
) | ||
|
||
ts_library( | ||
name = "test_lib", | ||
testonly = True, | ||
srcs = glob(["**/*.spec.ts"]), | ||
deps = [ | ||
":ng_update_lib", | ||
"@npm//@angular-devkit/core", | ||
"@npm//@angular-devkit/schematics", | ||
"@npm//@bazel/runfiles", | ||
"@npm//@types/jasmine", | ||
"@npm//@types/node", | ||
"@npm//@types/shelljs", | ||
], | ||
) | ||
|
||
spec_bundle( | ||
name = "spec_bundle", | ||
external = [ | ||
"*/paths.js", | ||
"shelljs", | ||
"@angular-devkit/core/node", | ||
], | ||
platform = "cjs-legacy", | ||
target = "es2020", | ||
deps = [":test_lib"], | ||
) | ||
|
||
jasmine_node_test( | ||
name = "test", | ||
data = [ | ||
":ng_update_index", | ||
"//src/google-maps/schematics:schematics_assets", | ||
"@npm//shelljs", | ||
], | ||
deps = [ | ||
":spec_bundle", | ||
], | ||
) |
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,141 @@ | ||
/** | ||
* @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.dev/license | ||
*/ | ||
|
||
import {Path} from '@angular-devkit/core'; | ||
import {Rule, Tree} from '@angular-devkit/schematics'; | ||
import ts from 'typescript'; | ||
|
||
/** Tag name of the clusterer component. */ | ||
const TAG_NAME = 'map-marker-clusterer'; | ||
|
||
/** Module from which the clusterer is being imported. */ | ||
const MODULE_NAME = '@angular/google-maps'; | ||
|
||
/** Old name of the clusterer class. */ | ||
const CLASS_NAME = 'MapMarkerClusterer'; | ||
|
||
/** New name of the clusterer class. */ | ||
const DEPRECATED_CLASS_NAME = 'DeprecatedMapMarkerClusterer'; | ||
|
||
/** Entry point for the migration schematics with target of Angular Material v19 */ | ||
export function updateToV19(): Rule { | ||
return tree => { | ||
tree.visit(path => { | ||
if (path.endsWith('.html')) { | ||
const content = tree.readText(path); | ||
|
||
if (content.includes('<' + TAG_NAME)) { | ||
tree.overwrite(path, migrateHtml(content)); | ||
} | ||
} else if (path.endsWith('.ts') && !path.endsWith('.d.ts')) { | ||
migrateTypeScript(path, tree); | ||
} | ||
}); | ||
}; | ||
} | ||
|
||
/** Migrates an HTML template from the old tag name to the new one. */ | ||
function migrateHtml(content: string): string { | ||
return content | ||
.replace(/<map-marker-clusterer/g, '<deprecated-map-marker-clusterer') | ||
.replace(/<\/map-marker-clusterer/g, '</deprecated-map-marker-clusterer'); | ||
} | ||
|
||
/** Migrates a TypeScript file from the old tag and class names to the new ones. */ | ||
function migrateTypeScript(path: Path, tree: Tree) { | ||
const content = tree.readText(path); | ||
|
||
// Exit early if none of the symbols we're looking for are mentioned. | ||
if ( | ||
!content.includes('<' + TAG_NAME) && | ||
!content.includes(MODULE_NAME) && | ||
!content.includes(CLASS_NAME) | ||
) { | ||
return; | ||
} | ||
|
||
const sourceFile = ts.createSourceFile(path, content, ts.ScriptTarget.Latest, true); | ||
const toMigrate = findTypeScriptNodesToMigrate(sourceFile); | ||
|
||
if (toMigrate.length === 0) { | ||
return; | ||
} | ||
|
||
const printer = ts.createPrinter(); | ||
const update = tree.beginUpdate(path); | ||
|
||
for (const node of toMigrate) { | ||
let replacement: ts.Node; | ||
|
||
if (ts.isStringLiteralLike(node)) { | ||
// Strings should be migrated as if they're HTML. | ||
if (ts.isStringLiteral(node)) { | ||
replacement = ts.factory.createStringLiteral( | ||
migrateHtml(node.text), | ||
node.getText()[0] === `'`, | ||
); | ||
} else { | ||
replacement = ts.factory.createNoSubstitutionTemplateLiteral(migrateHtml(node.text)); | ||
} | ||
} else { | ||
// Imports/exports should preserve the old name, but import the clusterer using the new one. | ||
const propertyName = ts.factory.createIdentifier(DEPRECATED_CLASS_NAME); | ||
const name = node.name as ts.Identifier; | ||
|
||
replacement = ts.isImportSpecifier(node) | ||
? ts.factory.updateImportSpecifier(node, node.isTypeOnly, propertyName, name) | ||
: ts.factory.updateExportSpecifier(node, node.isTypeOnly, propertyName, name); | ||
} | ||
|
||
update | ||
.remove(node.getStart(), node.getWidth()) | ||
.insertLeft( | ||
node.getStart(), | ||
printer.printNode(ts.EmitHint.Unspecified, replacement, sourceFile), | ||
); | ||
} | ||
|
||
tree.commitUpdate(update); | ||
} | ||
|
||
/** Finds the TypeScript nodes that need to be migrated from a specific file. */ | ||
function findTypeScriptNodesToMigrate(sourceFile: ts.SourceFile) { | ||
const results: (ts.StringLiteralLike | ts.ImportSpecifier | ts.ExportSpecifier)[] = []; | ||
|
||
sourceFile.forEachChild(function walk(node) { | ||
// Most likely a template using the clusterer. | ||
if (ts.isStringLiteral(node) && node.text.includes('<' + TAG_NAME)) { | ||
results.push(node); | ||
} else if ( | ||
// Import/export referencing the clusterer. | ||
(ts.isImportDeclaration(node) || ts.isExportDeclaration(node)) && | ||
node.moduleSpecifier && | ||
ts.isStringLiteralLike(node.moduleSpecifier) && | ||
node.moduleSpecifier.text === MODULE_NAME | ||
) { | ||
const bindings = ts.isImportDeclaration(node) | ||
? node.importClause?.namedBindings | ||
: node.exportClause; | ||
|
||
if (bindings && (ts.isNamedImports(bindings) || ts.isNamedExports(bindings))) { | ||
bindings.elements.forEach(element => { | ||
const symbolName = element.propertyName || element.name; | ||
|
||
if (ts.isIdentifier(symbolName) && symbolName.text === CLASS_NAME) { | ||
results.push(element); | ||
} | ||
}); | ||
} | ||
} else { | ||
node.forEachChild(walk); | ||
} | ||
}); | ||
|
||
// Sort the results in reverse order to make applying the updates easier. | ||
return results.sort((a, b) => b.getStart() - a.getStart()); | ||
} |
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,7 @@ | ||
{ | ||
"extends": "../../tsconfig.json", | ||
"compilerOptions": { | ||
"module": "esnext", | ||
"target": "es2015" | ||
} | ||
} |
Oops, something went wrong.