@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.
Files changed (42) hide show
  1. package/fesm2022/core.mjs +21183 -19410
  2. package/fesm2022/core.mjs.map +1 -1
  3. package/fesm2022/primitives/event-dispatch.mjs +71 -47
  4. package/fesm2022/primitives/event-dispatch.mjs.map +1 -1
  5. package/fesm2022/primitives/signals.mjs +8 -6
  6. package/fesm2022/primitives/signals.mjs.map +1 -1
  7. package/fesm2022/rxjs-interop.mjs +90 -10
  8. package/fesm2022/rxjs-interop.mjs.map +1 -1
  9. package/fesm2022/testing.mjs +174 -113
  10. package/fesm2022/testing.mjs.map +1 -1
  11. package/index.d.ts +524 -92
  12. package/package.json +1 -1
  13. package/primitives/event-dispatch/index.d.ts +7 -4
  14. package/primitives/signals/index.d.ts +3 -1
  15. package/rxjs-interop/index.d.ts +35 -4
  16. package/schematics/bundles/{checker-3b2ea20f.js → checker-9ca42e51.js} +2303 -1006
  17. package/schematics/bundles/combine_units-a16385aa.js +1634 -0
  18. package/schematics/bundles/{compiler_host-b4ba5a28.js → compiler_host-31afa4ed.js} +8 -5
  19. package/schematics/bundles/control-flow-migration.js +3 -3
  20. package/schematics/bundles/explicit-standalone-flag.js +29 -9
  21. package/schematics/bundles/imports-4ac08251.js +1 -1
  22. package/schematics/bundles/inject-migration.js +222 -54
  23. package/schematics/bundles/leading_space-d190b83b.js +1 -1
  24. package/schematics/bundles/migrate_ts_type_references-b2a28742.js +1463 -0
  25. package/schematics/bundles/nodes-0e7d45ca.js +1 -1
  26. package/schematics/bundles/output-migration.js +575 -0
  27. package/schematics/bundles/pending-tasks.js +3 -3
  28. package/schematics/bundles/{program-6534a30a.js → program-71beec0b.js} +1385 -460
  29. package/schematics/bundles/project_tsconfig_paths-e9ccccbf.js +1 -1
  30. package/schematics/bundles/provide-initializer.js +179 -0
  31. package/schematics/bundles/route-lazy-loading.js +9 -4
  32. package/schematics/bundles/signal-input-migration.js +183 -311
  33. package/schematics/bundles/signal-queries-migration.js +404 -146
  34. package/schematics/bundles/signals.js +54 -0
  35. package/schematics/bundles/standalone-migration.js +29 -12
  36. package/schematics/collection.json +11 -0
  37. package/schematics/migrations.json +7 -1
  38. package/schematics/ng-generate/output-migration/schema.json +19 -0
  39. package/schematics/ng-generate/signal-queries-migration/schema.json +11 -0
  40. package/schematics/ng-generate/signals/schema.json +65 -0
  41. package/testing/index.d.ts +1 -1
  42. package/schematics/bundles/group_replacements-e1b5cbf8.js +0 -31571
