@angular/core 19.0.0-rc.0 → 19.0.0-rc.2

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