@angular/core 19.2.0-next.2 → 19.2.0-rc.0
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 +1094 -1064
- package/fesm2022/core.mjs.map +1 -1
- package/fesm2022/primitives/event-dispatch.mjs +1 -1
- package/fesm2022/primitives/signals.mjs +5 -5
- package/fesm2022/primitives/signals.mjs.map +1 -1
- package/fesm2022/rxjs-interop.mjs +1 -1
- package/fesm2022/testing.mjs +5 -5
- package/index.d.ts +92 -44
- package/package.json +1 -1
- package/primitives/event-dispatch/index.d.ts +1 -1
- package/primitives/signals/index.d.ts +2 -2
- package/rxjs-interop/index.d.ts +1 -1
- package/schematics/bundles/apply_import_manager-a930fcf1.js +71 -0
- package/schematics/bundles/{checker-9af84be9.js → checker-2eecc677.js} +101 -63
- package/schematics/bundles/cleanup-unused-imports.js +17 -16
- package/schematics/bundles/{compiler_host-dbff2781.js → compiler_host-c280a924.js} +2 -2
- package/schematics/bundles/control-flow-migration.js +10 -3
- package/schematics/bundles/explicit-standalone-flag.js +5 -5
- package/schematics/bundles/{imports-31a38653.js → imports-abe29092.js} +1 -1
- package/schematics/bundles/{index-23b503a4.js → index-24a2ad1e.js} +9 -9
- package/schematics/bundles/{index-93e324de.js → index-3891dd55.js} +4 -4
- package/schematics/bundles/inject-migration.js +10 -9
- package/schematics/bundles/{leading_space-6e7a8ec6.js → leading_space-d190b83b.js} +1 -1
- package/schematics/bundles/{migrate_ts_type_references-c6615b87.js → migrate_ts_type_references-71b3a951.js} +21 -21
- package/schematics/bundles/{nodes-88c2157f.js → ng_decorators-e699c081.js} +2 -15
- package/schematics/bundles/nodes-a535b2be.js +27 -0
- package/schematics/bundles/output-migration.js +22 -21
- package/schematics/bundles/pending-tasks.js +5 -5
- package/schematics/bundles/{program-66386e72.js → program-24da9092.js} +113 -54
- package/schematics/bundles/{apply_import_manager-d8ea426b.js → project_paths-b073c4d6.js} +3 -57
- package/schematics/bundles/{project_tsconfig_paths-6c9cde78.js → project_tsconfig_paths-e9ccccbf.js} +1 -1
- package/schematics/bundles/property_name-7c8433f5.js +31 -0
- package/schematics/bundles/provide-initializer.js +5 -5
- package/schematics/bundles/route-lazy-loading.js +9 -13
- package/schematics/bundles/self-closing-tags-migration.js +448 -0
- package/schematics/bundles/signal-input-migration.js +22 -21
- package/schematics/bundles/signal-queries-migration.js +28 -27
- package/schematics/bundles/signals.js +9 -8
- package/schematics/bundles/standalone-migration.js +14 -13
- package/schematics/collection.json +6 -0
- package/schematics/ng-generate/self-closing-tags-migration/schema.json +14 -0
- package/testing/index.d.ts +1 -1
|
@@ -0,0 +1,448 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
/**
|
|
3
|
+
* @license Angular v19.2.0-rc.0
|
|
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 project_paths = require('./project_paths-b073c4d6.js');
|
|
14
|
+
require('os');
|
|
15
|
+
var ts = require('typescript');
|
|
16
|
+
var checker = require('./checker-2eecc677.js');
|
|
17
|
+
require('./program-24da9092.js');
|
|
18
|
+
require('path');
|
|
19
|
+
var ng_decorators = require('./ng_decorators-e699c081.js');
|
|
20
|
+
var property_name = require('./property_name-7c8433f5.js');
|
|
21
|
+
require('@angular-devkit/core');
|
|
22
|
+
require('node:path/posix');
|
|
23
|
+
require('fs');
|
|
24
|
+
require('module');
|
|
25
|
+
require('url');
|
|
26
|
+
require('./imports-abe29092.js');
|
|
27
|
+
|
|
28
|
+
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
|
|
29
|
+
|
|
30
|
+
var ts__default = /*#__PURE__*/_interopDefaultLegacy(ts);
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Unwraps a given expression TypeScript node. Expressions can be wrapped within multiple
|
|
34
|
+
* parentheses or as expression. e.g. "(((({exp}))))()". The function should return the
|
|
35
|
+
* TypeScript node referring to the inner expression. e.g "exp".
|
|
36
|
+
*/
|
|
37
|
+
function unwrapExpression(node) {
|
|
38
|
+
if (ts__default["default"].isParenthesizedExpression(node) || ts__default["default"].isAsExpression(node)) {
|
|
39
|
+
return unwrapExpression(node.expression);
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
return node;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Extracts `@Directive` or `@Component` metadata from the given class. */
|
|
47
|
+
function extractAngularClassMetadata(typeChecker, node) {
|
|
48
|
+
const decorators = ts__default["default"].getDecorators(node);
|
|
49
|
+
if (!decorators || !decorators.length) {
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
const ngDecorators = ng_decorators.getAngularDecorators(typeChecker, decorators);
|
|
53
|
+
const componentDecorator = ngDecorators.find((dec) => dec.name === 'Component');
|
|
54
|
+
const directiveDecorator = ngDecorators.find((dec) => dec.name === 'Directive');
|
|
55
|
+
const decorator = componentDecorator ?? directiveDecorator;
|
|
56
|
+
// In case no decorator could be found on the current class, skip.
|
|
57
|
+
if (!decorator) {
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
const decoratorCall = decorator.node.expression;
|
|
61
|
+
// In case the decorator call is not valid, skip this class declaration.
|
|
62
|
+
if (decoratorCall.arguments.length !== 1) {
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
const metadata = unwrapExpression(decoratorCall.arguments[0]);
|
|
66
|
+
// Ensure that the metadata is an object literal expression.
|
|
67
|
+
if (!ts__default["default"].isObjectLiteralExpression(metadata)) {
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
return {
|
|
71
|
+
type: componentDecorator ? 'component' : 'directive',
|
|
72
|
+
node: metadata,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const LF_CHAR = 10;
|
|
77
|
+
const CR_CHAR = 13;
|
|
78
|
+
const LINE_SEP_CHAR = 8232;
|
|
79
|
+
const PARAGRAPH_CHAR = 8233;
|
|
80
|
+
/** Gets the line and character for the given position from the line starts map. */
|
|
81
|
+
function getLineAndCharacterFromPosition(lineStartsMap, position) {
|
|
82
|
+
const lineIndex = findClosestLineStartPosition(lineStartsMap, position);
|
|
83
|
+
return { character: position - lineStartsMap[lineIndex], line: lineIndex };
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Computes the line start map of the given text. This can be used in order to
|
|
87
|
+
* retrieve the line and character of a given text position index.
|
|
88
|
+
*/
|
|
89
|
+
function computeLineStartsMap(text) {
|
|
90
|
+
const result = [0];
|
|
91
|
+
let pos = 0;
|
|
92
|
+
while (pos < text.length) {
|
|
93
|
+
const char = text.charCodeAt(pos++);
|
|
94
|
+
// Handles the "CRLF" line break. In that case we peek the character
|
|
95
|
+
// after the "CR" and check if it is a line feed.
|
|
96
|
+
if (char === CR_CHAR) {
|
|
97
|
+
if (text.charCodeAt(pos) === LF_CHAR) {
|
|
98
|
+
pos++;
|
|
99
|
+
}
|
|
100
|
+
result.push(pos);
|
|
101
|
+
}
|
|
102
|
+
else if (char === LF_CHAR || char === LINE_SEP_CHAR || char === PARAGRAPH_CHAR) {
|
|
103
|
+
result.push(pos);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
result.push(pos);
|
|
107
|
+
return result;
|
|
108
|
+
}
|
|
109
|
+
/** Finds the closest line start for the given position. */
|
|
110
|
+
function findClosestLineStartPosition(linesMap, position, low = 0, high = linesMap.length - 1) {
|
|
111
|
+
while (low <= high) {
|
|
112
|
+
const pivotIdx = Math.floor((low + high) / 2);
|
|
113
|
+
const pivotEl = linesMap[pivotIdx];
|
|
114
|
+
if (pivotEl === position) {
|
|
115
|
+
return pivotIdx;
|
|
116
|
+
}
|
|
117
|
+
else if (position > pivotEl) {
|
|
118
|
+
low = pivotIdx + 1;
|
|
119
|
+
}
|
|
120
|
+
else {
|
|
121
|
+
high = pivotIdx - 1;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
// In case there was no exact match, return the closest "lower" line index. We also
|
|
125
|
+
// subtract the index by one because want the index of the previous line start.
|
|
126
|
+
return low - 1;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Visitor that can be used to determine Angular templates referenced within given
|
|
131
|
+
* TypeScript source files (inline templates or external referenced templates)
|
|
132
|
+
*/
|
|
133
|
+
class NgComponentTemplateVisitor {
|
|
134
|
+
typeChecker;
|
|
135
|
+
resolvedTemplates = [];
|
|
136
|
+
fs = checker.getFileSystem();
|
|
137
|
+
constructor(typeChecker) {
|
|
138
|
+
this.typeChecker = typeChecker;
|
|
139
|
+
}
|
|
140
|
+
visitNode(node) {
|
|
141
|
+
if (node.kind === ts__default["default"].SyntaxKind.ClassDeclaration) {
|
|
142
|
+
this.visitClassDeclaration(node);
|
|
143
|
+
}
|
|
144
|
+
ts__default["default"].forEachChild(node, (n) => this.visitNode(n));
|
|
145
|
+
}
|
|
146
|
+
visitClassDeclaration(node) {
|
|
147
|
+
const metadata = extractAngularClassMetadata(this.typeChecker, node);
|
|
148
|
+
if (metadata === null || metadata.type !== 'component') {
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
const sourceFile = node.getSourceFile();
|
|
152
|
+
const sourceFileName = sourceFile.fileName;
|
|
153
|
+
// Walk through all component metadata properties and determine the referenced
|
|
154
|
+
// HTML templates (either external or inline)
|
|
155
|
+
metadata.node.properties.forEach((property) => {
|
|
156
|
+
if (!ts__default["default"].isPropertyAssignment(property)) {
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
const propertyName = property_name.getPropertyNameText(property.name);
|
|
160
|
+
// In case there is an inline template specified, ensure that the value is statically
|
|
161
|
+
// analyzable by checking if the initializer is a string literal-like node.
|
|
162
|
+
if (propertyName === 'template' && ts__default["default"].isStringLiteralLike(property.initializer)) {
|
|
163
|
+
// Need to add an offset of one to the start because the template quotes are
|
|
164
|
+
// not part of the template content.
|
|
165
|
+
// The `getText()` method gives us the original raw text.
|
|
166
|
+
// We could have used the `text` property, but if the template is defined as a backtick
|
|
167
|
+
// string then the `text` property contains a "cooked" version of the string. Such cooked
|
|
168
|
+
// strings will have converted CRLF characters to only LF. This messes up string
|
|
169
|
+
// replacements in template migrations.
|
|
170
|
+
// The raw text returned by `getText()` includes the enclosing quotes so we change the
|
|
171
|
+
// `content` and `start` values accordingly.
|
|
172
|
+
const content = property.initializer.getText().slice(1, -1);
|
|
173
|
+
const start = property.initializer.getStart() + 1;
|
|
174
|
+
this.resolvedTemplates.push({
|
|
175
|
+
filePath: sourceFileName,
|
|
176
|
+
container: node,
|
|
177
|
+
content,
|
|
178
|
+
inline: true,
|
|
179
|
+
start: start,
|
|
180
|
+
getCharacterAndLineOfPosition: (pos) => ts__default["default"].getLineAndCharacterOfPosition(sourceFile, pos + start),
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
if (propertyName === 'templateUrl' && ts__default["default"].isStringLiteralLike(property.initializer)) {
|
|
184
|
+
const absolutePath = this.fs.resolve(this.fs.dirname(sourceFileName), property.initializer.text);
|
|
185
|
+
if (!this.fs.exists(absolutePath)) {
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
const fileContent = this.fs.readFile(absolutePath);
|
|
189
|
+
const lineStartsMap = computeLineStartsMap(fileContent);
|
|
190
|
+
this.resolvedTemplates.push({
|
|
191
|
+
filePath: absolutePath,
|
|
192
|
+
container: node,
|
|
193
|
+
content: fileContent,
|
|
194
|
+
inline: false,
|
|
195
|
+
start: 0,
|
|
196
|
+
getCharacterAndLineOfPosition: (pos) => getLineAndCharacterFromPosition(lineStartsMap, pos),
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function parseTemplate(template) {
|
|
204
|
+
let parsed;
|
|
205
|
+
try {
|
|
206
|
+
// Note: we use the HtmlParser here, instead of the `parseTemplate` function, because the
|
|
207
|
+
// latter returns an Ivy AST, not an HTML AST. The HTML AST has the advantage of preserving
|
|
208
|
+
// interpolated text as text nodes containing a mixture of interpolation tokens and text tokens,
|
|
209
|
+
// rather than turning them into `BoundText` nodes like the Ivy AST does. This allows us to
|
|
210
|
+
// easily get the text-only ranges without having to reconstruct the original text.
|
|
211
|
+
parsed = new checker.HtmlParser().parse(template, '', {
|
|
212
|
+
// Allows for ICUs to be parsed.
|
|
213
|
+
tokenizeExpansionForms: true,
|
|
214
|
+
// Explicitly disable blocks so that their characters are treated as plain text.
|
|
215
|
+
tokenizeBlocks: true,
|
|
216
|
+
preserveLineEndings: true,
|
|
217
|
+
});
|
|
218
|
+
// Don't migrate invalid templates.
|
|
219
|
+
if (parsed.errors && parsed.errors.length > 0) {
|
|
220
|
+
const errors = parsed.errors.map((e) => ({ type: 'parse', error: e }));
|
|
221
|
+
return { tree: undefined, errors };
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
catch (e) {
|
|
225
|
+
return { tree: undefined, errors: [{ type: 'parse', error: e }] };
|
|
226
|
+
}
|
|
227
|
+
return { tree: parsed, errors: [] };
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
function migrateTemplateToSelfClosingTags(template) {
|
|
231
|
+
let parsed = parseTemplate(template);
|
|
232
|
+
if (parsed.tree === undefined) {
|
|
233
|
+
return { migrated: template, changed: false, replacementCount: 0 };
|
|
234
|
+
}
|
|
235
|
+
const visitor = new AngularElementCollector();
|
|
236
|
+
checker.visitAll(visitor, parsed.tree.rootNodes);
|
|
237
|
+
let newTemplate = template;
|
|
238
|
+
let changedOffset = 0;
|
|
239
|
+
let replacementCount = 0;
|
|
240
|
+
for (let element of visitor.elements) {
|
|
241
|
+
const { start, end, tagName } = element;
|
|
242
|
+
const currentLength = newTemplate.length;
|
|
243
|
+
const templatePart = newTemplate.slice(start + changedOffset, end + changedOffset);
|
|
244
|
+
const convertedTemplate = replaceWithSelfClosingTag(templatePart, tagName);
|
|
245
|
+
// if the template has changed, replace the original template with the new one
|
|
246
|
+
if (convertedTemplate.length !== templatePart.length) {
|
|
247
|
+
newTemplate = replaceTemplate(newTemplate, convertedTemplate, start, end, changedOffset);
|
|
248
|
+
changedOffset += newTemplate.length - currentLength;
|
|
249
|
+
replacementCount++;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
return { migrated: newTemplate, changed: changedOffset !== 0, replacementCount };
|
|
253
|
+
}
|
|
254
|
+
function replaceWithSelfClosingTag(html, tagName) {
|
|
255
|
+
const pattern = new RegExp(`<\\s*${tagName}\\s*([^>]*?(?:"[^"]*"|'[^']*'|[^'">])*)\\s*>([\\s\\S]*?)<\\s*/\\s*${tagName}\\s*>`, 'gi');
|
|
256
|
+
return html.replace(pattern, (_, content) => `<${tagName}${content ? ` ${content}` : ''} />`);
|
|
257
|
+
}
|
|
258
|
+
/**
|
|
259
|
+
* Replace the value in the template with the new value based on the start and end position + offset
|
|
260
|
+
*/
|
|
261
|
+
function replaceTemplate(template, replaceValue, start, end, offset) {
|
|
262
|
+
return template.slice(0, start + offset) + replaceValue + template.slice(end + offset);
|
|
263
|
+
}
|
|
264
|
+
const ALL_HTML_TAGS = new checker.DomElementSchemaRegistry().allKnownElementNames();
|
|
265
|
+
class AngularElementCollector extends checker.RecursiveVisitor {
|
|
266
|
+
elements = [];
|
|
267
|
+
constructor() {
|
|
268
|
+
super();
|
|
269
|
+
}
|
|
270
|
+
visitElement(element) {
|
|
271
|
+
const isHtmlTag = ALL_HTML_TAGS.includes(element.name);
|
|
272
|
+
if (isHtmlTag) {
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
const hasNoContent = this.elementHasNoContent(element);
|
|
276
|
+
const hasNoClosingTag = this.elementHasNoClosingTag(element);
|
|
277
|
+
if (hasNoContent && !hasNoClosingTag) {
|
|
278
|
+
this.elements.push({
|
|
279
|
+
tagName: element.name,
|
|
280
|
+
start: element.sourceSpan.start.offset,
|
|
281
|
+
end: element.sourceSpan.end.offset,
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
return super.visitElement(element, null);
|
|
285
|
+
}
|
|
286
|
+
elementHasNoContent(element) {
|
|
287
|
+
if (!element.children?.length) {
|
|
288
|
+
return true;
|
|
289
|
+
}
|
|
290
|
+
if (element.children.length === 1) {
|
|
291
|
+
const child = element.children[0];
|
|
292
|
+
return child instanceof checker.Text && /^\s*$/.test(child.value);
|
|
293
|
+
}
|
|
294
|
+
return false;
|
|
295
|
+
}
|
|
296
|
+
elementHasNoClosingTag(element) {
|
|
297
|
+
const { startSourceSpan, endSourceSpan } = element;
|
|
298
|
+
if (!endSourceSpan) {
|
|
299
|
+
return true;
|
|
300
|
+
}
|
|
301
|
+
return (startSourceSpan.start.offset === endSourceSpan.start.offset &&
|
|
302
|
+
startSourceSpan.end.offset === endSourceSpan.end.offset);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
class SelfClosingTagsMigration extends project_paths.TsurgeFunnelMigration {
|
|
307
|
+
config;
|
|
308
|
+
constructor(config = {}) {
|
|
309
|
+
super();
|
|
310
|
+
this.config = config;
|
|
311
|
+
}
|
|
312
|
+
async analyze(info) {
|
|
313
|
+
const { sourceFiles, program } = info;
|
|
314
|
+
const typeChecker = program.getTypeChecker();
|
|
315
|
+
const tagReplacements = [];
|
|
316
|
+
for (const sf of sourceFiles) {
|
|
317
|
+
ts__default["default"].forEachChild(sf, (node) => {
|
|
318
|
+
if (!ts__default["default"].isClassDeclaration(node)) {
|
|
319
|
+
return;
|
|
320
|
+
}
|
|
321
|
+
const file = project_paths.projectFile(node.getSourceFile(), info);
|
|
322
|
+
if (this.config.shouldMigrate && this.config.shouldMigrate(file) === false) {
|
|
323
|
+
return;
|
|
324
|
+
}
|
|
325
|
+
const templateVisitor = new NgComponentTemplateVisitor(typeChecker);
|
|
326
|
+
templateVisitor.visitNode(node);
|
|
327
|
+
templateVisitor.resolvedTemplates.forEach((template) => {
|
|
328
|
+
const { migrated, changed, replacementCount } = migrateTemplateToSelfClosingTags(template.content);
|
|
329
|
+
if (changed) {
|
|
330
|
+
const fileToMigrate = template.inline
|
|
331
|
+
? file
|
|
332
|
+
: project_paths.projectFile(template.filePath, info);
|
|
333
|
+
const end = template.start + template.content.length;
|
|
334
|
+
const replacements = [
|
|
335
|
+
prepareTextReplacement(fileToMigrate, migrated, template.start, end),
|
|
336
|
+
];
|
|
337
|
+
const fileReplacements = tagReplacements.find((tagReplacement) => tagReplacement.file === file);
|
|
338
|
+
if (fileReplacements) {
|
|
339
|
+
fileReplacements.replacements.push(...replacements);
|
|
340
|
+
fileReplacements.replacementCount += replacementCount;
|
|
341
|
+
}
|
|
342
|
+
else {
|
|
343
|
+
tagReplacements.push({ file, replacements, replacementCount });
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
});
|
|
347
|
+
});
|
|
348
|
+
}
|
|
349
|
+
return project_paths.confirmAsSerializable({ tagReplacements });
|
|
350
|
+
}
|
|
351
|
+
async combine(unitA, unitB) {
|
|
352
|
+
return project_paths.confirmAsSerializable({
|
|
353
|
+
tagReplacements: unitA.tagReplacements.concat(unitB.tagReplacements),
|
|
354
|
+
});
|
|
355
|
+
}
|
|
356
|
+
async globalMeta(combinedData) {
|
|
357
|
+
const globalMeta = {
|
|
358
|
+
tagReplacements: combinedData.tagReplacements,
|
|
359
|
+
};
|
|
360
|
+
return project_paths.confirmAsSerializable(globalMeta);
|
|
361
|
+
}
|
|
362
|
+
async stats(globalMetadata) {
|
|
363
|
+
const touchedFilesCount = globalMetadata.tagReplacements.length;
|
|
364
|
+
const replacementCount = globalMetadata.tagReplacements.reduce((acc, cur) => acc + cur.replacementCount, 0);
|
|
365
|
+
return {
|
|
366
|
+
counters: {
|
|
367
|
+
touchedFilesCount,
|
|
368
|
+
replacementCount,
|
|
369
|
+
},
|
|
370
|
+
};
|
|
371
|
+
}
|
|
372
|
+
async migrate(globalData) {
|
|
373
|
+
return { replacements: globalData.tagReplacements.flatMap(({ replacements }) => replacements) };
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
function prepareTextReplacement(file, replacement, start, end) {
|
|
377
|
+
return new project_paths.Replacement(file, new project_paths.TextUpdate({
|
|
378
|
+
position: start,
|
|
379
|
+
end: end,
|
|
380
|
+
toInsert: replacement,
|
|
381
|
+
}));
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
function migrate(options) {
|
|
385
|
+
return async (tree, context) => {
|
|
386
|
+
const { buildPaths, testPaths } = await project_tsconfig_paths.getProjectTsConfigPaths(tree);
|
|
387
|
+
if (!buildPaths.length && !testPaths.length) {
|
|
388
|
+
throw new schematics.SchematicsException('Could not find any tsconfig file. Cannot run self-closing tags migration.');
|
|
389
|
+
}
|
|
390
|
+
const fs = new project_paths.DevkitMigrationFilesystem(tree);
|
|
391
|
+
checker.setFileSystem(fs);
|
|
392
|
+
const migration = new SelfClosingTagsMigration({
|
|
393
|
+
shouldMigrate: (file) => {
|
|
394
|
+
return (file.rootRelativePath.startsWith(fs.normalize(options.path)) &&
|
|
395
|
+
!/(^|\/)node_modules\//.test(file.rootRelativePath));
|
|
396
|
+
},
|
|
397
|
+
});
|
|
398
|
+
const unitResults = [];
|
|
399
|
+
const programInfos = [...buildPaths, ...testPaths].map((tsconfigPath) => {
|
|
400
|
+
context.logger.info(`Preparing analysis for: ${tsconfigPath}..`);
|
|
401
|
+
const baseInfo = migration.createProgram(tsconfigPath, fs);
|
|
402
|
+
const info = migration.prepareProgram(baseInfo);
|
|
403
|
+
return { info, tsconfigPath };
|
|
404
|
+
});
|
|
405
|
+
// Analyze phase. Treat all projects as compilation units as
|
|
406
|
+
// this allows us to support references between those.
|
|
407
|
+
for (const { info, tsconfigPath } of programInfos) {
|
|
408
|
+
context.logger.info(`Scanning for component tags: ${tsconfigPath}..`);
|
|
409
|
+
unitResults.push(await migration.analyze(info));
|
|
410
|
+
}
|
|
411
|
+
context.logger.info(``);
|
|
412
|
+
context.logger.info(`Processing analysis data between targets..`);
|
|
413
|
+
context.logger.info(``);
|
|
414
|
+
const combined = await project_paths.synchronouslyCombineUnitData(migration, unitResults);
|
|
415
|
+
if (combined === null) {
|
|
416
|
+
context.logger.error('Migration failed unexpectedly with no analysis data');
|
|
417
|
+
return;
|
|
418
|
+
}
|
|
419
|
+
const globalMeta = await migration.globalMeta(combined);
|
|
420
|
+
const replacementsPerFile = new Map();
|
|
421
|
+
for (const { tsconfigPath } of programInfos) {
|
|
422
|
+
context.logger.info(`Migrating: ${tsconfigPath}..`);
|
|
423
|
+
const { replacements } = await migration.migrate(globalMeta);
|
|
424
|
+
const changesPerFile = project_paths.groupReplacementsByFile(replacements);
|
|
425
|
+
for (const [file, changes] of changesPerFile) {
|
|
426
|
+
if (!replacementsPerFile.has(file)) {
|
|
427
|
+
replacementsPerFile.set(file, changes);
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
context.logger.info(`Applying changes..`);
|
|
432
|
+
for (const [file, changes] of replacementsPerFile) {
|
|
433
|
+
const recorder = tree.beginUpdate(file);
|
|
434
|
+
for (const c of changes) {
|
|
435
|
+
recorder
|
|
436
|
+
.remove(c.data.position, c.data.end - c.data.position)
|
|
437
|
+
.insertLeft(c.data.position, c.data.toInsert);
|
|
438
|
+
}
|
|
439
|
+
tree.commitUpdate(recorder);
|
|
440
|
+
}
|
|
441
|
+
const { counters: { touchedFilesCount, replacementCount }, } = await migration.stats(globalMeta);
|
|
442
|
+
context.logger.info('');
|
|
443
|
+
context.logger.info(`Successfully migrated to self-closing tags 🎉`);
|
|
444
|
+
context.logger.info(` -> Migrated ${replacementCount} components to self-closing tags in ${touchedFilesCount} component files.`);
|
|
445
|
+
};
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
exports.migrate = migrate;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
/**
|
|
3
|
-
* @license Angular v19.2.0-
|
|
3
|
+
* @license Angular v19.2.0-rc.0
|
|
4
4
|
* (c) 2010-2024 Google LLC. https://angular.io/
|
|
5
5
|
* License: MIT
|
|
6
6
|
*/
|
|
@@ -9,17 +9,18 @@
|
|
|
9
9
|
Object.defineProperty(exports, '__esModule', { value: true });
|
|
10
10
|
|
|
11
11
|
var schematics = require('@angular-devkit/schematics');
|
|
12
|
-
var migrate_ts_type_references = require('./migrate_ts_type_references-
|
|
12
|
+
var migrate_ts_type_references = require('./migrate_ts_type_references-71b3a951.js');
|
|
13
13
|
var ts = require('typescript');
|
|
14
14
|
require('os');
|
|
15
|
-
var checker = require('./checker-
|
|
16
|
-
var program = require('./program-
|
|
15
|
+
var checker = require('./checker-2eecc677.js');
|
|
16
|
+
var program = require('./program-24da9092.js');
|
|
17
17
|
require('path');
|
|
18
|
-
var
|
|
19
|
-
var index = require('./index-
|
|
18
|
+
var project_paths = require('./project_paths-b073c4d6.js');
|
|
19
|
+
var index = require('./index-24a2ad1e.js');
|
|
20
20
|
var assert = require('assert');
|
|
21
|
-
var
|
|
22
|
-
require('./
|
|
21
|
+
var apply_import_manager = require('./apply_import_manager-a930fcf1.js');
|
|
22
|
+
var project_tsconfig_paths = require('./project_tsconfig_paths-e9ccccbf.js');
|
|
23
|
+
require('./leading_space-d190b83b.js');
|
|
23
24
|
require('fs');
|
|
24
25
|
require('module');
|
|
25
26
|
require('url');
|
|
@@ -108,7 +109,7 @@ function getInputDescriptor(hostOrInfo, node) {
|
|
|
108
109
|
className = node.parent.name?.text ?? '<anonymous>';
|
|
109
110
|
}
|
|
110
111
|
const info = hostOrInfo instanceof MigrationHost ? hostOrInfo.programInfo : hostOrInfo;
|
|
111
|
-
const file =
|
|
112
|
+
const file = project_paths.projectFile(node.getSourceFile(), info);
|
|
112
113
|
// Inputs may be detected in `.d.ts` files. Ensure that if the file IDs
|
|
113
114
|
// match regardless of extension. E.g. `/google3/blaze-out/bin/my_file.ts` should
|
|
114
115
|
// have the same ID as `/google3/my_file.ts`.
|
|
@@ -187,7 +188,7 @@ class KnownInputs {
|
|
|
187
188
|
}
|
|
188
189
|
const directiveInfo = this._classToDirectiveInfo.get(data.node.parent);
|
|
189
190
|
const inputInfo = {
|
|
190
|
-
file:
|
|
191
|
+
file: project_paths.projectFile(data.node.getSourceFile(), this.programInfo),
|
|
191
192
|
metadata: data.metadata,
|
|
192
193
|
descriptor: data.descriptor,
|
|
193
194
|
container: directiveInfo,
|
|
@@ -1041,7 +1042,7 @@ function convertToSignalInput(node, { resolvedMetadata: metadata, resolvedType,
|
|
|
1041
1042
|
if (leadingTodoText !== null) {
|
|
1042
1043
|
replacements.push(migrate_ts_type_references.insertPrecedingLine(node, info, '// TODO: Notes from signal input migration:'), ...migrate_ts_type_references.cutStringToLineLimit(leadingTodoText, 70).map((line) => migrate_ts_type_references.insertPrecedingLine(node, info, `// ${line}`)));
|
|
1043
1044
|
}
|
|
1044
|
-
replacements.push(new
|
|
1045
|
+
replacements.push(new project_paths.Replacement(project_paths.projectFile(node.getSourceFile(), info), new project_paths.TextUpdate({
|
|
1045
1046
|
position: node.getStart(),
|
|
1046
1047
|
end: node.getEnd(),
|
|
1047
1048
|
toInsert: newPropertyText,
|
|
@@ -1159,7 +1160,7 @@ function pass7__migrateTemplateReferences(host, references) {
|
|
|
1159
1160
|
const appendText = reference.from.isObjectShorthandExpression
|
|
1160
1161
|
? `: ${reference.from.read.name}()`
|
|
1161
1162
|
: `()`;
|
|
1162
|
-
host.replacements.push(new
|
|
1163
|
+
host.replacements.push(new project_paths.Replacement(reference.from.templateFile, new project_paths.TextUpdate({
|
|
1163
1164
|
position: reference.from.read.sourceSpan.end,
|
|
1164
1165
|
end: reference.from.read.sourceSpan.end,
|
|
1165
1166
|
toInsert: appendText,
|
|
@@ -1199,7 +1200,7 @@ function pass8__migrateHostBindings(host, references, info) {
|
|
|
1199
1200
|
const appendText = reference.from.isObjectShorthandExpression
|
|
1200
1201
|
? `: ${reference.from.read.name}()`
|
|
1201
1202
|
: `()`;
|
|
1202
|
-
host.replacements.push(new
|
|
1203
|
+
host.replacements.push(new project_paths.Replacement(project_paths.projectFile(bindingField.getSourceFile(), info), new project_paths.TextUpdate({ position: readEndPos, end: readEndPos, toInsert: appendText })));
|
|
1203
1204
|
}
|
|
1204
1205
|
}
|
|
1205
1206
|
|
|
@@ -1262,7 +1263,7 @@ function filterIncompatibilitiesForBestEffortMode(knownInputs) {
|
|
|
1262
1263
|
* Tsurge migration for migrating Angular `@Input()` declarations to
|
|
1263
1264
|
* signal inputs, with support for batch execution.
|
|
1264
1265
|
*/
|
|
1265
|
-
class SignalInputMigration extends
|
|
1266
|
+
class SignalInputMigration extends project_paths.TsurgeComplexMigration {
|
|
1266
1267
|
config;
|
|
1267
1268
|
upgradedAnalysisPhaseResults = null;
|
|
1268
1269
|
constructor(config = {}) {
|
|
@@ -1271,7 +1272,7 @@ class SignalInputMigration extends apply_import_manager.TsurgeComplexMigration {
|
|
|
1271
1272
|
}
|
|
1272
1273
|
// Override the default program creation, to add extra flags.
|
|
1273
1274
|
createProgram(tsconfigAbsPath, fs) {
|
|
1274
|
-
return
|
|
1275
|
+
return project_paths.createBaseProgramInfo(tsconfigAbsPath, fs, {
|
|
1275
1276
|
_compilePoisonedComponents: true,
|
|
1276
1277
|
// We want to migrate non-exported classes too.
|
|
1277
1278
|
compileNonExportedClasses: true,
|
|
@@ -1343,13 +1344,13 @@ class SignalInputMigration extends apply_import_manager.TsurgeComplexMigration {
|
|
|
1343
1344
|
knownInputs,
|
|
1344
1345
|
};
|
|
1345
1346
|
}
|
|
1346
|
-
return
|
|
1347
|
+
return project_paths.confirmAsSerializable(unitData);
|
|
1347
1348
|
}
|
|
1348
1349
|
async combine(unitA, unitB) {
|
|
1349
|
-
return
|
|
1350
|
+
return project_paths.confirmAsSerializable(combineCompilationUnitData(unitA, unitB));
|
|
1350
1351
|
}
|
|
1351
1352
|
async globalMeta(combinedData) {
|
|
1352
|
-
return
|
|
1353
|
+
return project_paths.confirmAsSerializable(convertToGlobalMeta(combinedData));
|
|
1353
1354
|
}
|
|
1354
1355
|
async migrate(globalMetadata, info, nonBatchData) {
|
|
1355
1356
|
const knownInputs = nonBatchData?.knownInputs ?? new KnownInputs(info, this.config);
|
|
@@ -1443,7 +1444,7 @@ function migrate(options) {
|
|
|
1443
1444
|
if (!buildPaths.length && !testPaths.length) {
|
|
1444
1445
|
throw new schematics.SchematicsException('Could not find any tsconfig file. Cannot run signal input migration.');
|
|
1445
1446
|
}
|
|
1446
|
-
const fs = new
|
|
1447
|
+
const fs = new project_paths.DevkitMigrationFilesystem(tree);
|
|
1447
1448
|
checker.setFileSystem(fs);
|
|
1448
1449
|
const migration = new SignalInputMigration({
|
|
1449
1450
|
bestEffortMode: options.bestEffortMode,
|
|
@@ -1475,7 +1476,7 @@ function migrate(options) {
|
|
|
1475
1476
|
context.logger.info(``);
|
|
1476
1477
|
context.logger.info(`Processing analysis data between targets..`);
|
|
1477
1478
|
context.logger.info(``);
|
|
1478
|
-
const combined = await
|
|
1479
|
+
const combined = await project_paths.synchronouslyCombineUnitData(migration, unitResults);
|
|
1479
1480
|
if (combined === null) {
|
|
1480
1481
|
context.logger.error('Migration failed unexpectedly with no analysis data');
|
|
1481
1482
|
return;
|
|
@@ -1485,7 +1486,7 @@ function migrate(options) {
|
|
|
1485
1486
|
for (const { info, tsconfigPath } of programInfos) {
|
|
1486
1487
|
context.logger.info(`Migrating: ${tsconfigPath}..`);
|
|
1487
1488
|
const { replacements } = await migration.migrate(globalMeta, info);
|
|
1488
|
-
const changesPerFile =
|
|
1489
|
+
const changesPerFile = project_paths.groupReplacementsByFile(replacements);
|
|
1489
1490
|
for (const [file, changes] of changesPerFile) {
|
|
1490
1491
|
if (!replacementsPerFile.has(file)) {
|
|
1491
1492
|
replacementsPerFile.set(file, changes);
|