@angular/core 19.1.0-next.4 → 19.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/LICENSE +1 -1
  2. package/fesm2022/core.mjs +85 -91
  3. package/fesm2022/core.mjs.map +1 -1
  4. package/fesm2022/primitives/event-dispatch.mjs +1 -24
  5. package/fesm2022/primitives/event-dispatch.mjs.map +1 -1
  6. package/fesm2022/primitives/signals.mjs +1 -1
  7. package/fesm2022/rxjs-interop.mjs +1 -1
  8. package/fesm2022/rxjs-interop.mjs.map +1 -1
  9. package/fesm2022/testing.mjs +4 -4
  10. package/index.d.ts +41 -36
  11. package/package.json +1 -1
  12. package/primitives/event-dispatch/index.d.ts +1 -1
  13. package/primitives/signals/index.d.ts +1 -1
  14. package/rxjs-interop/index.d.ts +1 -1
  15. package/schematics/bundles/apply_import_manager-5082ccea.js +732 -0
  16. package/schematics/bundles/{checker-884633eb.js → checker-aa999c96.js} +50 -25
  17. package/schematics/bundles/cleanup-unused-imports.js +295 -0
  18. package/schematics/bundles/{compiler_host-22f6513d.js → compiler_host-f0b570c8.js} +2 -2
  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-abe29092.js → imports-31a38653.js} +1 -1
  22. package/schematics/bundles/index-02a11f43.js +30 -0
  23. package/schematics/bundles/{combine_units-4a95b1b9.js → index-15b61bae.js} +10 -723
  24. package/schematics/bundles/inject-migration.js +6 -6
  25. package/schematics/bundles/{leading_space-d190b83b.js → leading_space-6e7a8ec6.js} +1 -1
  26. package/schematics/bundles/{migrate_ts_type_references-4b11f3f2.js → migrate_ts_type_references-042ca765.js} +30 -29
  27. package/schematics/bundles/{nodes-a9f0b985.js → nodes-88c2157f.js} +2 -2
  28. package/schematics/bundles/output-migration.js +27 -26
  29. package/schematics/bundles/pending-tasks.js +5 -5
  30. package/schematics/bundles/{program-094352ba.js → program-393ca8f3.js} +106 -46
  31. package/schematics/bundles/{project_tsconfig_paths-e9ccccbf.js → project_tsconfig_paths-6c9cde78.js} +1 -1
  32. package/schematics/bundles/provide-initializer.js +5 -5
  33. package/schematics/bundles/route-lazy-loading.js +4 -4
  34. package/schematics/bundles/signal-input-migration.js +33 -32
  35. package/schematics/bundles/signal-queries-migration.js +56 -49
  36. package/schematics/bundles/signals.js +8 -7
  37. package/schematics/bundles/standalone-migration.js +12 -28
  38. package/schematics/collection.json +5 -0
  39. package/schematics/ng-generate/cleanup-unused-imports/schema.json +7 -0
  40. package/testing/index.d.ts +1 -1