@@ -0,0 +1,1634 @@
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
+ var core = require('@angular-devkit/core');
10
+ var posixPath = require('node:path/posix');
11
+ var os = require('os');
12
+ var ts = require('typescript');
13
+ var checker = require('./checker-9ca42e51.js');
14
+ var program = require('./program-71beec0b.js');
15
+ require('path');
16
+
17
+ function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
18
+
19
+ function _interopNamespace(e) {
20
+ if (e && e.__esModule) return e;
21
+ var n = Object.create(null);
22
+ if (e) {
23
+ Object.keys(e).forEach(function (k) {
24
+ if (k !== 'default') {
25
+ var d = Object.getOwnPropertyDescriptor(e, k);
26
+ Object.defineProperty(n, k, d.get ? d : {
27
+ enumerable: true,
28
+ get: function () { return e[k]; }
29
+ });
30
+ }
31
+ });
32
+ }
33
+ n["default"] = e;
34
+ return Object.freeze(n);
35
+ }
36
+
37
+ var posixPath__namespace = /*#__PURE__*/_interopNamespace(posixPath);
38
+ var os__namespace = /*#__PURE__*/_interopNamespace(os);
39
+ var ts__default = /*#__PURE__*/_interopDefaultLegacy(ts);
40
+
41
+ /// <reference types="node" />
42
+ class NgtscCompilerHost {
43
+ fs;
44
+ options;
45
+ constructor(fs, options = {}) {
46
+ this.fs = fs;
47
+ this.options = options;
48
+ }
49
+ getSourceFile(fileName, languageVersion) {
50
+ const text = this.readFile(fileName);
51
+ return text !== undefined
52
+ ? ts__default["default"].createSourceFile(fileName, text, languageVersion, true)
53
+ : undefined;
54
+ }
55
+ getDefaultLibFileName(options) {
56
+ return this.fs.join(this.getDefaultLibLocation(), ts__default["default"].getDefaultLibFileName(options));
57
+ }
58
+ getDefaultLibLocation() {
59
+ return this.fs.getDefaultLibLocation();
60
+ }
61
+ writeFile(fileName, data, writeByteOrderMark, onError, sourceFiles) {
62
+ const path = checker.absoluteFrom(fileName);
63
+ this.fs.ensureDir(this.fs.dirname(path));
64
+ this.fs.writeFile(path, data);
65
+ }
66
+ getCurrentDirectory() {
67
+ return this.fs.pwd();
68
+ }
69
+ getCanonicalFileName(fileName) {
70
+ return this.useCaseSensitiveFileNames() ? fileName : fileName.toLowerCase();
71
+ }
72
+ useCaseSensitiveFileNames() {
73
+ return this.fs.isCaseSensitive();
74
+ }
75
+ getNewLine() {
76
+ switch (this.options.newLine) {
77
+ case ts__default["default"].NewLineKind.CarriageReturnLineFeed:
78
+ return '\r\n';
79
+ case ts__default["default"].NewLineKind.LineFeed:
80
+ return '\n';
81
+ default:
82
+ return os__namespace.EOL;
83
+ }
84
+ }
85
+ fileExists(fileName) {
86
+ const absPath = this.fs.resolve(fileName);
87
+ return this.fs.exists(absPath) && this.fs.stat(absPath).isFile();
88
+ }
89
+ readFile(fileName) {
90
+ const absPath = this.fs.resolve(fileName);
91
+ if (!this.fileExists(absPath)) {
92
+ return undefined;
93
+ }
94
+ return this.fs.readFile(absPath);
95
+ }
96
+ realpath(path) {
97
+ return this.fs.realpath(this.fs.resolve(path));
98
+ }
99
+ }
100
+
101
+ // We use TypeScript's native `ts.matchFiles` utility for the virtual file systems
102
+ /**
103
+ * Creates a {@link ts.CompilerHost#readDirectory} implementation function,
104
+ * that leverages the specified file system (that may be e.g. virtual).
105
+ */
106
+ function createFileSystemTsReadDirectoryFn(fs) {
107
+ if (ts__default["default"].matchFiles === undefined) {
108
+ throw Error('Unable to read directory in configured file system. This means that ' +
109
+ 'TypeScript changed its file matching internals.\n\nPlease consider downgrading your ' +
110
+ 'TypeScript version, and report an issue in the Angular framework repository.');
111
+ }
112
+ const matchFilesFn = ts__default["default"].matchFiles.bind(ts__default["default"]);
113
+ return (rootDir, extensions, excludes, includes, depth) => {
114
+ const directoryExists = (p) => {
115
+ const resolvedPath = fs.resolve(p);
116
+ return fs.exists(resolvedPath) && fs.stat(resolvedPath).isDirectory();
117
+ };
118
+ return matchFilesFn(rootDir, extensions, excludes, includes, fs.isCaseSensitive(), fs.pwd(), depth, (p) => {
119
+ const resolvedPath = fs.resolve(p);
120
+ // TS also gracefully returns an empty file set.
121
+ if (!directoryExists(resolvedPath)) {
122
+ return { directories: [], files: [] };
123
+ }
124
+ const children = fs.readdir(resolvedPath);
125
+ const files = [];
126
+ const directories = [];
127
+ for (const child of children) {
128
+ if (fs.stat(fs.join(resolvedPath, child))?.isDirectory()) {
129
+ directories.push(child);
130
+ }
131
+ else {
132
+ files.push(child);
133
+ }
134
+ }
135
+ return { files, directories };
136
+ }, (p) => fs.resolve(p), (p) => directoryExists(p));
137
+ };
138
+ }
139
+
140
+ function calcProjectFileAndBasePath(project, host = checker.getFileSystem()) {
141
+ const absProject = host.resolve(project);
142
+ const projectIsDir = host.lstat(absProject).isDirectory();
143
+ const projectFile = projectIsDir ? host.join(absProject, 'tsconfig.json') : absProject;
144
+ const projectDir = projectIsDir ? absProject : host.dirname(absProject);
145
+ const basePath = host.resolve(projectDir);
146
+ return { projectFile, basePath };
147
+ }
148
+ function readConfiguration(project, existingOptions, host = checker.getFileSystem()) {
149
+ try {
150
+ const fs = checker.getFileSystem();
151
+ const readConfigFile = (configFile) => ts__default["default"].readConfigFile(configFile, (file) => host.readFile(host.resolve(file)));
152
+ const readAngularCompilerOptions = (configFile, parentOptions = {}) => {
153
+ const { config, error } = readConfigFile(configFile);
154
+ if (error) {
155
+ // Errors are handled later on by 'parseJsonConfigFileContent'
156
+ return parentOptions;
157
+ }
158
+ // we are only interested into merging 'angularCompilerOptions' as
159
+ // other options like 'compilerOptions' are merged by TS
160
+ let existingNgCompilerOptions = { ...config.angularCompilerOptions, ...parentOptions };
161
+ if (!config.extends) {
162
+ return existingNgCompilerOptions;
163
+ }
164
+ const extendsPaths = typeof config.extends === 'string' ? [config.extends] : config.extends;
165
+ // Call readAngularCompilerOptions recursively to merge NG Compiler options
166
+ // Reverse the array so the overrides happen from right to left.
167
+ return [...extendsPaths].reverse().reduce((prevOptions, extendsPath) => {
168
+ const extendedConfigPath = getExtendedConfigPath(configFile, extendsPath, host, fs);
169
+ return extendedConfigPath === null
170
+ ? prevOptions
171
+ : readAngularCompilerOptions(extendedConfigPath, prevOptions);
172
+ }, existingNgCompilerOptions);
173
+ };
174
+ const { projectFile, basePath } = calcProjectFileAndBasePath(project, host);
175
+ const configFileName = host.resolve(host.pwd(), projectFile);
176
+ const { config, error } = readConfigFile(projectFile);
177
+ if (error) {
178
+ return {
179
+ project,
180
+ errors: [error],
181
+ rootNames: [],
182
+ options: {},
183
+ emitFlags: program.EmitFlags.Default,
184
+ };
185
+ }
186
+ const existingCompilerOptions = {
187
+ genDir: basePath,
188
+ basePath,
189
+ ...readAngularCompilerOptions(configFileName),
190
+ ...existingOptions,
191
+ };
192
+ const parseConfigHost = createParseConfigHost(host, fs);
193
+ const { options, errors, fileNames: rootNames, projectReferences, } = ts__default["default"].parseJsonConfigFileContent(config, parseConfigHost, basePath, existingCompilerOptions, configFileName);
194
+ let emitFlags = program.EmitFlags.Default;
195
+ if (!(options['skipMetadataEmit'] || options['flatModuleOutFile'])) {
196
+ emitFlags |= program.EmitFlags.Metadata;
197
+ }
198
+ if (options['skipTemplateCodegen']) {
199
+ emitFlags = emitFlags & ~program.EmitFlags.Codegen;
200
+ }
201
+ return { project: projectFile, rootNames, projectReferences, options, errors, emitFlags };
202
+ }
203
+ catch (e) {
204
+ const errors = [
205
+ {
206
+ category: ts__default["default"].DiagnosticCategory.Error,
207
+ messageText: e.stack ?? e.message,
208
+ file: undefined,
209
+ start: undefined,
210
+ length: undefined,
211
+ source: 'angular',
212
+ code: program.UNKNOWN_ERROR_CODE,
213
+ },
214
+ ];
215
+ return { project: '', errors, rootNames: [], options: {}, emitFlags: program.EmitFlags.Default };
216
+ }
217
+ }
218
+ function createParseConfigHost(host, fs = checker.getFileSystem()) {
219
+ return {
220
+ fileExists: host.exists.bind(host),
221
+ readDirectory: createFileSystemTsReadDirectoryFn(fs),
222
+ readFile: host.readFile.bind(host),
223
+ useCaseSensitiveFileNames: fs.isCaseSensitive(),
224
+ };
225
+ }
226
+ function getExtendedConfigPath(configFile, extendsValue, host, fs) {
227
+ const result = getExtendedConfigPathWorker(configFile, extendsValue, host, fs);
228
+ if (result !== null) {
229
+ return result;
230
+ }
231
+ // Try to resolve the paths with a json extension append a json extension to the file in case if
232
+ // it is missing and the resolution failed. This is to replicate TypeScript behaviour, see:
233
+ // https://github.com/microsoft/TypeScript/blob/294a5a7d784a5a95a8048ee990400979a6bc3a1c/src/compiler/commandLineParser.ts#L2806
234
+ return getExtendedConfigPathWorker(configFile, `${extendsValue}.json`, host, fs);
235
+ }
236
+ function getExtendedConfigPathWorker(configFile, extendsValue, host, fs) {
237
+ if (extendsValue.startsWith('.') || fs.isRooted(extendsValue)) {
238
+ const extendedConfigPath = host.resolve(host.dirname(configFile), extendsValue);
239
+ if (host.exists(extendedConfigPath)) {
240
+ return extendedConfigPath;
241
+ }
242
+ }
243
+ else {
244
+ const parseConfigHost = createParseConfigHost(host, fs);
245
+ // Path isn't a rooted or relative path, resolve like a module.
246
+ const { resolvedModule } = ts__default["default"].nodeModuleNameResolver(extendsValue, configFile, { moduleResolution: ts__default["default"].ModuleResolutionKind.Node10, resolveJsonModule: true }, parseConfigHost);
247
+ if (resolvedModule) {
248
+ return checker.absoluteFrom(resolvedModule.resolvedFileName);
249
+ }
250
+ }
251
+ return null;
252
+ }
253
+
254
+ /**
255
+ * Angular compiler file system implementation that leverages an
256
+ * CLI schematic virtual file tree.
257
+ */
258
+ class DevkitMigrationFilesystem {
259
+ tree;
260
+ constructor(tree) {
261
+ this.tree = tree;
262
+ }
263
+ extname(path) {
264
+ return core.extname(path);
265
+ }
266
+ isRoot(path) {
267
+ return path === core.normalize('/');
268
+ }
269
+ isRooted(path) {
270
+ return this.normalize(path).startsWith('/');
271
+ }
272
+ dirname(file) {
273
+ return this.normalize(core.dirname(file));
274
+ }
275
+ join(basePath, ...paths) {
276
+ return this.normalize(core.join(basePath, ...paths));
277
+ }
278
+ relative(from, to) {
279
+ return this.normalize(core.relative(from, to));
280
+ }
281
+ basename(filePath, extension) {
282
+ return posixPath__namespace.basename(filePath, extension);
283
+ }
284
+ normalize(path) {
285
+ return core.normalize(path);
286
+ }
287
+ resolve(...paths) {
288
+ const normalizedPaths = paths.map((p) => core.normalize(p));
289
+ // In dev-kit, the NodeJS working directory should never be
290
+ // considered, so `/` is the last resort over `cwd`.
291
+ return this.normalize(posixPath__namespace.resolve(core.normalize('/'), ...normalizedPaths));
292
+ }
293
+ pwd() {
294
+ return '/';
295
+ }
296
+ isCaseSensitive() {
297
+ return true;
298
+ }
299
+ exists(path) {
300
+ return statPath(this.tree, path) !== null;
301
+ }
302
+ readFile(path) {
303
+ return this.tree.readText(path);
304
+ }
305
+ readFileBuffer(path) {
306
+ const buffer = this.tree.read(path);
307
+ if (buffer === null) {
308
+ throw new Error(`File does not exist: ${path}`);
309
+ }
310
+ return buffer;
311
+ }
312
+ readdir(path) {
313
+ const dir = this.tree.getDir(path);
314
+ return [
315
+ ...dir.subdirs,
316
+ ...dir.subfiles,
317
+ ];
318
+ }
319
+ lstat(path) {
320
+ const stat = statPath(this.tree, path);
321
+ if (stat === null) {
322
+ throw new Error(`File does not exist for "lstat": ${path}`);
323
+ }
324
+ return stat;
325
+ }
326
+ stat(path) {
327
+ const stat = statPath(this.tree, path);
328
+ if (stat === null) {
329
+ throw new Error(`File does not exist for "stat": ${path}`);
330
+ }
331
+ return stat;
332
+ }
333
+ realpath(filePath) {
334
+ return filePath;
335
+ }
336
+ getDefaultLibLocation() {
337
+ return 'node_modules/typescript/lib';
338
+ }
339
+ ensureDir(path) {
340
+ // Migrations should compute replacements and not write directly.
341
+ throw new Error('DevkitFilesystem#ensureDir is not supported.');
342
+ }
343
+ writeFile(path, data) {
344
+ // Migrations should compute replacements and not write directly.
345
+ throw new Error('DevkitFilesystem#writeFile is not supported.');
346
+ }
347
+ removeFile(path) {
348
+ // Migrations should compute replacements and not write directly.
349
+ throw new Error('DevkitFilesystem#removeFile is not supported.');
350
+ }
351
+ copyFile(from, to) {
352
+ // Migrations should compute replacements and not write directly.
353
+ throw new Error('DevkitFilesystem#copyFile is not supported.');
354
+ }
355
+ moveFile(from, to) {
356
+ // Migrations should compute replacements and not write directly.
357
+ throw new Error('DevkitFilesystem#moveFile is not supported.');
358
+ }
359
+ removeDeep(path) {
360
+ // Migrations should compute replacements and not write directly.
361
+ throw new Error('DevkitFilesystem#removeDeep is not supported.');
362
+ }
363
+ chdir(_path) {
364
+ throw new Error('FileSystem#chdir is not supported.');
365
+ }
366
+ symlink() {
367
+ throw new Error('FileSystem#symlink is not supported.');
368
+ }
369
+ }
370
+ /** Stats the given path in the virtual tree. */
371
+ function statPath(tree, path) {
372
+ let fileInfo = null;
373
+ let dirInfo = null;
374
+ try {
375
+ fileInfo = tree.get(path);
376
+ }
377
+ catch (e) {
378
+ if (e.constructor.name === 'PathIsDirectoryException') {
379
+ dirInfo = tree.getDir(path);
380
+ }
381
+ else {
382
+ throw e;
383
+ }
384
+ }
385
+ if (fileInfo !== null || dirInfo !== null) {
386
+ return {
387
+ isDirectory: () => dirInfo !== null,
388
+ isFile: () => fileInfo !== null,
389
+ isSymbolicLink: () => false,
390
+ };
391
+ }
392
+ return null;
393
+ }
394
+
395
+ /**
396
+ * Groups the given replacements per project relative
397
+ * file path.
398
+ *
399
+ * This allows for simple execution of the replacements
400
+ * against a given file. E.g. via {@link applyTextUpdates}.
401
+ */
402
+ function groupReplacementsByFile(replacements) {
403
+ const result = new Map();
404
+ for (const { projectFile, update } of replacements) {
405
+ if (!result.has(projectFile.rootRelativePath)) {
406
+ result.set(projectFile.rootRelativePath, []);
407
+ }
408
+ result.get(projectFile.rootRelativePath).push(update);
409
+ }
410
+ return result;
411
+ }
412
+
413
+ /** Code of the error raised by TypeScript when a tsconfig doesn't match any files. */
414
+ const NO_INPUTS_ERROR_CODE = 18003;
415
+ /**
416
+ * Parses the configuration of the given TypeScript project and creates
417
+ * an instance of the Angular compiler for the project.
418
+ */
419
+ function createNgtscProgram(absoluteTsconfigPath, fs, optionOverrides = {}) {
420
+ if (fs === undefined) {
421
+ fs = new checker.NodeJSFileSystem();
422
+ checker.setFileSystem(fs);
423
+ }
424
+ const tsconfig = readConfiguration(absoluteTsconfigPath, {}, fs);
425
+ // Skip the "No inputs found..." error since we don't want to interrupt the migration if a
426
+ // tsconfig doesn't match a file. This will result in an empty `Program` which is still valid.
427
+ const errors = tsconfig.errors.filter((diag) => diag.code !== NO_INPUTS_ERROR_CODE);
428
+ if (errors.length) {
429
+ throw new Error(`Tsconfig could not be parsed or is invalid:\n\n` + `${errors.map((e) => e.messageText)}`);
430
+ }
431
+ const tsHost = new NgtscCompilerHost(fs, tsconfig.options);
432
+ const ngtscProgram = new program.NgtscProgram(tsconfig.rootNames, {
433
+ ...tsconfig.options,
434
+ // Avoid checking libraries to speed up migrations.
435
+ skipLibCheck: true,
436
+ skipDefaultLibCheck: true,
437
+ noEmit: true,
438
+ // Additional override options.
439
+ ...optionOverrides,
440
+ }, tsHost);
441
+ // Expose an easy way to debug-print ng semantic diagnostics.
442
+ if (process.env['DEBUG_NG_SEMANTIC_DIAGNOSTICS'] === '1') {
443
+ console.error(ts__default["default"].formatDiagnosticsWithColorAndContext(ngtscProgram.getNgSemanticDiagnostics(), tsHost));
444
+ }
445
+ return {
446
+ ngCompiler: ngtscProgram.compiler,
447
+ program: ngtscProgram.getTsProgram(),
448
+ userOptions: tsconfig.options,
449
+ programAbsoluteRootFileNames: tsconfig.rootNames,
450
+ host: tsHost,
451
+ };
452
+ }
453
+
454
+ /**
455
+ * @private
456
+ *
457
+ * Base class for the possible Tsurge migration variants.
458
+ *
459
+ * For example, this class exposes methods to conveniently create
460
+ * TypeScript programs, while also allowing migration authors to override.
461
+ */
462
+ class TsurgeBaseMigration {
463
+ // By default, ngtsc programs are being created.
464
+ createProgram(tsconfigAbsPath, fs) {
465
+ return createNgtscProgram(tsconfigAbsPath, fs);
466
+ }
467
+ // Optional function to prepare the base `ProgramInfo` even further,
468
+ // for the analyze and migrate phases. E.g. determining source files.
469
+ prepareProgram(info) {
470
+ const fullProgramSourceFiles = [...info.program.getSourceFiles()];
471
+ const sourceFiles = fullProgramSourceFiles.filter((f) => !f.isDeclarationFile &&
472
+ // Note `isShim` will work for the initial program, but for TCB programs, the shims are no longer annotated.
473
+ !checker.isShim(f) &&
474
+ !f.fileName.endsWith('.ngtypecheck.ts'));
475
+ // Sort it by length in reverse order (longest first). This speeds up lookups,
476
+ // since there's no need to keep going through the array once a match is found.
477
+ const sortedRootDirs = checker.getRootDirs(info.host, info.userOptions).sort((a, b) => b.length - a.length);
478
+ // TODO: Consider also following TS's logic here, finding the common source root.
479
+ // See: Program#getCommonSourceDirectory.
480
+ const primaryRoot = checker.absoluteFrom(info.userOptions.rootDir ?? sortedRootDirs.at(-1) ?? info.program.getCurrentDirectory());
481
+ return {
482
+ ...info,
483
+ sourceFiles,
484
+ fullProgramSourceFiles,
485
+ sortedRootDirs,
486
+ projectRoot: primaryRoot,
487
+ };
488
+ }
489
+ }
490
+
491
+ /**
492
+ * A simpler variant of a {@link TsurgeComplexMigration} that does not
493
+ * fan-out into multiple workers per compilation unit to compute
494
+ * the final migration replacements.
495
+ *
496
+ * This is faster and less resource intensive as workers and TS programs
497
+ * are only ever created once.
498
+ *
499
+ * This is commonly the case when migrations are refactored to eagerly
500
+ * compute replacements in the analyze stage, and then leverage the
501
+ * global unit data to filter replacements that turned out to be "invalid".
502
+ */
503
+ class TsurgeFunnelMigration extends TsurgeBaseMigration {
504
+ }
505
+ /**
506
+ * Complex variant of a `Tsurge` migration.
507
+ *
508
+ * For example, every analyze worker may contribute to a list of TS
509
+ * references that are later combined. The migrate phase can then compute actual
510
+ * file updates for all individual compilation units, leveraging the global metadata
511
+ * to e.g. see if there are any references from other compilation units that may be
512
+ * problematic and prevent migration of a given file.
513
+ */
514
+ class TsurgeComplexMigration extends TsurgeBaseMigration {
515
+ }
516
+
517
+ /** A text replacement for the given file. */
518
+ class Replacement {
519
+ projectFile;
520
+ update;
521
+ constructor(projectFile, update) {
522
+ this.projectFile = projectFile;
523
+ this.update = update;
524
+ }
525
+ }
526
+ /** An isolated text update that may be applied to a file. */
527
+ class TextUpdate {
528
+ data;
529
+ constructor(data) {
530
+ this.data = data;
531
+ }
532
+ }
533
+
534
+ /** Confirms that the given data `T` is serializable. */
535
+ function confirmAsSerializable(data) {
536
+ return data;
537
+ }
538
+
539
+ /**
540
+ * Gets a project file instance for the given file.
541
+ *
542
+ * Use this helper for dealing with project paths throughout your
543
+ * migration. The return type is serializable.
544
+ *
545
+ * See {@link ProjectFile}.
546
+ */
547
+ function projectFile(file, { sortedRootDirs, projectRoot }) {
548
+ const fs = checker.getFileSystem();
549
+ const filePath = fs.resolve(typeof file === 'string' ? file : file.fileName);
550
+ // Sorted root directories are sorted longest to shortest. First match
551
+ // is the appropriate root directory for ID computation.
552
+ for (const rootDir of sortedRootDirs) {
553
+ if (!isWithinBasePath(fs, rootDir, filePath)) {
554
+ continue;
555
+ }
556
+ return {
557
+ id: fs.relative(rootDir, filePath),
558
+ rootRelativePath: fs.relative(projectRoot, filePath),
559
+ };
560
+ }
561
+ // E.g. project directory may be `src/`, but files may be looked up
562
+ // from `node_modules/`. This is fine, but in those cases, no root
563
+ // directory matches.
564
+ const rootRelativePath = fs.relative(projectRoot, filePath);
565
+ return {
566
+ id: rootRelativePath,
567
+ rootRelativePath: rootRelativePath,
568
+ };
569
+ }
570
+ /**
571
+ * Whether `path` is a descendant of the `base`?
572
+ * E.g. `a/b/c` is within `a/b` but not within `a/x`.
573
+ */
574
+ function isWithinBasePath(fs, base, path) {
575
+ return checker.isLocalRelativePath(fs.relative(base, path));
576
+ }
577
+
578
+ /**
579
+ * Applies import manager changes, and writes them as replacements the
580
+ * given result array.
581
+ */
582
+ function applyImportManagerChanges(importManager, replacements, sourceFiles, info) {
583
+ const { newImports, updatedImports, deletedImports } = importManager.finalize();
584
+ const printer = ts__default["default"].createPrinter({});
585
+ const pathToFile = new Map(sourceFiles.map((s) => [s.fileName, s]));
586
+ // Capture new imports
587
+ newImports.forEach((newImports, fileName) => {
588
+ newImports.forEach((newImport) => {
589
+ const printedImport = printer.printNode(ts__default["default"].EmitHint.Unspecified, newImport, pathToFile.get(fileName));
590
+ replacements.push(new Replacement(projectFile(checker.absoluteFrom(fileName), info), new TextUpdate({ position: 0, end: 0, toInsert: `${printedImport}\n` })));
591
+ });
592
+ });
593
+ // Capture updated imports
594
+ for (const [oldBindings, newBindings] of updatedImports.entries()) {
595
+ // The import will be generated as multi-line if it already is multi-line,
596
+ // or if the number of elements significantly increased and it previously
597
+ // consisted of very few specifiers.
598
+ const isMultiline = oldBindings.getText().includes('\n') ||
599
+ (newBindings.elements.length >= 6 && oldBindings.elements.length <= 3);
600
+ const hasSpaceBetweenBraces = oldBindings.getText().startsWith('{ ');
601
+ let formatFlags = ts__default["default"].ListFormat.NamedImportsOrExportsElements |
602
+ ts__default["default"].ListFormat.Indented |
603
+ ts__default["default"].ListFormat.Braces |
604
+ ts__default["default"].ListFormat.PreserveLines |
605
+ (isMultiline ? ts__default["default"].ListFormat.MultiLine : ts__default["default"].ListFormat.SingleLine);
606
+ if (hasSpaceBetweenBraces) {
607
+ formatFlags |= ts__default["default"].ListFormat.SpaceBetweenBraces;
608
+ }
609
+ else {
610
+ formatFlags &= ~ts__default["default"].ListFormat.SpaceBetweenBraces;
611
+ }
612
+ const printedBindings = printer.printList(formatFlags, newBindings.elements, oldBindings.getSourceFile());
613
+ replacements.push(new Replacement(projectFile(oldBindings.getSourceFile(), info), new TextUpdate({
614
+ position: oldBindings.getStart(),
615
+ end: oldBindings.getEnd(),
616
+ // TS uses four spaces as indent. We migrate to two spaces as we
617
+ // assume this to be more common.
618
+ toInsert: printedBindings.replace(/^ {4}/gm, ' '),
619
+ })));
620
+ }
621
+ // Update removed imports
622
+ for (const removedImport of deletedImports) {
623
+ replacements.push(new Replacement(projectFile(removedImport.getSourceFile(), info), new TextUpdate({
624
+ position: removedImport.getStart(),
625
+ end: removedImport.getEnd(),
626
+ toInsert: '',
627
+ })));
628
+ }
629
+ }
630
+
631
+ function getMemberName(member) {
632
+ if (member.name === undefined) {
633
+ return null;
634
+ }
635
+ if (ts__default["default"].isIdentifier(member.name) || ts__default["default"].isStringLiteralLike(member.name)) {
636
+ return member.name.text;
637
+ }
638
+ if (ts__default["default"].isPrivateIdentifier(member.name)) {
639
+ return `#${member.name.text}`;
640
+ }
641
+ return null;
642
+ }
643
+
644
+ /** Checks whether the given node can be an `@Input()` declaration node. */
645
+ function isInputContainerNode(node) {
646
+ return (((ts__default["default"].isAccessor(node) && ts__default["default"].isClassDeclaration(node.parent)) ||
647
+ ts__default["default"].isPropertyDeclaration(node)) &&
648
+ getMemberName(node) !== null);
649
+ }
650
+
651
+ /**
652
+ * Detects `query(By.directive(T)).componentInstance` patterns and enhances
653
+ * them with information of `T`. This is important because `.componentInstance`
654
+ * is currently typed as `any` and may cause runtime test failures after input
655
+ * migrations then.
656
+ *
657
+ * The reference resolution pass leverages information from this pattern
658
+ * recognizer.
659
+ */
660
+ class DebugElementComponentInstance {
661
+ checker;
662
+ cache = new WeakMap();
663
+ constructor(checker) {
664
+ this.checker = checker;
665
+ }
666
+ detect(node) {
667
+ if (this.cache.has(node)) {
668
+ return this.cache.get(node);
669
+ }
670
+ if (!ts__default["default"].isPropertyAccessExpression(node)) {
671
+ return null;
672
+ }
673
+ // Check for `<>.componentInstance`.
674
+ if (!ts__default["default"].isIdentifier(node.name) || node.name.text !== 'componentInstance') {
675
+ return null;
676
+ }
677
+ // Check for `<>.query(..).<>`.
678
+ if (!ts__default["default"].isCallExpression(node.expression) ||
679
+ !ts__default["default"].isPropertyAccessExpression(node.expression.expression) ||
680
+ !ts__default["default"].isIdentifier(node.expression.expression.name) ||
681
+ node.expression.expression.name.text !== 'query') {
682
+ return null;
683
+ }
684
+ const queryCall = node.expression;
685
+ if (queryCall.arguments.length !== 1) {
686
+ return null;
687
+ }
688
+ const queryArg = queryCall.arguments[0];
689
+ let typeExpr;
690
+ if (ts__default["default"].isCallExpression(queryArg) &&
691
+ queryArg.arguments.length === 1 &&
692
+ ts__default["default"].isIdentifier(queryArg.arguments[0])) {
693
+ // Detect references, like: `query(By.directive(T))`.
694
+ typeExpr = queryArg.arguments[0];
695
+ }
696
+ else if (ts__default["default"].isIdentifier(queryArg)) {
697
+ // Detect references, like: `harness.query(T)`.
698
+ typeExpr = queryArg;
699
+ }
700
+ else {
701
+ return null;
702
+ }
703
+ const symbol = this.checker.getSymbolAtLocation(typeExpr);
704
+ if (symbol?.valueDeclaration === undefined ||
705
+ !ts__default["default"].isClassDeclaration(symbol?.valueDeclaration)) {
706
+ // Cache this as we use the expensive type checker.
707
+ this.cache.set(node, null);
708
+ return null;
709
+ }
710
+ const type = this.checker.getTypeAtLocation(symbol.valueDeclaration);
711
+ this.cache.set(node, type);
712
+ return type;
713
+ }
714
+ }
715
+
716
+ /**
717
+ * Recognizes `Partial<T>` instances in Catalyst tests. Those type queries
718
+ * are likely used for typing property initialization values for the given class `T`
719
+ * and we have a few scenarios:
720
+ *
721
+ * 1. The API does not unwrap signal inputs. In which case, the values are likely no
722
+ * longer assignable to an `InputSignal`.
723
+ * 2. The API does unwrap signal inputs, in which case we need to unwrap the `Partial`
724
+ * because the values are raw initial values, like they were before.
725
+ *
726
+ * We can enable this heuristic when we detect Catalyst as we know it supports unwrapping.
727
+ */
728
+ class PartialDirectiveTypeInCatalystTests {
729
+ checker;
730
+ knownFields;
731
+ constructor(checker, knownFields) {
732
+ this.checker = checker;
733
+ this.knownFields = knownFields;
734
+ }
735
+ detect(node) {
736
+ // Detect `Partial<...>`
737
+ if (!ts__default["default"].isTypeReferenceNode(node) ||
738
+ !ts__default["default"].isIdentifier(node.typeName) ||
739
+ node.typeName.text !== 'Partial') {
740
+ return null;
741
+ }
742
+ // Ignore if the source file doesn't reference Catalyst.
743
+ if (!node.getSourceFile().text.includes('angular2/testing/catalyst')) {
744
+ return null;
745
+ }
746
+ // Extract T of `Partial<T>`.
747
+ const cmpTypeArg = node.typeArguments?.[0];
748
+ if (!cmpTypeArg ||
749
+ !ts__default["default"].isTypeReferenceNode(cmpTypeArg) ||
750
+ !ts__default["default"].isIdentifier(cmpTypeArg.typeName)) {
751
+ return null;
752
+ }
753
+ const cmpType = cmpTypeArg.typeName;
754
+ const symbol = this.checker.getSymbolAtLocation(cmpType);
755
+ // Note: Technically the class might be derived of an input-containing class,
756
+ // but this is out of scope for now. We can expand if we see it's a common case.
757
+ if (symbol?.valueDeclaration === undefined ||
758
+ !ts__default["default"].isClassDeclaration(symbol.valueDeclaration) ||
759
+ !this.knownFields.shouldTrackClassReference(symbol.valueDeclaration)) {
760
+ return null;
761
+ }
762
+ return { referenceNode: node, targetClass: symbol.valueDeclaration };
763
+ }
764
+ }
765
+
766
+ /**
767
+ * Attempts to look up the given property access chain using
768
+ * the type checker.
769
+ *
770
+ * Notably this is not as safe as using the type checker directly to
771
+ * retrieve symbols of a given identifier, but in some cases this is
772
+ * a necessary approach to compensate e.g. for a lack of TCB information
773
+ * when processing Angular templates.
774
+ *
775
+ * The path is a list of properties to be accessed sequentially on the
776
+ * given type.
777
+ */
778
+ function lookupPropertyAccess(checker, type, path, options = {}) {
779
+ let symbol = null;
780
+ for (const propName of path) {
781
+ // Note: We support assuming `NonNullable` for the pathl This is necessary
782
+ // in some situations as otherwise the lookups would fail to resolve the target
783
+ // symbol just because of e.g. a ternary. This is used in the signal input migration
784
+ // for host bindings.
785
+ type = options.ignoreNullability ? type.getNonNullableType() : type;
786
+ const propSymbol = type.getProperty(propName);
787
+ if (propSymbol === undefined) {
788
+ return null;
789
+ }
790
+ symbol = propSymbol;
791
+ type = checker.getTypeOfSymbol(propSymbol);
792
+ }
793
+ if (symbol === null) {
794
+ return null;
795
+ }
796
+ return { symbol, type };
797
+ }
798
+
799
+ /**
800
+ * AST visitor that iterates through a template and finds all
801
+ * input references.
802
+ *
803
+ * This resolution is important to be able to migrate references to inputs
804
+ * that will be migrated to signal inputs.
805
+ */
806
+ class TemplateReferenceVisitor extends checker.RecursiveVisitor$1 {
807
+ result = [];
808
+ /**
809
+ * Whether we are currently descending into HTML AST nodes
810
+ * where all bound attributes are considered potentially narrowing.
811
+ *
812
+ * Keeps track of all referenced inputs in such attribute expressions.
813
+ */
814
+ templateAttributeReferencedFields = null;
815
+ expressionVisitor;
816
+ seenKnownFieldsCount = new Map();
817
+ constructor(typeChecker, templateTypeChecker, componentClass, knownFields, fieldNamesToConsiderForReferenceLookup) {
818
+ super();
819
+ this.expressionVisitor = new TemplateExpressionReferenceVisitor(typeChecker, templateTypeChecker, componentClass, knownFields, fieldNamesToConsiderForReferenceLookup);
820
+ }
821
+ checkExpressionForReferencedFields(activeNode, expressionNode) {
822
+ const referencedFields = this.expressionVisitor.checkTemplateExpression(activeNode, expressionNode);
823
+ // Add all references to the overall visitor result.
824
+ this.result.push(...referencedFields);
825
+ // Count usages of seen input references. We'll use this to make decisions
826
+ // based on whether inputs are potentially narrowed or not.
827
+ for (const input of referencedFields) {
828
+ this.seenKnownFieldsCount.set(input.targetField.key, (this.seenKnownFieldsCount.get(input.targetField.key) ?? 0) + 1);
829
+ }
830
+ return referencedFields;
831
+ }
832
+ descendAndCheckForNarrowedSimilarReferences(potentiallyNarrowedInputs, descend) {
833
+ const inputs = potentiallyNarrowedInputs.map((i) => ({
834
+ ref: i,
835
+ key: i.targetField.key,
836
+ pastCount: this.seenKnownFieldsCount.get(i.targetField.key) ?? 0,
837
+ }));
838
+ descend();
839
+ for (const input of inputs) {
840
+ // Input was referenced inside a narrowable spot, and is used in child nodes.
841
+ // This is a sign for the input to be narrowed. Mark it as such.
842
+ if ((this.seenKnownFieldsCount.get(input.key) ?? 0) > input.pastCount) {
843
+ input.ref.isLikelyNarrowed = true;
844
+ }
845
+ }
846
+ }
847
+ visitTemplate(template) {
848
+ // Note: We assume all bound expressions for templates may be subject
849
+ // to TCB narrowing. This is relevant for now until we support narrowing
850
+ // of signal calls in templates.
851
+ // TODO: Remove with: https://github.com/angular/angular/pull/55456.
852
+ this.templateAttributeReferencedFields = [];
853
+ checker.visitAll$1(this, template.attributes);
854
+ checker.visitAll$1(this, template.templateAttrs);
855
+ // If we are dealing with a microsyntax template, do not check
856
+ // inputs and outputs as those are already passed to the children.
857
+ // Template attributes may contain relevant expressions though.
858
+ if (template.tagName === 'ng-template') {
859
+ checker.visitAll$1(this, template.inputs);
860
+ checker.visitAll$1(this, template.outputs);
861
+ }
862
+ const referencedInputs = this.templateAttributeReferencedFields;
863
+ this.templateAttributeReferencedFields = null;
864
+ this.descendAndCheckForNarrowedSimilarReferences(referencedInputs, () => {
865
+ checker.visitAll$1(this, template.children);
866
+ checker.visitAll$1(this, template.references);
867
+ checker.visitAll$1(this, template.variables);
868
+ });
869
+ }
870
+ visitIfBlockBranch(block) {
871
+ if (block.expression) {
872
+ const referencedFields = this.checkExpressionForReferencedFields(block, block.expression);
873
+ this.descendAndCheckForNarrowedSimilarReferences(referencedFields, () => {
874
+ super.visitIfBlockBranch(block);
875
+ });
876
+ }
877
+ else {
878
+ super.visitIfBlockBranch(block);
879
+ }
880
+ }
881
+ visitForLoopBlock(block) {
882
+ this.checkExpressionForReferencedFields(block, block.expression);
883
+ this.checkExpressionForReferencedFields(block, block.trackBy);
884
+ super.visitForLoopBlock(block);
885
+ }
886
+ visitSwitchBlock(block) {
887
+ const referencedFields = this.checkExpressionForReferencedFields(block, block.expression);
888
+ this.descendAndCheckForNarrowedSimilarReferences(referencedFields, () => {
889
+ super.visitSwitchBlock(block);
890
+ });
891
+ }
892
+ visitSwitchBlockCase(block) {
893
+ if (block.expression) {
894
+ const referencedFields = this.checkExpressionForReferencedFields(block, block.expression);
895
+ this.descendAndCheckForNarrowedSimilarReferences(referencedFields, () => {
896
+ super.visitSwitchBlockCase(block);
897
+ });
898
+ }
899
+ else {
900
+ super.visitSwitchBlockCase(block);
901
+ }
902
+ }
903
+ visitDeferredBlock(deferred) {
904
+ if (deferred.triggers.when) {
905
+ this.checkExpressionForReferencedFields(deferred, deferred.triggers.when.value);
906
+ }
907
+ if (deferred.prefetchTriggers.when) {
908
+ this.checkExpressionForReferencedFields(deferred, deferred.prefetchTriggers.when.value);
909
+ }
910
+ super.visitDeferredBlock(deferred);
911
+ }
912
+ visitBoundText(text) {
913
+ this.checkExpressionForReferencedFields(text, text.value);
914
+ }
915
+ visitBoundEvent(attribute) {
916
+ this.checkExpressionForReferencedFields(attribute, attribute.handler);
917
+ }
918
+ visitBoundAttribute(attribute) {
919
+ const referencedFields = this.checkExpressionForReferencedFields(attribute, attribute.value);
920
+ // Attributes inside templates are potentially "narrowed" and hence we
921
+ // keep track of all referenced inputs to see if they actually are.
922
+ if (this.templateAttributeReferencedFields !== null) {
923
+ this.templateAttributeReferencedFields.push(...referencedFields);
924
+ }
925
+ }
926
+ }
927
+ /**
928
+ * Expression AST visitor that checks whether a given expression references
929
+ * a known `@Input()`.
930
+ *
931
+ * This resolution is important to be able to migrate references to inputs
932
+ * that will be migrated to signal inputs.
933
+ */
934
+ class TemplateExpressionReferenceVisitor extends checker.RecursiveAstVisitor$1 {
935
+ typeChecker;
936
+ templateTypeChecker;
937
+ componentClass;
938
+ knownFields;
939
+ fieldNamesToConsiderForReferenceLookup;
940
+ activeTmplAstNode = null;
941
+ detectedInputReferences = [];
942
+ isInsideObjectShorthandExpression = false;
943
+ insideConditionalExpressionsWithReads = [];
944
+ constructor(typeChecker, templateTypeChecker, componentClass, knownFields, fieldNamesToConsiderForReferenceLookup) {
945
+ super();
946
+ this.typeChecker = typeChecker;
947
+ this.templateTypeChecker = templateTypeChecker;
948
+ this.componentClass = componentClass;
949
+ this.knownFields = knownFields;
950
+ this.fieldNamesToConsiderForReferenceLookup = fieldNamesToConsiderForReferenceLookup;
951
+ }
952
+ /** Checks the given AST expression. */
953
+ checkTemplateExpression(activeNode, expressionNode) {
954
+ this.detectedInputReferences = [];
955
+ this.activeTmplAstNode = activeNode;
956
+ expressionNode.visit(this, []);
957
+ return this.detectedInputReferences;
958
+ }
959
+ visit(ast, context) {
960
+ super.visit(ast, [...context, ast]);
961
+ }
962
+ // Keep track when we are inside an object shorthand expression. This is
963
+ // necessary as we need to expand the shorthand to invoke a potential new signal.
964
+ // E.g. `{bla}` may be transformed to `{bla: bla()}`.
965
+ visitLiteralMap(ast, context) {
966
+ for (const [idx, key] of ast.keys.entries()) {
967
+ this.isInsideObjectShorthandExpression = !!key.isShorthandInitialized;
968
+ ast.values[idx].visit(this, context);
969
+ this.isInsideObjectShorthandExpression = false;
970
+ }
971
+ }
972
+ visitPropertyRead(ast, context) {
973
+ this._inspectPropertyAccess(ast, context);
974
+ super.visitPropertyRead(ast, context);
975
+ }
976
+ visitSafePropertyRead(ast, context) {
977
+ this._inspectPropertyAccess(ast, context);
978
+ super.visitPropertyRead(ast, context);
979
+ }
980
+ visitPropertyWrite(ast, context) {
981
+ this._inspectPropertyAccess(ast, context);
982
+ super.visitPropertyWrite(ast, context);
983
+ }
984
+ visitConditional(ast, context) {
985
+ this.visit(ast.condition, context);
986
+ this.insideConditionalExpressionsWithReads.push(ast.condition);
987
+ this.visit(ast.trueExp, context);
988
+ this.visit(ast.falseExp, context);
989
+ this.insideConditionalExpressionsWithReads.pop();
990
+ }
991
+ /**
992
+ * Inspects the property access and attempts to resolve whether they access
993
+ * a known field. If so, the result is captured.
994
+ */
995
+ _inspectPropertyAccess(ast, astPath) {
996
+ if (this.fieldNamesToConsiderForReferenceLookup !== null &&
997
+ !this.fieldNamesToConsiderForReferenceLookup.has(ast.name)) {
998
+ return;
999
+ }
1000
+ const isWrite = !!(ast instanceof checker.PropertyWrite ||
1001
+ (this.activeTmplAstNode && isTwoWayBindingNode(this.activeTmplAstNode)));
1002
+ this._checkAccessViaTemplateTypeCheckBlock(ast, isWrite, astPath) ||
1003
+ this._checkAccessViaOwningComponentClassType(ast, isWrite, astPath);
1004
+ }
1005
+ /**
1006
+ * Checks whether the node refers to an input using the TCB information.
1007
+ * Type check block may not exist for e.g. test components, so this can return `null`.
1008
+ */
1009
+ _checkAccessViaTemplateTypeCheckBlock(ast, isWrite, astPath) {
1010
+ // There might be no template type checker. E.g. if we check host bindings.
1011
+ if (this.templateTypeChecker === null) {
1012
+ return false;
1013
+ }
1014
+ const symbol = this.templateTypeChecker.getSymbolOfNode(ast, this.componentClass);
1015
+ if (symbol?.kind !== checker.SymbolKind.Expression || symbol.tsSymbol === null) {
1016
+ return false;
1017
+ }
1018
+ // Dangerous: Type checking symbol retrieval is a totally different `ts.Program`,
1019
+ // than the one where we analyzed `knownInputs`.
1020
+ // --> Find the input via its input id.
1021
+ const targetInput = this.knownFields.attemptRetrieveDescriptorFromSymbol(symbol.tsSymbol);
1022
+ if (targetInput === null) {
1023
+ return false;
1024
+ }
1025
+ this.detectedInputReferences.push({
1026
+ targetNode: targetInput.node,
1027
+ targetField: targetInput,
1028
+ read: ast,
1029
+ readAstPath: astPath,
1030
+ context: this.activeTmplAstNode,
1031
+ isLikelyNarrowed: this._isPartOfNarrowingTernary(ast),
1032
+ isObjectShorthandExpression: this.isInsideObjectShorthandExpression,
1033
+ isWrite,
1034
+ });
1035
+ return true;
1036
+ }
1037
+ /**
1038
+ * Simple resolution checking whether the given AST refers to a known input.
1039
+ * This is a fallback for when there is no type checking information (e.g. in host bindings).
1040
+ *
1041
+ * It attempts to resolve references by traversing accesses of the "component class" type.
1042
+ * e.g. `this.bla` is resolved via `CompType#bla` and further.
1043
+ */
1044
+ _checkAccessViaOwningComponentClassType(ast, isWrite, astPath) {
1045
+ // We might check host bindings, which can never point to template variables or local refs.
1046
+ const expressionTemplateTarget = this.templateTypeChecker === null
1047
+ ? null
1048
+ : this.templateTypeChecker.getExpressionTarget(ast, this.componentClass);
1049
+ // Skip checking if:
1050
+ // - the reference resolves to a template variable or local ref. No way to resolve without TCB.
1051
+ // - the owning component does not have a name (should not happen technically).
1052
+ if (expressionTemplateTarget !== null || this.componentClass.name === undefined) {
1053
+ return;
1054
+ }
1055
+ const property = traverseReceiverAndLookupSymbol(ast, this.componentClass, this.typeChecker);
1056
+ if (property === null) {
1057
+ return;
1058
+ }
1059
+ const matchingTarget = this.knownFields.attemptRetrieveDescriptorFromSymbol(property);
1060
+ if (matchingTarget === null) {
1061
+ return;
1062
+ }
1063
+ this.detectedInputReferences.push({
1064
+ targetNode: matchingTarget.node,
1065
+ targetField: matchingTarget,
1066
+ read: ast,
1067
+ readAstPath: astPath,
1068
+ context: this.activeTmplAstNode,
1069
+ isLikelyNarrowed: this._isPartOfNarrowingTernary(ast),
1070
+ isObjectShorthandExpression: this.isInsideObjectShorthandExpression,
1071
+ isWrite,
1072
+ });
1073
+ }
1074
+ _isPartOfNarrowingTernary(read) {
1075
+ // Note: We do not safe check that the reads are fully matching 1:1. This is acceptable
1076
+ // as worst case we just skip an input from being migrated. This is very unlikely too.
1077
+ return this.insideConditionalExpressionsWithReads.some((r) => (r instanceof checker.PropertyRead ||
1078
+ r instanceof checker.PropertyWrite ||
1079
+ r instanceof checker.SafePropertyRead) &&
1080
+ r.name === read.name);
1081
+ }
1082
+ }
1083
+ /**
1084
+ * Emulates an access to a given field using the TypeScript `ts.Type`
1085
+ * of the given class. The resolved symbol of the access is returned.
1086
+ */
1087
+ function traverseReceiverAndLookupSymbol(readOrWrite, componentClass, checker$1) {
1088
+ const path = [readOrWrite.name];
1089
+ let node = readOrWrite;
1090
+ while (node.receiver instanceof checker.PropertyRead || node.receiver instanceof checker.PropertyWrite) {
1091
+ node = node.receiver;
1092
+ path.unshift(node.name);
1093
+ }
1094
+ if (!(node.receiver instanceof checker.ImplicitReceiver || node.receiver instanceof checker.ThisReceiver)) {
1095
+ return null;
1096
+ }
1097
+ const classType = checker$1.getTypeAtLocation(componentClass.name);
1098
+ return (lookupPropertyAccess(checker$1, classType, path, {
1099
+ // Necessary to avoid breaking the resolution if there is
1100
+ // some narrowing involved. E.g. `myClass ? myClass.input`.
1101
+ ignoreNullability: true,
1102
+ })?.symbol ?? null);
1103
+ }
1104
+ /** Whether the given node refers to a two-way binding AST node. */
1105
+ function isTwoWayBindingNode(node) {
1106
+ return ((node instanceof checker.BoundAttribute && node.type === checker.BindingType.TwoWay) ||
1107
+ (node instanceof checker.BoundEvent && node.type === checker.ParsedEventType.TwoWay));
1108
+ }
1109
+
1110
+ /** Possible types of references to known fields detected. */
1111
+ exports.ReferenceKind = void 0;
1112
+ (function (ReferenceKind) {
1113
+ ReferenceKind[ReferenceKind["InTemplate"] = 0] = "InTemplate";
1114
+ ReferenceKind[ReferenceKind["InHostBinding"] = 1] = "InHostBinding";
1115
+ ReferenceKind[ReferenceKind["TsReference"] = 2] = "TsReference";
1116
+ ReferenceKind[ReferenceKind["TsClassTypeReference"] = 3] = "TsClassTypeReference";
1117
+ })(exports.ReferenceKind || (exports.ReferenceKind = {}));
1118
+ /** Whether the given reference is a TypeScript reference. */
1119
+ function isTsReference(ref) {
1120
+ return ref.kind === exports.ReferenceKind.TsReference;
1121
+ }
1122
+ /** Whether the given reference is a template reference. */
1123
+ function isTemplateReference(ref) {
1124
+ return ref.kind === exports.ReferenceKind.InTemplate;
1125
+ }
1126
+ /** Whether the given reference is a host binding reference. */
1127
+ function isHostBindingReference(ref) {
1128
+ return ref.kind === exports.ReferenceKind.InHostBinding;
1129
+ }
1130
+ /**
1131
+ * Whether the given reference is a TypeScript `ts.Type` reference
1132
+ * to a class containing known fields.
1133
+ */
1134
+ function isTsClassTypeReference(ref) {
1135
+ return ref.kind === exports.ReferenceKind.TsClassTypeReference;
1136
+ }
1137
+
1138
+ /**
1139
+ * Checks host bindings of the given class and tracks all
1140
+ * references to inputs within bindings.
1141
+ */
1142
+ function identifyHostBindingReferences(node, programInfo, checker$1, reflector, result, knownFields, fieldNamesToConsiderForReferenceLookup) {
1143
+ if (node.name === undefined) {
1144
+ return;
1145
+ }
1146
+ const decorators = reflector.getDecoratorsOfDeclaration(node);
1147
+ if (decorators === null) {
1148
+ return;
1149
+ }
1150
+ const angularDecorators = checker.getAngularDecorators(decorators, ['Directive', 'Component'],
1151
+ /* isAngularCore */ false);
1152
+ if (angularDecorators.length === 0) {
1153
+ return;
1154
+ }
1155
+ // Assume only one Angular decorator per class.
1156
+ const ngDecorator = angularDecorators[0];
1157
+ if (ngDecorator.args?.length !== 1) {
1158
+ return;
1159
+ }
1160
+ const metadataNode = checker.unwrapExpression(ngDecorator.args[0]);
1161
+ if (!ts__default["default"].isObjectLiteralExpression(metadataNode)) {
1162
+ return;
1163
+ }
1164
+ const metadata = checker.reflectObjectLiteral(metadataNode);
1165
+ if (!metadata.has('host')) {
1166
+ return;
1167
+ }
1168
+ let hostField = checker.unwrapExpression(metadata.get('host'));
1169
+ // Special-case in case host bindings are shared via a variable.
1170
+ // e.g. Material button shares host bindings as a constant in the same target.
1171
+ if (ts__default["default"].isIdentifier(hostField)) {
1172
+ let symbol = checker$1.getSymbolAtLocation(hostField);
1173
+ // Plain identifier references can point to alias symbols (e.g. imports).
1174
+ if (symbol !== undefined && symbol.flags & ts__default["default"].SymbolFlags.Alias) {
1175
+ symbol = checker$1.getAliasedSymbol(symbol);
1176
+ }
1177
+ if (symbol !== undefined &&
1178
+ symbol.valueDeclaration !== undefined &&
1179
+ ts__default["default"].isVariableDeclaration(symbol.valueDeclaration)) {
1180
+ hostField = symbol?.valueDeclaration.initializer;
1181
+ }
1182
+ }
1183
+ if (hostField === undefined || !ts__default["default"].isObjectLiteralExpression(hostField)) {
1184
+ return;
1185
+ }
1186
+ const hostMap = checker.reflectObjectLiteral(hostField);
1187
+ const expressionResult = [];
1188
+ const expressionVisitor = new TemplateExpressionReferenceVisitor(checker$1, null, node, knownFields, fieldNamesToConsiderForReferenceLookup);
1189
+ for (const [rawName, expression] of hostMap.entries()) {
1190
+ if (!ts__default["default"].isStringLiteralLike(expression)) {
1191
+ continue;
1192
+ }
1193
+ const isEventBinding = rawName.startsWith('(');
1194
+ const isPropertyBinding = rawName.startsWith('[');
1195
+ // Only migrate property or event bindings.
1196
+ if (!isPropertyBinding && !isEventBinding) {
1197
+ continue;
1198
+ }
1199
+ const parser = checker.makeBindingParser();
1200
+ const sourceSpan = new checker.ParseSourceSpan(
1201
+ // Fake source span to keep parsing offsets zero-based.
1202
+ // We then later combine these with the expression TS node offsets.
1203
+ new checker.ParseLocation({ content: '', url: '' }, 0, 0, 0), new checker.ParseLocation({ content: '', url: '' }, 0, 0, 0));
1204
+ const name = rawName.substring(1, rawName.length - 1);
1205
+ let parsed = undefined;
1206
+ if (isEventBinding) {
1207
+ const result = [];
1208
+ parser.parseEvent(name.substring(1, name.length - 1), expression.text, false, sourceSpan, sourceSpan, [], result, sourceSpan);
1209
+ parsed = result[0].handler;
1210
+ }
1211
+ else {
1212
+ const result = [];
1213
+ parser.parsePropertyBinding(name, expression.text, true,
1214
+ /* isTwoWayBinding */ false, sourceSpan, 0, sourceSpan, [], result, sourceSpan);
1215
+ parsed = result[0].expression;
1216
+ }
1217
+ if (parsed != null) {
1218
+ expressionResult.push(...expressionVisitor.checkTemplateExpression(expression, parsed));
1219
+ }
1220
+ }
1221
+ for (const ref of expressionResult) {
1222
+ result.references.push({
1223
+ kind: exports.ReferenceKind.InHostBinding,
1224
+ from: {
1225
+ read: ref.read,
1226
+ readAstPath: ref.readAstPath,
1227
+ isObjectShorthandExpression: ref.isObjectShorthandExpression,
1228
+ isWrite: ref.isWrite,
1229
+ file: projectFile(ref.context.getSourceFile(), programInfo),
1230
+ hostPropertyNode: ref.context,
1231
+ },
1232
+ target: ref.targetField,
1233
+ });
1234
+ }
1235
+ }
1236
+
1237
+ /**
1238
+ * Attempts to extract the `TemplateDefinition` for the given
1239
+ * class, if possible.
1240
+ *
1241
+ * The definition can then be used with the Angular compiler to
1242
+ * load/parse the given template.
1243
+ */
1244
+ function attemptExtractTemplateDefinition(node, checker$1, reflector, resourceLoader) {
1245
+ const classDecorators = reflector.getDecoratorsOfDeclaration(node);
1246
+ const evaluator = new program.PartialEvaluator(reflector, checker$1, null);
1247
+ const ngDecorators = classDecorators !== null
1248
+ ? checker.getAngularDecorators(classDecorators, ['Component'], /* isAngularCore */ false)
1249
+ : [];
1250
+ if (ngDecorators.length === 0 ||
1251
+ ngDecorators[0].args === null ||
1252
+ ngDecorators[0].args.length === 0 ||
1253
+ !ts__default["default"].isObjectLiteralExpression(ngDecorators[0].args[0])) {
1254
+ return null;
1255
+ }
1256
+ const properties = checker.reflectObjectLiteral(ngDecorators[0].args[0]);
1257
+ const templateProp = properties.get('template');
1258
+ const templateUrlProp = properties.get('templateUrl');
1259
+ const containingFile = node.getSourceFile().fileName;
1260
+ // inline template.
1261
+ if (templateProp !== undefined) {
1262
+ const templateStr = evaluator.evaluate(templateProp);
1263
+ if (typeof templateStr === 'string') {
1264
+ return {
1265
+ isInline: true,
1266
+ expression: templateProp,
1267
+ interpolationConfig: checker.DEFAULT_INTERPOLATION_CONFIG,
1268
+ preserveWhitespaces: false,
1269
+ resolvedTemplateUrl: containingFile,
1270
+ templateUrl: containingFile,
1271
+ };
1272
+ }
1273
+ }
1274
+ try {
1275
+ // external template.
1276
+ if (templateUrlProp !== undefined) {
1277
+ const templateUrl = evaluator.evaluate(templateUrlProp);
1278
+ if (typeof templateUrl === 'string') {
1279
+ return {
1280
+ isInline: false,
1281
+ interpolationConfig: checker.DEFAULT_INTERPOLATION_CONFIG,
1282
+ preserveWhitespaces: false,
1283
+ templateUrlExpression: templateUrlProp,
1284
+ templateUrl,
1285
+ resolvedTemplateUrl: resourceLoader.resolve(templateUrl, containingFile),
1286
+ };
1287
+ }
1288
+ }
1289
+ }
1290
+ catch (e) {
1291
+ console.error(`Could not parse external template: ${e}`);
1292
+ }
1293
+ return null;
1294
+ }
1295
+
1296
+ /**
1297
+ * Checks whether the given class has an Angular template, and resolves
1298
+ * all of the references to inputs.
1299
+ */
1300
+ function identifyTemplateReferences(programInfo, node, reflector, checker$1, evaluator, templateTypeChecker, resourceLoader, options, result, knownFields, fieldNamesToConsiderForReferenceLookup) {
1301
+ const template = templateTypeChecker.getTemplate(node, checker.OptimizeFor.WholeProgram) ??
1302
+ // If there is no template registered in the TCB or compiler, the template may
1303
+ // be skipped due to an explicit `jit: true` setting. We try to detect this case
1304
+ // and parse the template manually.
1305
+ extractTemplateWithoutCompilerAnalysis(node, checker$1, reflector, resourceLoader, evaluator, options);
1306
+ if (template !== null) {
1307
+ const visitor = new TemplateReferenceVisitor(checker$1, templateTypeChecker, node, knownFields, fieldNamesToConsiderForReferenceLookup);
1308
+ template.forEach((node) => node.visit(visitor));
1309
+ for (const res of visitor.result) {
1310
+ const templateFilePath = res.context.sourceSpan.start.file.url;
1311
+ // Templates without an URL are non-mappable artifacts of e.g.
1312
+ // string concatenated templates. See the `indirect` template
1313
+ // source mapping concept in the compiler. We skip such references
1314
+ // as those cannot be migrated, but print an error for now.
1315
+ if (templateFilePath === '') {
1316
+ // TODO: Incorporate a TODO potentially.
1317
+ console.error(`Found reference to field ${res.targetField.key} that cannot be ` +
1318
+ `migrated because the template cannot be parsed with source map information ` +
1319
+ `(in file: ${node.getSourceFile().fileName}).`);
1320
+ continue;
1321
+ }
1322
+ result.references.push({
1323
+ kind: exports.ReferenceKind.InTemplate,
1324
+ from: {
1325
+ read: res.read,
1326
+ readAstPath: res.readAstPath,
1327
+ node: res.context,
1328
+ isObjectShorthandExpression: res.isObjectShorthandExpression,
1329
+ originatingTsFile: projectFile(node.getSourceFile(), programInfo),
1330
+ templateFile: projectFile(checker.absoluteFrom(templateFilePath), programInfo),
1331
+ isLikelyPartOfNarrowing: res.isLikelyNarrowed,
1332
+ isWrite: res.isWrite,
1333
+ },
1334
+ target: res.targetField,
1335
+ });
1336
+ }
1337
+ }
1338
+ }
1339
+ /**
1340
+ * Attempts to extract a `@Component` template from the given class,
1341
+ * without relying on the `NgCompiler` program analysis.
1342
+ *
1343
+ * This is useful for JIT components using `jit: true` which were not
1344
+ * processed by the Angular compiler, but may still have templates that
1345
+ * contain references to inputs that we can resolve via the fallback
1346
+ * reference resolutions (that does not use the type check block).
1347
+ */
1348
+ function extractTemplateWithoutCompilerAnalysis(node, checker$1, reflector, resourceLoader, evaluator, options) {
1349
+ if (node.name === undefined) {
1350
+ return null;
1351
+ }
1352
+ const tmplDef = attemptExtractTemplateDefinition(node, checker$1, reflector, resourceLoader);
1353
+ if (tmplDef === null) {
1354
+ return null;
1355
+ }
1356
+ return program.extractTemplate(node, tmplDef, evaluator, null, resourceLoader, {
1357
+ enableBlockSyntax: true,
1358
+ enableLetSyntax: true,
1359
+ usePoisonedData: true,
1360
+ enableI18nLegacyMessageIdFormat: options.enableI18nLegacyMessageIdFormat !== false,
1361
+ i18nNormalizeLineEndingsInICUs: options.i18nNormalizeLineEndingsInICUs === true,
1362
+ }, checker.CompilationMode.FULL).nodes;
1363
+ }
1364
+
1365
+ /** Gets the pattern and property name for a given binding element. */
1366
+ function resolveBindingElement(node) {
1367
+ const name = node.propertyName ?? node.name;
1368
+ // If we are discovering a non-analyzable element in the path, abort.
1369
+ if (!ts__default["default"].isStringLiteralLike(name) && !ts__default["default"].isIdentifier(name)) {
1370
+ return null;
1371
+ }
1372
+ return {
1373
+ pattern: node.parent,
1374
+ propertyName: name.text,
1375
+ };
1376
+ }
1377
+ /** Gets the declaration node of the given binding element. */
1378
+ function getBindingElementDeclaration(node) {
1379
+ while (true) {
1380
+ if (ts__default["default"].isBindingElement(node.parent.parent)) {
1381
+ node = node.parent.parent;
1382
+ }
1383
+ else {
1384
+ return node.parent.parent;
1385
+ }
1386
+ }
1387
+ }
1388
+
1389
+ /**
1390
+ * Expands the given reference to its containing expression, capturing
1391
+ * the full context.
1392
+ *
1393
+ * E.g. `traverseAccess(ref<`bla`>)` may return `this.bla`
1394
+ * or `traverseAccess(ref<`bla`>)` may return `this.someObj.a.b.c.bla`.
1395
+ *
1396
+ * This helper is useful as we will replace the full access with a temporary
1397
+ * variable for narrowing. Replacing just the identifier is wrong.
1398
+ */
1399
+ function traverseAccess(access) {
1400
+ if (ts__default["default"].isPropertyAccessExpression(access.parent) && access.parent.name === access) {
1401
+ return access.parent;
1402
+ }
1403
+ else if (ts__default["default"].isElementAccessExpression(access.parent) &&
1404
+ access.parent.argumentExpression === access) {
1405
+ return access.parent;
1406
+ }
1407
+ return access;
1408
+ }
1409
+
1410
+ /**
1411
+ * Unwraps the parent of the given node, if it's a
1412
+ * parenthesized expression or `as` expression.
1413
+ */
1414
+ function unwrapParent(node) {
1415
+ if (ts__default["default"].isParenthesizedExpression(node.parent)) {
1416
+ return unwrapParent(node.parent);
1417
+ }
1418
+ else if (ts__default["default"].isAsExpression(node.parent)) {
1419
+ return unwrapParent(node.parent);
1420
+ }
1421
+ return node;
1422
+ }
1423
+
1424
+ /**
1425
+ * List of binary operators that indicate a write operation.
1426
+ *
1427
+ * Useful for figuring out whether an expression assigns to
1428
+ * something or not.
1429
+ */
1430
+ const writeBinaryOperators = [
1431
+ ts__default["default"].SyntaxKind.EqualsToken,
1432
+ ts__default["default"].SyntaxKind.BarBarEqualsToken,
1433
+ ts__default["default"].SyntaxKind.BarEqualsToken,
1434
+ ts__default["default"].SyntaxKind.AmpersandEqualsToken,
1435
+ ts__default["default"].SyntaxKind.AmpersandAmpersandEqualsToken,
1436
+ ts__default["default"].SyntaxKind.SlashEqualsToken,
1437
+ ts__default["default"].SyntaxKind.MinusEqualsToken,
1438
+ ts__default["default"].SyntaxKind.PlusEqualsToken,
1439
+ ts__default["default"].SyntaxKind.CaretEqualsToken,
1440
+ ts__default["default"].SyntaxKind.PercentEqualsToken,
1441
+ ts__default["default"].SyntaxKind.AsteriskEqualsToken,
1442
+ ts__default["default"].SyntaxKind.ExclamationEqualsToken,
1443
+ ];
1444
+
1445
+ /**
1446
+ * Checks whether given TypeScript reference refers to an Angular input, and captures
1447
+ * the reference if possible.
1448
+ *
1449
+ * @param fieldNamesToConsiderForReferenceLookup List of field names that should be
1450
+ * respected when expensively looking up references to known fields.
1451
+ * May be null if all identifiers should be inspected.
1452
+ */
1453
+ function identifyPotentialTypeScriptReference(node, programInfo, checker, knownFields, result, fieldNamesToConsiderForReferenceLookup, advisors) {
1454
+ // Skip all identifiers that never can point to a migrated field.
1455
+ // TODO: Capture these assumptions and performance optimizations in the design doc.
1456
+ if (fieldNamesToConsiderForReferenceLookup !== null &&
1457
+ !fieldNamesToConsiderForReferenceLookup.has(node.text)) {
1458
+ return;
1459
+ }
1460
+ let target = undefined;
1461
+ // Resolve binding elements to their declaration symbol.
1462
+ // Commonly inputs are accessed via object expansion. e.g. `const {input} = this;`.
1463
+ if (ts__default["default"].isBindingElement(node.parent)) {
1464
+ // Skip binding elements that are using spread.
1465
+ if (node.parent.dotDotDotToken !== undefined) {
1466
+ return;
1467
+ }
1468
+ const bindingInfo = resolveBindingElement(node.parent);
1469
+ if (bindingInfo === null) {
1470
+ // The declaration could not be resolved. Skip analyzing this.
1471
+ return;
1472
+ }
1473
+ const bindingType = checker.getTypeAtLocation(bindingInfo.pattern);
1474
+ const resolved = lookupPropertyAccess(checker, bindingType, [bindingInfo.propertyName]);
1475
+ target = resolved?.symbol;
1476
+ }
1477
+ else {
1478
+ target = checker.getSymbolAtLocation(node);
1479
+ }
1480
+ noTargetSymbolCheck: if (target === undefined) {
1481
+ if (ts__default["default"].isPropertyAccessExpression(node.parent) && node.parent.name === node) {
1482
+ const propAccessSymbol = checker.getSymbolAtLocation(node.parent.expression);
1483
+ if (propAccessSymbol !== undefined &&
1484
+ propAccessSymbol.valueDeclaration !== undefined &&
1485
+ ts__default["default"].isVariableDeclaration(propAccessSymbol.valueDeclaration) &&
1486
+ propAccessSymbol.valueDeclaration.initializer !== undefined) {
1487
+ target = advisors.debugElComponentInstanceTracker
1488
+ .detect(propAccessSymbol.valueDeclaration.initializer)
1489
+ ?.getProperty(node.text);
1490
+ // We found a target in the fallback path. Break out.
1491
+ if (target !== undefined) {
1492
+ break noTargetSymbolCheck;
1493
+ }
1494
+ }
1495
+ }
1496
+ return;
1497
+ }
1498
+ let targetInput = knownFields.attemptRetrieveDescriptorFromSymbol(target);
1499
+ if (targetInput === null) {
1500
+ return;
1501
+ }
1502
+ const access = unwrapParent(traverseAccess(node));
1503
+ const accessParent = access.parent;
1504
+ const isWriteReference = ts__default["default"].isBinaryExpression(accessParent) &&
1505
+ accessParent.left === access &&
1506
+ writeBinaryOperators.includes(accessParent.operatorToken.kind);
1507
+ // track accesses from source files to known fields.
1508
+ result.references.push({
1509
+ kind: exports.ReferenceKind.TsReference,
1510
+ from: {
1511
+ node,
1512
+ file: projectFile(node.getSourceFile(), programInfo),
1513
+ isWrite: isWriteReference,
1514
+ isPartOfElementBinding: ts__default["default"].isBindingElement(node.parent),
1515
+ },
1516
+ target: targetInput,
1517
+ });
1518
+ }
1519
+
1520
+ /**
1521
+ * Phase where we iterate through all source file references and
1522
+ * detect references to known fields (e.g. commonly inputs).
1523
+ *
1524
+ * This is useful, for example in the signal input migration whe
1525
+ * references need to be migrated to unwrap signals, given that
1526
+ * their target properties is no longer holding a raw value, but
1527
+ * instead an `InputSignal`.
1528
+ *
1529
+ * This phase detects references in all types of locations:
1530
+ * - TS source files
1531
+ * - Angular templates (inline or external)
1532
+ * - Host binding expressions.
1533
+ */
1534
+ function createFindAllSourceFileReferencesVisitor(programInfo, checker, reflector, resourceLoader, evaluator, templateTypeChecker, knownFields, fieldNamesToConsiderForReferenceLookup, result) {
1535
+ const debugElComponentInstanceTracker = new DebugElementComponentInstance(checker);
1536
+ const partialDirectiveCatalystTracker = new PartialDirectiveTypeInCatalystTests(checker, knownFields);
1537
+ const perfCounters = {
1538
+ template: 0,
1539
+ hostBindings: 0,
1540
+ tsReferences: 0,
1541
+ tsTypes: 0,
1542
+ };
1543
+ // Schematic NodeJS execution may not have `global.performance` defined.
1544
+ const currentTimeInMs = () => typeof global.performance !== 'undefined' ? global.performance.now() : Date.now();
1545
+ const visitor = (node) => {
1546
+ let lastTime = currentTimeInMs();
1547
+ if (ts__default["default"].isClassDeclaration(node)) {
1548
+ identifyTemplateReferences(programInfo, node, reflector, checker, evaluator, templateTypeChecker, resourceLoader, programInfo.userOptions, result, knownFields, fieldNamesToConsiderForReferenceLookup);
1549
+ perfCounters.template += (currentTimeInMs() - lastTime) / 1000;
1550
+ lastTime = currentTimeInMs();
1551
+ identifyHostBindingReferences(node, programInfo, checker, reflector, result, knownFields, fieldNamesToConsiderForReferenceLookup);
1552
+ perfCounters.hostBindings += (currentTimeInMs() - lastTime) / 1000;
1553
+ lastTime = currentTimeInMs();
1554
+ }
1555
+ lastTime = currentTimeInMs();
1556
+ // find references, but do not capture input declarations itself.
1557
+ if (ts__default["default"].isIdentifier(node) &&
1558
+ !(isInputContainerNode(node.parent) && node.parent.name === node)) {
1559
+ identifyPotentialTypeScriptReference(node, programInfo, checker, knownFields, result, fieldNamesToConsiderForReferenceLookup, {
1560
+ debugElComponentInstanceTracker,
1561
+ });
1562
+ }
1563
+ perfCounters.tsReferences += (currentTimeInMs() - lastTime) / 1000;
1564
+ lastTime = currentTimeInMs();
1565
+ // Detect `Partial<T>` references.
1566
+ // Those are relevant to be tracked as they may be updated in Catalyst to
1567
+ // unwrap signal inputs. Commonly people use `Partial` in Catalyst to type
1568
+ // some "component initial values".
1569
+ const partialDirectiveInCatalyst = partialDirectiveCatalystTracker.detect(node);
1570
+ if (partialDirectiveInCatalyst !== null) {
1571
+ result.references.push({
1572
+ kind: exports.ReferenceKind.TsClassTypeReference,
1573
+ from: {
1574
+ file: projectFile(partialDirectiveInCatalyst.referenceNode.getSourceFile(), programInfo),
1575
+ node: partialDirectiveInCatalyst.referenceNode,
1576
+ },
1577
+ isPartialReference: true,
1578
+ isPartOfCatalystFile: true,
1579
+ target: partialDirectiveInCatalyst.targetClass,
1580
+ });
1581
+ }
1582
+ perfCounters.tsTypes += (currentTimeInMs() - lastTime) / 1000;
1583
+ };
1584
+ return {
1585
+ visitor,
1586
+ debugPrintMetrics: () => {
1587
+ console.info('Source file analysis performance', perfCounters);
1588
+ },
1589
+ };
1590
+ }
1591
+
1592
+ /**
1593
+ * Synchronously combines unit data for the given migration.
1594
+ *
1595
+ * Note: This helper is useful for testing and execution of
1596
+ * Tsurge migrations in non-batchable environments. In general,
1597
+ * prefer parallel execution of combining via e.g. Beam combiners.
1598
+ */
1599
+ async function synchronouslyCombineUnitData(migration, unitDatas) {
1600
+ if (unitDatas.length === 0) {
1601
+ return null;
1602
+ }
1603
+ if (unitDatas.length === 1) {
1604
+ return unitDatas[0];
1605
+ }
1606
+ let combined = unitDatas[0];
1607
+ for (let i = 1; i < unitDatas.length; i++) {
1608
+ const other = unitDatas[i];
1609
+ combined = await migration.combine(combined, other);
1610
+ }
1611
+ return combined;
1612
+ }
1613
+
1614
+ exports.DevkitMigrationFilesystem = DevkitMigrationFilesystem;
1615
+ exports.Replacement = Replacement;
1616
+ exports.TextUpdate = TextUpdate;
1617
+ exports.TsurgeComplexMigration = TsurgeComplexMigration;
1618
+ exports.TsurgeFunnelMigration = TsurgeFunnelMigration;
1619
+ exports.applyImportManagerChanges = applyImportManagerChanges;
1620
+ exports.confirmAsSerializable = confirmAsSerializable;
1621
+ exports.createFindAllSourceFileReferencesVisitor = createFindAllSourceFileReferencesVisitor;
1622
+ exports.createNgtscProgram = createNgtscProgram;
1623
+ exports.getBindingElementDeclaration = getBindingElementDeclaration;
1624
+ exports.getMemberName = getMemberName;
1625
+ exports.groupReplacementsByFile = groupReplacementsByFile;
1626
+ exports.isHostBindingReference = isHostBindingReference;
1627
+ exports.isInputContainerNode = isInputContainerNode;
1628
+ exports.isTemplateReference = isTemplateReference;
1629
+ exports.isTsClassTypeReference = isTsClassTypeReference;
1630
+ exports.isTsReference = isTsReference;
1631
+ exports.projectFile = projectFile;
1632
+ exports.synchronouslyCombineUnitData = synchronouslyCombineUnitData;
1633
+ exports.traverseAccess = traverseAccess;
1634
+ exports.unwrapParent = unwrapParent;