@angular/core 19.0.0-next.9 → 19.0.0-rc.1
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 +21183 -19410
- package/fesm2022/core.mjs.map +1 -1
- package/fesm2022/primitives/event-dispatch.mjs +71 -47
- package/fesm2022/primitives/event-dispatch.mjs.map +1 -1
- package/fesm2022/primitives/signals.mjs +8 -6
- package/fesm2022/primitives/signals.mjs.map +1 -1
- package/fesm2022/rxjs-interop.mjs +90 -10
- package/fesm2022/rxjs-interop.mjs.map +1 -1
- package/fesm2022/testing.mjs +174 -113
- package/fesm2022/testing.mjs.map +1 -1
- package/index.d.ts +524 -92
- package/package.json +1 -1
- package/primitives/event-dispatch/index.d.ts +7 -4
- package/primitives/signals/index.d.ts +3 -1
- package/rxjs-interop/index.d.ts +35 -4
- package/schematics/bundles/{checker-3b2ea20f.js → checker-9ca42e51.js} +2303 -1006
- package/schematics/bundles/combine_units-a16385aa.js +1634 -0
- package/schematics/bundles/{compiler_host-b4ba5a28.js → compiler_host-31afa4ed.js} +8 -5
- package/schematics/bundles/control-flow-migration.js +3 -3
- package/schematics/bundles/explicit-standalone-flag.js +29 -9
- package/schematics/bundles/imports-4ac08251.js +1 -1
- package/schematics/bundles/inject-migration.js +222 -54
- package/schematics/bundles/leading_space-d190b83b.js +1 -1
- package/schematics/bundles/migrate_ts_type_references-b2a28742.js +1463 -0
- package/schematics/bundles/nodes-0e7d45ca.js +1 -1
- package/schematics/bundles/output-migration.js +575 -0
- package/schematics/bundles/pending-tasks.js +3 -3
- package/schematics/bundles/{program-6534a30a.js → program-71beec0b.js} +1385 -460
- package/schematics/bundles/project_tsconfig_paths-e9ccccbf.js +1 -1
- package/schematics/bundles/provide-initializer.js +179 -0
- package/schematics/bundles/route-lazy-loading.js +9 -4
- package/schematics/bundles/signal-input-migration.js +183 -311
- package/schematics/bundles/signal-queries-migration.js +404 -146
- package/schematics/bundles/signals.js +54 -0
- package/schematics/bundles/standalone-migration.js +29 -12
- package/schematics/collection.json +11 -0
- package/schematics/migrations.json +7 -1
- package/schematics/ng-generate/output-migration/schema.json +19 -0
- package/schematics/ng-generate/signal-queries-migration/schema.json +11 -0
- package/schematics/ng-generate/signals/schema.json +65 -0
- package/testing/index.d.ts +1 -1
- package/schematics/bundles/group_replacements-e1b5cbf8.js +0 -31571
|
@@ -0,0 +1,575 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
/**
|
|
3
|
+
* @license Angular v19.0.0-rc.1
|
|
4
|
+
* (c) 2010-2024 Google LLC. https://angular.io/
|
|
5
|
+
* License: MIT
|
|
6
|
+
*/
|
|
7
|
+
'use strict';
|
|
8
|
+
|
|
9
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
10
|
+
|
|
11
|
+
var schematics = require('@angular-devkit/schematics');
|
|
12
|
+
var project_tsconfig_paths = require('./project_tsconfig_paths-e9ccccbf.js');
|
|
13
|
+
var combine_units = require('./combine_units-a16385aa.js');
|
|
14
|
+
require('os');
|
|
15
|
+
var ts = require('typescript');
|
|
16
|
+
var checker = require('./checker-9ca42e51.js');
|
|
17
|
+
var assert = require('assert');
|
|
18
|
+
var program = require('./program-71beec0b.js');
|
|
19
|
+
require('path');
|
|
20
|
+
require('@angular-devkit/core');
|
|
21
|
+
require('node:path/posix');
|
|
22
|
+
require('fs');
|
|
23
|
+
require('module');
|
|
24
|
+
require('url');
|
|
25
|
+
|
|
26
|
+
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
|
|
27
|
+
|
|
28
|
+
var ts__default = /*#__PURE__*/_interopDefaultLegacy(ts);
|
|
29
|
+
var assert__default = /*#__PURE__*/_interopDefaultLegacy(assert);
|
|
30
|
+
|
|
31
|
+
function isOutputDeclarationEligibleForMigration(node) {
|
|
32
|
+
return (node.initializer !== undefined &&
|
|
33
|
+
ts__default["default"].isNewExpression(node.initializer) &&
|
|
34
|
+
ts__default["default"].isIdentifier(node.initializer.expression) &&
|
|
35
|
+
node.initializer.expression.text === 'EventEmitter');
|
|
36
|
+
}
|
|
37
|
+
function isPotentialOutputCallUsage(node, name) {
|
|
38
|
+
if (ts__default["default"].isCallExpression(node) &&
|
|
39
|
+
ts__default["default"].isPropertyAccessExpression(node.expression) &&
|
|
40
|
+
ts__default["default"].isIdentifier(node.expression.name)) {
|
|
41
|
+
return node.expression?.name.text === name;
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
function isPotentialPipeCallUsage(node) {
|
|
48
|
+
return isPotentialOutputCallUsage(node, 'pipe');
|
|
49
|
+
}
|
|
50
|
+
function isPotentialNextCallUsage(node) {
|
|
51
|
+
return isPotentialOutputCallUsage(node, 'next');
|
|
52
|
+
}
|
|
53
|
+
function isPotentialCompleteCallUsage(node) {
|
|
54
|
+
return isPotentialOutputCallUsage(node, 'complete');
|
|
55
|
+
}
|
|
56
|
+
function isTargetOutputDeclaration(node, checker, reflector, dtsReader) {
|
|
57
|
+
const targetSymbol = checker.getSymbolAtLocation(node);
|
|
58
|
+
if (targetSymbol !== undefined) {
|
|
59
|
+
const propertyDeclaration = getTargetPropertyDeclaration(targetSymbol);
|
|
60
|
+
if (propertyDeclaration !== null &&
|
|
61
|
+
isOutputDeclaration(propertyDeclaration, reflector, dtsReader)) {
|
|
62
|
+
return propertyDeclaration;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
/** Gets whether the given property is an Angular `@Output`. */
|
|
68
|
+
function isOutputDeclaration(node, reflector, dtsReader) {
|
|
69
|
+
// `.d.ts` file, so we check the `static ecmp` metadata on the `declare class`.
|
|
70
|
+
if (node.getSourceFile().isDeclarationFile) {
|
|
71
|
+
if (!ts__default["default"].isIdentifier(node.name) ||
|
|
72
|
+
!ts__default["default"].isClassDeclaration(node.parent) ||
|
|
73
|
+
node.parent.name === undefined) {
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
const ref = new checker.Reference(node.parent);
|
|
77
|
+
const directiveMeta = dtsReader.getDirectiveMetadata(ref);
|
|
78
|
+
return !!directiveMeta?.outputs.getByClassPropertyName(node.name.text);
|
|
79
|
+
}
|
|
80
|
+
// `.ts` file, so we check for the `@Output()` decorator.
|
|
81
|
+
return getOutputDecorator(node, reflector) !== null;
|
|
82
|
+
}
|
|
83
|
+
function getTargetPropertyDeclaration(targetSymbol) {
|
|
84
|
+
const valDeclaration = targetSymbol.valueDeclaration;
|
|
85
|
+
if (valDeclaration !== undefined && ts__default["default"].isPropertyDeclaration(valDeclaration)) {
|
|
86
|
+
return valDeclaration;
|
|
87
|
+
}
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
/** Returns Angular `@Output` decorator or null when a given property declaration is not an @Output */
|
|
91
|
+
function getOutputDecorator(node, reflector) {
|
|
92
|
+
const decorators = reflector.getDecoratorsOfDeclaration(node);
|
|
93
|
+
const ngDecorators = decorators !== null ? checker.getAngularDecorators(decorators, ['Output'], /* isCore */ false) : [];
|
|
94
|
+
return ngDecorators.length > 0 ? ngDecorators[0] : null;
|
|
95
|
+
}
|
|
96
|
+
// THINK: this utility + type is not specific to @Output, really, maybe move it to tsurge?
|
|
97
|
+
/** Computes an unique ID for a given Angular `@Output` property. */
|
|
98
|
+
function getUniqueIdForProperty(info, prop) {
|
|
99
|
+
const { id } = combine_units.projectFile(prop.getSourceFile(), info);
|
|
100
|
+
id.replace(/\.d\.ts$/, '.ts');
|
|
101
|
+
return `${id}@@${prop.parent.name ?? 'unknown-class'}@@${prop.name.getText()}`;
|
|
102
|
+
}
|
|
103
|
+
function isTestRunnerImport(node) {
|
|
104
|
+
if (ts__default["default"].isImportDeclaration(node)) {
|
|
105
|
+
const moduleSpecifier = node.moduleSpecifier.getText();
|
|
106
|
+
return moduleSpecifier.includes('jasmine') || moduleSpecifier.includes('catalyst');
|
|
107
|
+
}
|
|
108
|
+
return false;
|
|
109
|
+
}
|
|
110
|
+
// TODO: code duplication with signals migration - sort it out
|
|
111
|
+
/**
|
|
112
|
+
* Gets whether the given read is used to access
|
|
113
|
+
* the specified field.
|
|
114
|
+
*
|
|
115
|
+
* E.g. whether `<my-read>.toArray` is detected.
|
|
116
|
+
*/
|
|
117
|
+
function checkNonTsReferenceAccessesField(ref, fieldName) {
|
|
118
|
+
const readFromPath = ref.from.readAstPath.at(-1);
|
|
119
|
+
const parentRead = ref.from.readAstPath.at(-2);
|
|
120
|
+
if (ref.from.read !== readFromPath) {
|
|
121
|
+
return null;
|
|
122
|
+
}
|
|
123
|
+
if (!(parentRead instanceof checker.PropertyRead) || parentRead.name !== fieldName) {
|
|
124
|
+
return null;
|
|
125
|
+
}
|
|
126
|
+
return parentRead;
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Gets whether the given reference is accessed to call the
|
|
130
|
+
* specified function on it.
|
|
131
|
+
*
|
|
132
|
+
* E.g. whether `<my-read>.toArray()` is detected.
|
|
133
|
+
*/
|
|
134
|
+
function checkNonTsReferenceCallsField(ref, fieldName) {
|
|
135
|
+
const propertyAccess = checkNonTsReferenceAccessesField(ref, fieldName);
|
|
136
|
+
if (propertyAccess === null) {
|
|
137
|
+
return null;
|
|
138
|
+
}
|
|
139
|
+
const accessIdx = ref.from.readAstPath.indexOf(propertyAccess);
|
|
140
|
+
if (accessIdx === -1) {
|
|
141
|
+
return null;
|
|
142
|
+
}
|
|
143
|
+
const potentialRead = ref.from.readAstPath[accessIdx];
|
|
144
|
+
if (potentialRead === undefined) {
|
|
145
|
+
return null;
|
|
146
|
+
}
|
|
147
|
+
return potentialRead;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const printer = ts__default["default"].createPrinter();
|
|
151
|
+
function calculateDeclarationReplacement(info, node, aliasParam) {
|
|
152
|
+
const sf = node.getSourceFile();
|
|
153
|
+
const payloadTypes = node.initializer !== undefined && ts__default["default"].isNewExpression(node.initializer)
|
|
154
|
+
? node.initializer?.typeArguments
|
|
155
|
+
: undefined;
|
|
156
|
+
const outputCall = ts__default["default"].factory.createCallExpression(ts__default["default"].factory.createIdentifier('output'), payloadTypes, aliasParam !== undefined
|
|
157
|
+
? [
|
|
158
|
+
ts__default["default"].factory.createObjectLiteralExpression([
|
|
159
|
+
ts__default["default"].factory.createPropertyAssignment('alias', ts__default["default"].factory.createStringLiteral(aliasParam, true)),
|
|
160
|
+
], false),
|
|
161
|
+
]
|
|
162
|
+
: []);
|
|
163
|
+
const existingModifiers = (node.modifiers ?? []).filter((modifier) => !ts__default["default"].isDecorator(modifier) && modifier.kind !== ts__default["default"].SyntaxKind.ReadonlyKeyword);
|
|
164
|
+
const updatedOutputDeclaration = ts__default["default"].factory.createPropertyDeclaration(
|
|
165
|
+
// Think: this logic of dealing with modifiers is applicable to all signal-based migrations
|
|
166
|
+
ts__default["default"].factory.createNodeArray([
|
|
167
|
+
...existingModifiers,
|
|
168
|
+
ts__default["default"].factory.createModifier(ts__default["default"].SyntaxKind.ReadonlyKeyword),
|
|
169
|
+
]), node.name, undefined, undefined, outputCall);
|
|
170
|
+
return prepareTextReplacementForNode(info, node, printer.printNode(ts__default["default"].EmitHint.Unspecified, updatedOutputDeclaration, sf));
|
|
171
|
+
}
|
|
172
|
+
function calculateImportReplacements(info, sourceFiles) {
|
|
173
|
+
const importReplacements = {};
|
|
174
|
+
for (const sf of sourceFiles) {
|
|
175
|
+
const importManager = new checker.ImportManager();
|
|
176
|
+
const addOnly = [];
|
|
177
|
+
const addRemove = [];
|
|
178
|
+
const file = combine_units.projectFile(sf, info);
|
|
179
|
+
importManager.addImport({
|
|
180
|
+
requestedFile: sf,
|
|
181
|
+
exportModuleSpecifier: '@angular/core',
|
|
182
|
+
exportSymbolName: 'output',
|
|
183
|
+
});
|
|
184
|
+
combine_units.applyImportManagerChanges(importManager, addOnly, [sf], info);
|
|
185
|
+
importManager.removeImport(sf, 'Output', '@angular/core');
|
|
186
|
+
importManager.removeImport(sf, 'EventEmitter', '@angular/core');
|
|
187
|
+
combine_units.applyImportManagerChanges(importManager, addRemove, [sf], info);
|
|
188
|
+
importReplacements[file.id] = {
|
|
189
|
+
add: addOnly,
|
|
190
|
+
addAndRemove: addRemove,
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
return importReplacements;
|
|
194
|
+
}
|
|
195
|
+
function calculateNextFnReplacement(info, node) {
|
|
196
|
+
return prepareTextReplacementForNode(info, node, 'emit');
|
|
197
|
+
}
|
|
198
|
+
function calculateNextFnReplacementInTemplate(file, span) {
|
|
199
|
+
return prepareTextReplacement(file, 'emit', span.start, span.end);
|
|
200
|
+
}
|
|
201
|
+
function calculateNextFnReplacementInHostBinding(file, offset, span) {
|
|
202
|
+
return prepareTextReplacement(file, 'emit', offset + span.start, offset + span.end);
|
|
203
|
+
}
|
|
204
|
+
function calculateCompleteCallReplacement(info, node) {
|
|
205
|
+
return prepareTextReplacementForNode(info, node, '', node.getFullStart());
|
|
206
|
+
}
|
|
207
|
+
function calculatePipeCallReplacement(info, node) {
|
|
208
|
+
if (ts__default["default"].isPropertyAccessExpression(node.expression)) {
|
|
209
|
+
const sf = node.getSourceFile();
|
|
210
|
+
const importManager = new checker.ImportManager();
|
|
211
|
+
const outputToObservableIdent = importManager.addImport({
|
|
212
|
+
requestedFile: sf,
|
|
213
|
+
exportModuleSpecifier: '@angular/core/rxjs-interop',
|
|
214
|
+
exportSymbolName: 'outputToObservable',
|
|
215
|
+
});
|
|
216
|
+
const toObsCallExp = ts__default["default"].factory.createCallExpression(outputToObservableIdent, undefined, [
|
|
217
|
+
node.expression.expression,
|
|
218
|
+
]);
|
|
219
|
+
const pipePropAccessExp = ts__default["default"].factory.updatePropertyAccessExpression(node.expression, toObsCallExp, node.expression.name);
|
|
220
|
+
const pipeCallExp = ts__default["default"].factory.updateCallExpression(node, pipePropAccessExp, [], node.arguments);
|
|
221
|
+
const replacements = [
|
|
222
|
+
prepareTextReplacementForNode(info, node, printer.printNode(ts__default["default"].EmitHint.Unspecified, pipeCallExp, sf)),
|
|
223
|
+
];
|
|
224
|
+
combine_units.applyImportManagerChanges(importManager, replacements, [sf], info);
|
|
225
|
+
return replacements;
|
|
226
|
+
}
|
|
227
|
+
else {
|
|
228
|
+
// TODO: assert instead?
|
|
229
|
+
throw new Error(`Unexpected call expression for .pipe - expected a property access but got "${node.getText()}"`);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
function prepareTextReplacementForNode(info, node, replacement, start) {
|
|
233
|
+
const sf = node.getSourceFile();
|
|
234
|
+
return new combine_units.Replacement(combine_units.projectFile(sf, info), new combine_units.TextUpdate({
|
|
235
|
+
position: start ?? node.getStart(),
|
|
236
|
+
end: node.getEnd(),
|
|
237
|
+
toInsert: replacement,
|
|
238
|
+
}));
|
|
239
|
+
}
|
|
240
|
+
function prepareTextReplacement(file, replacement, start, end) {
|
|
241
|
+
return new combine_units.Replacement(file, new combine_units.TextUpdate({
|
|
242
|
+
position: start,
|
|
243
|
+
end: end,
|
|
244
|
+
toInsert: replacement,
|
|
245
|
+
}));
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
class OutputMigration extends combine_units.TsurgeFunnelMigration {
|
|
249
|
+
config;
|
|
250
|
+
constructor(config = {}) {
|
|
251
|
+
super();
|
|
252
|
+
this.config = config;
|
|
253
|
+
}
|
|
254
|
+
async analyze(info) {
|
|
255
|
+
const { sourceFiles, program: program$1 } = info;
|
|
256
|
+
const outputFieldReplacements = {};
|
|
257
|
+
const problematicUsages = {};
|
|
258
|
+
let problematicDeclarationCount = 0;
|
|
259
|
+
const filesWithOutputDeclarations = new Set();
|
|
260
|
+
const checker$1 = program$1.getTypeChecker();
|
|
261
|
+
const reflector = new checker.TypeScriptReflectionHost(checker$1);
|
|
262
|
+
const dtsReader = new program.DtsMetadataReader(checker$1, reflector);
|
|
263
|
+
const evaluator = new program.PartialEvaluator(reflector, checker$1, null);
|
|
264
|
+
const ngCompiler = info.ngCompiler;
|
|
265
|
+
assert__default["default"](ngCompiler !== null, 'Requires ngCompiler to run the migration');
|
|
266
|
+
const resourceLoader = ngCompiler['resourceManager'];
|
|
267
|
+
// Pre-Analyze the program and get access to the template type checker.
|
|
268
|
+
const { templateTypeChecker } = ngCompiler['ensureAnalyzed']();
|
|
269
|
+
const knownFields = {
|
|
270
|
+
// Note: We don't support cross-target migration of `Partial<T>` usages.
|
|
271
|
+
// This is an acceptable limitation for performance reasons.
|
|
272
|
+
shouldTrackClassReference: (node) => false,
|
|
273
|
+
attemptRetrieveDescriptorFromSymbol: (s) => {
|
|
274
|
+
const propDeclaration = getTargetPropertyDeclaration(s);
|
|
275
|
+
if (propDeclaration !== null) {
|
|
276
|
+
const classFieldID = getUniqueIdForProperty(info, propDeclaration);
|
|
277
|
+
if (classFieldID !== null) {
|
|
278
|
+
return {
|
|
279
|
+
node: propDeclaration,
|
|
280
|
+
key: classFieldID,
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
return null;
|
|
285
|
+
},
|
|
286
|
+
};
|
|
287
|
+
let isTestFile = false;
|
|
288
|
+
const outputMigrationVisitor = (node) => {
|
|
289
|
+
// detect output declarations
|
|
290
|
+
if (ts__default["default"].isPropertyDeclaration(node)) {
|
|
291
|
+
const outputDecorator = getOutputDecorator(node, reflector);
|
|
292
|
+
if (outputDecorator !== null) {
|
|
293
|
+
if (isOutputDeclarationEligibleForMigration(node)) {
|
|
294
|
+
const outputDef = {
|
|
295
|
+
id: getUniqueIdForProperty(info, node),
|
|
296
|
+
aliasParam: outputDecorator.args?.at(0),
|
|
297
|
+
};
|
|
298
|
+
const outputFile = combine_units.projectFile(node.getSourceFile(), info);
|
|
299
|
+
if (this.config.shouldMigrate === undefined ||
|
|
300
|
+
this.config.shouldMigrate({
|
|
301
|
+
key: outputDef.id,
|
|
302
|
+
node: node,
|
|
303
|
+
}, outputFile)) {
|
|
304
|
+
const aliasParam = outputDef.aliasParam;
|
|
305
|
+
const aliasOptionValue = aliasParam ? evaluator.evaluate(aliasParam) : undefined;
|
|
306
|
+
if (aliasOptionValue == undefined || typeof aliasOptionValue === 'string') {
|
|
307
|
+
filesWithOutputDeclarations.add(node.getSourceFile());
|
|
308
|
+
addOutputReplacement(outputFieldReplacements, outputDef.id, outputFile, calculateDeclarationReplacement(info, node, aliasOptionValue?.toString()));
|
|
309
|
+
}
|
|
310
|
+
else {
|
|
311
|
+
problematicUsages[outputDef.id] = true;
|
|
312
|
+
problematicDeclarationCount++;
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
else {
|
|
317
|
+
problematicDeclarationCount++;
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
// detect .next usages that should be migrated to .emit
|
|
322
|
+
if (isPotentialNextCallUsage(node) && ts__default["default"].isPropertyAccessExpression(node.expression)) {
|
|
323
|
+
const propertyDeclaration = isTargetOutputDeclaration(node.expression.expression, checker$1, reflector, dtsReader);
|
|
324
|
+
if (propertyDeclaration !== null) {
|
|
325
|
+
const id = getUniqueIdForProperty(info, propertyDeclaration);
|
|
326
|
+
const outputFile = combine_units.projectFile(node.getSourceFile(), info);
|
|
327
|
+
addOutputReplacement(outputFieldReplacements, id, outputFile, calculateNextFnReplacement(info, node.expression.name));
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
// detect .complete usages that should be removed
|
|
331
|
+
if (isPotentialCompleteCallUsage(node) && ts__default["default"].isPropertyAccessExpression(node.expression)) {
|
|
332
|
+
const propertyDeclaration = isTargetOutputDeclaration(node.expression.expression, checker$1, reflector, dtsReader);
|
|
333
|
+
if (propertyDeclaration !== null) {
|
|
334
|
+
const id = getUniqueIdForProperty(info, propertyDeclaration);
|
|
335
|
+
const outputFile = combine_units.projectFile(node.getSourceFile(), info);
|
|
336
|
+
if (ts__default["default"].isExpressionStatement(node.parent)) {
|
|
337
|
+
addOutputReplacement(outputFieldReplacements, id, outputFile, calculateCompleteCallReplacement(info, node.parent));
|
|
338
|
+
}
|
|
339
|
+
else {
|
|
340
|
+
problematicUsages[id] = true;
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
// detect imports of test runners
|
|
345
|
+
if (isTestRunnerImport(node)) {
|
|
346
|
+
isTestFile = true;
|
|
347
|
+
}
|
|
348
|
+
// detect unsafe access of the output property
|
|
349
|
+
if (isPotentialPipeCallUsage(node) && ts__default["default"].isPropertyAccessExpression(node.expression)) {
|
|
350
|
+
const propertyDeclaration = isTargetOutputDeclaration(node.expression.expression, checker$1, reflector, dtsReader);
|
|
351
|
+
if (propertyDeclaration !== null) {
|
|
352
|
+
const id = getUniqueIdForProperty(info, propertyDeclaration);
|
|
353
|
+
if (isTestFile) {
|
|
354
|
+
const outputFile = combine_units.projectFile(node.getSourceFile(), info);
|
|
355
|
+
addOutputReplacement(outputFieldReplacements, id, outputFile, ...calculatePipeCallReplacement(info, node));
|
|
356
|
+
}
|
|
357
|
+
else {
|
|
358
|
+
problematicUsages[id] = true;
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
ts__default["default"].forEachChild(node, outputMigrationVisitor);
|
|
363
|
+
};
|
|
364
|
+
// calculate output migration replacements
|
|
365
|
+
for (const sf of sourceFiles) {
|
|
366
|
+
isTestFile = false;
|
|
367
|
+
ts__default["default"].forEachChild(sf, outputMigrationVisitor);
|
|
368
|
+
}
|
|
369
|
+
// take care of the references in templates and host bindings
|
|
370
|
+
const referenceResult = { references: [] };
|
|
371
|
+
const { visitor: templateHostRefVisitor } = combine_units.createFindAllSourceFileReferencesVisitor(info, checker$1, reflector, resourceLoader, evaluator, templateTypeChecker, knownFields, null, // TODO: capture known output names as an optimization
|
|
372
|
+
referenceResult);
|
|
373
|
+
// calculate template / host binding replacements
|
|
374
|
+
for (const sf of sourceFiles) {
|
|
375
|
+
ts__default["default"].forEachChild(sf, templateHostRefVisitor);
|
|
376
|
+
}
|
|
377
|
+
for (const ref of referenceResult.references) {
|
|
378
|
+
// detect .next usages that should be migrated to .emit in template and host binding expressions
|
|
379
|
+
if (ref.kind === combine_units.ReferenceKind.InTemplate) {
|
|
380
|
+
const callExpr = checkNonTsReferenceCallsField(ref, 'next');
|
|
381
|
+
if (callExpr !== null) {
|
|
382
|
+
addOutputReplacement(outputFieldReplacements, ref.target.key, ref.from.templateFile, calculateNextFnReplacementInTemplate(ref.from.templateFile, callExpr.nameSpan));
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
else if (ref.kind === combine_units.ReferenceKind.InHostBinding) {
|
|
386
|
+
const callExpr = checkNonTsReferenceCallsField(ref, 'next');
|
|
387
|
+
if (callExpr !== null) {
|
|
388
|
+
addOutputReplacement(outputFieldReplacements, ref.target.key, ref.from.file, calculateNextFnReplacementInHostBinding(ref.from.file, ref.from.hostPropertyNode.getStart() + 1, callExpr.nameSpan));
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
// calculate import replacements but do so only for files that have output declarations
|
|
393
|
+
const importReplacements = calculateImportReplacements(info, filesWithOutputDeclarations);
|
|
394
|
+
return combine_units.confirmAsSerializable({
|
|
395
|
+
problematicDeclarationCount,
|
|
396
|
+
outputFields: outputFieldReplacements,
|
|
397
|
+
importReplacements,
|
|
398
|
+
problematicUsages,
|
|
399
|
+
});
|
|
400
|
+
}
|
|
401
|
+
async combine(unitA, unitB) {
|
|
402
|
+
const outputFields = {};
|
|
403
|
+
const importReplacements = {};
|
|
404
|
+
const problematicUsages = {};
|
|
405
|
+
let problematicDeclarationCount = 0;
|
|
406
|
+
for (const unit of [unitA, unitB]) {
|
|
407
|
+
for (const declIdStr of Object.keys(unit.outputFields)) {
|
|
408
|
+
const declId = declIdStr;
|
|
409
|
+
// THINK: detect clash? Should we have an utility to merge data based on unique IDs?
|
|
410
|
+
outputFields[declId] = unit.outputFields[declId];
|
|
411
|
+
}
|
|
412
|
+
for (const fileIDStr of Object.keys(unit.importReplacements)) {
|
|
413
|
+
const fileID = fileIDStr;
|
|
414
|
+
importReplacements[fileID] = unit.importReplacements[fileID];
|
|
415
|
+
}
|
|
416
|
+
problematicDeclarationCount += unit.problematicDeclarationCount;
|
|
417
|
+
}
|
|
418
|
+
for (const unit of [unitA, unitB]) {
|
|
419
|
+
for (const declIdStr of Object.keys(unit.problematicUsages)) {
|
|
420
|
+
const declId = declIdStr;
|
|
421
|
+
problematicUsages[declId] = unit.problematicUsages[declId];
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
return combine_units.confirmAsSerializable({
|
|
425
|
+
problematicDeclarationCount,
|
|
426
|
+
outputFields,
|
|
427
|
+
importReplacements,
|
|
428
|
+
problematicUsages,
|
|
429
|
+
});
|
|
430
|
+
}
|
|
431
|
+
async globalMeta(combinedData) {
|
|
432
|
+
const globalMeta = {
|
|
433
|
+
importReplacements: combinedData.importReplacements,
|
|
434
|
+
outputFields: combinedData.outputFields,
|
|
435
|
+
problematicDeclarationCount: combinedData.problematicDeclarationCount,
|
|
436
|
+
problematicUsages: {},
|
|
437
|
+
};
|
|
438
|
+
for (const keyStr of Object.keys(combinedData.problematicUsages)) {
|
|
439
|
+
const key = keyStr;
|
|
440
|
+
// it might happen that a problematic usage is detected but we didn't see the declaration - skipping those
|
|
441
|
+
if (globalMeta.outputFields[key] !== undefined) {
|
|
442
|
+
globalMeta.problematicUsages[key] = true;
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
// Noop here as we don't have any form of special global metadata.
|
|
446
|
+
return combine_units.confirmAsSerializable(combinedData);
|
|
447
|
+
}
|
|
448
|
+
async stats(globalMetadata) {
|
|
449
|
+
const detectedOutputs = new Set(Object.keys(globalMetadata.outputFields)).size +
|
|
450
|
+
globalMetadata.problematicDeclarationCount;
|
|
451
|
+
const problematicOutputs = new Set(Object.keys(globalMetadata.problematicUsages)).size +
|
|
452
|
+
globalMetadata.problematicDeclarationCount;
|
|
453
|
+
const successRate = detectedOutputs > 0 ? (detectedOutputs - problematicOutputs) / detectedOutputs : 1;
|
|
454
|
+
return {
|
|
455
|
+
counters: {
|
|
456
|
+
detectedOutputs,
|
|
457
|
+
problematicOutputs,
|
|
458
|
+
successRate,
|
|
459
|
+
},
|
|
460
|
+
};
|
|
461
|
+
}
|
|
462
|
+
async migrate(globalData) {
|
|
463
|
+
const migratedFiles = new Set();
|
|
464
|
+
const problematicFiles = new Set();
|
|
465
|
+
const replacements = [];
|
|
466
|
+
for (const declIdStr of Object.keys(globalData.outputFields)) {
|
|
467
|
+
const declId = declIdStr;
|
|
468
|
+
const outputField = globalData.outputFields[declId];
|
|
469
|
+
if (!globalData.problematicUsages[declId]) {
|
|
470
|
+
replacements.push(...outputField.replacements);
|
|
471
|
+
migratedFiles.add(outputField.file.id);
|
|
472
|
+
}
|
|
473
|
+
else {
|
|
474
|
+
problematicFiles.add(outputField.file.id);
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
for (const fileIDStr of Object.keys(globalData.importReplacements)) {
|
|
478
|
+
const fileID = fileIDStr;
|
|
479
|
+
if (migratedFiles.has(fileID)) {
|
|
480
|
+
const importReplacements = globalData.importReplacements[fileID];
|
|
481
|
+
if (problematicFiles.has(fileID)) {
|
|
482
|
+
replacements.push(...importReplacements.add);
|
|
483
|
+
}
|
|
484
|
+
else {
|
|
485
|
+
replacements.push(...importReplacements.addAndRemove);
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
return { replacements };
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
function addOutputReplacement(outputFieldReplacements, outputId, file, ...replacements) {
|
|
493
|
+
let existingReplacements = outputFieldReplacements[outputId];
|
|
494
|
+
if (existingReplacements === undefined) {
|
|
495
|
+
outputFieldReplacements[outputId] = existingReplacements = {
|
|
496
|
+
file: file,
|
|
497
|
+
replacements: [],
|
|
498
|
+
};
|
|
499
|
+
}
|
|
500
|
+
existingReplacements.replacements.push(...replacements);
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
function migrate(options) {
|
|
504
|
+
return async (tree, context) => {
|
|
505
|
+
const { buildPaths, testPaths } = await project_tsconfig_paths.getProjectTsConfigPaths(tree);
|
|
506
|
+
if (!buildPaths.length && !testPaths.length) {
|
|
507
|
+
throw new schematics.SchematicsException('Could not find any tsconfig file. Cannot run output migration.');
|
|
508
|
+
}
|
|
509
|
+
const fs = new combine_units.DevkitMigrationFilesystem(tree);
|
|
510
|
+
checker.setFileSystem(fs);
|
|
511
|
+
const migration = new OutputMigration({
|
|
512
|
+
shouldMigrate: (_, file) => {
|
|
513
|
+
return (file.rootRelativePath.startsWith(fs.normalize(options.path)) &&
|
|
514
|
+
!/(^|\/)node_modules\//.test(file.rootRelativePath));
|
|
515
|
+
},
|
|
516
|
+
});
|
|
517
|
+
const analysisPath = fs.resolve(options.analysisDir);
|
|
518
|
+
const unitResults = [];
|
|
519
|
+
const programInfos = [...buildPaths, ...testPaths].map((tsconfigPath) => {
|
|
520
|
+
context.logger.info(`Preparing analysis for: ${tsconfigPath}..`);
|
|
521
|
+
const baseInfo = migration.createProgram(tsconfigPath, fs);
|
|
522
|
+
const info = migration.prepareProgram(baseInfo);
|
|
523
|
+
// Support restricting the analysis to subfolders for larger projects.
|
|
524
|
+
if (analysisPath !== '/') {
|
|
525
|
+
info.sourceFiles = info.sourceFiles.filter((sf) => sf.fileName.startsWith(analysisPath));
|
|
526
|
+
info.fullProgramSourceFiles = info.fullProgramSourceFiles.filter((sf) => sf.fileName.startsWith(analysisPath));
|
|
527
|
+
}
|
|
528
|
+
return { info, tsconfigPath };
|
|
529
|
+
});
|
|
530
|
+
// Analyze phase. Treat all projects as compilation units as
|
|
531
|
+
// this allows us to support references between those.
|
|
532
|
+
for (const { info, tsconfigPath } of programInfos) {
|
|
533
|
+
context.logger.info(`Scanning for outputs: ${tsconfigPath}..`);
|
|
534
|
+
unitResults.push(await migration.analyze(info));
|
|
535
|
+
}
|
|
536
|
+
context.logger.info(``);
|
|
537
|
+
context.logger.info(`Processing analysis data between targets..`);
|
|
538
|
+
context.logger.info(``);
|
|
539
|
+
const combined = await combine_units.synchronouslyCombineUnitData(migration, unitResults);
|
|
540
|
+
if (combined === null) {
|
|
541
|
+
context.logger.error('Migration failed unexpectedly with no analysis data');
|
|
542
|
+
return;
|
|
543
|
+
}
|
|
544
|
+
const globalMeta = await migration.globalMeta(combined);
|
|
545
|
+
const replacementsPerFile = new Map();
|
|
546
|
+
for (const { info, tsconfigPath } of programInfos) {
|
|
547
|
+
context.logger.info(`Migrating: ${tsconfigPath}..`);
|
|
548
|
+
const { replacements } = await migration.migrate(globalMeta);
|
|
549
|
+
const changesPerFile = combine_units.groupReplacementsByFile(replacements);
|
|
550
|
+
for (const [file, changes] of changesPerFile) {
|
|
551
|
+
if (!replacementsPerFile.has(file)) {
|
|
552
|
+
replacementsPerFile.set(file, changes);
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
context.logger.info(`Applying changes..`);
|
|
557
|
+
for (const [file, changes] of replacementsPerFile) {
|
|
558
|
+
const recorder = tree.beginUpdate(file);
|
|
559
|
+
for (const c of changes) {
|
|
560
|
+
recorder
|
|
561
|
+
.remove(c.data.position, c.data.end - c.data.position)
|
|
562
|
+
.insertLeft(c.data.position, c.data.toInsert);
|
|
563
|
+
}
|
|
564
|
+
tree.commitUpdate(recorder);
|
|
565
|
+
}
|
|
566
|
+
const { counters: { detectedOutputs, problematicOutputs, successRate }, } = await migration.stats(globalMeta);
|
|
567
|
+
const migratedOutputs = detectedOutputs - problematicOutputs;
|
|
568
|
+
const successRatePercent = (successRate * 100).toFixed(2);
|
|
569
|
+
context.logger.info('');
|
|
570
|
+
context.logger.info(`Successfully migrated to outputs as functions 🎉`);
|
|
571
|
+
context.logger.info(` -> Migrated ${migratedOutputs} out of ${detectedOutputs} detected outputs (${successRatePercent} %).`);
|
|
572
|
+
};
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
exports.migrate = migrate;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
/**
|
|
3
|
-
* @license Angular v19.0.0-
|
|
3
|
+
* @license Angular v19.0.0-rc.1
|
|
4
4
|
* (c) 2010-2024 Google LLC. https://angular.io/
|
|
5
5
|
* License: MIT
|
|
6
6
|
*/
|
|
@@ -11,11 +11,11 @@ Object.defineProperty(exports, '__esModule', { value: true });
|
|
|
11
11
|
var schematics = require('@angular-devkit/schematics');
|
|
12
12
|
var p = require('path');
|
|
13
13
|
var project_tsconfig_paths = require('./project_tsconfig_paths-e9ccccbf.js');
|
|
14
|
-
var compiler_host = require('./compiler_host-
|
|
14
|
+
var compiler_host = require('./compiler_host-31afa4ed.js');
|
|
15
15
|
var ts = require('typescript');
|
|
16
16
|
var imports = require('./imports-4ac08251.js');
|
|
17
17
|
require('@angular-devkit/core');
|
|
18
|
-
require('./checker-
|
|
18
|
+
require('./checker-9ca42e51.js');
|
|
19
19
|
require('os');
|
|
20
20
|
require('fs');
|
|
21
21
|
require('module');
|