@angular/core 22.0.0-next.1 → 22.0.0-next.11
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/_attribute-chunk.mjs +1 -1
- package/fesm2022/_attribute-chunk.mjs.map +1 -1
- package/fesm2022/_debug_node-chunk.mjs +2440 -2014
- package/fesm2022/_debug_node-chunk.mjs.map +1 -1
- package/fesm2022/_effect-chunk.mjs +3 -5
- package/fesm2022/_effect-chunk.mjs.map +1 -1
- package/fesm2022/_not_found-chunk.mjs +1 -1
- package/fesm2022/_not_found-chunk.mjs.map +1 -1
- package/fesm2022/_pending_tasks-chunk.mjs +145 -44
- package/fesm2022/_pending_tasks-chunk.mjs.map +1 -1
- package/fesm2022/_resource-chunk.mjs +108 -33
- package/fesm2022/_resource-chunk.mjs.map +1 -1
- package/fesm2022/_untracked-chunk.mjs +10 -7
- package/fesm2022/_untracked-chunk.mjs.map +1 -1
- package/fesm2022/_weak_ref-chunk.mjs +1 -1
- package/fesm2022/_weak_ref-chunk.mjs.map +1 -1
- package/fesm2022/core.mjs +2368 -1892
- package/fesm2022/core.mjs.map +1 -1
- package/fesm2022/primitives-di.mjs +1 -1
- package/fesm2022/primitives-di.mjs.map +1 -1
- package/fesm2022/primitives-event-dispatch.mjs +2 -24
- package/fesm2022/primitives-event-dispatch.mjs.map +1 -1
- package/fesm2022/primitives-signals.mjs +2 -2
- package/fesm2022/primitives-signals.mjs.map +1 -1
- package/fesm2022/rxjs-interop.mjs +6 -3
- package/fesm2022/rxjs-interop.mjs.map +1 -1
- package/fesm2022/testing.mjs +56 -25
- package/fesm2022/testing.mjs.map +1 -1
- package/package.json +7 -3
- package/schematics/bundles/apply_import_manager-CxA_YYgB.cjs +1 -1
- package/schematics/bundles/can-match-snapshot-required.cjs +104 -0
- package/schematics/bundles/change-detection-eager.cjs +1 -1
- package/schematics/bundles/cleanup-unused-imports.cjs +1 -1
- package/schematics/bundles/common-to-standalone-migration.cjs +1 -1
- package/schematics/bundles/compiler_host-CY14HvaP.cjs +1 -1
- package/schematics/bundles/control-flow-migration.cjs +1 -1
- package/schematics/bundles/http-xhr-backend.cjs +6 -11
- package/schematics/bundles/imports-CKV-ITqD.cjs +1 -1
- package/schematics/bundles/incremental-hydration.cjs +94 -0
- package/schematics/bundles/{index-BtLcQH8g.cjs → index-DcezkXLN.cjs} +10 -3
- package/schematics/bundles/inject-migration.cjs +26 -18
- package/schematics/bundles/json-file-Drblb4E1.cjs +1916 -0
- package/schematics/bundles/leading_space-BTPRV0wu.cjs +1 -1
- package/schematics/bundles/{migrate_ts_type_references-MWoZx-Cb.cjs → migrate_ts_type_references-xRTTASnu.cjs} +48 -28
- package/schematics/bundles/model-output.cjs +200 -0
- package/schematics/bundles/ng_component_template-DPAF1aEA.cjs +1 -1
- package/schematics/bundles/ng_decorators-IVztR9rk.cjs +1 -1
- package/schematics/bundles/ngclass-to-class-migration.cjs +14 -6
- package/schematics/bundles/ngstyle-to-style-migration.cjs +14 -6
- package/schematics/bundles/nodes-ZSQ7WZRB.cjs +1 -1
- package/schematics/bundles/output-migration.cjs +2 -2
- package/schematics/bundles/parse_html-C8eKA9px.cjs +1 -1
- package/schematics/bundles/project_paths-D2V-Uh2L.cjs +1 -1
- package/schematics/bundles/project_tsconfig_paths-DkkMibv-.cjs +1 -1
- package/schematics/bundles/property_name-BCpALNpZ.cjs +1 -1
- package/schematics/bundles/route-lazy-loading.cjs +22 -2
- package/schematics/bundles/router-testing-module-migration.cjs +1 -1
- package/schematics/bundles/safe-optional-chaining.cjs +571 -0
- package/schematics/bundles/self-closing-tags-migration.cjs +1 -1
- package/schematics/bundles/signal-input-migration.cjs +3 -3
- package/schematics/bundles/signal-queries-migration.cjs +3 -3
- package/schematics/bundles/signals.cjs +3 -3
- package/schematics/bundles/standalone-migration.cjs +29 -17
- package/schematics/bundles/strict-safe-navigation-narrow.cjs +32 -0
- package/schematics/bundles/strict-templates-default.cjs +63 -0
- package/schematics/migrations.json +30 -0
- package/third_party/@mcp-b/webmcp-types/LICENSE +21 -0
- package/third_party/@mcp-b/webmcp-types/dist/common.d.ts +297 -0
- package/third_party/@mcp-b/webmcp-types/dist/json-schema.d.ts +196 -0
- package/third_party/@mcp-b/webmcp-types/index.d.ts +9 -0
- package/types/_api-chunk.d.ts +44 -17
- package/types/_chrome_dev_tools_performance-chunk.d.ts +4 -3
- package/types/{_discovery-chunk.d.ts → _debug_node-chunk.d.ts} +2157 -2258
- package/types/_effect-chunk.d.ts +1 -1
- package/types/_event_dispatcher-chunk.d.ts +1 -1
- package/types/_formatter-chunk.d.ts +1 -1
- package/types/_weak_ref-chunk.d.ts +1 -1
- package/types/core.d.ts +6833 -6485
- package/types/primitives-di.d.ts +1 -1
- package/types/primitives-event-dispatch.d.ts +1 -1
- package/types/primitives-signals.d.ts +1 -1
- package/types/rxjs-interop.d.ts +5 -4
- package/types/testing.d.ts +6 -6
|
@@ -0,0 +1,571 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
/**
|
|
3
|
+
* @license Angular v22.0.0-next.11
|
|
4
|
+
* (c) 2010-2026 Google LLC. https://angular.dev/
|
|
5
|
+
* License: MIT
|
|
6
|
+
*/
|
|
7
|
+
'use strict';
|
|
8
|
+
|
|
9
|
+
require('@angular-devkit/core');
|
|
10
|
+
require('node:path/posix');
|
|
11
|
+
var project_paths = require('./project_paths-D2V-Uh2L.cjs');
|
|
12
|
+
var compiler = require('@angular/compiler');
|
|
13
|
+
var ts = require('typescript');
|
|
14
|
+
var ng_component_template = require('./ng_component_template-DPAF1aEA.cjs');
|
|
15
|
+
var ng_decorators = require('./ng_decorators-IVztR9rk.cjs');
|
|
16
|
+
require('@angular/compiler-cli');
|
|
17
|
+
require('@angular/compiler-cli/private/migrations');
|
|
18
|
+
require('node:path');
|
|
19
|
+
var property_name = require('./property_name-BCpALNpZ.cjs');
|
|
20
|
+
require('@angular-devkit/schematics');
|
|
21
|
+
require('./project_tsconfig_paths-DkkMibv-.cjs');
|
|
22
|
+
require('./imports-CKV-ITqD.cjs');
|
|
23
|
+
|
|
24
|
+
import('@angular/compiler');
|
|
25
|
+
/**
|
|
26
|
+
* This migration wraps optional chaining expressions in Angular templates with a call to the
|
|
27
|
+
* `$safeNavigationMigration()` magic function. This function doesn't exist at runtime, but is
|
|
28
|
+
* used as a marker for the Angular compiler to transform the expression to keep the legacy
|
|
29
|
+
* behavior of returning `null`.
|
|
30
|
+
*
|
|
31
|
+
* The migration uses a top-down "sink" approach: each expression is visited with a boolean
|
|
32
|
+
* `nullSensitive` context that indicates whether the consumer of the expression's value
|
|
33
|
+
* distinguishes between `null` and `undefined`. Safe navigation operators (`?.`) wrap themselves
|
|
34
|
+
* when their sink is null-sensitive.
|
|
35
|
+
*/
|
|
36
|
+
class SafeOptionalChainingMigration extends project_paths.TsurgeFunnelMigration {
|
|
37
|
+
config;
|
|
38
|
+
constructor(config = {}) {
|
|
39
|
+
super();
|
|
40
|
+
this.config = config;
|
|
41
|
+
}
|
|
42
|
+
async analyze(info) {
|
|
43
|
+
const replacements = [];
|
|
44
|
+
// Template Iteration
|
|
45
|
+
const templateVisitor = new ng_component_template.NgComponentTemplateVisitor(info.program.getTypeChecker());
|
|
46
|
+
for (const sourceFile of info.sourceFiles) {
|
|
47
|
+
templateVisitor.visitNode(sourceFile);
|
|
48
|
+
}
|
|
49
|
+
for (const template of templateVisitor.resolvedTemplates) {
|
|
50
|
+
const nodes = compiler.parseTemplate(template.content, template.filePath.toString(), {
|
|
51
|
+
preserveWhitespaces: true,
|
|
52
|
+
preserveLineEndings: true,
|
|
53
|
+
preserveSignificantWhitespace: true,
|
|
54
|
+
leadingTriviaChars: [],
|
|
55
|
+
}).nodes;
|
|
56
|
+
const file = template.inline
|
|
57
|
+
? project_paths.projectFile(template.container.getSourceFile(), info)
|
|
58
|
+
: project_paths.projectFile(template.filePath, info);
|
|
59
|
+
const exprMigrator = new ExpressionMigrator(file, template.start);
|
|
60
|
+
const visitor = new TmplVisitor(exprMigrator);
|
|
61
|
+
for (const node of nodes) {
|
|
62
|
+
if (node.visit) {
|
|
63
|
+
node.visit(visitor);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
replacements.push(...exprMigrator.replacements);
|
|
67
|
+
}
|
|
68
|
+
for (const sourceFile of info.sourceFiles) {
|
|
69
|
+
replacements.push(...migrateHostBindingsInSourceFile(sourceFile, info));
|
|
70
|
+
}
|
|
71
|
+
return project_paths.confirmAsSerializable({ replacements });
|
|
72
|
+
}
|
|
73
|
+
async combine(unitA, unitB) {
|
|
74
|
+
const seen = new Set();
|
|
75
|
+
const deduped = [];
|
|
76
|
+
for (const r of [...unitA.replacements, ...unitB.replacements]) {
|
|
77
|
+
const key = `${r.projectFile.rootRelativePath}:${r.update.data.position}:${r.update.data.end}:${r.update.data.toInsert}`;
|
|
78
|
+
if (!seen.has(key)) {
|
|
79
|
+
seen.add(key);
|
|
80
|
+
deduped.push(r);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return project_paths.confirmAsSerializable({ replacements: deduped });
|
|
84
|
+
}
|
|
85
|
+
async globalMeta(data) {
|
|
86
|
+
return project_paths.confirmAsSerializable(data);
|
|
87
|
+
}
|
|
88
|
+
async stats(data) {
|
|
89
|
+
return project_paths.confirmAsSerializable({});
|
|
90
|
+
}
|
|
91
|
+
async migrate(data) {
|
|
92
|
+
return { replacements: data.replacements };
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
function migrateHostBindingsInSourceFile(sourceFile, info) {
|
|
96
|
+
const replacements = [];
|
|
97
|
+
const file = project_paths.projectFile(sourceFile, info);
|
|
98
|
+
const typeChecker = info.program.getTypeChecker();
|
|
99
|
+
const visitNode = (node) => {
|
|
100
|
+
if (ts.isClassDeclaration(node)) {
|
|
101
|
+
const decorators = ts.getDecorators(node) ?? [];
|
|
102
|
+
const ngDecorators = ng_decorators.getAngularDecorators(typeChecker, decorators);
|
|
103
|
+
for (const decorator of ngDecorators) {
|
|
104
|
+
if (decorator.name !== 'Component' && decorator.name !== 'Directive') {
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
const metadata = decorator.node.expression.arguments[0];
|
|
108
|
+
if (!metadata || !ts.isObjectLiteralExpression(metadata)) {
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
for (const prop of metadata.properties) {
|
|
112
|
+
if (!ts.isPropertyAssignment(prop)) {
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
const propName = property_name.getPropertyNameText(prop.name);
|
|
116
|
+
if (propName !== 'host' || !ts.isObjectLiteralExpression(prop.initializer)) {
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
for (const hostProp of prop.initializer.properties) {
|
|
120
|
+
if (!ts.isPropertyAssignment(hostProp) ||
|
|
121
|
+
!ts.isStringLiteralLike(hostProp.initializer)) {
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
const hostKey = property_name.getPropertyNameText(hostProp.name);
|
|
125
|
+
if (hostKey === null || (!hostKey.startsWith('[') && !hostKey.startsWith('('))) {
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
// Preserve raw text between quotes/backticks so source offsets stay aligned.
|
|
129
|
+
const hostExpression = hostProp.initializer.getText().slice(1, -1);
|
|
130
|
+
const fakeTemplatePrefix = `<div ${hostKey}="`;
|
|
131
|
+
const fakeTemplate = `${fakeTemplatePrefix}${hostExpression}"></div>`;
|
|
132
|
+
const parsedNodes = compiler.parseTemplate(fakeTemplate, sourceFile.fileName, {
|
|
133
|
+
preserveWhitespaces: true,
|
|
134
|
+
}).nodes;
|
|
135
|
+
const hostExpressionStart = hostProp.initializer.getStart() + 1;
|
|
136
|
+
const exprMigrator = new ExpressionMigrator(file, hostExpressionStart - fakeTemplatePrefix.length);
|
|
137
|
+
const visitor = new HostBindingVisitor(exprMigrator);
|
|
138
|
+
for (const parsedNode of parsedNodes) {
|
|
139
|
+
if (parsedNode.visit) {
|
|
140
|
+
parsedNode.visit(visitor);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
replacements.push(...exprMigrator.replacements);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
ts.forEachChild(node, visitNode);
|
|
149
|
+
};
|
|
150
|
+
visitNode(sourceFile);
|
|
151
|
+
return replacements;
|
|
152
|
+
}
|
|
153
|
+
/** Returns whether the given attribute is a class, style, or attribute binding. */
|
|
154
|
+
function isClassStyleOrAttrBinding(attribute) {
|
|
155
|
+
return (attribute.type === compiler.BindingType.Class ||
|
|
156
|
+
attribute.type === compiler.BindingType.Style ||
|
|
157
|
+
attribute.type === compiler.BindingType.Attribute ||
|
|
158
|
+
(attribute.type === compiler.BindingType.Property &&
|
|
159
|
+
(attribute.name === 'class' || attribute.name === 'className' || attribute.name === 'style')));
|
|
160
|
+
}
|
|
161
|
+
class HostBindingVisitor extends compiler.TmplAstRecursiveVisitor {
|
|
162
|
+
hostExprMigrator;
|
|
163
|
+
constructor(hostExprMigrator) {
|
|
164
|
+
super();
|
|
165
|
+
this.hostExprMigrator = hostExprMigrator;
|
|
166
|
+
}
|
|
167
|
+
visitBoundAttribute(attribute) {
|
|
168
|
+
if (isClassStyleOrAttrBinding(attribute)) {
|
|
169
|
+
// Class/style/attr bindings use truthiness — not null-sensitive by default.
|
|
170
|
+
// Safe navs inside function calls or pipes will still be wrapped by the migrator.
|
|
171
|
+
attribute.value.visit(this.hostExprMigrator, false);
|
|
172
|
+
}
|
|
173
|
+
else {
|
|
174
|
+
// Regular property bindings are null-sensitive.
|
|
175
|
+
attribute.value.visit(this.hostExprMigrator, true);
|
|
176
|
+
}
|
|
177
|
+
super.visitBoundAttribute(attribute);
|
|
178
|
+
}
|
|
179
|
+
visitBoundEvent(event) {
|
|
180
|
+
if (event.handler && event.handler.visit) {
|
|
181
|
+
// Event handlers are not null-sensitive; safe navs inside function calls will
|
|
182
|
+
// still be wrapped because function arguments are always null-sensitive.
|
|
183
|
+
event.handler.visit(this.hostExprMigrator, false);
|
|
184
|
+
}
|
|
185
|
+
super.visitBoundEvent(event);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Visits template expressions and inserts `$safeNavigationMigration(…)` wrappers around safe
|
|
190
|
+
* navigation chains whose result is consumed by a null-sensitive sink.
|
|
191
|
+
*
|
|
192
|
+
* The `context` parameter at every visit call is a boolean `nullSensitive` flag that answers:
|
|
193
|
+
* "does the parent care whether this expression's value is `null` vs. `undefined`?"
|
|
194
|
+
*
|
|
195
|
+
* Propagation rules:
|
|
196
|
+
* - `||`, `&&`, `??`: children are **not** null-sensitive (both normalise null/undefined).
|
|
197
|
+
* - `===` / `!==` with a nullish literal on one side: the other operand **is** null-sensitive.
|
|
198
|
+
* - All other binary operators: propagate the parent's null-sensitivity.
|
|
199
|
+
* - `!` (prefix not): operand is **not** null-sensitive (uses truthiness).
|
|
200
|
+
* - Ternary condition: **not** null-sensitive; branches inherit the parent's null-sensitivity.
|
|
201
|
+
* - Function call arguments and pipe inputs: **always** null-sensitive.
|
|
202
|
+
* - Receivers of property/keyed/call reads: **not** null-sensitive by default.
|
|
203
|
+
* For property/keyed/call continuations, when the receiver is a safe node and the parent sink
|
|
204
|
+
* is null-sensitive, wrap the full continuation node (e.g. `foo?.bar.baz`, `foo?.save()`) rather
|
|
205
|
+
* than the inner safe receiver (`foo?.bar`, `foo?.save`) so runtime behavior is preserved.
|
|
206
|
+
* - `NonNullAssert` (`!`): propagates the parent's null-sensitivity (type-only assertion).
|
|
207
|
+
*/
|
|
208
|
+
class ExpressionMigrator extends compiler.RecursiveAstVisitor {
|
|
209
|
+
file;
|
|
210
|
+
templateStart;
|
|
211
|
+
replacements = [];
|
|
212
|
+
constructor(file, templateStart) {
|
|
213
|
+
super();
|
|
214
|
+
this.file = file;
|
|
215
|
+
this.templateStart = templateStart;
|
|
216
|
+
}
|
|
217
|
+
// ---------------------------------------------------------------------------
|
|
218
|
+
// Safe navigation nodes — wrap when the sink is null-sensitive
|
|
219
|
+
// ---------------------------------------------------------------------------
|
|
220
|
+
visitSafePropertyRead(ast, nullSensitive) {
|
|
221
|
+
if (nullSensitive) {
|
|
222
|
+
this.addReplacement(ast);
|
|
223
|
+
}
|
|
224
|
+
// Receiver: not null-sensitive (further access on null/undefined throws either way).
|
|
225
|
+
this.visit(ast.receiver, false);
|
|
226
|
+
}
|
|
227
|
+
visitSafeKeyedRead(ast, nullSensitive) {
|
|
228
|
+
if (nullSensitive) {
|
|
229
|
+
this.addReplacement(ast);
|
|
230
|
+
}
|
|
231
|
+
this.visit(ast.receiver, false);
|
|
232
|
+
this.visit(ast.key, false);
|
|
233
|
+
}
|
|
234
|
+
visitSafeCall(ast, nullSensitive) {
|
|
235
|
+
if (nullSensitive) {
|
|
236
|
+
this.addReplacement(ast);
|
|
237
|
+
}
|
|
238
|
+
this.visit(ast.receiver, false);
|
|
239
|
+
this.visitAll(ast.args, true);
|
|
240
|
+
}
|
|
241
|
+
hasSafeReceiver(receiver) {
|
|
242
|
+
if (receiver instanceof compiler.SafePropertyRead ||
|
|
243
|
+
receiver instanceof compiler.SafeKeyedRead ||
|
|
244
|
+
receiver instanceof compiler.SafeCall) {
|
|
245
|
+
return true;
|
|
246
|
+
}
|
|
247
|
+
if (receiver instanceof compiler.NonNullAssert) {
|
|
248
|
+
return this.hasSafeReceiver(receiver.expression);
|
|
249
|
+
}
|
|
250
|
+
return false;
|
|
251
|
+
}
|
|
252
|
+
// ---------------------------------------------------------------------------
|
|
253
|
+
// Non-safe access — receiver is never null-sensitive
|
|
254
|
+
// ---------------------------------------------------------------------------
|
|
255
|
+
visitPropertyRead(ast, nullSensitive) {
|
|
256
|
+
if (nullSensitive && this.hasSafeReceiver(ast.receiver)) {
|
|
257
|
+
this.addReplacement(ast);
|
|
258
|
+
}
|
|
259
|
+
this.visit(ast.receiver, false);
|
|
260
|
+
}
|
|
261
|
+
visitKeyedRead(ast, nullSensitive) {
|
|
262
|
+
if (nullSensitive && this.hasSafeReceiver(ast.receiver)) {
|
|
263
|
+
this.addReplacement(ast);
|
|
264
|
+
}
|
|
265
|
+
this.visit(ast.receiver, false);
|
|
266
|
+
this.visit(ast.key, false);
|
|
267
|
+
}
|
|
268
|
+
// ---------------------------------------------------------------------------
|
|
269
|
+
// Function calls — arguments are always null-sensitive
|
|
270
|
+
// ---------------------------------------------------------------------------
|
|
271
|
+
visitCall(ast, nullSensitive) {
|
|
272
|
+
if (nullSensitive && this.hasSafeReceiver(ast.receiver)) {
|
|
273
|
+
this.addReplacement(ast);
|
|
274
|
+
}
|
|
275
|
+
this.visit(ast.receiver, false);
|
|
276
|
+
this.visitAll(ast.args, true);
|
|
277
|
+
}
|
|
278
|
+
// ---------------------------------------------------------------------------
|
|
279
|
+
// Pipes — input and arguments are always null-sensitive
|
|
280
|
+
// ---------------------------------------------------------------------------
|
|
281
|
+
visitPipe(ast, _nullSensitive) {
|
|
282
|
+
this.visit(ast.exp, true);
|
|
283
|
+
this.visitAll(ast.args, true);
|
|
284
|
+
}
|
|
285
|
+
// ---------------------------------------------------------------------------
|
|
286
|
+
// Binary operators
|
|
287
|
+
// ---------------------------------------------------------------------------
|
|
288
|
+
visitBinary(ast, nullSensitive) {
|
|
289
|
+
if (ast.operation === '||' || ast.operation === '&&' || ast.operation === '??') {
|
|
290
|
+
// These operators normalise null and undefined (both produce the same result),
|
|
291
|
+
// so the operands are not null-sensitive.
|
|
292
|
+
this.visit(ast.left, false);
|
|
293
|
+
this.visit(ast.right, false);
|
|
294
|
+
}
|
|
295
|
+
else if (ast.operation === '===' || ast.operation === '!==') {
|
|
296
|
+
// A strict comparison with a nullish literal makes the other side null-sensitive
|
|
297
|
+
// (null === null is true but undefined === null is false).
|
|
298
|
+
const leftIsNullish = isNullishLiteralAST(ast.left);
|
|
299
|
+
const rightIsNullish = isNullishLiteralAST(ast.right);
|
|
300
|
+
this.visit(ast.left, rightIsNullish);
|
|
301
|
+
this.visit(ast.right, leftIsNullish);
|
|
302
|
+
}
|
|
303
|
+
else {
|
|
304
|
+
// All other binary operators (<, >, +, -, …): propagate the parent's null-sensitivity
|
|
305
|
+
// because null and undefined can produce different numeric results (null → 0,
|
|
306
|
+
// undefined → NaN for arithmetic/comparison).
|
|
307
|
+
this.visit(ast.left, nullSensitive);
|
|
308
|
+
this.visit(ast.right, nullSensitive);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
// ---------------------------------------------------------------------------
|
|
312
|
+
// Unary / logical operators
|
|
313
|
+
// ---------------------------------------------------------------------------
|
|
314
|
+
visitPrefixNot(ast, _nullSensitive) {
|
|
315
|
+
// Logical negation uses truthiness — null and undefined are both falsy.
|
|
316
|
+
this.visit(ast.expression, false);
|
|
317
|
+
}
|
|
318
|
+
// ---------------------------------------------------------------------------
|
|
319
|
+
// Ternary
|
|
320
|
+
// ---------------------------------------------------------------------------
|
|
321
|
+
visitConditional(ast, nullSensitive) {
|
|
322
|
+
// The condition is evaluated as a boolean — not null-sensitive.
|
|
323
|
+
this.visit(ast.condition, false);
|
|
324
|
+
// The result of a branch is consumed by the parent, so it inherits null-sensitivity.
|
|
325
|
+
this.visit(ast.trueExp, nullSensitive);
|
|
326
|
+
this.visit(ast.falseExp, nullSensitive);
|
|
327
|
+
}
|
|
328
|
+
// ---------------------------------------------------------------------------
|
|
329
|
+
// NonNullAssert — a compile-time type annotation, no runtime effect
|
|
330
|
+
// ---------------------------------------------------------------------------
|
|
331
|
+
visitNonNullAssert(ast, nullSensitive) {
|
|
332
|
+
this.visit(ast.expression, nullSensitive);
|
|
333
|
+
}
|
|
334
|
+
// ---------------------------------------------------------------------------
|
|
335
|
+
// Chain (event handler statements) — never null-sensitive
|
|
336
|
+
// ---------------------------------------------------------------------------
|
|
337
|
+
visitChain(ast, _nullSensitive) {
|
|
338
|
+
this.visitAll(ast.expressions, false);
|
|
339
|
+
}
|
|
340
|
+
// ---------------------------------------------------------------------------
|
|
341
|
+
// Interpolation — coerces to string, so null and undefined produce identical
|
|
342
|
+
// output; sub-expressions are therefore never null-sensitive.
|
|
343
|
+
// ---------------------------------------------------------------------------
|
|
344
|
+
visitInterpolation(ast, _nullSensitive) {
|
|
345
|
+
this.visitAll(ast.expressions, false);
|
|
346
|
+
}
|
|
347
|
+
// ---------------------------------------------------------------------------
|
|
348
|
+
// All other nodes (LiteralArray, LiteralMap, Unary, …) use the default
|
|
349
|
+
// RecursiveAstVisitor which propagates the current context to every child —
|
|
350
|
+
// the correct "inherit parent null-sensitivity" fallback.
|
|
351
|
+
// ---------------------------------------------------------------------------
|
|
352
|
+
addReplacement(ast) {
|
|
353
|
+
const startArg = ast.sourceSpan.start;
|
|
354
|
+
const endArg = ast.sourceSpan.end;
|
|
355
|
+
this.replacements.push(new project_paths.Replacement(this.file, new project_paths.TextUpdate({
|
|
356
|
+
position: this.templateStart + endArg,
|
|
357
|
+
end: this.templateStart + endArg,
|
|
358
|
+
toInsert: ')',
|
|
359
|
+
})), new project_paths.Replacement(this.file, new project_paths.TextUpdate({
|
|
360
|
+
position: this.templateStart + startArg,
|
|
361
|
+
end: this.templateStart + startArg,
|
|
362
|
+
toInsert: '$safeNavigationMigration(',
|
|
363
|
+
})));
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
// ---------------------------------------------------------------------------
|
|
367
|
+
// Helper utilities
|
|
368
|
+
// ---------------------------------------------------------------------------
|
|
369
|
+
/** Returns true if the AST node is a literal `null` or `undefined`. */
|
|
370
|
+
function isNullishLiteralAST(ast) {
|
|
371
|
+
const innerAst = ast instanceof compiler.ASTWithSource ? ast.ast : ast;
|
|
372
|
+
return (innerAst instanceof compiler.LiteralPrimitive &&
|
|
373
|
+
(innerAst.value === null || innerAst.value === undefined));
|
|
374
|
+
}
|
|
375
|
+
/** Returns true if the AST node is a non-null, non-undefined primitive literal. */
|
|
376
|
+
function isNonNullishLiteralAST(ast) {
|
|
377
|
+
const innerAst = ast instanceof compiler.ASTWithSource ? ast.ast : ast;
|
|
378
|
+
return (innerAst instanceof compiler.LiteralPrimitive && innerAst.value !== null && innerAst.value !== undefined);
|
|
379
|
+
}
|
|
380
|
+
// ---------------------------------------------------------------------------
|
|
381
|
+
// Template visitor
|
|
382
|
+
// ---------------------------------------------------------------------------
|
|
383
|
+
/**
|
|
384
|
+
* Returns true if all *ngSwitchCase bindings in the given nodes (and their children)
|
|
385
|
+
* are non-null/non-undefined literal expressions — meaning the switch expression
|
|
386
|
+
* doesn't need null-sensitivity migration.
|
|
387
|
+
*/
|
|
388
|
+
function allNgSwitchCasesAreLiterals(nodes) {
|
|
389
|
+
for (const node of nodes) {
|
|
390
|
+
for (const input of node.inputs) {
|
|
391
|
+
if (input.name === 'ngSwitchCase' && input.value) {
|
|
392
|
+
if (!isNonNullishLiteralAST(input.value)) {
|
|
393
|
+
return false;
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
if (node instanceof compiler.TmplAstTemplate) {
|
|
398
|
+
for (const attr of node.templateAttrs) {
|
|
399
|
+
if (attr instanceof compiler.TmplAstBoundAttribute && attr.name === 'ngSwitchCase' && attr.value) {
|
|
400
|
+
if (!isNonNullishLiteralAST(attr.value)) {
|
|
401
|
+
return false;
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
const childHosts = node.children.filter((child) => child instanceof compiler.TmplAstElement || child instanceof compiler.TmplAstTemplate);
|
|
407
|
+
if (!allNgSwitchCasesAreLiterals(childHosts)) {
|
|
408
|
+
return false;
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
return true;
|
|
412
|
+
}
|
|
413
|
+
class TmplVisitor extends compiler.TmplAstRecursiveVisitor {
|
|
414
|
+
exprMigrator;
|
|
415
|
+
migratableSwitchCases = new WeakSet();
|
|
416
|
+
/**
|
|
417
|
+
* Stack tracking whether the current ngSwitch context should be migrated.
|
|
418
|
+
* False when all *ngSwitchCase expressions are non-null literals.
|
|
419
|
+
*/
|
|
420
|
+
ngSwitchShouldMigrateStack = [];
|
|
421
|
+
constructor(exprMigrator) {
|
|
422
|
+
super();
|
|
423
|
+
this.exprMigrator = exprMigrator;
|
|
424
|
+
}
|
|
425
|
+
shouldMigrateCurrentNgSwitchContext() {
|
|
426
|
+
return this.ngSwitchShouldMigrateStack[this.ngSwitchShouldMigrateStack.length - 1] ?? true;
|
|
427
|
+
}
|
|
428
|
+
hasNgSwitchBinding(node) {
|
|
429
|
+
return (node.inputs.some((attr) => attr.name === 'ngSwitch') ||
|
|
430
|
+
(node instanceof compiler.TmplAstTemplate &&
|
|
431
|
+
node.templateAttrs.some((attr) => attr.name === 'ngSwitch')));
|
|
432
|
+
}
|
|
433
|
+
visitElement(element) {
|
|
434
|
+
const hasNgSwitch = this.hasNgSwitchBinding(element);
|
|
435
|
+
if (hasNgSwitch) {
|
|
436
|
+
const childHosts = element.children.filter((child) => child instanceof compiler.TmplAstElement || child instanceof compiler.TmplAstTemplate);
|
|
437
|
+
this.ngSwitchShouldMigrateStack.push(!allNgSwitchCasesAreLiterals(childHosts));
|
|
438
|
+
}
|
|
439
|
+
super.visitElement(element);
|
|
440
|
+
if (hasNgSwitch) {
|
|
441
|
+
this.ngSwitchShouldMigrateStack.pop();
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
visitBoundAttribute(attribute) {
|
|
445
|
+
if (attribute.name === 'ngForOf') {
|
|
446
|
+
// ngFor/@for item expressions are not null-sensitive by default.
|
|
447
|
+
// Still visit so inner null-sensitive sinks (e.g. pipes/functions) are migrated.
|
|
448
|
+
attribute.value.visit(this.exprMigrator, false);
|
|
449
|
+
}
|
|
450
|
+
else if (attribute.name === 'ngIf') {
|
|
451
|
+
// ngIf evaluates as a boolean; null-sensitivity only arises when the expression
|
|
452
|
+
// itself contains a strict null comparison (handled inside ExpressionMigrator).
|
|
453
|
+
attribute.value.visit(this.exprMigrator, false);
|
|
454
|
+
}
|
|
455
|
+
else if (attribute.name === 'ngSwitch' || attribute.name === 'ngSwitchCase') {
|
|
456
|
+
attribute.value.visit(this.exprMigrator, this.shouldMigrateCurrentNgSwitchContext());
|
|
457
|
+
}
|
|
458
|
+
else if (isClassStyleOrAttrBinding(attribute)) {
|
|
459
|
+
// Class/style/attr bindings use truthiness — not null-sensitive by default.
|
|
460
|
+
attribute.value.visit(this.exprMigrator, false);
|
|
461
|
+
}
|
|
462
|
+
else {
|
|
463
|
+
// Regular property bindings are null-sensitive.
|
|
464
|
+
attribute.value.visit(this.exprMigrator, true);
|
|
465
|
+
}
|
|
466
|
+
super.visitBoundAttribute(attribute);
|
|
467
|
+
}
|
|
468
|
+
visitBoundEvent(event) {
|
|
469
|
+
if (event.handler && event.handler.visit) {
|
|
470
|
+
// Event handlers are not null-sensitive. Safe navs inside function calls
|
|
471
|
+
// (e.g. `compute(user?.save())`) are still migrated because function arguments
|
|
472
|
+
// are always null-sensitive in ExpressionMigrator.
|
|
473
|
+
event.handler.visit(this.exprMigrator, false);
|
|
474
|
+
}
|
|
475
|
+
super.visitBoundEvent(event);
|
|
476
|
+
}
|
|
477
|
+
visitBoundText(text) {
|
|
478
|
+
// Interpolation text is not null-sensitive by default; null-sensitivity is
|
|
479
|
+
// introduced by pipes, function calls, or strict null comparisons inside
|
|
480
|
+
// the expression, all handled by ExpressionMigrator.
|
|
481
|
+
text.value.visit(this.exprMigrator, false);
|
|
482
|
+
super.visitBoundText(text);
|
|
483
|
+
}
|
|
484
|
+
visitTemplate(template) {
|
|
485
|
+
const hasNgSwitch = this.hasNgSwitchBinding(template);
|
|
486
|
+
if (hasNgSwitch) {
|
|
487
|
+
const childHosts = template.children.filter((child) => child instanceof compiler.TmplAstElement || child instanceof compiler.TmplAstTemplate);
|
|
488
|
+
this.ngSwitchShouldMigrateStack.push(!allNgSwitchCasesAreLiterals(childHosts));
|
|
489
|
+
}
|
|
490
|
+
for (const attr of template.templateAttrs) {
|
|
491
|
+
if (!(attr instanceof compiler.TmplAstBoundAttribute)) {
|
|
492
|
+
continue;
|
|
493
|
+
}
|
|
494
|
+
if (attr.name === 'ngIf') {
|
|
495
|
+
attr.value.visit(this.exprMigrator, false);
|
|
496
|
+
}
|
|
497
|
+
else if (attr.name === 'ngSwitch' || attr.name === 'ngSwitchCase') {
|
|
498
|
+
attr.value.visit(this.exprMigrator, this.shouldMigrateCurrentNgSwitchContext());
|
|
499
|
+
}
|
|
500
|
+
else if (attr.name === 'ngForOf') {
|
|
501
|
+
// ngFor microsyntax expressions are not null-sensitive by default.
|
|
502
|
+
// Still visit so nested null-sensitive sinks are handled.
|
|
503
|
+
attr.value.visit(this.exprMigrator, false);
|
|
504
|
+
}
|
|
505
|
+
else {
|
|
506
|
+
attr.value.visit(this.exprMigrator, true);
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
super.visitTemplate(template);
|
|
510
|
+
if (hasNgSwitch) {
|
|
511
|
+
this.ngSwitchShouldMigrateStack.pop();
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
visitIfBlockBranch(block) {
|
|
515
|
+
if (block.expression) {
|
|
516
|
+
// @if condition: not null-sensitive by default (same logic as ngIf).
|
|
517
|
+
block.expression.visit(this.exprMigrator, false);
|
|
518
|
+
}
|
|
519
|
+
super.visitIfBlockBranch(block);
|
|
520
|
+
}
|
|
521
|
+
visitForLoopBlock(block) {
|
|
522
|
+
block.expression.visit(this.exprMigrator, false);
|
|
523
|
+
block.trackBy.visit(this.exprMigrator, false);
|
|
524
|
+
super.visitForLoopBlock(block);
|
|
525
|
+
}
|
|
526
|
+
visitLetDeclaration(decl) {
|
|
527
|
+
// @let value is assigned directly — null-sensitive.
|
|
528
|
+
decl.value.visit(this.exprMigrator, true);
|
|
529
|
+
super.visitLetDeclaration(decl);
|
|
530
|
+
}
|
|
531
|
+
visitSwitchBlock(block) {
|
|
532
|
+
const switchCases = block.groups.flatMap((group) => group.cases);
|
|
533
|
+
// Don't migrate if every case expression is a non-null/non-undefined literal
|
|
534
|
+
// (e.g. strings, numbers, booleans). In that case null-vs-undefined can never
|
|
535
|
+
// match a case, so wrapping the switch expression would be pointless.
|
|
536
|
+
const shouldMigrate = !switchCases
|
|
537
|
+
.filter((switchCase) => switchCase.expression)
|
|
538
|
+
.every((switchCase) => isNonNullishLiteralAST(switchCase.expression));
|
|
539
|
+
if (shouldMigrate) {
|
|
540
|
+
block.expression.visit(this.exprMigrator, true);
|
|
541
|
+
for (const switchCase of switchCases) {
|
|
542
|
+
this.migratableSwitchCases.add(switchCase);
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
super.visitSwitchBlock(block);
|
|
546
|
+
}
|
|
547
|
+
visitSwitchBlockCase(block) {
|
|
548
|
+
if (this.migratableSwitchCases.has(block) && block.expression) {
|
|
549
|
+
block.expression.visit(this.exprMigrator, true);
|
|
550
|
+
}
|
|
551
|
+
super.visitSwitchBlockCase(block);
|
|
552
|
+
}
|
|
553
|
+
visitDeferredTrigger(trigger) {
|
|
554
|
+
if (trigger instanceof compiler.TmplAstBoundDeferredTrigger) {
|
|
555
|
+
// @defer (when …): same logic as @if — not null-sensitive by default.
|
|
556
|
+
trigger.value.visit(this.exprMigrator, false);
|
|
557
|
+
}
|
|
558
|
+
super.visitDeferredTrigger(trigger);
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
function migrate() {
|
|
563
|
+
return async (tree) => {
|
|
564
|
+
await project_paths.runMigrationInDevkit({
|
|
565
|
+
tree,
|
|
566
|
+
getMigration: () => new SafeOptionalChainingMigration(),
|
|
567
|
+
});
|
|
568
|
+
};
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
exports.migrate = migrate;
|
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
/**
|
|
3
|
-
* @license Angular v22.0.0-next.
|
|
3
|
+
* @license Angular v22.0.0-next.11
|
|
4
4
|
* (c) 2010-2026 Google LLC. https://angular.dev/
|
|
5
5
|
* License: MIT
|
|
6
6
|
*/
|
|
7
7
|
'use strict';
|
|
8
8
|
|
|
9
|
-
var migrate_ts_type_references = require('./migrate_ts_type_references-
|
|
9
|
+
var migrate_ts_type_references = require('./migrate_ts_type_references-xRTTASnu.cjs');
|
|
10
10
|
var ts = require('typescript');
|
|
11
11
|
require('@angular/compiler-cli');
|
|
12
12
|
var migrations = require('@angular/compiler-cli/private/migrations');
|
|
13
13
|
require('node:path');
|
|
14
14
|
var project_paths = require('./project_paths-D2V-Uh2L.cjs');
|
|
15
|
-
var index = require('./index-
|
|
15
|
+
var index = require('./index-DcezkXLN.cjs');
|
|
16
16
|
var assert = require('assert');
|
|
17
17
|
var apply_import_manager = require('./apply_import_manager-CxA_YYgB.cjs');
|
|
18
18
|
require('@angular-devkit/core');
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
/**
|
|
3
|
-
* @license Angular v22.0.0-next.
|
|
3
|
+
* @license Angular v22.0.0-next.11
|
|
4
4
|
* (c) 2010-2026 Google LLC. https://angular.dev/
|
|
5
5
|
* License: MIT
|
|
6
6
|
*/
|
|
@@ -12,9 +12,9 @@ require('@angular/compiler-cli');
|
|
|
12
12
|
require('node:path');
|
|
13
13
|
var project_paths = require('./project_paths-D2V-Uh2L.cjs');
|
|
14
14
|
var apply_import_manager = require('./apply_import_manager-CxA_YYgB.cjs');
|
|
15
|
-
var migrate_ts_type_references = require('./migrate_ts_type_references-
|
|
15
|
+
var migrate_ts_type_references = require('./migrate_ts_type_references-xRTTASnu.cjs');
|
|
16
16
|
var assert = require('assert');
|
|
17
|
-
var index = require('./index-
|
|
17
|
+
var index = require('./index-DcezkXLN.cjs');
|
|
18
18
|
var compiler = require('@angular/compiler');
|
|
19
19
|
require('@angular-devkit/core');
|
|
20
20
|
require('node:path/posix');
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
/**
|
|
3
|
-
* @license Angular v22.0.0-next.
|
|
3
|
+
* @license Angular v22.0.0-next.11
|
|
4
4
|
* (c) 2010-2026 Google LLC. https://angular.dev/
|
|
5
5
|
* License: MIT
|
|
6
6
|
*/
|
|
@@ -19,9 +19,9 @@ require('@angular-devkit/core');
|
|
|
19
19
|
require('node:path/posix');
|
|
20
20
|
require('./project_tsconfig_paths-DkkMibv-.cjs');
|
|
21
21
|
require('./apply_import_manager-CxA_YYgB.cjs');
|
|
22
|
-
require('./migrate_ts_type_references-
|
|
22
|
+
require('./migrate_ts_type_references-xRTTASnu.cjs');
|
|
23
23
|
require('assert');
|
|
24
|
-
require('./index-
|
|
24
|
+
require('./index-DcezkXLN.cjs');
|
|
25
25
|
require('@angular/compiler');
|
|
26
26
|
require('./leading_space-BTPRV0wu.cjs');
|
|
27
27
|
|