@@ -0,0 +1,732 @@
1
+ 'use strict';
2
+ /**
3
+ * @license Angular v19.1.0
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-aa999c96.js');
14
+ var program = require('./program-393ca8f3.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
+ * Synchronously combines unit data for the given migration.
420
+ *
421
+ * Note: This helper is useful for testing and execution of
422
+ * Tsurge migrations in non-batchable environments. In general,
423
+ * prefer parallel execution of combining via e.g. Beam combiners.
424
+ */
425
+ async function synchronouslyCombineUnitData(migration, unitDatas) {
426
+ if (unitDatas.length === 0) {
427
+ return null;
428
+ }
429
+ if (unitDatas.length === 1) {
430
+ return unitDatas[0];
431
+ }
432
+ let combined = unitDatas[0];
433
+ for (let i = 1; i < unitDatas.length; i++) {
434
+ const other = unitDatas[i];
435
+ combined = await migration.combine(combined, other);
436
+ }
437
+ return combined;
438
+ }
439
+
440
+ /**
441
+ * By default, Tsurge will always create an Angular compiler program
442
+ * for projects analyzed and migrated. This works perfectly fine in
443
+ * third-party where Tsurge migrations run in Angular CLI projects.
444
+ *
445
+ * In first party, when running against full Google3, creating an Angular
446
+ * program for e.g. plain `ts_library` targets is overly expensive and
447
+ * can result in out of memory issues for large TS targets. In 1P we can
448
+ * reliably distinguish between TS and Angular targets via the `angularCompilerOptions`.
449
+ */
450
+ function google3UsePlainTsProgramIfNoKnownAngularOption() {
451
+ return process.env['GOOGLE3_TSURGE'] === '1';
452
+ }
453
+
454
+ /** Options that are good defaults for Tsurge migrations. */
455
+ const defaultMigrationTsOptions = {
456
+ // Avoid checking libraries to speed up migrations.
457
+ skipLibCheck: true,
458
+ skipDefaultLibCheck: true,
459
+ noEmit: true,
460
+ // Does not apply to g3 and externally is enforced when the app is built by the compiler.
461
+ disableTypeScriptVersionCheck: true,
462
+ };
463
+ /**
464
+ * Creates an instance of a TypeScript program for the given project.
465
+ */
466
+ function createPlainTsProgram(tsHost, tsconfig, optionOverrides) {
467
+ const program = ts__default["default"].createProgram({
468
+ rootNames: tsconfig.rootNames,
469
+ options: {
470
+ ...tsconfig.options,
471
+ ...defaultMigrationTsOptions,
472
+ ...optionOverrides,
473
+ },
474
+ });
475
+ return {
476
+ ngCompiler: null,
477
+ program,
478
+ userOptions: tsconfig.options,
479
+ programAbsoluteRootFileNames: tsconfig.rootNames,
480
+ host: tsHost,
481
+ };
482
+ }
483
+
484
+ /**
485
+ * Parses the configuration of the given TypeScript project and creates
486
+ * an instance of the Angular compiler for the project.
487
+ */
488
+ function createNgtscProgram(tsHost, tsconfig, optionOverrides) {
489
+ const ngtscProgram = new program.NgtscProgram(tsconfig.rootNames, {
490
+ ...tsconfig.options,
491
+ ...defaultMigrationTsOptions,
492
+ ...optionOverrides,
493
+ }, tsHost);
494
+ // Expose an easy way to debug-print ng semantic diagnostics.
495
+ if (process.env['DEBUG_NG_SEMANTIC_DIAGNOSTICS'] === '1') {
496
+ console.error(ts__default["default"].formatDiagnosticsWithColorAndContext(ngtscProgram.getNgSemanticDiagnostics(), tsHost));
497
+ }
498
+ return {
499
+ ngCompiler: ngtscProgram.compiler,
500
+ program: ngtscProgram.getTsProgram(),
501
+ userOptions: tsconfig.options,
502
+ programAbsoluteRootFileNames: tsconfig.rootNames,
503
+ host: tsHost,
504
+ };
505
+ }
506
+
507
+ /** Code of the error raised by TypeScript when a tsconfig doesn't match any files. */
508
+ const NO_INPUTS_ERROR_CODE = 18003;
509
+ /** Parses the given tsconfig file, supporting Angular compiler options. */
510
+ function parseTsconfigOrDie(absoluteTsconfigPath, fs) {
511
+ const tsconfig = readConfiguration(absoluteTsconfigPath, {}, fs);
512
+ // Skip the "No inputs found..." error since we don't want to interrupt the migration if a
513
+ // tsconfig doesn't match a file. This will result in an empty `Program` which is still valid.
514
+ const errors = tsconfig.errors.filter((diag) => diag.code !== NO_INPUTS_ERROR_CODE);
515
+ if (errors.length) {
516
+ throw new Error(`Tsconfig could not be parsed or is invalid:\n\n` + `${errors.map((e) => e.messageText)}`);
517
+ }
518
+ return tsconfig;
519
+ }
520
+
521
+ /** Creates the base program info for the given tsconfig path. */
522
+ function createBaseProgramInfo(absoluteTsconfigPath, fs, optionOverrides = {}) {
523
+ if (fs === undefined) {
524
+ fs = new checker.NodeJSFileSystem();
525
+ checker.setFileSystem(fs);
526
+ }
527
+ const tsconfig = parseTsconfigOrDie(absoluteTsconfigPath, fs);
528
+ const tsHost = new NgtscCompilerHost(fs, tsconfig.options);
529
+ // When enabled, use a plain TS program if we are sure it's not
530
+ // an Angular project based on the `tsconfig.json`.
531
+ if (google3UsePlainTsProgramIfNoKnownAngularOption() &&
532
+ tsconfig.options['_useHostForImportGeneration'] === undefined) {
533
+ return createPlainTsProgram(tsHost, tsconfig, optionOverrides);
534
+ }
535
+ return createNgtscProgram(tsHost, tsconfig, optionOverrides);
536
+ }
537
+
538
+ /**
539
+ * @private
540
+ *
541
+ * Base class for the possible Tsurge migration variants.
542
+ *
543
+ * For example, this class exposes methods to conveniently create
544
+ * TypeScript programs, while also allowing migration authors to override.
545
+ */
546
+ class TsurgeBaseMigration {
547
+ /**
548
+ * Advanced Tsurge users can override this method, but most of the time,
549
+ * overriding {@link prepareProgram} is more desirable.
550
+ *
551
+ * By default:
552
+ * - In 3P: Ngtsc programs are being created.
553
+ * - In 1P: Ngtsc or TS programs are created based on the Blaze target.
554
+ */
555
+ createProgram(tsconfigAbsPath, fs, optionOverrides) {
556
+ return createBaseProgramInfo(tsconfigAbsPath, fs, optionOverrides);
557
+ }
558
+ // Optional function to prepare the base `ProgramInfo` even further,
559
+ // for the analyze and migrate phases. E.g. determining source files.
560
+ prepareProgram(info) {
561
+ const fullProgramSourceFiles = [...info.program.getSourceFiles()];
562
+ const sourceFiles = fullProgramSourceFiles.filter((f) => !f.isDeclarationFile &&
563
+ // Note `isShim` will work for the initial program, but for TCB programs, the shims are no longer annotated.
564
+ !checker.isShim(f) &&
565
+ !f.fileName.endsWith('.ngtypecheck.ts'));
566
+ // Sort it by length in reverse order (longest first). This speeds up lookups,
567
+ // since there's no need to keep going through the array once a match is found.
568
+ const sortedRootDirs = checker.getRootDirs(info.host, info.userOptions).sort((a, b) => b.length - a.length);
569
+ // TODO: Consider also following TS's logic here, finding the common source root.
570
+ // See: Program#getCommonSourceDirectory.
571
+ const primaryRoot = checker.absoluteFrom(info.userOptions.rootDir ?? sortedRootDirs.at(-1) ?? info.program.getCurrentDirectory());
572
+ return {
573
+ ...info,
574
+ sourceFiles,
575
+ fullProgramSourceFiles,
576
+ sortedRootDirs,
577
+ projectRoot: primaryRoot,
578
+ };
579
+ }
580
+ }
581
+
582
+ /**
583
+ * A simpler variant of a {@link TsurgeComplexMigration} that does not
584
+ * fan-out into multiple workers per compilation unit to compute
585
+ * the final migration replacements.
586
+ *
587
+ * This is faster and less resource intensive as workers and TS programs
588
+ * are only ever created once.
589
+ *
590
+ * This is commonly the case when migrations are refactored to eagerly
591
+ * compute replacements in the analyze stage, and then leverage the
592
+ * global unit data to filter replacements that turned out to be "invalid".
593
+ */
594
+ class TsurgeFunnelMigration extends TsurgeBaseMigration {
595
+ }
596
+ /**
597
+ * Complex variant of a `Tsurge` migration.
598
+ *
599
+ * For example, every analyze worker may contribute to a list of TS
600
+ * references that are later combined. The migrate phase can then compute actual
601
+ * file updates for all individual compilation units, leveraging the global metadata
602
+ * to e.g. see if there are any references from other compilation units that may be
603
+ * problematic and prevent migration of a given file.
604
+ */
605
+ class TsurgeComplexMigration extends TsurgeBaseMigration {
606
+ }
607
+
608
+ /** A text replacement for the given file. */
609
+ class Replacement {
610
+ projectFile;
611
+ update;
612
+ constructor(projectFile, update) {
613
+ this.projectFile = projectFile;
614
+ this.update = update;
615
+ }
616
+ }
617
+ /** An isolated text update that may be applied to a file. */
618
+ class TextUpdate {
619
+ data;
620
+ constructor(data) {
621
+ this.data = data;
622
+ }
623
+ }
624
+
625
+ /** Confirms that the given data `T` is serializable. */
626
+ function confirmAsSerializable(data) {
627
+ return data;
628
+ }
629
+
630
+ /**
631
+ * Gets a project file instance for the given file.
632
+ *
633
+ * Use this helper for dealing with project paths throughout your
634
+ * migration. The return type is serializable.
635
+ *
636
+ * See {@link ProjectFile}.
637
+ */
638
+ function projectFile(file, { sortedRootDirs, projectRoot }) {
639
+ const fs = checker.getFileSystem();
640
+ const filePath = fs.resolve(typeof file === 'string' ? file : file.fileName);
641
+ // Sorted root directories are sorted longest to shortest. First match
642
+ // is the appropriate root directory for ID computation.
643
+ for (const rootDir of sortedRootDirs) {
644
+ if (!isWithinBasePath(fs, rootDir, filePath)) {
645
+ continue;
646
+ }
647
+ return {
648
+ id: fs.relative(rootDir, filePath),
649
+ rootRelativePath: fs.relative(projectRoot, filePath),
650
+ };
651
+ }
652
+ // E.g. project directory may be `src/`, but files may be looked up
653
+ // from `node_modules/`. This is fine, but in those cases, no root
654
+ // directory matches.
655
+ const rootRelativePath = fs.relative(projectRoot, filePath);
656
+ return {
657
+ id: rootRelativePath,
658
+ rootRelativePath: rootRelativePath,
659
+ };
660
+ }
661
+ /**
662
+ * Whether `path` is a descendant of the `base`?
663
+ * E.g. `a/b/c` is within `a/b` but not within `a/x`.
664
+ */
665
+ function isWithinBasePath(fs, base, path) {
666
+ return checker.isLocalRelativePath(fs.relative(base, path));
667
+ }
668
+
669
+ /**
670
+ * Applies import manager changes, and writes them as replacements the
671
+ * given result array.
672
+ */
673
+ function applyImportManagerChanges(importManager, replacements, sourceFiles, info) {
674
+ const { newImports, updatedImports, deletedImports } = importManager.finalize();
675
+ const printer = ts__default["default"].createPrinter({});
676
+ const pathToFile = new Map(sourceFiles.map((s) => [s.fileName, s]));
677
+ // Capture new imports
678
+ newImports.forEach((newImports, fileName) => {
679
+ newImports.forEach((newImport) => {
680
+ const printedImport = printer.printNode(ts__default["default"].EmitHint.Unspecified, newImport, pathToFile.get(fileName));
681
+ replacements.push(new Replacement(projectFile(checker.absoluteFrom(fileName), info), new TextUpdate({ position: 0, end: 0, toInsert: `${printedImport}\n` })));
682
+ });
683
+ });
684
+ // Capture updated imports
685
+ for (const [oldBindings, newBindings] of updatedImports.entries()) {
686
+ // The import will be generated as multi-line if it already is multi-line,
687
+ // or if the number of elements significantly increased and it previously
688
+ // consisted of very few specifiers.
689
+ const isMultiline = oldBindings.getText().includes('\n') ||
690
+ (newBindings.elements.length >= 6 && oldBindings.elements.length <= 3);
691
+ const hasSpaceBetweenBraces = oldBindings.getText().startsWith('{ ');
692
+ let formatFlags = ts__default["default"].ListFormat.NamedImportsOrExportsElements |
693
+ ts__default["default"].ListFormat.Indented |
694
+ ts__default["default"].ListFormat.Braces |
695
+ ts__default["default"].ListFormat.PreserveLines |
696
+ (isMultiline ? ts__default["default"].ListFormat.MultiLine : ts__default["default"].ListFormat.SingleLine);
697
+ if (hasSpaceBetweenBraces) {
698
+ formatFlags |= ts__default["default"].ListFormat.SpaceBetweenBraces;
699
+ }
700
+ else {
701
+ formatFlags &= ~ts__default["default"].ListFormat.SpaceBetweenBraces;
702
+ }
703
+ const printedBindings = printer.printList(formatFlags, newBindings.elements, oldBindings.getSourceFile());
704
+ replacements.push(new Replacement(projectFile(oldBindings.getSourceFile(), info), new TextUpdate({
705
+ position: oldBindings.getStart(),
706
+ end: oldBindings.getEnd(),
707
+ // TS uses four spaces as indent. We migrate to two spaces as we
708
+ // assume this to be more common.
709
+ toInsert: printedBindings.replace(/^ {4}/gm, ' '),
710
+ })));
711
+ }
712
+ // Update removed imports
713
+ for (const removedImport of deletedImports) {
714
+ replacements.push(new Replacement(projectFile(removedImport.getSourceFile(), info), new TextUpdate({
715
+ position: removedImport.getStart(),
716
+ end: removedImport.getEnd(),
717
+ toInsert: '',
718
+ })));
719
+ }
720
+ }
721
+
722
+ exports.DevkitMigrationFilesystem = DevkitMigrationFilesystem;
723
+ exports.Replacement = Replacement;
724
+ exports.TextUpdate = TextUpdate;
725
+ exports.TsurgeComplexMigration = TsurgeComplexMigration;
726
+ exports.TsurgeFunnelMigration = TsurgeFunnelMigration;
727
+ exports.applyImportManagerChanges = applyImportManagerChanges;
728
+ exports.confirmAsSerializable = confirmAsSerializable;
729
+ exports.createBaseProgramInfo = createBaseProgramInfo;
730
+ exports.groupReplacementsByFile = groupReplacementsByFile;
731
+ exports.projectFile = projectFile;
732
+ exports.synchronouslyCombineUnitData = synchronouslyCombineUnitData;