@angular/core 19.0.0-next.3 → 19.0.0-next.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/fesm2022/core.mjs +3 -3
- package/fesm2022/core.mjs.map +1 -1
- package/fesm2022/primitives/event-dispatch.mjs +1 -1
- package/fesm2022/primitives/signals.mjs +1 -1
- package/fesm2022/rxjs-interop.mjs +1 -1
- package/fesm2022/testing.mjs +4 -4
- package/index.d.ts +1 -1
- package/package.json +1 -1
- package/primitives/event-dispatch/index.d.ts +1 -1
- package/primitives/signals/index.d.ts +1 -1
- package/rxjs-interop/index.d.ts +1 -1
- package/schematics/bundles/{compiler_host-bbb5d8fd.js → compiler_host-ca7ba733.js} +15 -15
- package/schematics/bundles/control-flow-migration.js +2 -2
- package/schematics/bundles/explicit-standalone-flag.js +157 -0
- package/schematics/bundles/{nodes-ddfa1613.js → imports-4ac08251.js} +1 -42
- package/schematics/bundles/inject-migration.js +6 -5
- package/schematics/bundles/nodes-0e7d45ca.js +56 -0
- package/schematics/bundles/project_tsconfig_paths-e9ccccbf.js +1 -1
- package/schematics/bundles/route-lazy-loading.js +2 -2
- package/schematics/bundles/standalone-migration.js +871 -769
- package/schematics/migrations.json +7 -1
- package/testing/index.d.ts +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
/**
|
|
3
|
-
* @license Angular v19.0.0-next.
|
|
3
|
+
* @license Angular v19.0.0-next.4
|
|
4
4
|
* (c) 2010-2024 Google LLC. https://angular.io/
|
|
5
5
|
* License: MIT
|
|
6
6
|
*/
|
|
@@ -11,11 +11,12 @@ Object.defineProperty(exports, '__esModule', { value: true });
|
|
|
11
11
|
var schematics = require('@angular-devkit/schematics');
|
|
12
12
|
require('os');
|
|
13
13
|
var ts = require('typescript');
|
|
14
|
-
var compiler_host = require('./compiler_host-
|
|
14
|
+
var compiler_host = require('./compiler_host-ca7ba733.js');
|
|
15
15
|
var p = require('path');
|
|
16
16
|
var fs = require('fs');
|
|
17
17
|
var project_tsconfig_paths = require('./project_tsconfig_paths-e9ccccbf.js');
|
|
18
|
-
var nodes = require('./nodes-
|
|
18
|
+
var nodes = require('./nodes-0e7d45ca.js');
|
|
19
|
+
var imports = require('./imports-4ac08251.js');
|
|
19
20
|
require('module');
|
|
20
21
|
require('url');
|
|
21
22
|
require('@angular-devkit/core');
|
|
@@ -873,7 +874,7 @@ const MINIMUM_PARTIAL_LINKER_DEFER_SUPPORT_VERSION = '18.0.0';
|
|
|
873
874
|
function compileDeclareClassMetadata(metadata) {
|
|
874
875
|
const definitionMap = new compiler_host.DefinitionMap();
|
|
875
876
|
definitionMap.set('minVersion', compiler_host.literal(MINIMUM_PARTIAL_LINKER_VERSION$5));
|
|
876
|
-
definitionMap.set('version', compiler_host.literal('19.0.0-next.
|
|
877
|
+
definitionMap.set('version', compiler_host.literal('19.0.0-next.4'));
|
|
877
878
|
definitionMap.set('ngImport', compiler_host.importExpr(compiler_host.Identifiers.core));
|
|
878
879
|
definitionMap.set('type', metadata.type);
|
|
879
880
|
definitionMap.set('decorators', metadata.decorators);
|
|
@@ -891,7 +892,7 @@ function compileComponentDeclareClassMetadata(metadata, dependencies) {
|
|
|
891
892
|
callbackReturnDefinitionMap.set('ctorParameters', metadata.ctorParameters ?? compiler_host.literal(null));
|
|
892
893
|
callbackReturnDefinitionMap.set('propDecorators', metadata.propDecorators ?? compiler_host.literal(null));
|
|
893
894
|
definitionMap.set('minVersion', compiler_host.literal(MINIMUM_PARTIAL_LINKER_DEFER_SUPPORT_VERSION));
|
|
894
|
-
definitionMap.set('version', compiler_host.literal('19.0.0-next.
|
|
895
|
+
definitionMap.set('version', compiler_host.literal('19.0.0-next.4'));
|
|
895
896
|
definitionMap.set('ngImport', compiler_host.importExpr(compiler_host.Identifiers.core));
|
|
896
897
|
definitionMap.set('type', metadata.type);
|
|
897
898
|
definitionMap.set('resolveDeferredDeps', compileComponentMetadataAsyncResolver(dependencies));
|
|
@@ -986,7 +987,7 @@ function createDirectiveDefinitionMap(meta) {
|
|
|
986
987
|
const definitionMap = new compiler_host.DefinitionMap();
|
|
987
988
|
const minVersion = getMinimumVersionForPartialOutput(meta);
|
|
988
989
|
definitionMap.set('minVersion', compiler_host.literal(minVersion));
|
|
989
|
-
definitionMap.set('version', compiler_host.literal('19.0.0-next.
|
|
990
|
+
definitionMap.set('version', compiler_host.literal('19.0.0-next.4'));
|
|
990
991
|
// e.g. `type: MyDirective`
|
|
991
992
|
definitionMap.set('type', meta.type.value);
|
|
992
993
|
if (meta.isStandalone) {
|
|
@@ -1405,7 +1406,7 @@ const MINIMUM_PARTIAL_LINKER_VERSION$4 = '12.0.0';
|
|
|
1405
1406
|
function compileDeclareFactoryFunction(meta) {
|
|
1406
1407
|
const definitionMap = new compiler_host.DefinitionMap();
|
|
1407
1408
|
definitionMap.set('minVersion', compiler_host.literal(MINIMUM_PARTIAL_LINKER_VERSION$4));
|
|
1408
|
-
definitionMap.set('version', compiler_host.literal('19.0.0-next.
|
|
1409
|
+
definitionMap.set('version', compiler_host.literal('19.0.0-next.4'));
|
|
1409
1410
|
definitionMap.set('ngImport', compiler_host.importExpr(compiler_host.Identifiers.core));
|
|
1410
1411
|
definitionMap.set('type', meta.type.value);
|
|
1411
1412
|
definitionMap.set('deps', compileDependencies(meta.deps));
|
|
@@ -1440,7 +1441,7 @@ function compileDeclareInjectableFromMetadata(meta) {
|
|
|
1440
1441
|
function createInjectableDefinitionMap(meta) {
|
|
1441
1442
|
const definitionMap = new compiler_host.DefinitionMap();
|
|
1442
1443
|
definitionMap.set('minVersion', compiler_host.literal(MINIMUM_PARTIAL_LINKER_VERSION$3));
|
|
1443
|
-
definitionMap.set('version', compiler_host.literal('19.0.0-next.
|
|
1444
|
+
definitionMap.set('version', compiler_host.literal('19.0.0-next.4'));
|
|
1444
1445
|
definitionMap.set('ngImport', compiler_host.importExpr(compiler_host.Identifiers.core));
|
|
1445
1446
|
definitionMap.set('type', meta.type.value);
|
|
1446
1447
|
// Only generate providedIn property if it has a non-null value
|
|
@@ -1491,7 +1492,7 @@ function compileDeclareInjectorFromMetadata(meta) {
|
|
|
1491
1492
|
function createInjectorDefinitionMap(meta) {
|
|
1492
1493
|
const definitionMap = new compiler_host.DefinitionMap();
|
|
1493
1494
|
definitionMap.set('minVersion', compiler_host.literal(MINIMUM_PARTIAL_LINKER_VERSION$2));
|
|
1494
|
-
definitionMap.set('version', compiler_host.literal('19.0.0-next.
|
|
1495
|
+
definitionMap.set('version', compiler_host.literal('19.0.0-next.4'));
|
|
1495
1496
|
definitionMap.set('ngImport', compiler_host.importExpr(compiler_host.Identifiers.core));
|
|
1496
1497
|
definitionMap.set('type', meta.type.value);
|
|
1497
1498
|
definitionMap.set('providers', meta.providers);
|
|
@@ -1524,7 +1525,7 @@ function createNgModuleDefinitionMap(meta) {
|
|
|
1524
1525
|
throw new Error('Invalid path! Local compilation mode should not get into the partial compilation path');
|
|
1525
1526
|
}
|
|
1526
1527
|
definitionMap.set('minVersion', compiler_host.literal(MINIMUM_PARTIAL_LINKER_VERSION$1));
|
|
1527
|
-
definitionMap.set('version', compiler_host.literal('19.0.0-next.
|
|
1528
|
+
definitionMap.set('version', compiler_host.literal('19.0.0-next.4'));
|
|
1528
1529
|
definitionMap.set('ngImport', compiler_host.importExpr(compiler_host.Identifiers.core));
|
|
1529
1530
|
definitionMap.set('type', meta.type.value);
|
|
1530
1531
|
// We only generate the keys in the metadata if the arrays contain values.
|
|
@@ -1575,7 +1576,7 @@ function compileDeclarePipeFromMetadata(meta) {
|
|
|
1575
1576
|
function createPipeDefinitionMap(meta) {
|
|
1576
1577
|
const definitionMap = new compiler_host.DefinitionMap();
|
|
1577
1578
|
definitionMap.set('minVersion', compiler_host.literal(MINIMUM_PARTIAL_LINKER_VERSION));
|
|
1578
|
-
definitionMap.set('version', compiler_host.literal('19.0.0-next.
|
|
1579
|
+
definitionMap.set('version', compiler_host.literal('19.0.0-next.4'));
|
|
1579
1580
|
definitionMap.set('ngImport', compiler_host.importExpr(compiler_host.Identifiers.core));
|
|
1580
1581
|
// e.g. `type: MyPipe`
|
|
1581
1582
|
definitionMap.set('type', meta.type.value);
|
|
@@ -11535,7 +11536,7 @@ class PipeDecoratorHandler {
|
|
|
11535
11536
|
* @description
|
|
11536
11537
|
* Entry point for all public APIs of the compiler-cli package.
|
|
11537
11538
|
*/
|
|
11538
|
-
new compiler_host.Version('19.0.0-next.
|
|
11539
|
+
new compiler_host.Version('19.0.0-next.4');
|
|
11539
11540
|
|
|
11540
11541
|
/**
|
|
11541
11542
|
* Whether a given decorator should be treated as an Angular decorator.
|
|
@@ -13744,7 +13745,7 @@ class DocsExtractor {
|
|
|
13744
13745
|
*
|
|
13745
13746
|
* @param sourceFile The file from which to extract documentable entries.
|
|
13746
13747
|
*/
|
|
13747
|
-
extractAll(sourceFile, rootDir) {
|
|
13748
|
+
extractAll(sourceFile, rootDir, privateModules) {
|
|
13748
13749
|
const entries = [];
|
|
13749
13750
|
const symbols = new Map();
|
|
13750
13751
|
const exportedDeclarations = this.getExportedDeclarations(sourceFile);
|
|
@@ -13768,8 +13769,7 @@ class DocsExtractor {
|
|
|
13768
13769
|
*/
|
|
13769
13770
|
const importedSymbols = getImportedSymbols(realSourceFile);
|
|
13770
13771
|
importedSymbols.forEach((moduleName, symbolName) => {
|
|
13771
|
-
|
|
13772
|
-
if (symbolName.startsWith('ɵ')) {
|
|
13772
|
+
if (symbolName.startsWith('ɵ') || privateModules.has(moduleName)) {
|
|
13773
13773
|
return;
|
|
13774
13774
|
}
|
|
13775
13775
|
if (symbols.has(symbolName) && symbols.get(symbolName) !== moduleName) {
|
|
@@ -18893,7 +18893,7 @@ var semver$1 = /*@__PURE__*/getDefaultExportFromCjs(semver);
|
|
|
18893
18893
|
* @param minVersion Minimum required version for the feature.
|
|
18894
18894
|
*/
|
|
18895
18895
|
function coreVersionSupportsFeature(coreVersion, minVersion) {
|
|
18896
|
-
// A version of `19.0.0-next.
|
|
18896
|
+
// A version of `19.0.0-next.4` usually means that core is at head so it supports
|
|
18897
18897
|
// all features. Use string interpolation prevent the placeholder from being replaced
|
|
18898
18898
|
// with the current version during build time.
|
|
18899
18899
|
if (coreVersion === `0.0.0-${'PLACEHOLDER'}`) {
|
|
@@ -19364,7 +19364,7 @@ class NgCompiler {
|
|
|
19364
19364
|
*
|
|
19365
19365
|
* @returns A map of symbols with their associated module, eg: ApplicationRef => @angular/core
|
|
19366
19366
|
*/
|
|
19367
|
-
getApiDocumentation(entryPoint) {
|
|
19367
|
+
getApiDocumentation(entryPoint, privateModules) {
|
|
19368
19368
|
const compilation = this.ensureAnalyzed();
|
|
19369
19369
|
const checker = this.inputProgram.getTypeChecker();
|
|
19370
19370
|
const docsExtractor = new DocsExtractor(checker, compilation.metaReader);
|
|
@@ -19379,7 +19379,7 @@ class NgCompiler {
|
|
|
19379
19379
|
// TODO: Technically the current directory is not the root dir.
|
|
19380
19380
|
// Should probably be derived from the config.
|
|
19381
19381
|
const rootDir = this.inputProgram.getCurrentDirectory();
|
|
19382
|
-
return docsExtractor.extractAll(entryPointSourceFile, rootDir);
|
|
19382
|
+
return docsExtractor.extractAll(entryPointSourceFile, rootDir, privateModules);
|
|
19383
19383
|
}
|
|
19384
19384
|
/**
|
|
19385
19385
|
* Collect i18n messages into the `Xi18nContext`.
|
|
@@ -20462,8 +20462,8 @@ class NgtscProgram {
|
|
|
20462
20462
|
* @param entryPoint Path to the entry point for the package for which API
|
|
20463
20463
|
* docs should be extracted.
|
|
20464
20464
|
*/
|
|
20465
|
-
getApiDocumentation(entryPoint) {
|
|
20466
|
-
return this.compiler.getApiDocumentation(entryPoint);
|
|
20465
|
+
getApiDocumentation(entryPoint, privateModules) {
|
|
20466
|
+
return this.compiler.getApiDocumentation(entryPoint, privateModules);
|
|
20467
20467
|
}
|
|
20468
20468
|
getEmittedSourceFiles() {
|
|
20469
20469
|
throw new Error('Method not implemented.');
|
|
@@ -20743,6 +20743,20 @@ function isClassReferenceInAngularModule(node, className, moduleName, typeChecke
|
|
|
20743
20743
|
});
|
|
20744
20744
|
}
|
|
20745
20745
|
|
|
20746
|
+
/** Checks whether a node is referring to a specific import specifier. */
|
|
20747
|
+
function isReferenceToImport(typeChecker, node, importSpecifier) {
|
|
20748
|
+
// If this function is called on an identifier (should be most cases), we can quickly rule out
|
|
20749
|
+
// non-matches by comparing the identifier's string and the local name of the import specifier
|
|
20750
|
+
// which saves us some calls to the type checker.
|
|
20751
|
+
if (ts__default["default"].isIdentifier(node) && node.text !== importSpecifier.name.text) {
|
|
20752
|
+
return false;
|
|
20753
|
+
}
|
|
20754
|
+
const nodeSymbol = typeChecker.getTypeAtLocation(node).getSymbol();
|
|
20755
|
+
const importSymbol = typeChecker.getTypeAtLocation(importSpecifier).getSymbol();
|
|
20756
|
+
return (!!(nodeSymbol?.declarations?.[0] && importSymbol?.declarations?.[0]) &&
|
|
20757
|
+
nodeSymbol.declarations[0] === importSymbol.declarations[0]);
|
|
20758
|
+
}
|
|
20759
|
+
|
|
20746
20760
|
/*!
|
|
20747
20761
|
* @license
|
|
20748
20762
|
* Copyright Google LLC All Rights Reserved.
|
|
@@ -20750,906 +20764,994 @@ function isClassReferenceInAngularModule(node, className, moduleName, typeChecke
|
|
|
20750
20764
|
* Use of this source code is governed by an MIT-style license that can be
|
|
20751
20765
|
* found in the LICENSE file at https://angular.io/license
|
|
20752
20766
|
*/
|
|
20753
|
-
|
|
20754
|
-
|
|
20755
|
-
|
|
20756
|
-
|
|
20757
|
-
|
|
20758
|
-
|
|
20759
|
-
|
|
20760
|
-
|
|
20761
|
-
|
|
20762
|
-
|
|
20763
|
-
|
|
20764
|
-
|
|
20765
|
-
const
|
|
20766
|
-
const
|
|
20767
|
-
const
|
|
20768
|
-
|
|
20769
|
-
|
|
20770
|
-
|
|
20771
|
-
|
|
20772
|
-
|
|
20773
|
-
|
|
20774
|
-
|
|
20775
|
-
|
|
20776
|
-
|
|
20777
|
-
|
|
20778
|
-
const exportedSourceFile = typeChecker
|
|
20779
|
-
.getSymbolAtLocation(node.moduleSpecifier)
|
|
20780
|
-
?.valueDeclaration?.getSourceFile();
|
|
20781
|
-
if (exportedSourceFile) {
|
|
20782
|
-
barrelExports.track(exportedSourceFile, node);
|
|
20783
|
-
}
|
|
20784
|
-
}
|
|
20785
|
-
node.forEachChild(walk);
|
|
20786
|
-
});
|
|
20787
|
-
// We collect all the places where we need to remove references first before generating the
|
|
20788
|
-
// removal instructions since we may have to remove multiple references from one node.
|
|
20789
|
-
removeArrayReferences(removalLocations.arrays, tracker);
|
|
20790
|
-
removeImportReferences(removalLocations.imports, tracker);
|
|
20791
|
-
removeExportReferences(removalLocations.exports, tracker);
|
|
20792
|
-
addRemovalTodos(removalLocations.unknown, tracker);
|
|
20793
|
-
// Collect all the nodes to be removed before determining which files to delete since we need
|
|
20794
|
-
// to know it ahead of time when deleting barrel files that export other barrel files.
|
|
20795
|
-
(function trackNodesToRemove(nodes) {
|
|
20796
|
-
for (const node of nodes) {
|
|
20797
|
-
const sourceFile = node.getSourceFile();
|
|
20798
|
-
if (!filesToRemove.has(sourceFile) && canRemoveFile(sourceFile, nodes)) {
|
|
20799
|
-
const barrelExportsForFile = barrelExports.get(sourceFile);
|
|
20800
|
-
nodesToRemove.add(node);
|
|
20801
|
-
filesToRemove.add(sourceFile);
|
|
20802
|
-
barrelExportsForFile && trackNodesToRemove(barrelExportsForFile);
|
|
20803
|
-
}
|
|
20804
|
-
else {
|
|
20805
|
-
nodesToRemove.add(node);
|
|
20767
|
+
/**
|
|
20768
|
+
* Converts all declarations in the specified files to standalone.
|
|
20769
|
+
* @param sourceFiles Files that should be migrated.
|
|
20770
|
+
* @param program
|
|
20771
|
+
* @param printer
|
|
20772
|
+
* @param fileImportRemapper Optional function that can be used to remap file-level imports.
|
|
20773
|
+
* @param componentImportRemapper Optional function that can be used to remap component-level
|
|
20774
|
+
* imports.
|
|
20775
|
+
*/
|
|
20776
|
+
function toStandalone(sourceFiles, program, printer, fileImportRemapper, componentImportRemapper) {
|
|
20777
|
+
const templateTypeChecker = program.compiler.getTemplateTypeChecker();
|
|
20778
|
+
const typeChecker = program.getTsProgram().getTypeChecker();
|
|
20779
|
+
const modulesToMigrate = new Set();
|
|
20780
|
+
const testObjectsToMigrate = new Set();
|
|
20781
|
+
const declarations = new Set();
|
|
20782
|
+
const tracker = new compiler_host.ChangeTracker(printer, fileImportRemapper);
|
|
20783
|
+
for (const sourceFile of sourceFiles) {
|
|
20784
|
+
const modules = findNgModuleClassesToMigrate(sourceFile, typeChecker);
|
|
20785
|
+
const testObjects = findTestObjectsToMigrate(sourceFile, typeChecker);
|
|
20786
|
+
for (const module of modules) {
|
|
20787
|
+
const allModuleDeclarations = extractDeclarationsFromModule(module, templateTypeChecker);
|
|
20788
|
+
const unbootstrappedDeclarations = filterNonBootstrappedDeclarations(allModuleDeclarations, module, templateTypeChecker, typeChecker);
|
|
20789
|
+
if (unbootstrappedDeclarations.length > 0) {
|
|
20790
|
+
modulesToMigrate.add(module);
|
|
20791
|
+
unbootstrappedDeclarations.forEach((decl) => declarations.add(decl));
|
|
20806
20792
|
}
|
|
20807
20793
|
}
|
|
20808
|
-
|
|
20809
|
-
for (const node of nodesToRemove) {
|
|
20810
|
-
const sourceFile = node.getSourceFile();
|
|
20811
|
-
if (!filesToRemove.has(sourceFile) && canRemoveFile(sourceFile, nodesToRemove)) {
|
|
20812
|
-
filesToRemove.add(sourceFile);
|
|
20813
|
-
}
|
|
20814
|
-
else {
|
|
20815
|
-
tracker.removeNode(node);
|
|
20816
|
-
}
|
|
20794
|
+
testObjects.forEach((obj) => testObjectsToMigrate.add(obj));
|
|
20817
20795
|
}
|
|
20818
|
-
|
|
20796
|
+
for (const declaration of declarations) {
|
|
20797
|
+
convertNgModuleDeclarationToStandalone(declaration, declarations, tracker, templateTypeChecker, componentImportRemapper);
|
|
20798
|
+
}
|
|
20799
|
+
for (const node of modulesToMigrate) {
|
|
20800
|
+
migrateNgModuleClass(node, declarations, tracker, typeChecker, templateTypeChecker);
|
|
20801
|
+
}
|
|
20802
|
+
migrateTestDeclarations(testObjectsToMigrate, declarations, tracker, templateTypeChecker, typeChecker);
|
|
20803
|
+
return tracker.recordChanges();
|
|
20819
20804
|
}
|
|
20820
20805
|
/**
|
|
20821
|
-
*
|
|
20822
|
-
* @param
|
|
20823
|
-
* @param
|
|
20824
|
-
* @param
|
|
20825
|
-
* @param
|
|
20806
|
+
* Converts a single declaration defined through an NgModule to standalone.
|
|
20807
|
+
* @param decl Declaration being converted.
|
|
20808
|
+
* @param tracker Tracker used to track the file changes.
|
|
20809
|
+
* @param allDeclarations All the declarations that are being converted as a part of this migration.
|
|
20810
|
+
* @param typeChecker
|
|
20811
|
+
* @param importRemapper
|
|
20826
20812
|
*/
|
|
20827
|
-
function
|
|
20828
|
-
const
|
|
20829
|
-
|
|
20830
|
-
|
|
20831
|
-
|
|
20832
|
-
|
|
20833
|
-
|
|
20834
|
-
|
|
20813
|
+
function convertNgModuleDeclarationToStandalone(decl, allDeclarations, tracker, typeChecker, importRemapper) {
|
|
20814
|
+
const directiveMeta = typeChecker.getDirectiveMetadata(decl);
|
|
20815
|
+
if (directiveMeta && directiveMeta.decorator && !directiveMeta.isStandalone) {
|
|
20816
|
+
let decorator = addStandaloneToDecorator(directiveMeta.decorator);
|
|
20817
|
+
if (directiveMeta.isComponent) {
|
|
20818
|
+
const importsToAdd = getComponentImportExpressions(decl, allDeclarations, tracker, typeChecker, importRemapper);
|
|
20819
|
+
if (importsToAdd.length > 0) {
|
|
20820
|
+
const hasTrailingComma = importsToAdd.length > 2 &&
|
|
20821
|
+
!!extractMetadataLiteral(directiveMeta.decorator)?.properties.hasTrailingComma;
|
|
20822
|
+
decorator = addPropertyToAngularDecorator(decorator, ts__default["default"].factory.createPropertyAssignment('imports', ts__default["default"].factory.createArrayLiteralExpression(
|
|
20823
|
+
// Create a multi-line array when it has a trailing comma.
|
|
20824
|
+
ts__default["default"].factory.createNodeArray(importsToAdd, hasTrailingComma), hasTrailingComma)));
|
|
20825
|
+
}
|
|
20835
20826
|
}
|
|
20827
|
+
tracker.replaceNode(directiveMeta.decorator, decorator);
|
|
20836
20828
|
}
|
|
20837
|
-
|
|
20838
|
-
const
|
|
20839
|
-
if (
|
|
20840
|
-
|
|
20841
|
-
continue;
|
|
20842
|
-
}
|
|
20843
|
-
const closestImport = nodes.closestNode(node, ts__default["default"].isNamedImports);
|
|
20844
|
-
if (closestImport) {
|
|
20845
|
-
removalLocations.imports.track(closestImport, node);
|
|
20846
|
-
continue;
|
|
20847
|
-
}
|
|
20848
|
-
const closestExport = nodes.closestNode(node, ts__default["default"].isNamedExports);
|
|
20849
|
-
if (closestExport) {
|
|
20850
|
-
removalLocations.exports.track(closestExport, node);
|
|
20851
|
-
continue;
|
|
20829
|
+
else {
|
|
20830
|
+
const pipeMeta = typeChecker.getPipeMetadata(decl);
|
|
20831
|
+
if (pipeMeta && pipeMeta.decorator && !pipeMeta.isStandalone) {
|
|
20832
|
+
tracker.replaceNode(pipeMeta.decorator, addStandaloneToDecorator(pipeMeta.decorator));
|
|
20852
20833
|
}
|
|
20853
|
-
removalLocations.unknown.add(node);
|
|
20854
20834
|
}
|
|
20855
20835
|
}
|
|
20856
20836
|
/**
|
|
20857
|
-
*
|
|
20858
|
-
*
|
|
20859
|
-
* @param
|
|
20837
|
+
* Gets the expressions that should be added to a component's
|
|
20838
|
+
* `imports` array based on its template dependencies.
|
|
20839
|
+
* @param decl Component class declaration.
|
|
20840
|
+
* @param allDeclarations All the declarations that are being converted as a part of this migration.
|
|
20841
|
+
* @param tracker
|
|
20842
|
+
* @param typeChecker
|
|
20843
|
+
* @param importRemapper
|
|
20860
20844
|
*/
|
|
20861
|
-
function
|
|
20862
|
-
|
|
20863
|
-
|
|
20864
|
-
|
|
20845
|
+
function getComponentImportExpressions(decl, allDeclarations, tracker, typeChecker, importRemapper) {
|
|
20846
|
+
const templateDependencies = findTemplateDependencies(decl, typeChecker);
|
|
20847
|
+
const usedDependenciesInMigration = new Set(templateDependencies.filter((dep) => allDeclarations.has(dep.node)));
|
|
20848
|
+
const seenImports = new Set();
|
|
20849
|
+
const resolvedDependencies = [];
|
|
20850
|
+
for (const dep of templateDependencies) {
|
|
20851
|
+
const importLocation = findImportLocation(dep, decl, usedDependenciesInMigration.has(dep)
|
|
20852
|
+
? compiler_host.PotentialImportMode.ForceDirect
|
|
20853
|
+
: compiler_host.PotentialImportMode.Normal, typeChecker);
|
|
20854
|
+
if (importLocation && !seenImports.has(importLocation.symbolName)) {
|
|
20855
|
+
seenImports.add(importLocation.symbolName);
|
|
20856
|
+
resolvedDependencies.push(importLocation);
|
|
20857
|
+
}
|
|
20865
20858
|
}
|
|
20859
|
+
return potentialImportsToExpressions(resolvedDependencies, decl, tracker, importRemapper);
|
|
20866
20860
|
}
|
|
20867
20861
|
/**
|
|
20868
|
-
*
|
|
20869
|
-
*
|
|
20870
|
-
* @param
|
|
20862
|
+
* Converts an array of potential imports to an array of expressions that can be
|
|
20863
|
+
* added to the `imports` array.
|
|
20864
|
+
* @param potentialImports Imports to be converted.
|
|
20865
|
+
* @param component Component class to which the imports will be added.
|
|
20866
|
+
* @param tracker
|
|
20867
|
+
* @param importRemapper
|
|
20871
20868
|
*/
|
|
20872
|
-
function
|
|
20873
|
-
|
|
20874
|
-
|
|
20875
|
-
|
|
20876
|
-
|
|
20877
|
-
|
|
20878
|
-
|
|
20879
|
-
// e.g. `import Foo, {ModuleToRemove} from './foo';` becomes `import Foo from './foo';`.
|
|
20880
|
-
if (importClause && importClause.name) {
|
|
20881
|
-
tracker.replaceNode(importClause, ts__default["default"].factory.updateImportClause(importClause, importClause.isTypeOnly, importClause.name, undefined));
|
|
20882
|
-
}
|
|
20883
|
-
else {
|
|
20884
|
-
// Otherwise we can drop the entire declaration.
|
|
20885
|
-
const declaration = nodes.closestNode(namedImports, ts__default["default"].isImportDeclaration);
|
|
20886
|
-
if (declaration) {
|
|
20887
|
-
tracker.removeNode(declaration);
|
|
20888
|
-
}
|
|
20889
|
-
}
|
|
20869
|
+
function potentialImportsToExpressions(potentialImports, component, tracker, importRemapper) {
|
|
20870
|
+
const processedDependencies = importRemapper
|
|
20871
|
+
? importRemapper(potentialImports, component)
|
|
20872
|
+
: potentialImports;
|
|
20873
|
+
return processedDependencies.map((importLocation) => {
|
|
20874
|
+
if (importLocation.moduleSpecifier) {
|
|
20875
|
+
return tracker.addImport(component.getSourceFile(), importLocation.symbolName, importLocation.moduleSpecifier);
|
|
20890
20876
|
}
|
|
20891
|
-
|
|
20892
|
-
|
|
20893
|
-
|
|
20877
|
+
const identifier = ts__default["default"].factory.createIdentifier(importLocation.symbolName);
|
|
20878
|
+
if (!importLocation.isForwardReference) {
|
|
20879
|
+
return identifier;
|
|
20894
20880
|
}
|
|
20895
|
-
|
|
20881
|
+
const forwardRefExpression = tracker.addImport(component.getSourceFile(), 'forwardRef', '@angular/core');
|
|
20882
|
+
const arrowFunction = ts__default["default"].factory.createArrowFunction(undefined, undefined, [], undefined, undefined, identifier);
|
|
20883
|
+
return ts__default["default"].factory.createCallExpression(forwardRefExpression, undefined, [arrowFunction]);
|
|
20884
|
+
});
|
|
20896
20885
|
}
|
|
20897
20886
|
/**
|
|
20898
|
-
*
|
|
20899
|
-
* @param
|
|
20900
|
-
* @param
|
|
20887
|
+
* Moves all of the declarations of a class decorated with `@NgModule` to its imports.
|
|
20888
|
+
* @param node Class being migrated.
|
|
20889
|
+
* @param allDeclarations All the declarations that are being converted as a part of this migration.
|
|
20890
|
+
* @param tracker
|
|
20891
|
+
* @param typeChecker
|
|
20892
|
+
* @param templateTypeChecker
|
|
20901
20893
|
*/
|
|
20902
|
-
function
|
|
20903
|
-
|
|
20904
|
-
|
|
20905
|
-
|
|
20906
|
-
|
|
20907
|
-
const declaration = nodes.closestNode(namedExports, ts__default["default"].isExportDeclaration);
|
|
20908
|
-
if (declaration) {
|
|
20909
|
-
tracker.removeNode(declaration);
|
|
20910
|
-
}
|
|
20911
|
-
}
|
|
20912
|
-
else {
|
|
20913
|
-
// Otherwise we just drop the exported symbols and keep the declaration intact.
|
|
20914
|
-
tracker.replaceNode(namedExports, ts__default["default"].factory.updateNamedExports(namedExports, newElements));
|
|
20915
|
-
}
|
|
20894
|
+
function migrateNgModuleClass(node, allDeclarations, tracker, typeChecker, templateTypeChecker) {
|
|
20895
|
+
const decorator = templateTypeChecker.getNgModuleMetadata(node)?.decorator;
|
|
20896
|
+
const metadata = decorator ? extractMetadataLiteral(decorator) : null;
|
|
20897
|
+
if (metadata) {
|
|
20898
|
+
moveDeclarationsToImports(metadata, allDeclarations, typeChecker, templateTypeChecker, tracker);
|
|
20916
20899
|
}
|
|
20917
20900
|
}
|
|
20918
20901
|
/**
|
|
20919
|
-
*
|
|
20920
|
-
*
|
|
20921
|
-
*
|
|
20922
|
-
*
|
|
20923
|
-
* 4. It has no `ModuleWithProviders` in its `imports`.
|
|
20924
|
-
* 5. It has no class members. Empty construstors are ignored.
|
|
20925
|
-
* @param node Class that is being checked.
|
|
20902
|
+
* Moves all the symbol references from the `declarations` array to the `imports`
|
|
20903
|
+
* array of an `NgModule` class and removes the `declarations`.
|
|
20904
|
+
* @param literal Object literal used to configure the module that should be migrated.
|
|
20905
|
+
* @param allDeclarations All the declarations that are being converted as a part of this migration.
|
|
20926
20906
|
* @param typeChecker
|
|
20907
|
+
* @param tracker
|
|
20927
20908
|
*/
|
|
20928
|
-
function
|
|
20929
|
-
const
|
|
20930
|
-
|
|
20931
|
-
|
|
20932
|
-
return false;
|
|
20933
|
-
}
|
|
20934
|
-
// Unsupported case, e.g. `@NgModule(SOME_VALUE)`.
|
|
20935
|
-
if (decorator.expression.arguments.length > 0 &&
|
|
20936
|
-
!ts__default["default"].isObjectLiteralExpression(decorator.expression.arguments[0])) {
|
|
20937
|
-
return false;
|
|
20938
|
-
}
|
|
20939
|
-
// We can't remove modules that have class members. We make an exception for an
|
|
20940
|
-
// empty constructor which may have been generated by a tool and forgotten.
|
|
20941
|
-
if (node.members.length > 0 && node.members.some((member) => !isEmptyConstructor(member))) {
|
|
20942
|
-
return false;
|
|
20943
|
-
}
|
|
20944
|
-
// An empty `NgModule` call can be removed.
|
|
20945
|
-
if (decorator.expression.arguments.length === 0) {
|
|
20946
|
-
return true;
|
|
20909
|
+
function moveDeclarationsToImports(literal, allDeclarations, typeChecker, templateTypeChecker, tracker) {
|
|
20910
|
+
const declarationsProp = findLiteralProperty(literal, 'declarations');
|
|
20911
|
+
if (!declarationsProp) {
|
|
20912
|
+
return;
|
|
20947
20913
|
}
|
|
20948
|
-
const
|
|
20949
|
-
const
|
|
20950
|
-
|
|
20951
|
-
|
|
20952
|
-
|
|
20953
|
-
|
|
20954
|
-
|
|
20955
|
-
|
|
20956
|
-
|
|
20957
|
-
|
|
20958
|
-
|
|
20959
|
-
|
|
20960
|
-
|
|
20961
|
-
|
|
20962
|
-
|
|
20963
|
-
|
|
20964
|
-
|
|
20965
|
-
|
|
20966
|
-
|
|
20914
|
+
const declarationsToPreserve = [];
|
|
20915
|
+
const declarationsToCopy = [];
|
|
20916
|
+
const properties = [];
|
|
20917
|
+
const importsProp = findLiteralProperty(literal, 'imports');
|
|
20918
|
+
const hasAnyArrayTrailingComma = literal.properties.some((prop) => ts__default["default"].isPropertyAssignment(prop) &&
|
|
20919
|
+
ts__default["default"].isArrayLiteralExpression(prop.initializer) &&
|
|
20920
|
+
prop.initializer.elements.hasTrailingComma);
|
|
20921
|
+
// Separate the declarations that we want to keep and ones we need to copy into the `imports`.
|
|
20922
|
+
if (ts__default["default"].isPropertyAssignment(declarationsProp)) {
|
|
20923
|
+
// If the declarations are an array, we can analyze it to
|
|
20924
|
+
// find any classes from the current migration.
|
|
20925
|
+
if (ts__default["default"].isArrayLiteralExpression(declarationsProp.initializer)) {
|
|
20926
|
+
for (const el of declarationsProp.initializer.elements) {
|
|
20927
|
+
if (ts__default["default"].isIdentifier(el)) {
|
|
20928
|
+
const correspondingClass = findClassDeclaration(el, typeChecker);
|
|
20929
|
+
if (!correspondingClass ||
|
|
20930
|
+
// Check whether the declaration is either standalone already or is being converted
|
|
20931
|
+
// in this migration. We need to check if it's standalone already, in order to correct
|
|
20932
|
+
// some cases where the main app and the test files are being migrated in separate
|
|
20933
|
+
// programs.
|
|
20934
|
+
isStandaloneDeclaration(correspondingClass, allDeclarations, templateTypeChecker)) {
|
|
20935
|
+
declarationsToCopy.push(el);
|
|
20936
|
+
}
|
|
20937
|
+
else {
|
|
20938
|
+
declarationsToPreserve.push(el);
|
|
20939
|
+
}
|
|
20940
|
+
}
|
|
20941
|
+
else {
|
|
20942
|
+
declarationsToCopy.push(el);
|
|
20943
|
+
}
|
|
20967
20944
|
}
|
|
20968
20945
|
}
|
|
20946
|
+
else {
|
|
20947
|
+
// Otherwise create a spread that will be copied into the `imports`.
|
|
20948
|
+
declarationsToCopy.push(ts__default["default"].factory.createSpreadElement(declarationsProp.initializer));
|
|
20949
|
+
}
|
|
20950
|
+
}
|
|
20951
|
+
// If there are no `imports`, create them with the declarations we want to copy.
|
|
20952
|
+
if (!importsProp && declarationsToCopy.length > 0) {
|
|
20953
|
+
properties.push(ts__default["default"].factory.createPropertyAssignment('imports', ts__default["default"].factory.createArrayLiteralExpression(ts__default["default"].factory.createNodeArray(declarationsToCopy, hasAnyArrayTrailingComma && declarationsToCopy.length > 2))));
|
|
20969
20954
|
}
|
|
20970
|
-
// We can't remove classes that have any `declarations`, `providers` or `bootstrap` elements.
|
|
20971
|
-
// Also err on the side of caution and don't remove modules where any of the aforementioned
|
|
20972
|
-
// properties aren't initialized to an array literal.
|
|
20973
20955
|
for (const prop of literal.properties) {
|
|
20974
|
-
if (
|
|
20975
|
-
(prop
|
|
20976
|
-
|
|
20977
|
-
|
|
20978
|
-
|
|
20956
|
+
if (!isNamedPropertyAssignment(prop)) {
|
|
20957
|
+
properties.push(prop);
|
|
20958
|
+
continue;
|
|
20959
|
+
}
|
|
20960
|
+
// If we have declarations to preserve, update the existing property, otherwise drop it.
|
|
20961
|
+
if (prop === declarationsProp) {
|
|
20962
|
+
if (declarationsToPreserve.length > 0) {
|
|
20963
|
+
const hasTrailingComma = ts__default["default"].isArrayLiteralExpression(prop.initializer)
|
|
20964
|
+
? prop.initializer.elements.hasTrailingComma
|
|
20965
|
+
: hasAnyArrayTrailingComma;
|
|
20966
|
+
properties.push(ts__default["default"].factory.updatePropertyAssignment(prop, prop.name, ts__default["default"].factory.createArrayLiteralExpression(ts__default["default"].factory.createNodeArray(declarationsToPreserve, hasTrailingComma && declarationsToPreserve.length > 2))));
|
|
20967
|
+
}
|
|
20968
|
+
continue;
|
|
20969
|
+
}
|
|
20970
|
+
// If we have an `imports` array and declarations
|
|
20971
|
+
// that should be copied, we merge the two arrays.
|
|
20972
|
+
if (prop === importsProp && declarationsToCopy.length > 0) {
|
|
20973
|
+
let initializer;
|
|
20974
|
+
if (ts__default["default"].isArrayLiteralExpression(prop.initializer)) {
|
|
20975
|
+
initializer = ts__default["default"].factory.updateArrayLiteralExpression(prop.initializer, ts__default["default"].factory.createNodeArray([...prop.initializer.elements, ...declarationsToCopy], prop.initializer.elements.hasTrailingComma));
|
|
20976
|
+
}
|
|
20977
|
+
else {
|
|
20978
|
+
initializer = ts__default["default"].factory.createArrayLiteralExpression(ts__default["default"].factory.createNodeArray([ts__default["default"].factory.createSpreadElement(prop.initializer), ...declarationsToCopy],
|
|
20979
|
+
// Expect the declarations to be greater than 1 since
|
|
20980
|
+
// we have the pre-existing initializer already.
|
|
20981
|
+
hasAnyArrayTrailingComma && declarationsToCopy.length > 1));
|
|
20982
|
+
}
|
|
20983
|
+
properties.push(ts__default["default"].factory.updatePropertyAssignment(prop, prop.name, initializer));
|
|
20984
|
+
continue;
|
|
20979
20985
|
}
|
|
20986
|
+
// Retain any remaining properties.
|
|
20987
|
+
properties.push(prop);
|
|
20980
20988
|
}
|
|
20981
|
-
|
|
20989
|
+
tracker.replaceNode(literal, ts__default["default"].factory.updateObjectLiteralExpression(literal, ts__default["default"].factory.createNodeArray(properties, literal.properties.hasTrailingComma)), ts__default["default"].EmitHint.Expression);
|
|
20990
|
+
}
|
|
20991
|
+
/** Adds `standalone: true` to a decorator node. */
|
|
20992
|
+
function addStandaloneToDecorator(node) {
|
|
20993
|
+
return addPropertyToAngularDecorator(node, ts__default["default"].factory.createPropertyAssignment('standalone', ts__default["default"].factory.createToken(ts__default["default"].SyntaxKind.TrueKeyword)));
|
|
20982
20994
|
}
|
|
20983
20995
|
/**
|
|
20984
|
-
*
|
|
20985
|
-
*
|
|
20986
|
-
*
|
|
20987
|
-
* @param node Node to be checked.
|
|
20996
|
+
* Adds a property to an Angular decorator node.
|
|
20997
|
+
* @param node Decorator to which to add the property.
|
|
20998
|
+
* @param property Property to add.
|
|
20988
20999
|
*/
|
|
20989
|
-
function
|
|
20990
|
-
|
|
20991
|
-
|
|
20992
|
-
|
|
20993
|
-
|
|
21000
|
+
function addPropertyToAngularDecorator(node, property) {
|
|
21001
|
+
// Invalid decorator.
|
|
21002
|
+
if (!ts__default["default"].isCallExpression(node.expression) || node.expression.arguments.length > 1) {
|
|
21003
|
+
return node;
|
|
21004
|
+
}
|
|
21005
|
+
let literalProperties;
|
|
21006
|
+
let hasTrailingComma = false;
|
|
21007
|
+
if (node.expression.arguments.length === 0) {
|
|
21008
|
+
literalProperties = [property];
|
|
21009
|
+
}
|
|
21010
|
+
else if (ts__default["default"].isObjectLiteralExpression(node.expression.arguments[0])) {
|
|
21011
|
+
hasTrailingComma = node.expression.arguments[0].properties.hasTrailingComma;
|
|
21012
|
+
literalProperties = [...node.expression.arguments[0].properties, property];
|
|
21013
|
+
}
|
|
21014
|
+
else {
|
|
21015
|
+
// Unsupported case (e.g. `@Component(SOME_CONST)`). Return the original node.
|
|
21016
|
+
return node;
|
|
21017
|
+
}
|
|
21018
|
+
// Use `createDecorator` instead of `updateDecorator`, because
|
|
21019
|
+
// the latter ends up duplicating the node's leading comment.
|
|
21020
|
+
return ts__default["default"].factory.createDecorator(ts__default["default"].factory.createCallExpression(node.expression.expression, node.expression.typeArguments, [
|
|
21021
|
+
ts__default["default"].factory.createObjectLiteralExpression(ts__default["default"].factory.createNodeArray(literalProperties, hasTrailingComma), literalProperties.length > 1),
|
|
21022
|
+
]));
|
|
21023
|
+
}
|
|
21024
|
+
/** Checks if a node is a `PropertyAssignment` with a name. */
|
|
21025
|
+
function isNamedPropertyAssignment(node) {
|
|
21026
|
+
return ts__default["default"].isPropertyAssignment(node) && node.name && ts__default["default"].isIdentifier(node.name);
|
|
20994
21027
|
}
|
|
20995
21028
|
/**
|
|
20996
|
-
*
|
|
20997
|
-
*
|
|
20998
|
-
* @param
|
|
20999
|
-
* @param
|
|
21029
|
+
* Finds the import from which to bring in a template dependency of a component.
|
|
21030
|
+
* @param target Dependency that we're searching for.
|
|
21031
|
+
* @param inComponent Component in which the dependency is used.
|
|
21032
|
+
* @param importMode Mode in which to resolve the import target.
|
|
21033
|
+
* @param typeChecker
|
|
21000
21034
|
*/
|
|
21001
|
-
function
|
|
21002
|
-
|
|
21003
|
-
|
|
21004
|
-
|
|
21035
|
+
function findImportLocation(target, inComponent, importMode, typeChecker) {
|
|
21036
|
+
const importLocations = typeChecker.getPotentialImportsFor(target, inComponent, importMode);
|
|
21037
|
+
let firstSameFileImport = null;
|
|
21038
|
+
let firstModuleImport = null;
|
|
21039
|
+
for (const location of importLocations) {
|
|
21040
|
+
// Prefer a standalone import, if we can find one.
|
|
21041
|
+
// Otherwise fall back to the first module-based import.
|
|
21042
|
+
if (location.kind === compiler_host.PotentialImportKind.Standalone) {
|
|
21043
|
+
return location;
|
|
21005
21044
|
}
|
|
21006
|
-
if (
|
|
21007
|
-
|
|
21008
|
-
|
|
21009
|
-
|
|
21045
|
+
if (!location.moduleSpecifier && !firstSameFileImport) {
|
|
21046
|
+
firstSameFileImport = location;
|
|
21047
|
+
}
|
|
21048
|
+
if (location.kind === compiler_host.PotentialImportKind.NgModule &&
|
|
21049
|
+
!firstModuleImport &&
|
|
21050
|
+
// ɵ is used for some internal Angular modules that we want to skip over.
|
|
21051
|
+
!location.symbolName.startsWith('ɵ')) {
|
|
21052
|
+
firstModuleImport = location;
|
|
21010
21053
|
}
|
|
21011
21054
|
}
|
|
21012
|
-
return
|
|
21055
|
+
return firstSameFileImport || firstModuleImport || importLocations[0] || null;
|
|
21013
21056
|
}
|
|
21014
21057
|
/**
|
|
21015
|
-
*
|
|
21016
|
-
*
|
|
21017
|
-
*
|
|
21058
|
+
* Checks whether a node is an `NgModule` metadata element with at least one element.
|
|
21059
|
+
* E.g. `declarations: [Foo]` or `declarations: SOME_VAR` would match this description,
|
|
21060
|
+
* but not `declarations: []`.
|
|
21018
21061
|
*/
|
|
21019
|
-
function
|
|
21020
|
-
return (
|
|
21021
|
-
(
|
|
21022
|
-
child.getStart() >= parent.getStart() &&
|
|
21023
|
-
child.getStart() <= parent.getEnd()));
|
|
21062
|
+
function hasNgModuleMetadataElements(node) {
|
|
21063
|
+
return (ts__default["default"].isPropertyAssignment(node) &&
|
|
21064
|
+
(!ts__default["default"].isArrayLiteralExpression(node.initializer) || node.initializer.elements.length > 0));
|
|
21024
21065
|
}
|
|
21025
|
-
/**
|
|
21026
|
-
|
|
21027
|
-
|
|
21028
|
-
|
|
21029
|
-
|
|
21030
|
-
|
|
21031
|
-
|
|
21032
|
-
|
|
21033
|
-
|
|
21034
|
-
|
|
21035
|
-
|
|
21036
|
-
|
|
21037
|
-
|
|
21066
|
+
/** Finds all modules whose declarations can be migrated. */
|
|
21067
|
+
function findNgModuleClassesToMigrate(sourceFile, typeChecker) {
|
|
21068
|
+
const modules = [];
|
|
21069
|
+
if (imports.getImportSpecifier(sourceFile, '@angular/core', 'NgModule')) {
|
|
21070
|
+
sourceFile.forEachChild(function walk(node) {
|
|
21071
|
+
if (ts__default["default"].isClassDeclaration(node)) {
|
|
21072
|
+
const decorator = nodes.getAngularDecorators(typeChecker, ts__default["default"].getDecorators(node) || []).find((current) => current.name === 'NgModule');
|
|
21073
|
+
const metadata = decorator ? extractMetadataLiteral(decorator.node) : null;
|
|
21074
|
+
if (metadata) {
|
|
21075
|
+
const declarations = findLiteralProperty(metadata, 'declarations');
|
|
21076
|
+
if (declarations != null && hasNgModuleMetadataElements(declarations)) {
|
|
21077
|
+
modules.push(node);
|
|
21078
|
+
}
|
|
21079
|
+
}
|
|
21038
21080
|
}
|
|
21039
|
-
|
|
21040
|
-
|
|
21041
|
-
}
|
|
21081
|
+
node.forEachChild(walk);
|
|
21082
|
+
});
|
|
21083
|
+
}
|
|
21084
|
+
return modules;
|
|
21042
21085
|
}
|
|
21043
|
-
/**
|
|
21044
|
-
function
|
|
21045
|
-
|
|
21046
|
-
|
|
21047
|
-
|
|
21086
|
+
/** Finds all testing object literals that need to be migrated. */
|
|
21087
|
+
function findTestObjectsToMigrate(sourceFile, typeChecker) {
|
|
21088
|
+
const testObjects = [];
|
|
21089
|
+
const testBedImport = imports.getImportSpecifier(sourceFile, '@angular/core/testing', 'TestBed');
|
|
21090
|
+
const catalystImport = imports.getImportSpecifier(sourceFile, /testing\/catalyst$/, 'setupModule');
|
|
21091
|
+
if (testBedImport || catalystImport) {
|
|
21092
|
+
sourceFile.forEachChild(function walk(node) {
|
|
21093
|
+
const isObjectLiteralCall = ts__default["default"].isCallExpression(node) &&
|
|
21094
|
+
node.arguments.length > 0 &&
|
|
21095
|
+
// `arguments[0]` is the testing module config.
|
|
21096
|
+
ts__default["default"].isObjectLiteralExpression(node.arguments[0]);
|
|
21097
|
+
const config = isObjectLiteralCall ? node.arguments[0] : null;
|
|
21098
|
+
const isTestBedCall = isObjectLiteralCall &&
|
|
21099
|
+
testBedImport &&
|
|
21100
|
+
ts__default["default"].isPropertyAccessExpression(node.expression) &&
|
|
21101
|
+
node.expression.name.text === 'configureTestingModule' &&
|
|
21102
|
+
isReferenceToImport(typeChecker, node.expression.expression, testBedImport);
|
|
21103
|
+
const isCatalystCall = isObjectLiteralCall &&
|
|
21104
|
+
catalystImport &&
|
|
21105
|
+
ts__default["default"].isIdentifier(node.expression) &&
|
|
21106
|
+
isReferenceToImport(typeChecker, node.expression, catalystImport);
|
|
21107
|
+
if ((isTestBedCall || isCatalystCall) && config) {
|
|
21108
|
+
const declarations = findLiteralProperty(config, 'declarations');
|
|
21109
|
+
if (declarations &&
|
|
21110
|
+
ts__default["default"].isPropertyAssignment(declarations) &&
|
|
21111
|
+
ts__default["default"].isArrayLiteralExpression(declarations.initializer) &&
|
|
21112
|
+
declarations.initializer.elements.length > 0) {
|
|
21113
|
+
testObjects.push(config);
|
|
21114
|
+
}
|
|
21115
|
+
}
|
|
21116
|
+
node.forEachChild(walk);
|
|
21117
|
+
});
|
|
21118
|
+
}
|
|
21119
|
+
return testObjects;
|
|
21048
21120
|
}
|
|
21049
21121
|
/**
|
|
21050
|
-
*
|
|
21051
|
-
* @param
|
|
21052
|
-
* @param
|
|
21122
|
+
* Finds the classes corresponding to dependencies used in a component's template.
|
|
21123
|
+
* @param decl Component in whose template we're looking for dependencies.
|
|
21124
|
+
* @param typeChecker
|
|
21053
21125
|
*/
|
|
21054
|
-
function
|
|
21055
|
-
|
|
21056
|
-
|
|
21057
|
-
|
|
21058
|
-
|
|
21059
|
-
|
|
21060
|
-
|
|
21061
|
-
|
|
21126
|
+
function findTemplateDependencies(decl, typeChecker) {
|
|
21127
|
+
const results = [];
|
|
21128
|
+
const usedDirectives = typeChecker.getUsedDirectives(decl);
|
|
21129
|
+
const usedPipes = typeChecker.getUsedPipes(decl);
|
|
21130
|
+
if (usedDirectives !== null) {
|
|
21131
|
+
for (const dir of usedDirectives) {
|
|
21132
|
+
if (ts__default["default"].isClassDeclaration(dir.ref.node)) {
|
|
21133
|
+
results.push(dir.ref);
|
|
21134
|
+
}
|
|
21135
|
+
}
|
|
21062
21136
|
}
|
|
21063
|
-
|
|
21064
|
-
|
|
21065
|
-
|
|
21066
|
-
|
|
21067
|
-
|
|
21068
|
-
|
|
21069
|
-
|
|
21070
|
-
|
|
21071
|
-
function isReferenceToImport(typeChecker, node, importSpecifier) {
|
|
21072
|
-
// If this function is called on an identifier (should be most cases), we can quickly rule out
|
|
21073
|
-
// non-matches by comparing the identifier's string and the local name of the import specifier
|
|
21074
|
-
// which saves us some calls to the type checker.
|
|
21075
|
-
if (ts__default["default"].isIdentifier(node) && node.text !== importSpecifier.name.text) {
|
|
21076
|
-
return false;
|
|
21137
|
+
if (usedPipes !== null) {
|
|
21138
|
+
const potentialPipes = typeChecker.getPotentialPipes(decl);
|
|
21139
|
+
for (const pipe of potentialPipes) {
|
|
21140
|
+
if (ts__default["default"].isClassDeclaration(pipe.ref.node) &&
|
|
21141
|
+
usedPipes.some((current) => pipe.name === current)) {
|
|
21142
|
+
results.push(pipe.ref);
|
|
21143
|
+
}
|
|
21144
|
+
}
|
|
21077
21145
|
}
|
|
21078
|
-
|
|
21079
|
-
const importSymbol = typeChecker.getTypeAtLocation(importSpecifier).getSymbol();
|
|
21080
|
-
return (!!(nodeSymbol?.declarations?.[0] && importSymbol?.declarations?.[0]) &&
|
|
21081
|
-
nodeSymbol.declarations[0] === importSymbol.declarations[0]);
|
|
21146
|
+
return results;
|
|
21082
21147
|
}
|
|
21083
|
-
|
|
21084
|
-
/*!
|
|
21085
|
-
* @license
|
|
21086
|
-
* Copyright Google LLC All Rights Reserved.
|
|
21087
|
-
*
|
|
21088
|
-
* Use of this source code is governed by an MIT-style license that can be
|
|
21089
|
-
* found in the LICENSE file at https://angular.io/license
|
|
21090
|
-
*/
|
|
21091
21148
|
/**
|
|
21092
|
-
*
|
|
21093
|
-
*
|
|
21094
|
-
* @param
|
|
21095
|
-
* @param
|
|
21096
|
-
* @param
|
|
21097
|
-
* @param
|
|
21098
|
-
* imports.
|
|
21149
|
+
* Removes any declarations that are a part of a module's `bootstrap`
|
|
21150
|
+
* array from an array of declarations.
|
|
21151
|
+
* @param declarations Anaalyzed declarations of the module.
|
|
21152
|
+
* @param ngModule Module whote declarations are being filtered.
|
|
21153
|
+
* @param templateTypeChecker
|
|
21154
|
+
* @param typeChecker
|
|
21099
21155
|
*/
|
|
21100
|
-
function
|
|
21101
|
-
const
|
|
21102
|
-
const
|
|
21103
|
-
const
|
|
21104
|
-
|
|
21105
|
-
|
|
21106
|
-
|
|
21107
|
-
for (const sourceFile of sourceFiles) {
|
|
21108
|
-
const modules = findNgModuleClassesToMigrate(sourceFile, typeChecker);
|
|
21109
|
-
const testObjects = findTestObjectsToMigrate(sourceFile, typeChecker);
|
|
21110
|
-
for (const module of modules) {
|
|
21111
|
-
const allModuleDeclarations = extractDeclarationsFromModule(module, templateTypeChecker);
|
|
21112
|
-
const unbootstrappedDeclarations = filterNonBootstrappedDeclarations(allModuleDeclarations, module, templateTypeChecker, typeChecker);
|
|
21113
|
-
if (unbootstrappedDeclarations.length > 0) {
|
|
21114
|
-
modulesToMigrate.add(module);
|
|
21115
|
-
unbootstrappedDeclarations.forEach((decl) => declarations.add(decl));
|
|
21116
|
-
}
|
|
21117
|
-
}
|
|
21118
|
-
testObjects.forEach((obj) => testObjectsToMigrate.add(obj));
|
|
21156
|
+
function filterNonBootstrappedDeclarations(declarations, ngModule, templateTypeChecker, typeChecker) {
|
|
21157
|
+
const metadata = templateTypeChecker.getNgModuleMetadata(ngModule);
|
|
21158
|
+
const metaLiteral = metadata && metadata.decorator ? extractMetadataLiteral(metadata.decorator) : null;
|
|
21159
|
+
const bootstrapProp = metaLiteral ? findLiteralProperty(metaLiteral, 'bootstrap') : null;
|
|
21160
|
+
// If there's no `bootstrap`, we can't filter.
|
|
21161
|
+
if (!bootstrapProp) {
|
|
21162
|
+
return declarations;
|
|
21119
21163
|
}
|
|
21120
|
-
|
|
21121
|
-
|
|
21164
|
+
// If we can't analyze the `bootstrap` property, we can't safely determine which
|
|
21165
|
+
// declarations aren't bootstrapped so we assume that all of them are.
|
|
21166
|
+
if (!ts__default["default"].isPropertyAssignment(bootstrapProp) ||
|
|
21167
|
+
!ts__default["default"].isArrayLiteralExpression(bootstrapProp.initializer)) {
|
|
21168
|
+
return [];
|
|
21122
21169
|
}
|
|
21123
|
-
|
|
21124
|
-
|
|
21170
|
+
const bootstrappedClasses = new Set();
|
|
21171
|
+
for (const el of bootstrapProp.initializer.elements) {
|
|
21172
|
+
const referencedClass = ts__default["default"].isIdentifier(el) ? findClassDeclaration(el, typeChecker) : null;
|
|
21173
|
+
// If we can resolve an element to a class, we can filter it out,
|
|
21174
|
+
// otherwise assume that the array isn't static.
|
|
21175
|
+
if (referencedClass) {
|
|
21176
|
+
bootstrappedClasses.add(referencedClass);
|
|
21177
|
+
}
|
|
21178
|
+
else {
|
|
21179
|
+
return [];
|
|
21180
|
+
}
|
|
21125
21181
|
}
|
|
21126
|
-
|
|
21127
|
-
return tracker.recordChanges();
|
|
21182
|
+
return declarations.filter((ref) => !bootstrappedClasses.has(ref));
|
|
21128
21183
|
}
|
|
21129
21184
|
/**
|
|
21130
|
-
*
|
|
21131
|
-
* @param
|
|
21132
|
-
* @param
|
|
21133
|
-
|
|
21185
|
+
* Extracts all classes that are referenced in a module's `declarations` array.
|
|
21186
|
+
* @param ngModule Module whose declarations are being extraced.
|
|
21187
|
+
* @param templateTypeChecker
|
|
21188
|
+
*/
|
|
21189
|
+
function extractDeclarationsFromModule(ngModule, templateTypeChecker) {
|
|
21190
|
+
const metadata = templateTypeChecker.getNgModuleMetadata(ngModule);
|
|
21191
|
+
return metadata
|
|
21192
|
+
? metadata.declarations
|
|
21193
|
+
.filter((decl) => ts__default["default"].isClassDeclaration(decl.node))
|
|
21194
|
+
.map((decl) => decl.node)
|
|
21195
|
+
: [];
|
|
21196
|
+
}
|
|
21197
|
+
/**
|
|
21198
|
+
* Migrates the `declarations` from a unit test file to standalone.
|
|
21199
|
+
* @param testObjects Object literals used to configure the testing modules.
|
|
21200
|
+
* @param declarationsOutsideOfTestFiles Non-testing declarations that are part of this migration.
|
|
21201
|
+
* @param tracker
|
|
21202
|
+
* @param templateTypeChecker
|
|
21134
21203
|
* @param typeChecker
|
|
21135
|
-
* @param importRemapper
|
|
21136
21204
|
*/
|
|
21137
|
-
function
|
|
21138
|
-
const
|
|
21139
|
-
|
|
21140
|
-
|
|
21141
|
-
|
|
21142
|
-
|
|
21143
|
-
|
|
21144
|
-
|
|
21145
|
-
|
|
21146
|
-
decorator = addPropertyToAngularDecorator(decorator, ts__default["default"].factory.createPropertyAssignment('imports', ts__default["default"].factory.createArrayLiteralExpression(
|
|
21147
|
-
// Create a multi-line array when it has a trailing comma.
|
|
21148
|
-
ts__default["default"].factory.createNodeArray(importsToAdd, hasTrailingComma), hasTrailingComma)));
|
|
21205
|
+
function migrateTestDeclarations(testObjects, declarationsOutsideOfTestFiles, tracker, templateTypeChecker, typeChecker) {
|
|
21206
|
+
const { decorators, componentImports } = analyzeTestingModules(testObjects, typeChecker);
|
|
21207
|
+
const allDeclarations = new Set(declarationsOutsideOfTestFiles);
|
|
21208
|
+
for (const decorator of decorators) {
|
|
21209
|
+
const closestClass = nodes.closestNode(decorator.node, ts__default["default"].isClassDeclaration);
|
|
21210
|
+
if (decorator.name === 'Pipe' || decorator.name === 'Directive') {
|
|
21211
|
+
tracker.replaceNode(decorator.node, addStandaloneToDecorator(decorator.node));
|
|
21212
|
+
if (closestClass) {
|
|
21213
|
+
allDeclarations.add(closestClass);
|
|
21149
21214
|
}
|
|
21150
21215
|
}
|
|
21151
|
-
|
|
21152
|
-
|
|
21153
|
-
|
|
21154
|
-
|
|
21155
|
-
|
|
21156
|
-
|
|
21216
|
+
else if (decorator.name === 'Component') {
|
|
21217
|
+
const newDecorator = addStandaloneToDecorator(decorator.node);
|
|
21218
|
+
const importsToAdd = componentImports.get(decorator.node);
|
|
21219
|
+
if (closestClass) {
|
|
21220
|
+
allDeclarations.add(closestClass);
|
|
21221
|
+
}
|
|
21222
|
+
if (importsToAdd && importsToAdd.size > 0) {
|
|
21223
|
+
const hasTrailingComma = importsToAdd.size > 2 &&
|
|
21224
|
+
!!extractMetadataLiteral(decorator.node)?.properties.hasTrailingComma;
|
|
21225
|
+
const importsArray = ts__default["default"].factory.createNodeArray(Array.from(importsToAdd), hasTrailingComma);
|
|
21226
|
+
tracker.replaceNode(decorator.node, addPropertyToAngularDecorator(newDecorator, ts__default["default"].factory.createPropertyAssignment('imports', ts__default["default"].factory.createArrayLiteralExpression(importsArray))));
|
|
21227
|
+
}
|
|
21228
|
+
else {
|
|
21229
|
+
tracker.replaceNode(decorator.node, newDecorator);
|
|
21230
|
+
}
|
|
21157
21231
|
}
|
|
21158
21232
|
}
|
|
21233
|
+
for (const obj of testObjects) {
|
|
21234
|
+
moveDeclarationsToImports(obj, allDeclarations, typeChecker, templateTypeChecker, tracker);
|
|
21235
|
+
}
|
|
21159
21236
|
}
|
|
21160
21237
|
/**
|
|
21161
|
-
*
|
|
21162
|
-
*
|
|
21163
|
-
*
|
|
21164
|
-
* @param
|
|
21165
|
-
* @param tracker
|
|
21166
|
-
* @param typeChecker
|
|
21167
|
-
* @param importRemapper
|
|
21238
|
+
* Analyzes a set of objects used to configure testing modules and returns the AST
|
|
21239
|
+
* nodes that need to be migrated and the imports that should be added to the imports
|
|
21240
|
+
* of any declared components.
|
|
21241
|
+
* @param testObjects Object literals that should be analyzed.
|
|
21168
21242
|
*/
|
|
21169
|
-
function
|
|
21170
|
-
const
|
|
21171
|
-
const
|
|
21172
|
-
const
|
|
21173
|
-
const
|
|
21174
|
-
|
|
21175
|
-
|
|
21176
|
-
|
|
21177
|
-
? compiler_host.PotentialImportMode.ForceDirect
|
|
21178
|
-
: compiler_host.PotentialImportMode.Normal, typeChecker);
|
|
21179
|
-
if (importLocation && !seenImports.has(importLocation.symbolName)) {
|
|
21180
|
-
seenImports.add(importLocation.symbolName);
|
|
21181
|
-
resolvedDependencies.push(importLocation);
|
|
21182
|
-
}
|
|
21183
|
-
}
|
|
21184
|
-
const processedDependencies = importRemapper
|
|
21185
|
-
? importRemapper(resolvedDependencies, decl)
|
|
21186
|
-
: resolvedDependencies;
|
|
21187
|
-
for (const importLocation of processedDependencies) {
|
|
21188
|
-
if (importLocation.moduleSpecifier) {
|
|
21189
|
-
const identifier = tracker.addImport(decl.getSourceFile(), importLocation.symbolName, importLocation.moduleSpecifier);
|
|
21190
|
-
imports.push(identifier);
|
|
21243
|
+
function analyzeTestingModules(testObjects, typeChecker) {
|
|
21244
|
+
const seenDeclarations = new Set();
|
|
21245
|
+
const decorators = [];
|
|
21246
|
+
const componentImports = new Map();
|
|
21247
|
+
for (const obj of testObjects) {
|
|
21248
|
+
const declarations = extractDeclarationsFromTestObject(obj, typeChecker);
|
|
21249
|
+
if (declarations.length === 0) {
|
|
21250
|
+
continue;
|
|
21191
21251
|
}
|
|
21192
|
-
|
|
21193
|
-
|
|
21194
|
-
|
|
21195
|
-
|
|
21196
|
-
|
|
21197
|
-
|
|
21252
|
+
const importsProp = findLiteralProperty(obj, 'imports');
|
|
21253
|
+
const importElements = importsProp &&
|
|
21254
|
+
hasNgModuleMetadataElements(importsProp) &&
|
|
21255
|
+
ts__default["default"].isArrayLiteralExpression(importsProp.initializer)
|
|
21256
|
+
? importsProp.initializer.elements.filter((el) => {
|
|
21257
|
+
// Filter out calls since they may be a `ModuleWithProviders`.
|
|
21258
|
+
return (!ts__default["default"].isCallExpression(el) &&
|
|
21259
|
+
// Also filter out the animations modules since they throw errors if they're imported
|
|
21260
|
+
// multiple times and it's common for apps to use the `NoopAnimationsModule` to
|
|
21261
|
+
// disable animations in screenshot tests.
|
|
21262
|
+
!isClassReferenceInAngularModule(el, /^BrowserAnimationsModule|NoopAnimationsModule$/, 'platform-browser/animations', typeChecker));
|
|
21263
|
+
})
|
|
21264
|
+
: null;
|
|
21265
|
+
for (const decl of declarations) {
|
|
21266
|
+
if (seenDeclarations.has(decl)) {
|
|
21267
|
+
continue;
|
|
21198
21268
|
}
|
|
21199
|
-
|
|
21200
|
-
|
|
21269
|
+
const [decorator] = nodes.getAngularDecorators(typeChecker, ts__default["default"].getDecorators(decl) || []);
|
|
21270
|
+
if (decorator) {
|
|
21271
|
+
seenDeclarations.add(decl);
|
|
21272
|
+
decorators.push(decorator);
|
|
21273
|
+
if (decorator.name === 'Component' && importElements) {
|
|
21274
|
+
// We try to de-duplicate the imports being added to a component, because it may be
|
|
21275
|
+
// declared in different testing modules with a different set of imports.
|
|
21276
|
+
let imports = componentImports.get(decorator.node);
|
|
21277
|
+
if (!imports) {
|
|
21278
|
+
imports = new Set();
|
|
21279
|
+
componentImports.set(decorator.node, imports);
|
|
21280
|
+
}
|
|
21281
|
+
importElements.forEach((imp) => imports.add(imp));
|
|
21282
|
+
}
|
|
21201
21283
|
}
|
|
21202
21284
|
}
|
|
21203
21285
|
}
|
|
21204
|
-
return
|
|
21286
|
+
return { decorators, componentImports };
|
|
21205
21287
|
}
|
|
21206
21288
|
/**
|
|
21207
|
-
*
|
|
21208
|
-
*
|
|
21209
|
-
* @param
|
|
21210
|
-
* @param tracker
|
|
21289
|
+
* Finds the class declarations that are being referred
|
|
21290
|
+
* to in the `declarations` of an object literal.
|
|
21291
|
+
* @param obj Object literal that may contain the declarations.
|
|
21211
21292
|
* @param typeChecker
|
|
21212
|
-
* @param templateTypeChecker
|
|
21213
21293
|
*/
|
|
21214
|
-
function
|
|
21215
|
-
const
|
|
21216
|
-
const
|
|
21217
|
-
if (
|
|
21218
|
-
|
|
21294
|
+
function extractDeclarationsFromTestObject(obj, typeChecker) {
|
|
21295
|
+
const results = [];
|
|
21296
|
+
const declarations = findLiteralProperty(obj, 'declarations');
|
|
21297
|
+
if (declarations &&
|
|
21298
|
+
hasNgModuleMetadataElements(declarations) &&
|
|
21299
|
+
ts__default["default"].isArrayLiteralExpression(declarations.initializer)) {
|
|
21300
|
+
for (const element of declarations.initializer.elements) {
|
|
21301
|
+
const declaration = findClassDeclaration(element, typeChecker);
|
|
21302
|
+
// Note that we only migrate classes that are in the same file as the testing module,
|
|
21303
|
+
// because external fixture components are somewhat rare and handling them is going
|
|
21304
|
+
// to involve a lot of assumptions that are likely to be incorrect.
|
|
21305
|
+
if (declaration && declaration.getSourceFile().fileName === obj.getSourceFile().fileName) {
|
|
21306
|
+
results.push(declaration);
|
|
21307
|
+
}
|
|
21308
|
+
}
|
|
21219
21309
|
}
|
|
21310
|
+
return results;
|
|
21311
|
+
}
|
|
21312
|
+
/** Extracts the metadata object literal from an Angular decorator. */
|
|
21313
|
+
function extractMetadataLiteral(decorator) {
|
|
21314
|
+
// `arguments[0]` is the metadata object literal.
|
|
21315
|
+
return ts__default["default"].isCallExpression(decorator.expression) &&
|
|
21316
|
+
decorator.expression.arguments.length === 1 &&
|
|
21317
|
+
ts__default["default"].isObjectLiteralExpression(decorator.expression.arguments[0])
|
|
21318
|
+
? decorator.expression.arguments[0]
|
|
21319
|
+
: null;
|
|
21220
21320
|
}
|
|
21221
21321
|
/**
|
|
21222
|
-
*
|
|
21223
|
-
*
|
|
21224
|
-
* @param
|
|
21225
|
-
* @param
|
|
21226
|
-
* @param typeChecker
|
|
21227
|
-
* @param tracker
|
|
21322
|
+
* Checks whether a class is a standalone declaration.
|
|
21323
|
+
* @param node Class being checked.
|
|
21324
|
+
* @param declarationsInMigration Classes that are being converted to standalone in this migration.
|
|
21325
|
+
* @param templateTypeChecker
|
|
21228
21326
|
*/
|
|
21229
|
-
function
|
|
21230
|
-
|
|
21231
|
-
|
|
21232
|
-
return;
|
|
21233
|
-
}
|
|
21234
|
-
const declarationsToPreserve = [];
|
|
21235
|
-
const declarationsToCopy = [];
|
|
21236
|
-
const properties = [];
|
|
21237
|
-
const importsProp = findLiteralProperty(literal, 'imports');
|
|
21238
|
-
const hasAnyArrayTrailingComma = literal.properties.some((prop) => ts__default["default"].isPropertyAssignment(prop) &&
|
|
21239
|
-
ts__default["default"].isArrayLiteralExpression(prop.initializer) &&
|
|
21240
|
-
prop.initializer.elements.hasTrailingComma);
|
|
21241
|
-
// Separate the declarations that we want to keep and ones we need to copy into the `imports`.
|
|
21242
|
-
if (ts__default["default"].isPropertyAssignment(declarationsProp)) {
|
|
21243
|
-
// If the declarations are an array, we can analyze it to
|
|
21244
|
-
// find any classes from the current migration.
|
|
21245
|
-
if (ts__default["default"].isArrayLiteralExpression(declarationsProp.initializer)) {
|
|
21246
|
-
for (const el of declarationsProp.initializer.elements) {
|
|
21247
|
-
if (ts__default["default"].isIdentifier(el)) {
|
|
21248
|
-
const correspondingClass = findClassDeclaration(el, typeChecker);
|
|
21249
|
-
if (!correspondingClass ||
|
|
21250
|
-
// Check whether the declaration is either standalone already or is being converted
|
|
21251
|
-
// in this migration. We need to check if it's standalone already, in order to correct
|
|
21252
|
-
// some cases where the main app and the test files are being migrated in separate
|
|
21253
|
-
// programs.
|
|
21254
|
-
isStandaloneDeclaration(correspondingClass, allDeclarations, templateTypeChecker)) {
|
|
21255
|
-
declarationsToCopy.push(el);
|
|
21256
|
-
}
|
|
21257
|
-
else {
|
|
21258
|
-
declarationsToPreserve.push(el);
|
|
21259
|
-
}
|
|
21260
|
-
}
|
|
21261
|
-
else {
|
|
21262
|
-
declarationsToCopy.push(el);
|
|
21263
|
-
}
|
|
21264
|
-
}
|
|
21265
|
-
}
|
|
21266
|
-
else {
|
|
21267
|
-
// Otherwise create a spread that will be copied into the `imports`.
|
|
21268
|
-
declarationsToCopy.push(ts__default["default"].factory.createSpreadElement(declarationsProp.initializer));
|
|
21269
|
-
}
|
|
21270
|
-
}
|
|
21271
|
-
// If there are no `imports`, create them with the declarations we want to copy.
|
|
21272
|
-
if (!importsProp && declarationsToCopy.length > 0) {
|
|
21273
|
-
properties.push(ts__default["default"].factory.createPropertyAssignment('imports', ts__default["default"].factory.createArrayLiteralExpression(ts__default["default"].factory.createNodeArray(declarationsToCopy, hasAnyArrayTrailingComma && declarationsToCopy.length > 2))));
|
|
21327
|
+
function isStandaloneDeclaration(node, declarationsInMigration, templateTypeChecker) {
|
|
21328
|
+
if (declarationsInMigration.has(node)) {
|
|
21329
|
+
return true;
|
|
21274
21330
|
}
|
|
21275
|
-
|
|
21276
|
-
|
|
21277
|
-
|
|
21278
|
-
|
|
21331
|
+
const metadata = templateTypeChecker.getDirectiveMetadata(node) || templateTypeChecker.getPipeMetadata(node);
|
|
21332
|
+
return metadata != null && metadata.isStandalone;
|
|
21333
|
+
}
|
|
21334
|
+
|
|
21335
|
+
/*!
|
|
21336
|
+
* @license
|
|
21337
|
+
* Copyright Google LLC All Rights Reserved.
|
|
21338
|
+
*
|
|
21339
|
+
* Use of this source code is governed by an MIT-style license that can be
|
|
21340
|
+
* found in the LICENSE file at https://angular.io/license
|
|
21341
|
+
*/
|
|
21342
|
+
function pruneNgModules(program, host, basePath, rootFileNames, sourceFiles, printer, importRemapper, referenceLookupExcludedFiles, componentImportRemapper) {
|
|
21343
|
+
const filesToRemove = new Set();
|
|
21344
|
+
const tracker = new compiler_host.ChangeTracker(printer, importRemapper);
|
|
21345
|
+
const tsProgram = program.getTsProgram();
|
|
21346
|
+
const typeChecker = tsProgram.getTypeChecker();
|
|
21347
|
+
const templateTypeChecker = program.compiler.getTemplateTypeChecker();
|
|
21348
|
+
const referenceResolver = new ReferenceResolver(program, host, rootFileNames, basePath, referenceLookupExcludedFiles);
|
|
21349
|
+
const removalLocations = {
|
|
21350
|
+
arrays: new UniqueItemTracker(),
|
|
21351
|
+
imports: new UniqueItemTracker(),
|
|
21352
|
+
exports: new UniqueItemTracker(),
|
|
21353
|
+
unknown: new Set(),
|
|
21354
|
+
};
|
|
21355
|
+
const classesToRemove = new Set();
|
|
21356
|
+
const barrelExports = new UniqueItemTracker();
|
|
21357
|
+
const componentImportArrays = new UniqueItemTracker();
|
|
21358
|
+
const nodesToRemove = new Set();
|
|
21359
|
+
sourceFiles.forEach(function walk(node) {
|
|
21360
|
+
if (ts__default["default"].isClassDeclaration(node) && canRemoveClass(node, typeChecker)) {
|
|
21361
|
+
collectChangeLocations(node, removalLocations, componentImportArrays, templateTypeChecker, referenceResolver, program);
|
|
21362
|
+
classesToRemove.add(node);
|
|
21279
21363
|
}
|
|
21280
|
-
|
|
21281
|
-
|
|
21282
|
-
|
|
21283
|
-
|
|
21284
|
-
|
|
21285
|
-
|
|
21286
|
-
|
|
21364
|
+
else if (ts__default["default"].isExportDeclaration(node) &&
|
|
21365
|
+
!node.exportClause &&
|
|
21366
|
+
node.moduleSpecifier &&
|
|
21367
|
+
ts__default["default"].isStringLiteralLike(node.moduleSpecifier) &&
|
|
21368
|
+
node.moduleSpecifier.text.startsWith('.')) {
|
|
21369
|
+
const exportedSourceFile = typeChecker
|
|
21370
|
+
.getSymbolAtLocation(node.moduleSpecifier)
|
|
21371
|
+
?.valueDeclaration?.getSourceFile();
|
|
21372
|
+
if (exportedSourceFile) {
|
|
21373
|
+
barrelExports.track(exportedSourceFile, node);
|
|
21287
21374
|
}
|
|
21288
|
-
continue;
|
|
21289
21375
|
}
|
|
21290
|
-
|
|
21291
|
-
|
|
21292
|
-
|
|
21293
|
-
|
|
21294
|
-
|
|
21295
|
-
|
|
21376
|
+
node.forEachChild(walk);
|
|
21377
|
+
});
|
|
21378
|
+
replaceInImportsArray(componentImportArrays, classesToRemove, tracker, typeChecker, templateTypeChecker, componentImportRemapper);
|
|
21379
|
+
// We collect all the places where we need to remove references first before generating the
|
|
21380
|
+
// removal instructions since we may have to remove multiple references from one node.
|
|
21381
|
+
removeArrayReferences(removalLocations.arrays, tracker);
|
|
21382
|
+
removeImportReferences(removalLocations.imports, tracker);
|
|
21383
|
+
removeExportReferences(removalLocations.exports, tracker);
|
|
21384
|
+
addRemovalTodos(removalLocations.unknown, tracker);
|
|
21385
|
+
// Collect all the nodes to be removed before determining which files to delete since we need
|
|
21386
|
+
// to know it ahead of time when deleting barrel files that export other barrel files.
|
|
21387
|
+
(function trackNodesToRemove(nodes) {
|
|
21388
|
+
for (const node of nodes) {
|
|
21389
|
+
const sourceFile = node.getSourceFile();
|
|
21390
|
+
if (!filesToRemove.has(sourceFile) && canRemoveFile(sourceFile, nodes)) {
|
|
21391
|
+
const barrelExportsForFile = barrelExports.get(sourceFile);
|
|
21392
|
+
nodesToRemove.add(node);
|
|
21393
|
+
filesToRemove.add(sourceFile);
|
|
21394
|
+
barrelExportsForFile && trackNodesToRemove(barrelExportsForFile);
|
|
21296
21395
|
}
|
|
21297
21396
|
else {
|
|
21298
|
-
|
|
21299
|
-
// Expect the declarations to be greater than 1 since
|
|
21300
|
-
// we have the pre-existing initializer already.
|
|
21301
|
-
hasAnyArrayTrailingComma && declarationsToCopy.length > 1));
|
|
21397
|
+
nodesToRemove.add(node);
|
|
21302
21398
|
}
|
|
21303
|
-
properties.push(ts__default["default"].factory.updatePropertyAssignment(prop, prop.name, initializer));
|
|
21304
|
-
continue;
|
|
21305
21399
|
}
|
|
21306
|
-
|
|
21307
|
-
|
|
21400
|
+
})(classesToRemove);
|
|
21401
|
+
for (const node of nodesToRemove) {
|
|
21402
|
+
const sourceFile = node.getSourceFile();
|
|
21403
|
+
if (!filesToRemove.has(sourceFile) && canRemoveFile(sourceFile, nodesToRemove)) {
|
|
21404
|
+
filesToRemove.add(sourceFile);
|
|
21405
|
+
}
|
|
21406
|
+
else {
|
|
21407
|
+
tracker.removeNode(node);
|
|
21408
|
+
}
|
|
21308
21409
|
}
|
|
21309
|
-
|
|
21310
|
-
}
|
|
21311
|
-
/** Adds `standalone: true` to a decorator node. */
|
|
21312
|
-
function addStandaloneToDecorator(node) {
|
|
21313
|
-
return addPropertyToAngularDecorator(node, ts__default["default"].factory.createPropertyAssignment('standalone', ts__default["default"].factory.createToken(ts__default["default"].SyntaxKind.TrueKeyword)));
|
|
21410
|
+
return { pendingChanges: tracker.recordChanges(), filesToRemove };
|
|
21314
21411
|
}
|
|
21315
21412
|
/**
|
|
21316
|
-
*
|
|
21317
|
-
* @param
|
|
21318
|
-
* @param
|
|
21413
|
+
* Collects all the nodes that a module needs to be removed from.
|
|
21414
|
+
* @param ngModule Module being removed.
|
|
21415
|
+
* @param removalLocations Tracks the different places from which the class should be removed.
|
|
21416
|
+
* @param componentImportArrays Set of `imports` arrays of components that need to be adjusted.
|
|
21417
|
+
* @param referenceResolver
|
|
21418
|
+
* @param program
|
|
21319
21419
|
*/
|
|
21320
|
-
function
|
|
21321
|
-
|
|
21322
|
-
|
|
21323
|
-
|
|
21324
|
-
|
|
21325
|
-
|
|
21326
|
-
|
|
21327
|
-
|
|
21328
|
-
|
|
21329
|
-
}
|
|
21330
|
-
else if (ts__default["default"].isObjectLiteralExpression(node.expression.arguments[0])) {
|
|
21331
|
-
hasTrailingComma = node.expression.arguments[0].properties.hasTrailingComma;
|
|
21332
|
-
literalProperties = [...node.expression.arguments[0].properties, property];
|
|
21420
|
+
function collectChangeLocations(ngModule, removalLocations, componentImportArrays, templateTypeChecker, referenceResolver, program) {
|
|
21421
|
+
const refsByFile = referenceResolver.findReferencesInProject(ngModule.name);
|
|
21422
|
+
const tsProgram = program.getTsProgram();
|
|
21423
|
+
const nodes$1 = new Set();
|
|
21424
|
+
for (const [fileName, refs] of refsByFile) {
|
|
21425
|
+
const sourceFile = tsProgram.getSourceFile(fileName);
|
|
21426
|
+
if (sourceFile) {
|
|
21427
|
+
offsetsToNodes(getNodeLookup(sourceFile), refs, nodes$1);
|
|
21428
|
+
}
|
|
21333
21429
|
}
|
|
21334
|
-
|
|
21335
|
-
|
|
21336
|
-
|
|
21430
|
+
for (const node of nodes$1) {
|
|
21431
|
+
const closestArray = nodes.closestNode(node, ts__default["default"].isArrayLiteralExpression);
|
|
21432
|
+
if (closestArray) {
|
|
21433
|
+
const closestAssignment = nodes.closestNode(closestArray, ts__default["default"].isPropertyAssignment);
|
|
21434
|
+
// If the module was flagged as being removable, but it's still being used in a standalone
|
|
21435
|
+
// component's `imports` array, it means that it was likely changed outside of the migration
|
|
21436
|
+
// and deleting it now will be breaking. Track it separately so it can be handled properly.
|
|
21437
|
+
if (closestAssignment && isInImportsArray(closestAssignment, closestArray)) {
|
|
21438
|
+
const closestDecorator = nodes.closestNode(closestAssignment, ts__default["default"].isDecorator);
|
|
21439
|
+
const closestClass = closestDecorator
|
|
21440
|
+
? nodes.closestNode(closestDecorator, ts__default["default"].isClassDeclaration)
|
|
21441
|
+
: null;
|
|
21442
|
+
const directiveMeta = closestClass
|
|
21443
|
+
? templateTypeChecker.getDirectiveMetadata(closestClass)
|
|
21444
|
+
: null;
|
|
21445
|
+
if (directiveMeta && directiveMeta.isComponent && directiveMeta.isStandalone) {
|
|
21446
|
+
componentImportArrays.track(closestArray, node);
|
|
21447
|
+
continue;
|
|
21448
|
+
}
|
|
21449
|
+
}
|
|
21450
|
+
removalLocations.arrays.track(closestArray, node);
|
|
21451
|
+
continue;
|
|
21452
|
+
}
|
|
21453
|
+
const closestImport = nodes.closestNode(node, ts__default["default"].isNamedImports);
|
|
21454
|
+
if (closestImport) {
|
|
21455
|
+
removalLocations.imports.track(closestImport, node);
|
|
21456
|
+
continue;
|
|
21457
|
+
}
|
|
21458
|
+
const closestExport = nodes.closestNode(node, ts__default["default"].isNamedExports);
|
|
21459
|
+
if (closestExport) {
|
|
21460
|
+
removalLocations.exports.track(closestExport, node);
|
|
21461
|
+
continue;
|
|
21462
|
+
}
|
|
21463
|
+
removalLocations.unknown.add(node);
|
|
21337
21464
|
}
|
|
21338
|
-
// Use `createDecorator` instead of `updateDecorator`, because
|
|
21339
|
-
// the latter ends up duplicating the node's leading comment.
|
|
21340
|
-
return ts__default["default"].factory.createDecorator(ts__default["default"].factory.createCallExpression(node.expression.expression, node.expression.typeArguments, [
|
|
21341
|
-
ts__default["default"].factory.createObjectLiteralExpression(ts__default["default"].factory.createNodeArray(literalProperties, hasTrailingComma), literalProperties.length > 1),
|
|
21342
|
-
]));
|
|
21343
|
-
}
|
|
21344
|
-
/** Checks if a node is a `PropertyAssignment` with a name. */
|
|
21345
|
-
function isNamedPropertyAssignment(node) {
|
|
21346
|
-
return ts__default["default"].isPropertyAssignment(node) && node.name && ts__default["default"].isIdentifier(node.name);
|
|
21347
21465
|
}
|
|
21348
21466
|
/**
|
|
21349
|
-
*
|
|
21350
|
-
* @param
|
|
21351
|
-
* @param
|
|
21352
|
-
* @param
|
|
21467
|
+
* Replaces all the leftover modules in imports arrays with their exports.
|
|
21468
|
+
* @param componentImportArrays All the imports arrays and their nodes that represent NgModules.
|
|
21469
|
+
* @param classesToRemove Set of classes that were marked for removal.
|
|
21470
|
+
* @param tracker
|
|
21353
21471
|
* @param typeChecker
|
|
21472
|
+
* @param templateTypeChecker
|
|
21473
|
+
* @param importRemapper
|
|
21354
21474
|
*/
|
|
21355
|
-
function
|
|
21356
|
-
const
|
|
21357
|
-
|
|
21358
|
-
|
|
21359
|
-
|
|
21360
|
-
// Prefer a standalone import, if we can find one.
|
|
21361
|
-
// Otherwise fall back to the first module-based import.
|
|
21362
|
-
if (location.kind === compiler_host.PotentialImportKind.Standalone) {
|
|
21363
|
-
return location;
|
|
21364
|
-
}
|
|
21365
|
-
if (!location.moduleSpecifier && !firstSameFileImport) {
|
|
21366
|
-
firstSameFileImport = location;
|
|
21475
|
+
function replaceInImportsArray(componentImportArrays, classesToRemove, tracker, typeChecker, templateTypeChecker, importRemapper) {
|
|
21476
|
+
for (const [array, toReplace] of componentImportArrays.getEntries()) {
|
|
21477
|
+
const closestClass = nodes.closestNode(array, ts__default["default"].isClassDeclaration);
|
|
21478
|
+
if (!closestClass) {
|
|
21479
|
+
continue;
|
|
21367
21480
|
}
|
|
21368
|
-
|
|
21369
|
-
|
|
21370
|
-
|
|
21371
|
-
|
|
21372
|
-
|
|
21481
|
+
const replacements = new UniqueItemTracker();
|
|
21482
|
+
const usedImports = new Set(findTemplateDependencies(closestClass, templateTypeChecker).map((ref) => ref.node));
|
|
21483
|
+
for (const node of toReplace) {
|
|
21484
|
+
const moduleDecl = findClassDeclaration(node, typeChecker);
|
|
21485
|
+
if (moduleDecl) {
|
|
21486
|
+
const moduleMeta = templateTypeChecker.getNgModuleMetadata(moduleDecl);
|
|
21487
|
+
if (moduleMeta) {
|
|
21488
|
+
moduleMeta.exports.forEach((exp) => {
|
|
21489
|
+
if (usedImports.has(exp.node)) {
|
|
21490
|
+
replacements.track(node, exp);
|
|
21491
|
+
}
|
|
21492
|
+
});
|
|
21493
|
+
}
|
|
21494
|
+
else {
|
|
21495
|
+
// It's unlikely not to have module metadata at this point, but just in
|
|
21496
|
+
// case unmark the class for removal to reduce the chance of breakages.
|
|
21497
|
+
classesToRemove.delete(moduleDecl);
|
|
21498
|
+
}
|
|
21499
|
+
}
|
|
21373
21500
|
}
|
|
21501
|
+
replaceModulesInImportsArray(array, closestClass, replacements, tracker, templateTypeChecker, importRemapper);
|
|
21374
21502
|
}
|
|
21375
|
-
return firstSameFileImport || firstModuleImport || importLocations[0] || null;
|
|
21376
21503
|
}
|
|
21377
21504
|
/**
|
|
21378
|
-
*
|
|
21379
|
-
*
|
|
21380
|
-
*
|
|
21505
|
+
* Replaces any leftover modules in `imports` arrays with their exports that are used within a
|
|
21506
|
+
* component.
|
|
21507
|
+
* @param array Imports array which is being migrated.
|
|
21508
|
+
* @param componentClass Class that the imports array belongs to.
|
|
21509
|
+
* @param replacements Map of NgModule references to their exports.
|
|
21510
|
+
* @param tracker
|
|
21511
|
+
* @param templateTypeChecker
|
|
21512
|
+
* @param importRemapper
|
|
21381
21513
|
*/
|
|
21382
|
-
function
|
|
21383
|
-
|
|
21384
|
-
|
|
21385
|
-
|
|
21386
|
-
|
|
21387
|
-
|
|
21388
|
-
|
|
21389
|
-
|
|
21390
|
-
|
|
21391
|
-
|
|
21392
|
-
|
|
21393
|
-
|
|
21394
|
-
|
|
21395
|
-
const declarations = findLiteralProperty(metadata, 'declarations');
|
|
21396
|
-
if (declarations != null && hasNgModuleMetadataElements(declarations)) {
|
|
21397
|
-
modules.push(node);
|
|
21398
|
-
}
|
|
21399
|
-
}
|
|
21514
|
+
function replaceModulesInImportsArray(array, componentClass, replacements, tracker, templateTypeChecker, importRemapper) {
|
|
21515
|
+
const newElements = [];
|
|
21516
|
+
for (const element of array.elements) {
|
|
21517
|
+
const replacementRefs = replacements.get(element);
|
|
21518
|
+
if (!replacementRefs) {
|
|
21519
|
+
newElements.push(element);
|
|
21520
|
+
continue;
|
|
21521
|
+
}
|
|
21522
|
+
const potentialImports = [];
|
|
21523
|
+
for (const ref of replacementRefs) {
|
|
21524
|
+
const importLocation = findImportLocation(ref, componentClass, compiler_host.PotentialImportMode.Normal, templateTypeChecker);
|
|
21525
|
+
if (importLocation) {
|
|
21526
|
+
potentialImports.push(importLocation);
|
|
21400
21527
|
}
|
|
21401
|
-
|
|
21402
|
-
|
|
21528
|
+
}
|
|
21529
|
+
newElements.push(...potentialImportsToExpressions(potentialImports, componentClass, tracker, importRemapper));
|
|
21403
21530
|
}
|
|
21404
|
-
|
|
21531
|
+
tracker.replaceNode(array, ts__default["default"].factory.updateArrayLiteralExpression(array, newElements));
|
|
21405
21532
|
}
|
|
21406
|
-
/**
|
|
21407
|
-
|
|
21408
|
-
|
|
21409
|
-
|
|
21410
|
-
|
|
21411
|
-
|
|
21412
|
-
|
|
21413
|
-
|
|
21414
|
-
|
|
21415
|
-
// `arguments[0]` is the testing module config.
|
|
21416
|
-
ts__default["default"].isObjectLiteralExpression(node.arguments[0]);
|
|
21417
|
-
const config = isObjectLiteralCall ? node.arguments[0] : null;
|
|
21418
|
-
const isTestBedCall = isObjectLiteralCall &&
|
|
21419
|
-
testBedImport &&
|
|
21420
|
-
ts__default["default"].isPropertyAccessExpression(node.expression) &&
|
|
21421
|
-
node.expression.name.text === 'configureTestingModule' &&
|
|
21422
|
-
isReferenceToImport(typeChecker, node.expression.expression, testBedImport);
|
|
21423
|
-
const isCatalystCall = isObjectLiteralCall &&
|
|
21424
|
-
catalystImport &&
|
|
21425
|
-
ts__default["default"].isIdentifier(node.expression) &&
|
|
21426
|
-
isReferenceToImport(typeChecker, node.expression, catalystImport);
|
|
21427
|
-
if ((isTestBedCall || isCatalystCall) && config) {
|
|
21428
|
-
const declarations = findLiteralProperty(config, 'declarations');
|
|
21429
|
-
if (declarations &&
|
|
21430
|
-
ts__default["default"].isPropertyAssignment(declarations) &&
|
|
21431
|
-
ts__default["default"].isArrayLiteralExpression(declarations.initializer) &&
|
|
21432
|
-
declarations.initializer.elements.length > 0) {
|
|
21433
|
-
testObjects.push(config);
|
|
21434
|
-
}
|
|
21435
|
-
}
|
|
21436
|
-
node.forEachChild(walk);
|
|
21437
|
-
});
|
|
21533
|
+
/**
|
|
21534
|
+
* Removes all tracked array references.
|
|
21535
|
+
* @param locations Locations from which to remove the references.
|
|
21536
|
+
* @param tracker Tracker in which to register the changes.
|
|
21537
|
+
*/
|
|
21538
|
+
function removeArrayReferences(locations, tracker) {
|
|
21539
|
+
for (const [array, toRemove] of locations.getEntries()) {
|
|
21540
|
+
const newElements = filterRemovedElements(array.elements, toRemove);
|
|
21541
|
+
tracker.replaceNode(array, ts__default["default"].factory.updateArrayLiteralExpression(array, ts__default["default"].factory.createNodeArray(newElements, array.elements.hasTrailingComma)));
|
|
21438
21542
|
}
|
|
21439
|
-
return testObjects;
|
|
21440
21543
|
}
|
|
21441
21544
|
/**
|
|
21442
|
-
*
|
|
21443
|
-
* @param
|
|
21444
|
-
* @param
|
|
21545
|
+
* Removes all tracked import references.
|
|
21546
|
+
* @param locations Locations from which to remove the references.
|
|
21547
|
+
* @param tracker Tracker in which to register the changes.
|
|
21445
21548
|
*/
|
|
21446
|
-
function
|
|
21447
|
-
const
|
|
21448
|
-
|
|
21449
|
-
|
|
21450
|
-
|
|
21451
|
-
|
|
21452
|
-
|
|
21453
|
-
|
|
21549
|
+
function removeImportReferences(locations, tracker) {
|
|
21550
|
+
for (const [namedImports, toRemove] of locations.getEntries()) {
|
|
21551
|
+
const newElements = filterRemovedElements(namedImports.elements, toRemove);
|
|
21552
|
+
// If no imports are left, we can try to drop the entire import.
|
|
21553
|
+
if (newElements.length === 0) {
|
|
21554
|
+
const importClause = nodes.closestNode(namedImports, ts__default["default"].isImportClause);
|
|
21555
|
+
// If the import clause has a name we can only drop then named imports.
|
|
21556
|
+
// e.g. `import Foo, {ModuleToRemove} from './foo';` becomes `import Foo from './foo';`.
|
|
21557
|
+
if (importClause && importClause.name) {
|
|
21558
|
+
tracker.replaceNode(importClause, ts__default["default"].factory.updateImportClause(importClause, importClause.isTypeOnly, importClause.name, undefined));
|
|
21454
21559
|
}
|
|
21455
|
-
|
|
21456
|
-
|
|
21457
|
-
|
|
21458
|
-
|
|
21459
|
-
|
|
21460
|
-
|
|
21461
|
-
usedPipes.some((current) => pipe.name === current)) {
|
|
21462
|
-
results.push(pipe.ref);
|
|
21560
|
+
else {
|
|
21561
|
+
// Otherwise we can drop the entire declaration.
|
|
21562
|
+
const declaration = nodes.closestNode(namedImports, ts__default["default"].isImportDeclaration);
|
|
21563
|
+
if (declaration) {
|
|
21564
|
+
tracker.removeNode(declaration);
|
|
21565
|
+
}
|
|
21463
21566
|
}
|
|
21464
21567
|
}
|
|
21568
|
+
else {
|
|
21569
|
+
// Otherwise we just drop the imported symbols and keep the declaration intact.
|
|
21570
|
+
tracker.replaceNode(namedImports, ts__default["default"].factory.updateNamedImports(namedImports, newElements));
|
|
21571
|
+
}
|
|
21465
21572
|
}
|
|
21466
|
-
return results;
|
|
21467
21573
|
}
|
|
21468
21574
|
/**
|
|
21469
|
-
* Removes
|
|
21470
|
-
*
|
|
21471
|
-
* @param
|
|
21472
|
-
* @param ngModule Module whote declarations are being filtered.
|
|
21473
|
-
* @param templateTypeChecker
|
|
21474
|
-
* @param typeChecker
|
|
21575
|
+
* Removes all tracked export references.
|
|
21576
|
+
* @param locations Locations from which to remove the references.
|
|
21577
|
+
* @param tracker Tracker in which to register the changes.
|
|
21475
21578
|
*/
|
|
21476
|
-
function
|
|
21477
|
-
const
|
|
21478
|
-
|
|
21479
|
-
|
|
21480
|
-
|
|
21481
|
-
|
|
21482
|
-
|
|
21483
|
-
|
|
21484
|
-
|
|
21485
|
-
// declarations aren't bootstrapped so we assume that all of them are.
|
|
21486
|
-
if (!ts__default["default"].isPropertyAssignment(bootstrapProp) ||
|
|
21487
|
-
!ts__default["default"].isArrayLiteralExpression(bootstrapProp.initializer)) {
|
|
21488
|
-
return [];
|
|
21489
|
-
}
|
|
21490
|
-
const bootstrappedClasses = new Set();
|
|
21491
|
-
for (const el of bootstrapProp.initializer.elements) {
|
|
21492
|
-
const referencedClass = ts__default["default"].isIdentifier(el) ? findClassDeclaration(el, typeChecker) : null;
|
|
21493
|
-
// If we can resolve an element to a class, we can filter it out,
|
|
21494
|
-
// otherwise assume that the array isn't static.
|
|
21495
|
-
if (referencedClass) {
|
|
21496
|
-
bootstrappedClasses.add(referencedClass);
|
|
21579
|
+
function removeExportReferences(locations, tracker) {
|
|
21580
|
+
for (const [namedExports, toRemove] of locations.getEntries()) {
|
|
21581
|
+
const newElements = filterRemovedElements(namedExports.elements, toRemove);
|
|
21582
|
+
// If no exports are left, we can drop the entire declaration.
|
|
21583
|
+
if (newElements.length === 0) {
|
|
21584
|
+
const declaration = nodes.closestNode(namedExports, ts__default["default"].isExportDeclaration);
|
|
21585
|
+
if (declaration) {
|
|
21586
|
+
tracker.removeNode(declaration);
|
|
21587
|
+
}
|
|
21497
21588
|
}
|
|
21498
21589
|
else {
|
|
21499
|
-
|
|
21590
|
+
// Otherwise we just drop the exported symbols and keep the declaration intact.
|
|
21591
|
+
tracker.replaceNode(namedExports, ts__default["default"].factory.updateNamedExports(namedExports, newElements));
|
|
21500
21592
|
}
|
|
21501
21593
|
}
|
|
21502
|
-
return declarations.filter((ref) => !bootstrappedClasses.has(ref));
|
|
21503
|
-
}
|
|
21504
|
-
/**
|
|
21505
|
-
* Extracts all classes that are referenced in a module's `declarations` array.
|
|
21506
|
-
* @param ngModule Module whose declarations are being extraced.
|
|
21507
|
-
* @param templateTypeChecker
|
|
21508
|
-
*/
|
|
21509
|
-
function extractDeclarationsFromModule(ngModule, templateTypeChecker) {
|
|
21510
|
-
const metadata = templateTypeChecker.getNgModuleMetadata(ngModule);
|
|
21511
|
-
return metadata
|
|
21512
|
-
? metadata.declarations
|
|
21513
|
-
.filter((decl) => ts__default["default"].isClassDeclaration(decl.node))
|
|
21514
|
-
.map((decl) => decl.node)
|
|
21515
|
-
: [];
|
|
21516
21594
|
}
|
|
21517
21595
|
/**
|
|
21518
|
-
*
|
|
21519
|
-
*
|
|
21520
|
-
*
|
|
21521
|
-
*
|
|
21522
|
-
*
|
|
21596
|
+
* Determines whether an `@NgModule` class is safe to remove. A module is safe to remove if:
|
|
21597
|
+
* 1. It has no `declarations`.
|
|
21598
|
+
* 2. It has no `providers`.
|
|
21599
|
+
* 3. It has no `bootstrap` components.
|
|
21600
|
+
* 4. It has no `ModuleWithProviders` in its `imports`.
|
|
21601
|
+
* 5. It has no class members. Empty construstors are ignored.
|
|
21602
|
+
* @param node Class that is being checked.
|
|
21523
21603
|
* @param typeChecker
|
|
21524
21604
|
*/
|
|
21525
|
-
function
|
|
21526
|
-
const
|
|
21527
|
-
|
|
21528
|
-
|
|
21529
|
-
|
|
21530
|
-
|
|
21531
|
-
|
|
21532
|
-
|
|
21533
|
-
|
|
21534
|
-
|
|
21535
|
-
|
|
21536
|
-
|
|
21537
|
-
|
|
21538
|
-
|
|
21539
|
-
|
|
21540
|
-
|
|
21541
|
-
|
|
21542
|
-
|
|
21543
|
-
|
|
21544
|
-
|
|
21545
|
-
|
|
21546
|
-
|
|
21605
|
+
function canRemoveClass(node, typeChecker) {
|
|
21606
|
+
const decorator = findNgModuleDecorator(node, typeChecker)?.node;
|
|
21607
|
+
// We can't remove a declaration if it's not a valid `NgModule`.
|
|
21608
|
+
if (!decorator || !ts__default["default"].isCallExpression(decorator.expression)) {
|
|
21609
|
+
return false;
|
|
21610
|
+
}
|
|
21611
|
+
// Unsupported case, e.g. `@NgModule(SOME_VALUE)`.
|
|
21612
|
+
if (decorator.expression.arguments.length > 0 &&
|
|
21613
|
+
!ts__default["default"].isObjectLiteralExpression(decorator.expression.arguments[0])) {
|
|
21614
|
+
return false;
|
|
21615
|
+
}
|
|
21616
|
+
// We can't remove modules that have class members. We make an exception for an
|
|
21617
|
+
// empty constructor which may have been generated by a tool and forgotten.
|
|
21618
|
+
if (node.members.length > 0 && node.members.some((member) => !isEmptyConstructor(member))) {
|
|
21619
|
+
return false;
|
|
21620
|
+
}
|
|
21621
|
+
// An empty `NgModule` call can be removed.
|
|
21622
|
+
if (decorator.expression.arguments.length === 0) {
|
|
21623
|
+
return true;
|
|
21624
|
+
}
|
|
21625
|
+
const literal = decorator.expression.arguments[0];
|
|
21626
|
+
const imports = findLiteralProperty(literal, 'imports');
|
|
21627
|
+
if (imports && isNonEmptyNgModuleProperty(imports)) {
|
|
21628
|
+
// We can't remove the class if at least one import isn't identifier, because it may be a
|
|
21629
|
+
// `ModuleWithProviders` which is the equivalent of having something in the `providers` array.
|
|
21630
|
+
for (const dep of imports.initializer.elements) {
|
|
21631
|
+
if (!ts__default["default"].isIdentifier(dep)) {
|
|
21632
|
+
return false;
|
|
21547
21633
|
}
|
|
21548
|
-
|
|
21549
|
-
|
|
21634
|
+
const depDeclaration = findClassDeclaration(dep, typeChecker);
|
|
21635
|
+
const depNgModule = depDeclaration
|
|
21636
|
+
? findNgModuleDecorator(depDeclaration, typeChecker)
|
|
21637
|
+
: null;
|
|
21638
|
+
// If any of the dependencies of the class is an `NgModule` that can't be removed, the class
|
|
21639
|
+
// itself can't be removed either, because it may be part of a transitive dependency chain.
|
|
21640
|
+
if (depDeclaration !== null &&
|
|
21641
|
+
depNgModule !== null &&
|
|
21642
|
+
!canRemoveClass(depDeclaration, typeChecker)) {
|
|
21643
|
+
return false;
|
|
21550
21644
|
}
|
|
21551
21645
|
}
|
|
21552
21646
|
}
|
|
21553
|
-
|
|
21554
|
-
|
|
21647
|
+
// We can't remove classes that have any `declarations`, `providers` or `bootstrap` elements.
|
|
21648
|
+
// Also err on the side of caution and don't remove modules where any of the aforementioned
|
|
21649
|
+
// properties aren't initialized to an array literal.
|
|
21650
|
+
for (const prop of literal.properties) {
|
|
21651
|
+
if (isNonEmptyNgModuleProperty(prop) &&
|
|
21652
|
+
(prop.name.text === 'declarations' ||
|
|
21653
|
+
prop.name.text === 'providers' ||
|
|
21654
|
+
prop.name.text === 'bootstrap')) {
|
|
21655
|
+
return false;
|
|
21656
|
+
}
|
|
21555
21657
|
}
|
|
21658
|
+
return true;
|
|
21556
21659
|
}
|
|
21557
21660
|
/**
|
|
21558
|
-
*
|
|
21559
|
-
*
|
|
21560
|
-
*
|
|
21561
|
-
* @param
|
|
21661
|
+
* Checks whether a node is a non-empty property from an NgModule's metadata. This is defined as a
|
|
21662
|
+
* property assignment with a static name, initialized to an array literal with more than one
|
|
21663
|
+
* element.
|
|
21664
|
+
* @param node Node to be checked.
|
|
21562
21665
|
*/
|
|
21563
|
-
function
|
|
21564
|
-
|
|
21565
|
-
|
|
21566
|
-
|
|
21567
|
-
|
|
21568
|
-
|
|
21569
|
-
|
|
21666
|
+
function isNonEmptyNgModuleProperty(node) {
|
|
21667
|
+
return (ts__default["default"].isPropertyAssignment(node) &&
|
|
21668
|
+
ts__default["default"].isIdentifier(node.name) &&
|
|
21669
|
+
ts__default["default"].isArrayLiteralExpression(node.initializer) &&
|
|
21670
|
+
node.initializer.elements.length > 0);
|
|
21671
|
+
}
|
|
21672
|
+
/**
|
|
21673
|
+
* Determines if a file is safe to delete. A file is safe to delete if all it contains are
|
|
21674
|
+
* import statements, class declarations that are about to be deleted and non-exported code.
|
|
21675
|
+
* @param sourceFile File that is being checked.
|
|
21676
|
+
* @param nodesToBeRemoved Nodes that are being removed as a part of the migration.
|
|
21677
|
+
*/
|
|
21678
|
+
function canRemoveFile(sourceFile, nodesToBeRemoved) {
|
|
21679
|
+
for (const node of sourceFile.statements) {
|
|
21680
|
+
if (ts__default["default"].isImportDeclaration(node) || nodesToBeRemoved.has(node)) {
|
|
21570
21681
|
continue;
|
|
21571
21682
|
}
|
|
21572
|
-
|
|
21573
|
-
|
|
21574
|
-
|
|
21575
|
-
|
|
21576
|
-
? importsProp.initializer.elements.filter((el) => {
|
|
21577
|
-
// Filter out calls since they may be a `ModuleWithProviders`.
|
|
21578
|
-
return (!ts__default["default"].isCallExpression(el) &&
|
|
21579
|
-
// Also filter out the animations modules since they throw errors if they're imported
|
|
21580
|
-
// multiple times and it's common for apps to use the `NoopAnimationsModule` to
|
|
21581
|
-
// disable animations in screenshot tests.
|
|
21582
|
-
!isClassReferenceInAngularModule(el, /^BrowserAnimationsModule|NoopAnimationsModule$/, 'platform-browser/animations', typeChecker));
|
|
21583
|
-
})
|
|
21584
|
-
: null;
|
|
21585
|
-
for (const decl of declarations) {
|
|
21586
|
-
if (seenDeclarations.has(decl)) {
|
|
21587
|
-
continue;
|
|
21588
|
-
}
|
|
21589
|
-
const [decorator] = nodes.getAngularDecorators(typeChecker, ts__default["default"].getDecorators(decl) || []);
|
|
21590
|
-
if (decorator) {
|
|
21591
|
-
seenDeclarations.add(decl);
|
|
21592
|
-
decorators.push(decorator);
|
|
21593
|
-
if (decorator.name === 'Component' && importElements) {
|
|
21594
|
-
// We try to de-duplicate the imports being added to a component, because it may be
|
|
21595
|
-
// declared in different testing modules with a different set of imports.
|
|
21596
|
-
let imports = componentImports.get(decorator.node);
|
|
21597
|
-
if (!imports) {
|
|
21598
|
-
imports = new Set();
|
|
21599
|
-
componentImports.set(decorator.node, imports);
|
|
21600
|
-
}
|
|
21601
|
-
importElements.forEach((imp) => imports.add(imp));
|
|
21602
|
-
}
|
|
21603
|
-
}
|
|
21683
|
+
if (ts__default["default"].isExportDeclaration(node) ||
|
|
21684
|
+
(ts__default["default"].canHaveModifiers(node) &&
|
|
21685
|
+
ts__default["default"].getModifiers(node)?.some((m) => m.kind === ts__default["default"].SyntaxKind.ExportKeyword))) {
|
|
21686
|
+
return false;
|
|
21604
21687
|
}
|
|
21605
21688
|
}
|
|
21606
|
-
return
|
|
21689
|
+
return true;
|
|
21607
21690
|
}
|
|
21608
21691
|
/**
|
|
21609
|
-
*
|
|
21610
|
-
*
|
|
21611
|
-
* @param
|
|
21612
|
-
* @param typeChecker
|
|
21692
|
+
* Gets whether an AST node contains another AST node.
|
|
21693
|
+
* @param parent Parent node that may contain the child.
|
|
21694
|
+
* @param child Child node that is being checked.
|
|
21613
21695
|
*/
|
|
21614
|
-
function
|
|
21615
|
-
|
|
21616
|
-
|
|
21617
|
-
|
|
21618
|
-
|
|
21619
|
-
|
|
21620
|
-
|
|
21621
|
-
|
|
21622
|
-
|
|
21623
|
-
|
|
21624
|
-
|
|
21625
|
-
|
|
21626
|
-
|
|
21696
|
+
function contains(parent, child) {
|
|
21697
|
+
return (parent === child ||
|
|
21698
|
+
(parent.getSourceFile().fileName === child.getSourceFile().fileName &&
|
|
21699
|
+
child.getStart() >= parent.getStart() &&
|
|
21700
|
+
child.getStart() <= parent.getEnd()));
|
|
21701
|
+
}
|
|
21702
|
+
/**
|
|
21703
|
+
* Removes AST nodes from a node array.
|
|
21704
|
+
* @param elements Array from which to remove the nodes.
|
|
21705
|
+
* @param toRemove Nodes that should be removed.
|
|
21706
|
+
*/
|
|
21707
|
+
function filterRemovedElements(elements, toRemove) {
|
|
21708
|
+
return elements.filter((el) => {
|
|
21709
|
+
for (const node of toRemove) {
|
|
21710
|
+
// Check that the element contains the node, despite knowing with relative certainty that it
|
|
21711
|
+
// does, because this allows us to unwrap some nodes. E.g. if we have `[((toRemove))]`, we
|
|
21712
|
+
// want to remove the entire parenthesized expression, rather than just `toRemove`.
|
|
21713
|
+
if (contains(el, node)) {
|
|
21714
|
+
return false;
|
|
21627
21715
|
}
|
|
21628
21716
|
}
|
|
21629
|
-
|
|
21630
|
-
|
|
21717
|
+
return true;
|
|
21718
|
+
});
|
|
21631
21719
|
}
|
|
21632
|
-
/**
|
|
21633
|
-
function
|
|
21634
|
-
|
|
21635
|
-
|
|
21636
|
-
|
|
21637
|
-
ts__default["default"].isObjectLiteralExpression(decorator.expression.arguments[0])
|
|
21638
|
-
? decorator.expression.arguments[0]
|
|
21639
|
-
: null;
|
|
21720
|
+
/** Returns whether a node as an empty constructor. */
|
|
21721
|
+
function isEmptyConstructor(node) {
|
|
21722
|
+
return (ts__default["default"].isConstructorDeclaration(node) &&
|
|
21723
|
+
node.parameters.length === 0 &&
|
|
21724
|
+
(node.body == null || node.body.statements.length === 0));
|
|
21640
21725
|
}
|
|
21641
21726
|
/**
|
|
21642
|
-
*
|
|
21643
|
-
* @param
|
|
21644
|
-
* @param
|
|
21645
|
-
* @param templateTypeChecker
|
|
21727
|
+
* Adds TODO comments to nodes that couldn't be removed manually.
|
|
21728
|
+
* @param nodes Nodes to which to add the TODO.
|
|
21729
|
+
* @param tracker Tracker in which to register the changes.
|
|
21646
21730
|
*/
|
|
21647
|
-
function
|
|
21648
|
-
|
|
21649
|
-
|
|
21731
|
+
function addRemovalTodos(nodes, tracker) {
|
|
21732
|
+
for (const node of nodes) {
|
|
21733
|
+
// Note: the comment is inserted using string manipulation, instead of going through the AST,
|
|
21734
|
+
// because this way we preserve more of the app's original formatting.
|
|
21735
|
+
// Note: in theory this can duplicate comments if the module pruning runs multiple times on
|
|
21736
|
+
// the same node. In practice it is unlikely, because the second time the node won't be picked
|
|
21737
|
+
// up by the language service as a reference, because the class won't exist anymore.
|
|
21738
|
+
tracker.insertText(node.getSourceFile(), node.getFullStart(), ` /* TODO(standalone-migration): clean up removed NgModule reference manually. */ `);
|
|
21650
21739
|
}
|
|
21651
|
-
|
|
21652
|
-
|
|
21740
|
+
}
|
|
21741
|
+
/** Finds the `NgModule` decorator in a class, if it exists. */
|
|
21742
|
+
function findNgModuleDecorator(node, typeChecker) {
|
|
21743
|
+
const decorators = nodes.getAngularDecorators(typeChecker, ts__default["default"].getDecorators(node) || []);
|
|
21744
|
+
return decorators.find((decorator) => decorator.name === 'NgModule') || null;
|
|
21745
|
+
}
|
|
21746
|
+
/**
|
|
21747
|
+
* Checks whether a node is used inside of an `imports` array.
|
|
21748
|
+
* @param closestAssignment The closest property assignment to the node.
|
|
21749
|
+
* @param closestArray The closest array to the node.
|
|
21750
|
+
*/
|
|
21751
|
+
function isInImportsArray(closestAssignment, closestArray) {
|
|
21752
|
+
return (closestAssignment.initializer === closestArray &&
|
|
21753
|
+
(ts__default["default"].isIdentifier(closestAssignment.name) || ts__default["default"].isStringLiteralLike(closestAssignment.name)) &&
|
|
21754
|
+
closestAssignment.name.text === 'imports');
|
|
21653
21755
|
}
|
|
21654
21756
|
|
|
21655
21757
|
/*!
|
|
@@ -22297,7 +22399,7 @@ function standaloneMigration(tree, tsconfigPath, basePath, pathToMigrate, schema
|
|
|
22297
22399
|
let pendingChanges;
|
|
22298
22400
|
let filesToRemove = null;
|
|
22299
22401
|
if (schematicOptions.mode === MigrationMode.pruneModules) {
|
|
22300
|
-
const result = pruneNgModules(program, host, basePath, rootNames, sourceFiles, printer, undefined, referenceLookupExcludedFiles);
|
|
22402
|
+
const result = pruneNgModules(program, host, basePath, rootNames, sourceFiles, printer, undefined, referenceLookupExcludedFiles, knownInternalAliasRemapper);
|
|
22301
22403
|
pendingChanges = result.pendingChanges;
|
|
22302
22404
|
filesToRemove = result.filesToRemove;
|
|
22303
22405
|
}
|