From 48aa96ea13ebfadf2f6b13516c7702dae740a7be Mon Sep 17 00:00:00 2001 From: Alan Agius Date: Thu, 16 Feb 2023 13:28:23 +0000 Subject: [PATCH] refactor: remove Angular Compatibility Compiler (ngcc) (#49101) This commit removes the NGCC code and all the related infra setup required to support it. BREAKING CHANGE: Angular Compatibility Compiler (ngcc) has been removed and as a result Angular View Engine libraries will no longer work PR Close #49101 --- .bazelignore | 1 - .ng-dev/commit-message.mts | 1 - .ng-dev/google-sync-config.json | 2 - .pullapprove.yml | 14 +- CONTRIBUTING.md | 6 +- aio/content/guide/glossary.md | 8 +- .../guide/migration-module-with-providers.md | 6 +- .../guide/migration-undecorated-classes.md | 6 +- aio/content/guide/roadmap.md | 2 +- aio/package.json | 4 + aio/tools/examples/shared/package.json | 1 - aio/yarn.lock | 12 +- docs/TRIAGE_AND_LABELS.md | 1 - goldens/circular-deps/packages.json | 4 - integration/ngcc/BUILD.bazel | 15 - integration/ngcc/debug-test.sh | 30 - integration/ngcc/mock-ngcc-version-marker.js | 50 - integration/ngcc/ngcc.config.js | 6 - integration/ngcc/package.json | 26 - integration/ngcc/run_test.sh | 221 - integration/ngcc/src/main.ts | 24 - integration/ngcc/tsconfig-app.json | 17 - integration/npm_package_archives.bzl | 8 - package.json | 11 +- packages/common/BUILD.bazel | 1 - packages/compiler-cli/BUILD.bazel | 7 - packages/compiler-cli/ngcc/BUILD.bazel | 46 - packages/compiler-cli/ngcc/README.md | 30 - packages/compiler-cli/ngcc/index.ts | 38 - packages/compiler-cli/ngcc/main-ngcc.ts | 28 - .../ngcc/src/analysis/decoration_analyzer.ts | 271 -- .../ngcc/src/analysis/migration_host.ts | 94 - .../module_with_providers_analyzer.ts | 231 - .../src/analysis/ngcc_references_registry.ts | 51 - .../ngcc/src/analysis/ngcc_trait_compiler.ts | 98 - .../analysis/private_declarations_analyzer.ts | 73 - .../compiler-cli/ngcc/src/analysis/types.ts | 33 - .../compiler-cli/ngcc/src/analysis/util.ts | 22 - .../ngcc/src/command_line_options.ts | 161 - packages/compiler-cli/ngcc/src/constants.ts | 10 - .../dependencies/commonjs_dependency_host.ts | 93 - .../ngcc/src/dependencies/dependency_host.ts | 143 - .../src/dependencies/dependency_resolver.ts | 239 - .../src/dependencies/dts_dependency_host.ts | 33 - .../src/dependencies/esm_dependency_host.ts | 310 -- .../ngcc/src/dependencies/module_resolver.ts | 287 -- .../src/dependencies/umd_dependency_host.ts | 41 - .../directory_walker_entry_point_finder.ts | 60 - .../entry_point_collector.ts | 153 - .../ngcc/src/entry_point_finder/interface.ts | 15 - .../program_based_entry_point_finder.ts | 121 - .../targeted_entry_point_finder.ts | 254 - .../tracing_entry_point_finder.ts | 97 - .../ngcc/src/entry_point_finder/utils.ts | 205 - .../src/execution/analyze_entry_points.ts | 182 - .../compiler-cli/ngcc/src/execution/api.ts | 34 - .../ngcc/src/execution/cluster/api.ts | 61 - .../ngcc/src/execution/cluster/executor.ts | 40 - .../ngcc/src/execution/cluster/master.ts | 357 -- .../execution/cluster/ngcc_cluster_worker.ts | 46 - .../execution/cluster/package_json_updater.ts | 58 - .../ngcc/src/execution/cluster/utils.ts | 87 - .../ngcc/src/execution/cluster/worker.ts | 68 - .../src/execution/create_compile_function.ts | 92 - .../src/execution/single_process_executor.ts | 70 - .../ngcc/src/execution/tasks/api.ts | 158 - .../ngcc/src/execution/tasks/completion.ts | 90 - .../execution/tasks/queues/base_task_queue.ts | 103 - .../tasks/queues/parallel_task_queue.ts | 78 - .../tasks/queues/serial_task_queue.ts | 37 - .../ngcc/src/execution/tasks/utils.ts | 139 - .../ngcc/src/host/commonjs_host.ts | 256 -- .../ngcc/src/host/commonjs_umd_utils.ts | 284 -- .../ngcc/src/host/delegating_host.ts | 164 - .../ngcc/src/host/esm2015_host.ts | 2633 ----------- .../compiler-cli/ngcc/src/host/esm5_host.ts | 803 ---- .../compiler-cli/ngcc/src/host/ngcc_host.ts | 96 - .../compiler-cli/ngcc/src/host/umd_host.ts | 885 ---- packages/compiler-cli/ngcc/src/host/utils.ts | 12 - .../ngcc/src/locking/async_locker.ts | 81 - .../ngcc/src/locking/lock_file.ts | 47 - .../lock_file_with_child_process/index.ts | 103 - .../ngcc_lock_unlocker.ts | 41 - .../lock_file_with_child_process/util.ts | 37 - .../ngcc/src/locking/sync_locker.ts | 61 - packages/compiler-cli/ngcc/src/main.ts | 217 - .../ngcc/src/migrations/README.md | 18 - .../ngcc/src/migrations/migration.ts | 66 - .../missing_injectable_migration.ts | 192 - .../migrations/undecorated_child_migration.ts | 75 - .../undecorated_parent_migration.ts | 109 - .../compiler-cli/ngcc/src/migrations/utils.ts | 134 - .../compiler-cli/ngcc/src/ngcc_options.ts | 301 -- .../src/packages/adjust_cjs_umd_exports.ts | 82 - .../ngcc/src/packages/build_marker.ts | 108 - .../ngcc/src/packages/bundle_program.ts | 87 - .../ngcc/src/packages/configuration.ts | 458 -- .../ngcc/src/packages/entry_point.ts | 419 -- .../ngcc/src/packages/entry_point_bundle.ts | 130 - .../ngcc/src/packages/entry_point_manifest.ts | 207 - .../ngcc/src/packages/ngcc_compiler_host.ts | 97 - .../packages/patch_ts_expando_initializer.ts | 182 - .../ngcc/src/packages/source_file_cache.ts | 201 - .../ngcc/src/packages/transformer.ts | 187 - .../compiler-cli/ngcc/src/path_mappings.ts | 29 - .../rendering/commonjs_rendering_formatter.ts | 89 - .../ngcc/src/rendering/dts_renderer.ts | 186 - .../src/rendering/esm5_rendering_formatter.ts | 75 - .../src/rendering/esm_rendering_formatter.ts | 292 -- .../src/rendering/ngcc_import_rewriter.ts | 34 - .../ngcc/src/rendering/renderer.ts | 219 - .../ngcc/src/rendering/rendering_formatter.ts | 47 - .../ngcc/src/rendering/source_maps.ts | 73 - .../src/rendering/umd_rendering_formatter.ts | 252 - .../compiler-cli/ngcc/src/rendering/utils.ts | 39 - packages/compiler-cli/ngcc/src/utils.ts | 242 - .../writing/cleaning/cleaning_strategies.ts | 67 - .../src/writing/cleaning/package_cleaner.ts | 80 - .../ngcc/src/writing/cleaning/utils.ts | 23 - .../ngcc/src/writing/file_writer.ts | 35 - .../ngcc/src/writing/in_place_file_writer.ts | 77 - .../writing/new_entry_point_file_writer.ts | 201 - .../ngcc/src/writing/package_json_updater.ts | 226 - packages/compiler-cli/ngcc/test/BUILD.bazel | 107 - .../test/analysis/decoration_analyzer_spec.ts | 487 -- .../ngcc/test/analysis/migration_host_spec.ts | 244 - .../module_with_providers_analyzer_spec.ts | 836 ---- .../test/analysis/ngcc_trait_compiler_spec.ts | 358 -- .../private_declarations_analyzer_spec.ts | 196 - .../test/analysis/references_registry_spec.ts | 66 - .../ngcc/test/analysis/util_spec.ts | 42 - .../commonjs_dependency_host_spec.ts | 467 -- .../dependencies/dependency_resolver_spec.ts | 507 -- .../dependencies/dts_dependency_host_spec.ts | 273 -- .../dependencies/esm_dependency_host_spec.ts | 355 -- .../test/dependencies/module_resolver_spec.ts | 316 -- .../dependencies/umd_dependency_host_spec.ts | 322 -- ...irectory_walker_entry_point_finder_spec.ts | 464 -- .../program_based_entry_point_finder_spec.ts | 238 - .../targeted_entry_point_finder_spec.ts | 700 --- .../test/entry_point_finder/utils_spec.ts | 180 - .../test/execution/cluster/executor_spec.ts | 155 - .../cluster/package_json_updater_spec.ts | 216 - .../test/execution/cluster/worker_spec.ts | 226 - .../ngcc/test/execution/helpers.ts | 85 - .../single_processor_executor_spec.ts | 146 - .../tasks/queues/parallel_task_queue_spec.ts | 584 --- .../tasks/queues/serial_task_queue_spec.ts | 309 -- .../ngcc/test/execution/utils_spec.ts | 84 - .../ngcc/test/helpers/BUILD.bazel | 17 - .../ngcc/test/helpers/mock_lock_file.ts | 28 - .../ngcc/test/helpers/spy_utils.ts | 112 - .../ngcc/test/helpers/umd_utils.ts | 173 - .../compiler-cli/ngcc/test/helpers/utils.ts | 151 - .../host/commonjs_host_import_helper_spec.ts | 127 - .../ngcc/test/host/commonjs_host_spec.ts | 3140 ------------- .../host/esm2015_host_import_helper_spec.ts | 584 --- .../ngcc/test/host/esm2015_host_spec.ts | 2696 ----------- .../test/host/esm5_host_import_helper_spec.ts | 673 --- .../ngcc/test/host/esm5_host_spec.ts | 3157 ------------- .../test/host/umd_host_import_helper_spec.ts | 162 - .../ngcc/test/host/umd_host_spec.ts | 3570 --------------- packages/compiler-cli/ngcc/test/host/util.ts | 68 - .../ngcc/test/integration/ngcc_spec.ts | 4076 ----------------- .../ngcc/test/integration/util.ts | 30 - .../ngcc/test/locking/async_locker_spec.ts | 183 - .../lockfile_with_child_process/index_spec.ts | 133 - .../unlocker_spec.ts | 19 - .../lockfile_with_child_process/util_spec.ts | 52 - .../ngcc/test/locking/sync_locker_spec.ts | 67 - .../missing_injectable_migration_spec.ts | 595 --- .../undecorated_parent_migration_spec.ts | 264 -- .../ngcc/test/ngcc_options_spec.ts | 202 - .../test/packages/adjust_cjs_exports_spec.ts | 48 - .../ngcc/test/packages/build_marker_spec.ts | 305 -- .../ngcc/test/packages/configuration_spec.ts | 888 ---- .../test/packages/entry_point_bundle_spec.ts | 428 -- .../packages/entry_point_manifest_spec.ts | 365 -- .../ngcc/test/packages/entry_point_spec.ts | 1032 ----- .../test/packages/source_file_cache_spec.ts | 231 - .../commonjs_rendering_formatter_spec.ts | 483 -- .../ngcc/test/rendering/dts_renderer_spec.ts | 239 - .../esm5_rendering_formatter_spec.ts | 514 --- .../rendering/esm_rendering_formatter_spec.ts | 644 --- .../ngcc/test/rendering/renderer_spec.ts | 582 --- .../rendering/umd_rendering_formatter_spec.ts | 755 --- packages/compiler-cli/ngcc/test/utils_spec.ts | 389 -- .../cleaning/cleaning_strategies_spec.ts | 239 - .../writing/cleaning/package_cleaner_spec.ts | 86 - .../test/writing/in_place_file_writer_spec.ts | 177 - .../new_entry_point_file_writer_spec.ts | 737 --- .../test/writing/package_json_updater_spec.ts | 296 -- packages/compiler-cli/package.json | 13 - packages/core/BUILD.bazel | 1 - renovate.json | 8 - yarn.lock | 403 +- 196 files changed, 202 insertions(+), 53383 deletions(-) delete mode 100644 integration/ngcc/BUILD.bazel delete mode 100755 integration/ngcc/debug-test.sh delete mode 100644 integration/ngcc/mock-ngcc-version-marker.js delete mode 100644 integration/ngcc/ngcc.config.js delete mode 100644 integration/ngcc/package.json delete mode 100755 integration/ngcc/run_test.sh delete mode 100644 integration/ngcc/src/main.ts delete mode 100644 integration/ngcc/tsconfig-app.json delete mode 100644 packages/compiler-cli/ngcc/BUILD.bazel delete mode 100644 packages/compiler-cli/ngcc/README.md delete mode 100644 packages/compiler-cli/ngcc/index.ts delete mode 100644 packages/compiler-cli/ngcc/main-ngcc.ts delete mode 100644 packages/compiler-cli/ngcc/src/analysis/decoration_analyzer.ts delete mode 100644 packages/compiler-cli/ngcc/src/analysis/migration_host.ts delete mode 100644 packages/compiler-cli/ngcc/src/analysis/module_with_providers_analyzer.ts delete mode 100644 packages/compiler-cli/ngcc/src/analysis/ngcc_references_registry.ts delete mode 100644 packages/compiler-cli/ngcc/src/analysis/ngcc_trait_compiler.ts delete mode 100644 packages/compiler-cli/ngcc/src/analysis/private_declarations_analyzer.ts delete mode 100644 packages/compiler-cli/ngcc/src/analysis/types.ts delete mode 100644 packages/compiler-cli/ngcc/src/analysis/util.ts delete mode 100644 packages/compiler-cli/ngcc/src/command_line_options.ts delete mode 100644 packages/compiler-cli/ngcc/src/constants.ts delete mode 100644 packages/compiler-cli/ngcc/src/dependencies/commonjs_dependency_host.ts delete mode 100644 packages/compiler-cli/ngcc/src/dependencies/dependency_host.ts delete mode 100644 packages/compiler-cli/ngcc/src/dependencies/dependency_resolver.ts delete mode 100644 packages/compiler-cli/ngcc/src/dependencies/dts_dependency_host.ts delete mode 100644 packages/compiler-cli/ngcc/src/dependencies/esm_dependency_host.ts delete mode 100644 packages/compiler-cli/ngcc/src/dependencies/module_resolver.ts delete mode 100644 packages/compiler-cli/ngcc/src/dependencies/umd_dependency_host.ts delete mode 100644 packages/compiler-cli/ngcc/src/entry_point_finder/directory_walker_entry_point_finder.ts delete mode 100644 packages/compiler-cli/ngcc/src/entry_point_finder/entry_point_collector.ts delete mode 100644 packages/compiler-cli/ngcc/src/entry_point_finder/interface.ts delete mode 100644 packages/compiler-cli/ngcc/src/entry_point_finder/program_based_entry_point_finder.ts delete mode 100644 packages/compiler-cli/ngcc/src/entry_point_finder/targeted_entry_point_finder.ts delete mode 100644 packages/compiler-cli/ngcc/src/entry_point_finder/tracing_entry_point_finder.ts delete mode 100644 packages/compiler-cli/ngcc/src/entry_point_finder/utils.ts delete mode 100644 packages/compiler-cli/ngcc/src/execution/analyze_entry_points.ts delete mode 100644 packages/compiler-cli/ngcc/src/execution/api.ts delete mode 100644 packages/compiler-cli/ngcc/src/execution/cluster/api.ts delete mode 100644 packages/compiler-cli/ngcc/src/execution/cluster/executor.ts delete mode 100644 packages/compiler-cli/ngcc/src/execution/cluster/master.ts delete mode 100644 packages/compiler-cli/ngcc/src/execution/cluster/ngcc_cluster_worker.ts delete mode 100644 packages/compiler-cli/ngcc/src/execution/cluster/package_json_updater.ts delete mode 100644 packages/compiler-cli/ngcc/src/execution/cluster/utils.ts delete mode 100644 packages/compiler-cli/ngcc/src/execution/cluster/worker.ts delete mode 100644 packages/compiler-cli/ngcc/src/execution/create_compile_function.ts delete mode 100644 packages/compiler-cli/ngcc/src/execution/single_process_executor.ts delete mode 100644 packages/compiler-cli/ngcc/src/execution/tasks/api.ts delete mode 100644 packages/compiler-cli/ngcc/src/execution/tasks/completion.ts delete mode 100644 packages/compiler-cli/ngcc/src/execution/tasks/queues/base_task_queue.ts delete mode 100644 packages/compiler-cli/ngcc/src/execution/tasks/queues/parallel_task_queue.ts delete mode 100644 packages/compiler-cli/ngcc/src/execution/tasks/queues/serial_task_queue.ts delete mode 100644 packages/compiler-cli/ngcc/src/execution/tasks/utils.ts delete mode 100644 packages/compiler-cli/ngcc/src/host/commonjs_host.ts delete mode 100644 packages/compiler-cli/ngcc/src/host/commonjs_umd_utils.ts delete mode 100644 packages/compiler-cli/ngcc/src/host/delegating_host.ts delete mode 100644 packages/compiler-cli/ngcc/src/host/esm2015_host.ts delete mode 100644 packages/compiler-cli/ngcc/src/host/esm5_host.ts delete mode 100644 packages/compiler-cli/ngcc/src/host/ngcc_host.ts delete mode 100644 packages/compiler-cli/ngcc/src/host/umd_host.ts delete mode 100644 packages/compiler-cli/ngcc/src/host/utils.ts delete mode 100644 packages/compiler-cli/ngcc/src/locking/async_locker.ts delete mode 100644 packages/compiler-cli/ngcc/src/locking/lock_file.ts delete mode 100644 packages/compiler-cli/ngcc/src/locking/lock_file_with_child_process/index.ts delete mode 100644 packages/compiler-cli/ngcc/src/locking/lock_file_with_child_process/ngcc_lock_unlocker.ts delete mode 100644 packages/compiler-cli/ngcc/src/locking/lock_file_with_child_process/util.ts delete mode 100644 packages/compiler-cli/ngcc/src/locking/sync_locker.ts delete mode 100644 packages/compiler-cli/ngcc/src/main.ts delete mode 100644 packages/compiler-cli/ngcc/src/migrations/README.md delete mode 100644 packages/compiler-cli/ngcc/src/migrations/migration.ts delete mode 100644 packages/compiler-cli/ngcc/src/migrations/missing_injectable_migration.ts delete mode 100644 packages/compiler-cli/ngcc/src/migrations/undecorated_child_migration.ts delete mode 100644 packages/compiler-cli/ngcc/src/migrations/undecorated_parent_migration.ts delete mode 100644 packages/compiler-cli/ngcc/src/migrations/utils.ts delete mode 100644 packages/compiler-cli/ngcc/src/ngcc_options.ts delete mode 100644 packages/compiler-cli/ngcc/src/packages/adjust_cjs_umd_exports.ts delete mode 100644 packages/compiler-cli/ngcc/src/packages/build_marker.ts delete mode 100644 packages/compiler-cli/ngcc/src/packages/bundle_program.ts delete mode 100644 packages/compiler-cli/ngcc/src/packages/configuration.ts delete mode 100644 packages/compiler-cli/ngcc/src/packages/entry_point.ts delete mode 100644 packages/compiler-cli/ngcc/src/packages/entry_point_bundle.ts delete mode 100644 packages/compiler-cli/ngcc/src/packages/entry_point_manifest.ts delete mode 100644 packages/compiler-cli/ngcc/src/packages/ngcc_compiler_host.ts delete mode 100644 packages/compiler-cli/ngcc/src/packages/patch_ts_expando_initializer.ts delete mode 100644 packages/compiler-cli/ngcc/src/packages/source_file_cache.ts delete mode 100644 packages/compiler-cli/ngcc/src/packages/transformer.ts delete mode 100644 packages/compiler-cli/ngcc/src/path_mappings.ts delete mode 100644 packages/compiler-cli/ngcc/src/rendering/commonjs_rendering_formatter.ts delete mode 100644 packages/compiler-cli/ngcc/src/rendering/dts_renderer.ts delete mode 100644 packages/compiler-cli/ngcc/src/rendering/esm5_rendering_formatter.ts delete mode 100644 packages/compiler-cli/ngcc/src/rendering/esm_rendering_formatter.ts delete mode 100644 packages/compiler-cli/ngcc/src/rendering/ngcc_import_rewriter.ts delete mode 100644 packages/compiler-cli/ngcc/src/rendering/renderer.ts delete mode 100644 packages/compiler-cli/ngcc/src/rendering/rendering_formatter.ts delete mode 100644 packages/compiler-cli/ngcc/src/rendering/source_maps.ts delete mode 100644 packages/compiler-cli/ngcc/src/rendering/umd_rendering_formatter.ts delete mode 100644 packages/compiler-cli/ngcc/src/rendering/utils.ts delete mode 100644 packages/compiler-cli/ngcc/src/utils.ts delete mode 100644 packages/compiler-cli/ngcc/src/writing/cleaning/cleaning_strategies.ts delete mode 100644 packages/compiler-cli/ngcc/src/writing/cleaning/package_cleaner.ts delete mode 100644 packages/compiler-cli/ngcc/src/writing/cleaning/utils.ts delete mode 100644 packages/compiler-cli/ngcc/src/writing/file_writer.ts delete mode 100644 packages/compiler-cli/ngcc/src/writing/in_place_file_writer.ts delete mode 100644 packages/compiler-cli/ngcc/src/writing/new_entry_point_file_writer.ts delete mode 100644 packages/compiler-cli/ngcc/src/writing/package_json_updater.ts delete mode 100644 packages/compiler-cli/ngcc/test/BUILD.bazel delete mode 100644 packages/compiler-cli/ngcc/test/analysis/decoration_analyzer_spec.ts delete mode 100644 packages/compiler-cli/ngcc/test/analysis/migration_host_spec.ts delete mode 100644 packages/compiler-cli/ngcc/test/analysis/module_with_providers_analyzer_spec.ts delete mode 100644 packages/compiler-cli/ngcc/test/analysis/ngcc_trait_compiler_spec.ts delete mode 100644 packages/compiler-cli/ngcc/test/analysis/private_declarations_analyzer_spec.ts delete mode 100644 packages/compiler-cli/ngcc/test/analysis/references_registry_spec.ts delete mode 100644 packages/compiler-cli/ngcc/test/analysis/util_spec.ts delete mode 100644 packages/compiler-cli/ngcc/test/dependencies/commonjs_dependency_host_spec.ts delete mode 100644 packages/compiler-cli/ngcc/test/dependencies/dependency_resolver_spec.ts delete mode 100644 packages/compiler-cli/ngcc/test/dependencies/dts_dependency_host_spec.ts delete mode 100644 packages/compiler-cli/ngcc/test/dependencies/esm_dependency_host_spec.ts delete mode 100644 packages/compiler-cli/ngcc/test/dependencies/module_resolver_spec.ts delete mode 100644 packages/compiler-cli/ngcc/test/dependencies/umd_dependency_host_spec.ts delete mode 100644 packages/compiler-cli/ngcc/test/entry_point_finder/directory_walker_entry_point_finder_spec.ts delete mode 100644 packages/compiler-cli/ngcc/test/entry_point_finder/program_based_entry_point_finder_spec.ts delete mode 100644 packages/compiler-cli/ngcc/test/entry_point_finder/targeted_entry_point_finder_spec.ts delete mode 100644 packages/compiler-cli/ngcc/test/entry_point_finder/utils_spec.ts delete mode 100644 packages/compiler-cli/ngcc/test/execution/cluster/executor_spec.ts delete mode 100644 packages/compiler-cli/ngcc/test/execution/cluster/package_json_updater_spec.ts delete mode 100644 packages/compiler-cli/ngcc/test/execution/cluster/worker_spec.ts delete mode 100644 packages/compiler-cli/ngcc/test/execution/helpers.ts delete mode 100644 packages/compiler-cli/ngcc/test/execution/single_processor_executor_spec.ts delete mode 100644 packages/compiler-cli/ngcc/test/execution/tasks/queues/parallel_task_queue_spec.ts delete mode 100644 packages/compiler-cli/ngcc/test/execution/tasks/queues/serial_task_queue_spec.ts delete mode 100644 packages/compiler-cli/ngcc/test/execution/utils_spec.ts delete mode 100644 packages/compiler-cli/ngcc/test/helpers/BUILD.bazel delete mode 100644 packages/compiler-cli/ngcc/test/helpers/mock_lock_file.ts delete mode 100644 packages/compiler-cli/ngcc/test/helpers/spy_utils.ts delete mode 100644 packages/compiler-cli/ngcc/test/helpers/umd_utils.ts delete mode 100644 packages/compiler-cli/ngcc/test/helpers/utils.ts delete mode 100644 packages/compiler-cli/ngcc/test/host/commonjs_host_import_helper_spec.ts delete mode 100644 packages/compiler-cli/ngcc/test/host/commonjs_host_spec.ts delete mode 100644 packages/compiler-cli/ngcc/test/host/esm2015_host_import_helper_spec.ts delete mode 100644 packages/compiler-cli/ngcc/test/host/esm2015_host_spec.ts delete mode 100644 packages/compiler-cli/ngcc/test/host/esm5_host_import_helper_spec.ts delete mode 100644 packages/compiler-cli/ngcc/test/host/esm5_host_spec.ts delete mode 100644 packages/compiler-cli/ngcc/test/host/umd_host_import_helper_spec.ts delete mode 100644 packages/compiler-cli/ngcc/test/host/umd_host_spec.ts delete mode 100644 packages/compiler-cli/ngcc/test/host/util.ts delete mode 100644 packages/compiler-cli/ngcc/test/integration/ngcc_spec.ts delete mode 100644 packages/compiler-cli/ngcc/test/integration/util.ts delete mode 100644 packages/compiler-cli/ngcc/test/locking/async_locker_spec.ts delete mode 100644 packages/compiler-cli/ngcc/test/locking/lockfile_with_child_process/index_spec.ts delete mode 100644 packages/compiler-cli/ngcc/test/locking/lockfile_with_child_process/unlocker_spec.ts delete mode 100644 packages/compiler-cli/ngcc/test/locking/lockfile_with_child_process/util_spec.ts delete mode 100644 packages/compiler-cli/ngcc/test/locking/sync_locker_spec.ts delete mode 100644 packages/compiler-cli/ngcc/test/migrations/missing_injectable_migration_spec.ts delete mode 100644 packages/compiler-cli/ngcc/test/migrations/undecorated_parent_migration_spec.ts delete mode 100644 packages/compiler-cli/ngcc/test/ngcc_options_spec.ts delete mode 100644 packages/compiler-cli/ngcc/test/packages/adjust_cjs_exports_spec.ts delete mode 100644 packages/compiler-cli/ngcc/test/packages/build_marker_spec.ts delete mode 100644 packages/compiler-cli/ngcc/test/packages/configuration_spec.ts delete mode 100644 packages/compiler-cli/ngcc/test/packages/entry_point_bundle_spec.ts delete mode 100644 packages/compiler-cli/ngcc/test/packages/entry_point_manifest_spec.ts delete mode 100644 packages/compiler-cli/ngcc/test/packages/entry_point_spec.ts delete mode 100644 packages/compiler-cli/ngcc/test/packages/source_file_cache_spec.ts delete mode 100644 packages/compiler-cli/ngcc/test/rendering/commonjs_rendering_formatter_spec.ts delete mode 100644 packages/compiler-cli/ngcc/test/rendering/dts_renderer_spec.ts delete mode 100644 packages/compiler-cli/ngcc/test/rendering/esm5_rendering_formatter_spec.ts delete mode 100644 packages/compiler-cli/ngcc/test/rendering/esm_rendering_formatter_spec.ts delete mode 100644 packages/compiler-cli/ngcc/test/rendering/renderer_spec.ts delete mode 100644 packages/compiler-cli/ngcc/test/rendering/umd_rendering_formatter_spec.ts delete mode 100644 packages/compiler-cli/ngcc/test/utils_spec.ts delete mode 100644 packages/compiler-cli/ngcc/test/writing/cleaning/cleaning_strategies_spec.ts delete mode 100644 packages/compiler-cli/ngcc/test/writing/cleaning/package_cleaner_spec.ts delete mode 100644 packages/compiler-cli/ngcc/test/writing/in_place_file_writer_spec.ts delete mode 100644 packages/compiler-cli/ngcc/test/writing/new_entry_point_file_writer_spec.ts delete mode 100644 packages/compiler-cli/ngcc/test/writing/package_json_updater_spec.ts diff --git a/.bazelignore b/.bazelignore index 1d5643db1aa..9e506afd0a0 100644 --- a/.bazelignore +++ b/.bazelignore @@ -31,7 +31,6 @@ integration/ng_elements/node_modules integration/ng_elements_schematics/node_modules integration/ng_update/node_modules integration/ng_update_migrations/node_modules -integration/ngcc/node_modules integration/platform-server/node_modules integration/service-worker-schema/node_modules integration/side-effects/node_modules diff --git a/.ng-dev/commit-message.mts b/.ng-dev/commit-message.mts index d717a095207..faac36981e6 100644 --- a/.ng-dev/commit-message.mts +++ b/.ng-dev/commit-message.mts @@ -25,7 +25,6 @@ export const commitMessage: CommitMessageConfig = { 'language-service', 'localize', 'migrations', - 'ngcc', 'packaging', 'platform-browser', 'platform-browser-dynamic', diff --git a/.ng-dev/google-sync-config.json b/.ng-dev/google-sync-config.json index d2af6d79ed5..dc702202b3c 100644 --- a/.ng-dev/google-sync-config.json +++ b/.ng-dev/google-sync-config.json @@ -11,9 +11,7 @@ "packages/bazel/src/protractor/**", "packages/bazel/src/schematics/**", "packages/bazel/src/types_bundle/**", - "packages/compiler-cli/src/ngcc/**", "packages/compiler-cli/linker/**", - "packages/compiler-cli/ngcc/**", "packages/compiler-cli/src/ngtsc/sourcemaps/**", "packages/compiler-cli/src/ngtsc/testing/**", "packages/compiler-cli/private/bazel.ts", diff --git a/.pullapprove.yml b/.pullapprove.yml index ddd01beebed..ac14c433040 100644 --- a/.pullapprove.yml +++ b/.pullapprove.yml @@ -149,7 +149,7 @@ groups: <<: *defaults conditions: - > - contains_any_globs(files.exclude('packages/compiler-cli/ngcc/*'), [ + contains_any_globs(files, [ 'packages/compiler/**/{*,.*}', 'packages/examples/compiler/**/{*,.*}', 'aio/content/examples/angular-compiler-options/**/{*,.*}', @@ -166,18 +166,6 @@ groups: - atscott - JoostK - # ========================================================= - # Framework: Compiler / ngcc - # ========================================================= - fw-ngcc: - <<: *defaults - conditions: - - files.include('packages/compiler-cli/ngcc/*') - reviewers: - users: - - alxhub - - JoostK - # ========================================================= # Framework: Migrations # ========================================================= diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 9ee99217de8..99c842141c0 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -246,7 +246,7 @@ The `footer` is optional. The [Commit Message Footer](#commit-footer) format des │ └─⫸ Commit Scope: animations|bazel|benchpress|common|compiler|compiler-cli|core| │ elements|forms|http|language-service|localize|platform-browser| │ platform-browser-dynamic|platform-server|router|service-worker| - │ upgrade|zone.js|packaging|changelog|docs-infra|migrations|ngcc|ve| + │ upgrade|zone.js|packaging|changelog|docs-infra|migrations| │ devtools │ └─⫸ Commit Type: build|ci|docs|feat|fix|perf|refactor|test @@ -306,10 +306,6 @@ There are currently a few exceptions to the "use package name" rule: * `migrations`: used for changes to the `ng update` migrations. -* `ngcc`: used for changes to the [Angular Compatibility Compiler](./packages/compiler-cli/ngcc/README.md) - -* `ve`: used for changes specific to ViewEngine (legacy compiler/renderer). - * `devtools`: used for changes in the [browser extension](./devtools/README.md). * none/empty string: useful for `test` and `refactor` changes that are done across all packages (e.g. `test: add missing unit tests`) and for docs changes that are not related to a specific package (e.g. `docs: fix typo in tutorial`). diff --git a/aio/content/guide/glossary.md b/aio/content/guide/glossary.md index abcb478e15e..de9e0f52e69 100644 --- a/aio/content/guide/glossary.md +++ b/aio/content/guide/glossary.md @@ -500,11 +500,6 @@ Install Angular libraries with the [npm package manager][NpmjsDocsAboutNpm] and Compare to [NgModule][AioGuideGlossaryNgmodule]. -## ngcc - -Angular compatibility compiler. -If you build your application using [Ivy][AioGuideGlossaryIvy], but it depends on libraries that have not been compiled with Ivy, the Angular CLI uses `ngcc` to automatically update the dependent libraries to use Ivy. - ## NgModule A class definition preceded by the `@NgModule()` [decorator][AioGuideGlossaryDecoratorDecoration], which declares and serves as a manifest for a block of code dedicated to an application domain, a workflow, or a closely related set of capabilities. @@ -1085,7 +1080,6 @@ Learn more about zones in this [Brian Ford video][YoutubeWatchV3iqtmusceU]. [AioGuideGlossaryLibrary]: guide/glossary#library "library - Glossary | Angular" [AioGuideGlossaryM]: guide/glossary#module "M - Glossary | Angular" [AioGuideGlossaryModule]: guide/glossary#module "module - Glossary | Angular" -[AioGuideGlossaryN]: guide/glossary#ngcc "N - Glossary | Angular" [AioGuideGlossaryNgmodule]: guide/glossary#ngmodule "NgModule - Glossary | Angular" [AioGuideGlossaryNpmPackage]: guide/glossary#npm-package "npm package - Glossary | Angular" [AioGuideGlossaryO]: guide/glossary#observable "O - Glossary | Angular" @@ -1212,4 +1206,4 @@ Learn more about zones in this [Brian Ford video][YoutubeWatchV3iqtmusceU]. -@reviewed 2022-02-28 +@reviewed 2023-02-16 diff --git a/aio/content/guide/migration-module-with-providers.md b/aio/content/guide/migration-module-with-providers.md index 98a88b852ce..e46e3f3c534 100644 --- a/aio/content/guide/migration-module-with-providers.md +++ b/aio/content/guide/migration-module-with-providers.md @@ -71,14 +71,10 @@ It should match the type of whichever `NgModule` is returned in the `ModuleWithP Libraries should add the generic type to any usages of the `ModuleWithProviders` type. -## What about applications using non-migrated libraries? - -The [Angular compatibility compiler](guide/glossary#ngcc) \(`ngcc`\) should automatically transform any non-migrated libraries to generate the proper code. - -@reviewed 2022-02-28 +@reviewed 2023-02-16 diff --git a/aio/content/guide/migration-undecorated-classes.md b/aio/content/guide/migration-undecorated-classes.md index 9d8993bbb50..803700781fb 100644 --- a/aio/content/guide/migration-undecorated-classes.md +++ b/aio/content/guide/migration-undecorated-classes.md @@ -198,14 +198,10 @@ Classes that don't use Angular features don't need an Angular decorator. As support for selectorless decorators is introduced in Angular version 9, if you want to support Angular version 8 and earlier, you shouldn't add a selectorless `@Directive()` decorator. You can either add `@Directive()` with a selector or move the Angular-specific features to affected subclasses. -## What about applications using non-migrated libraries? - -The [Angular compatibility compiler](guide/glossary#ngcc) \(`ngcc`\) should automatically transform any non-migrated libraries to generate the proper code. - -@reviewed 2022-02-28 +@reviewed 2023-02-16 diff --git a/aio/content/guide/roadmap.md b/aio/content/guide/roadmap.md index 19a70a9ad53..5ef10597bbf 100644 --- a/aio/content/guide/roadmap.md +++ b/aio/content/guide/roadmap.md @@ -217,7 +217,7 @@ As the next step, we need to finalize the recommendations and compile a list of Earlier in 2020, we shared an [RFC](https://github.com/angular/angular/issues/38366) for Ivy library distribution. After invaluable feedback from the community, we developed a design of the project. -We are now investing in the development of Ivy library distribution, including an update of the library package format to use Ivy compilation, unblock the deprecation of the View Engine library format, and [ngcc](guide/glossary#ngcc). +We are now investing in the development of Ivy library distribution, including an update of the library package format to use Ivy compilation, unblock the deprecation of the View Engine library format, and ngcc. ### Improve test times and debugging with automatic test environment tear down diff --git a/aio/package.json b/aio/package.json index 61a693e120e..6d01124bc67 100644 --- a/aio/package.json +++ b/aio/package.json @@ -60,6 +60,10 @@ "npm": "Please use yarn instead of NPM to install dependencies" }, "private": true, + "// resolutions": "Temp resolutions until CLI releases v16.0.0-next.0", + "resolutions": { + "@ngtools/webpack": "https://github.com/angular/ngtools-webpack-builds.git#c784933671856a6529d7dd560ba1026890898f57" + }, "dependencies": { "@angular/animations": "15.2.0-next.4", "@angular/cdk": "15.2.0-next.4", diff --git a/aio/tools/examples/shared/package.json b/aio/tools/examples/shared/package.json index 7997a02313a..dc85314bf0c 100644 --- a/aio/tools/examples/shared/package.json +++ b/aio/tools/examples/shared/package.json @@ -6,7 +6,6 @@ "scripts": { "http-server": "http-server", "protractor": "protractor", - "postinstall": "ngcc --properties es2015 main", "sync-deps": "node sync-boilerplate-dependencies" }, "//engines-comment": "Keep this in sync with /package.json and /aio/package.json", diff --git a/aio/yarn.lock b/aio/yarn.lock index 19b856a63bb..aa762ccc5cf 100644 --- a/aio/yarn.lock +++ b/aio/yarn.lock @@ -2877,15 +2877,9 @@ resolved "https://registry.yarnpkg.com/@microsoft/tsdoc/-/tsdoc-0.14.2.tgz#c3ec604a0b54b9a9b87e9735dfc59e1a5da6a5fb" integrity sha512-9b8mPpKrfeGRuhFH5iO1iwCLeIIsV6+H1sRfxbkoGXIyQE2BTsPd9zqSqQJ+pv5sJ/hT5M1zvOFL02MnEezFug== -"@ngtools/webpack@15.2.0-next.3": - version "15.2.0-next.3" - resolved "https://registry.yarnpkg.com/@ngtools/webpack/-/webpack-15.2.0-next.3.tgz#3fc8b605d1be5ad6a66b908b89847681319ee95e" - integrity sha512-Je7rwxLPcpmkHIvOzzdhWr9xqK6FMi1lGh5rEW49E9O9E1ESfh5OMnC3pgC8gOEzrhcnOuXgV7JZ5AjJwCgmgQ== - -"@ngtools/webpack@15.2.0-next.4": - version "15.2.0-next.4" - resolved "https://registry.yarnpkg.com/@ngtools/webpack/-/webpack-15.2.0-next.4.tgz#354d6096329a7f33e787d218385dbb610ad42bee" - integrity sha512-Ftqqo9AHVVbDLKS5q5AzN+InimGwCZQTCPfnA5WoDOPHjEDoA0Ekg+7IaRjRWpOd7lPYr9fh62KQLXVc2vIydg== +"@ngtools/webpack@15.2.0-next.3", "@ngtools/webpack@15.2.0-next.4", "@ngtools/webpack@https://github.com/angular/ngtools-webpack-builds.git#c784933671856a6529d7dd560ba1026890898f57": + version "16.0.0-next.0" + resolved "https://github.com/angular/ngtools-webpack-builds.git#c784933671856a6529d7dd560ba1026890898f57" "@nodelib/fs.scandir@2.1.5": version "2.1.5" diff --git a/docs/TRIAGE_AND_LABELS.md b/docs/TRIAGE_AND_LABELS.md index a1988a66d4d..040b67beb5e 100644 --- a/docs/TRIAGE_AND_LABELS.md +++ b/docs/TRIAGE_AND_LABELS.md @@ -21,7 +21,6 @@ of this repo. * `area: core & compiler` - because core, compiler, compiler-cli and browser-platforms are very intertwined, we will be treating them as one * `area: ivy` - a subset of core representing the new Ivy renderer. -* `area: ngcc` - a subset of ivy representing the [Angular Compatibility Compiler](../packages/compiler-cli/ngcc/README.md) * `area: ve` - a subset of `core & compiler` representing changes specific to ViewEngine (legacy compiler/renderer). * `area: docs-infra` - the angular.io application and docs-related tooling * `area: elements` diff --git a/goldens/circular-deps/packages.json b/goldens/circular-deps/packages.json index 540b37493a0..38d9a2aab39 100644 --- a/goldens/circular-deps/packages.json +++ b/goldens/circular-deps/packages.json @@ -3,10 +3,6 @@ "packages/animations/browser/src/render/animation_driver.ts", "packages/animations/browser/src/render/shared.ts" ], - [ - "packages/compiler-cli/ngcc/src/packages/configuration.ts", - "packages/compiler-cli/ngcc/src/packages/entry_point.ts" - ], [ "packages/compiler-cli/src/ngtsc/partial_evaluator/src/interface.ts", "packages/compiler-cli/src/ngtsc/partial_evaluator/src/interpreter.ts" diff --git a/integration/ngcc/BUILD.bazel b/integration/ngcc/BUILD.bazel deleted file mode 100644 index e3a47caa068..00000000000 --- a/integration/ngcc/BUILD.bazel +++ /dev/null @@ -1,15 +0,0 @@ -load("//integration:index.bzl", "ng_integration_test") - -ng_integration_test( - name = "test", - use_view_engine_packages = [ - "@angular/animations", - "@angular/common", - "@angular/core", - "@angular/forms", - "@angular/platform-browser", - "@angular/platform-browser-dynamic", - "@angular/platform-server", - "@angular/router", - ], -) diff --git a/integration/ngcc/debug-test.sh b/integration/ngcc/debug-test.sh deleted file mode 100755 index 88cf0cffabd..00000000000 --- a/integration/ngcc/debug-test.sh +++ /dev/null @@ -1,30 +0,0 @@ -#!/usr/bin/env bash - -##### Test Debug Utility ##### -############################## - -# Use this script to run the ngcc integration test locally -# in isolation from the other integration tests. -# This is useful when debugging the ngcc code-base. - -set -u -e -o pipefail - -cd "$(dirname "$0")" - -# Go to the project directory and build the release packages. -(cd $(pwd)/../../ && yarn build) - -# Workaround https://github.com/yarnpkg/yarn/issues/2165 -# Yarn will cache file://dist URIs and not update Angular code -export readonly cache=../.yarn_local_cache -function rm_cache { - rm -rf $cache -} -rm_cache -mkdir $cache -trap rm_cache EXIT - -rm -rf dist -rm -rf node_modules -yarn install --cache-folder $cache -yarn test diff --git a/integration/ngcc/mock-ngcc-version-marker.js b/integration/ngcc/mock-ngcc-version-marker.js deleted file mode 100644 index b5896f54132..00000000000 --- a/integration/ngcc/mock-ngcc-version-marker.js +++ /dev/null @@ -1,50 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ - -/** - * **Usage:** - * ```sh - * node mock-ngcc-version-marker - * - * # Example: - * # node mock-ngcc-version-marker @angular/material/button 3.0.0 - * ``` - * - * Ngcc marks entry-points as processed by adding marker properties in their `package.json`. It uses - * a version as a marker in order to be able to distinguish entry-points processed by a different - * ngcc version (e.g. an older version after an update) and perform the necessary clean-up. - * - * This script replaces the ngcc version marker in an entry-point's `package.json` to make it appear - * as if it were processed by a different version of ngcc. This allows testing the clean-up logic - * without the overhead of actually installing a different ngcc version, compiling the dependencies, - * then installing the current version and compiling again. - */ - -const {writeFileSync} = require('fs'); -const {basename} = require('path'); - -const entryPointName = process.argv[2]; -const mockNgccVersion = process.argv[3]; -const actualNgccVersion = require('@angular/compiler-cli/package.json').version; - -if (!entryPointName || !mockNgccVersion) { - throw new Error( - 'Missing required argument(s).\n' + - `Usage: node ${basename(__filename)} \n`); -} - -const entryPointPkgJsonPath = require.resolve(`${entryPointName}/package.json`); -const entryPointPkgJson = require(entryPointPkgJsonPath); -const processedMarkers = entryPointPkgJson.__processed_by_ivy_ngcc__; - -Object.keys(processedMarkers).forEach(key => processedMarkers[key] = mockNgccVersion); -writeFileSync(entryPointPkgJsonPath, JSON.stringify(entryPointPkgJson, null, 2)); - -console.log( - `Successfully mocked ngcc version marker in '${entryPointName}': ` + - `${actualNgccVersion} --> ${mockNgccVersion}`); diff --git a/integration/ngcc/ngcc.config.js b/integration/ngcc/ngcc.config.js deleted file mode 100644 index 9924f3af6df..00000000000 --- a/integration/ngcc/ngcc.config.js +++ /dev/null @@ -1,6 +0,0 @@ -module.exports = { - locking: { - retryAttempts: 5, - retryDelay: 100, - } -}; diff --git a/integration/ngcc/package.json b/integration/ngcc/package.json deleted file mode 100644 index 0d9a89c12cb..00000000000 --- a/integration/ngcc/package.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "name": "angular-integration", - "version": "0.0.0", - "license": "MIT", - "dependencies": { - "@angular/animations": "file:../../node_modules/@angular/animations-12", - "@angular/cdk": "9.0.0-rc.4", - "@angular/common": "file:../../node_modules/@angular/common-12", - "@angular/compiler": "file:../../dist/packages-dist/compiler", - "@angular/compiler-cli": "file:../../dist/packages-dist/compiler-cli", - "@angular/core": "file:../../node_modules/@angular/core-12", - "@angular/forms": "file:../../node_modules/@angular/forms-12", - "@angular/material": "9.0.0-rc.4", - "@angular/platform-browser": "file:../../node_modules/@angular/platform-browser-12", - "@angular/platform-browser-dynamic": "file:../../node_modules/@angular/platform-browser-dynamic-12", - "@angular/platform-server": "file:../../node_modules/@angular/platform-server-12", - "@angular/router": "file:../../node_modules/@angular/router-12", - "@types/node": "file:../../node_modules/@types/node", - "rxjs": "file:../../node_modules/rxjs", - "typescript": "file:../../node_modules/typescript", - "zone.js": "file:../../dist/zone.js-dist/archive/zone.js.tgz" - }, - "scripts": { - "test": "./run_test.sh" - } -} diff --git a/integration/ngcc/run_test.sh b/integration/ngcc/run_test.sh deleted file mode 100755 index dc832fa0fa9..00000000000 --- a/integration/ngcc/run_test.sh +++ /dev/null @@ -1,221 +0,0 @@ -#!/bin/bash - -# Do not immediately exit on error to allow the `assertSucceeded` function to handle the error. -# -# NOTE: -# Each statement should be followed by an `assert*` or `exit 1` statement. -set +e -x - -function assertFailed { - if [[ $? -eq 0 ]]; then - echo "FAIL: $1"; - exit 1; - fi -} - -function assertSucceeded { - if [[ $? -ne 0 ]]; then - echo "FAIL: $1"; - exit 1; - fi -} - -function assertEquals { - local value1=$1; - local value2=$2; - - if [[ "$value1" != "$value2" ]]; then - echo "FAIL: Expected '$value1' to equal '$value2'." - exit 1; - fi -} - -function assertNotEquals { - local value1=$1; - local value2=$2; - - if [[ "$value1" == "$value2" ]]; then - echo "FAIL: Expected '$value1' not to equal '$value2'." - exit 1; - fi -} - -ngcc --help -assertSucceeded "Expected 'ngcc --help' to succeed." - -ngcc --unknown-option 2>&1 | grep 'Unknown arguments: unknown-option, unknownOption' -assertSucceeded "Expected ngcc to report bad option." - -# node --inspect-brk $(npm bin)/ngcc -p esm2015 -# Run ngcc and check it logged compilation output as expected -ngcc | grep -e '- @angular/core \[fesm2015/esm2015\] (https://github\.com/angular/angular\.git)' -assertSucceeded "Expected 'ngcc' to log '- @angular/core [fesm2015/esm2015] (https://github.com/angular/angular.git)'." - - -# Did it add the appropriate build markers? - - # - esm2015 - cat node_modules/@angular/common/package.json | awk 'ORS=" "' | grep '"__processed_by_ivy_ngcc__":[^}]*"esm2015": "' - assertSucceeded "Expected 'ngcc' to add build marker for 'esm2015' in '@angular/common'." - - # - fesm2015 - cat node_modules/@angular/common/package.json | awk 'ORS=" "' | grep '"__processed_by_ivy_ngcc__":[^}]*"fesm2015": "' - assertSucceeded "Expected 'ngcc' to add build marker for 'fesm2015' in '@angular/common'." - - # `es2015` is an alias of `fesm2015`. - cat node_modules/@angular/common/package.json | awk 'ORS=" "' | grep '"__processed_by_ivy_ngcc__":[^}]*"es2015": "' - assertSucceeded "Expected 'ngcc' to add build marker for 'es2015' in '@angular/common'." - - # `module` is an alias of `fesm2015` - cat node_modules/@angular/common/package.json | awk 'ORS=" "' | grep '"__processed_by_ivy_ngcc__":[^}]*"module": "' - assertSucceeded "Expected 'ngcc' to add build marker for 'module' in '@angular/common'." - -# Did it compile @angular/core/ApplicationModule correctly? - grep "ApplicationModule.ɵmod = /\*@__PURE__\*/ ɵɵdefineNgModule" node_modules/@angular/core/fesm2015/core.js - assertSucceeded "Expected 'ngcc' to correctly compile 'ApplicationModule' in '@angular/core' (fesm2015)." - - grep "ApplicationModule.ɵmod = /\*@__PURE__\*/ ɵɵdefineNgModule" node_modules/@angular/core/bundles/core.umd.js - assertSucceeded "Expected 'ngcc' to correctly compile 'ApplicationModule' in '@angular/core' (main)." - - grep "ApplicationModule.ɵmod = /\*@__PURE__\*/ ɵngcc0.ɵɵdefineNgModule" node_modules/@angular/core/esm2015/src/application_module.js - assertSucceeded "Expected 'ngcc' to correctly compile 'ApplicationModule' in '@angular/core' (esm2015)." - -# Did it place the `setClassMetadata` call correctly? - cat node_modules/@angular/core/fesm2015/core.js | awk 'ORS=" "' | grep "ApplicationRef.ctorParameters.*setClassMetadata(ApplicationRef" - assertSucceeded "Expected 'ngcc' to place 'setClassMetadata' after static properties like 'ctorParameters' in '@angular/core' (fesm2015)." - - -# Did it transform @angular/core typing files correctly? - grep "import [*] as ɵngcc0 from './src/r3_symbols';" node_modules/@angular/core/core.d.ts - assertSucceeded "Expected 'ngcc' to add an import for 'src/r3_symbols' in '@angular/core' typings." - - grep "static ɵinj: ɵngcc0.ɵɵInjectorDeclaration;" node_modules/@angular/core/core.d.ts - assertSucceeded "Expected 'ngcc' to add a definition for 'ApplicationModule.ɵinj' in '@angular/core' typings." - - -# Did it generate a base factory call for synthesized constructors correctly? - grep "/\*@__PURE__\*/ function () { let ɵMatTable_BaseFactory; return function MatTable_Factory(t) { return (ɵMatTable_BaseFactory || (ɵMatTable_BaseFactory = ɵngcc0.ɵɵgetInheritedFactory(MatTable)))(t || MatTable); }; }();" node_modules/@angular/material/esm2015/table/table.js - assertSucceeded "Expected 'ngcc' to generate a base factory for 'MatTable' in '@angular/material' (esm2015)." - - grep "/\*@__PURE__\*/ function () { var ɵMatTable_BaseFactory; return function MatTable_Factory(t) { return (ɵMatTable_BaseFactory || (ɵMatTable_BaseFactory = ɵngcc0.ɵɵgetInheritedFactory(MatTable)))(t || MatTable); }; }();" node_modules/@angular/material/esm5/table/table.js - assertSucceeded "Expected 'ngcc' to generate a base factory for 'MatTable' in '@angular/material' (esm5)." - - -# Did it generate an abstract directive definition for undecorated classes with inputs and view queries? - grep "_MatMenuBase.ɵdir = /\*@__PURE__\*/ ɵngcc0.ɵɵdefineDirective({ type: _MatMenuBase" node_modules/@angular/material/esm2015/menu/menu.js - assertSucceeded "Expected 'ngcc' to generate an abstract directive definition for 'MatMenuBase' in '@angular/material' (esm2015)." - - grep "_MatMenuBase.ɵdir = /\*@__PURE__\*/ ɵngcc0.ɵɵdefineDirective({ type: _MatMenuBase" node_modules/@angular/material/esm5/menu/menu.js - assertSucceeded "Expected 'ngcc' to generate an abstract directive definition for 'MatMenuBase' in '@angular/material' (esm5)." - - -# TODO: This assertion is disabled because @angular/common no longer contains __decorate calls. -# We should either remove this assertion or use a syntentic JS file as input. -# Discuss with the ngcc folks. -# Did it handle namespace imported decorators in UMD using `__decorate` syntax? - #grep "type: i0.Injectable" node_modules/@angular/common/bundles/common.umd.js - #assertSucceeded "Expected 'ngcc' to correctly handle '__decorate' syntax in '@angular/common' (umd)." - -# TODO: This assertion is disabled because @angular/common no longer contains __decorate calls. -# We should either remove this assertion or use a syntentic JS file as input. -# Discuss with the ngcc folks. - # (and ensure the @angular/common package is indeed using `__decorate` syntax) - #grep "JsonPipe = __decorate(" node_modules/@angular/common/bundles/common.umd.js.__ivy_ngcc_bak - #assertSucceeded "Expected '@angular/common' (umd) to actually use '__decorate' syntax." - - -# Did it handle namespace imported decorators in UMD using static properties? - grep "type: i0.Injectable," node_modules/@angular/cdk/bundles/cdk-a11y.umd.js - assertSucceeded "Expected 'ngcc' to correctly handle decorators via static properties in '@angular/cdk/a11y' (umd)." - - # (and ensure the @angular/cdk/a11y package is indeed using static properties) - grep "FocusMonitor.decorators =" node_modules/@angular/cdk/bundles/cdk-a11y.umd.js.__ivy_ngcc_bak - assertSucceeded "Expected '@angular/cdk/a11y' (umd) to actually have decorators via static properties." - - -# Did it transform imports in UMD correctly? -# (E.g. no trailing commas, so that it remains compatible with legacy browsers, such as IE11.) - grep "factory(exports, require('rxjs'), require('rxjs/operators'))" node_modules/@angular/core/bundles/core.umd.js - assertSucceeded "Expected 'ngcc' to not add trailing commas to CommonJS block in UMD." - - grep "define('@angular/core', \['exports', 'rxjs', 'rxjs/operators'], factory)" node_modules/@angular/core/bundles/core.umd.js - assertSucceeded "Expected 'ngcc' to not add trailing commas to AMD block in UMD." - - grep "factory((global.ng = global.ng || {}, global.ng.core = {}), global.rxjs, global.rxjs.operators)" node_modules/@angular/core/bundles/core.umd.js - assertSucceeded "Expected 'ngcc' to not add trailing commas to globals block in UMD." - - grep "(this, (function (exports, rxjs, operators) {" node_modules/@angular/core/bundles/core.umd.js - assertSucceeded "Expected 'ngcc' to not add trailing commas to factory function parameters in UMD." - - -# Can it correctly clean up and re-compile when dependencies are already compiled by a different version? - readonly actualNgccVersion=`node --print "require('@angular/compiler-cli/package.json').version"` - readonly mockNgccVersion="3.0.0" - - # Mock the ngcc version marker on a package to make it appear as if it is compiled by a different ngcc version. - node mock-ngcc-version-marker @angular/material/button $mockNgccVersion - assertSucceeded "Expected to successfully mock the 'ngcc' version marker in '@angular/material/button'." - assertEquals $mockNgccVersion `node --print "require('@angular/material/button/package.json').__processed_by_ivy_ngcc__.main"` - assertEquals 1 `cat node_modules/@angular/material/button/button.d.ts | grep 'import \* as ɵngcc0' | wc -l` - - # Re-compile packages (which requires cleaning up those compiled by a different ngcc version). - ngcc --properties main - assertSucceeded "Expected 'ngcc' to successfully re-compile the packages." - - # Ensure previously compiled packages were correctly cleaned up (i.e. no multiple - # `import ... ɵngcc0` statements) and re-compiled by the current ngcc version. - assertEquals $actualNgccVersion `node --print "require('@angular/material/button/package.json').__processed_by_ivy_ngcc__.main"` - assertEquals 1 `cat node_modules/@angular/material/button/button.d.ts | grep 'import \* as ɵngcc0' | wc -l` - - -# Can it compile `@angular/platform-server` in UMD + typings without errors? -# (The CLI prefers the `main` property (which maps to UMD) over `module` when compiling `@angular/platform-server`. -# See https://github.com/angular/angular-cli/blob/e36853338/packages/angular_devkit/build_angular/src/angular-cli-files/models/webpack-configs/server.ts#L34) - if [[ -z "$cache" ]]; then - cache=".yarn_local_cache" - fi - rm -rf node_modules/@angular/platform-server && \ - yarn install --cache-folder $cache --check-files && \ - test -d node_modules/@angular/platform-server - assertSucceeded "Expected to re-install '@angular/platform-server'." - - ngcc --properties main --target @angular/platform-server - assertSucceeded "Expected 'ngcc' to successfully compile '@angular/platform-server' (main)." - - -# Can it be safely run again (as a noop)? -# And check that it logged skipping compilation as expected -ngcc -l debug | grep 'Skipping' -assertSucceeded "Expected 'ngcc -l debug' to successfully rerun (as a noop) and log 'Skipping'." - -# Does it process the tasks in parallel? -ngcc -l debug | grep 'Running ngcc on ClusterExecutor' -assertSucceeded "Expected 'ngcc -l debug' to run in parallel mode (using 'ClusterExecutor')." - -# Check that running it with logging level error outputs nothing -ngcc -l error | grep '.' -assertFailed "Expected 'ngcc -l error' to not output anything." - -# Does running it with --formats fail? -ngcc --formats fesm2015 -assertFailed "Expected 'ngcc --formats fesm2015' to fail (since '--formats' is deprecated)." - -# Does it timeout if there is another ngcc process running -LOCKFILE=node_modules/.ngcc_lock_file -touch $LOCKFILE -trap "[[ -f $LOCKFILE ]] && rm $LOCKFILE" EXIT -ngcc -exitCode=$? -assertEquals $exitCode 177 -rm $LOCKFILE - -# Now try compiling the app using the ngcc compiled libraries -ngc -p tsconfig-app.json -assertSucceeded "Expected the app to successfully compile with the ngcc-processed libraries." - -# Did it compile the main.ts correctly (including the ngIf and MatButton directives)? -grep "dependencies: \[.*\.NgIf.*\]" dist/src/main.js -assertSucceeded "Expected the compiled app's 'main.ts' to list 'NgIf' in 'dependencies'." - -grep "dependencies: \[.*\.MatButton.*\]" dist/src/main.js -assertSucceeded "Expected the compiled app's 'main.ts' to list 'MatButton' in 'dependencies'." diff --git a/integration/ngcc/src/main.ts b/integration/ngcc/src/main.ts deleted file mode 100644 index 50aca7fe166..00000000000 --- a/integration/ngcc/src/main.ts +++ /dev/null @@ -1,24 +0,0 @@ -import {Component, NgModule} from '@angular/core'; -import {MatButtonModule} from '@angular/material/button'; -import {BrowserModule, platformBrowser} from '@angular/platform-browser'; - -@Component({ - selector: 'hello-world', - template: ` - -

Hello World

- `, -}) -class HelloWorld { - visible = false; -} - -@NgModule({ - declarations: [HelloWorld], - imports: [BrowserModule, MatButtonModule], - bootstrap: [HelloWorld], -}) -class Module { -} - -platformBrowser().bootstrapModule(Module); diff --git a/integration/ngcc/tsconfig-app.json b/integration/ngcc/tsconfig-app.json deleted file mode 100644 index 2bb4b94a490..00000000000 --- a/integration/ngcc/tsconfig-app.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "compilerOptions": { - "target": "es5", - "module": "commonjs", - "moduleResolution": "node", - "lib": ["es2015", "dom"], - "experimentalDecorators": true, - "emitDecoratorMetadata": true, - "outDir": "dist", - "types": ["node"], - "rootDir": ".", - "skipLibCheck": true - }, - "files": [ - "src/main.ts" - ] -} diff --git a/integration/npm_package_archives.bzl b/integration/npm_package_archives.bzl index 94a6458a970..5a642e00347 100644 --- a/integration/npm_package_archives.bzl +++ b/integration/npm_package_archives.bzl @@ -1,14 +1,6 @@ # The @npm packages at the root node_modules are used by integration tests # with `file:../../node_modules/foobar` references NPM_PACKAGE_ARCHIVES = [ - "@angular/animations-12", - "@angular/common-12", - "@angular/core-12", - "@angular/forms-12", - "@angular/platform-browser-12", - "@angular/platform-browser-dynamic-12", - "@angular/platform-server-12", - "@angular/router-12", "@babel/core", "@rollup/plugin-babel", "@rollup/plugin-node-resolve", diff --git a/package.json b/package.json index 96d8baa3a43..e0fca0d7a8e 100644 --- a/package.json +++ b/package.json @@ -45,20 +45,12 @@ }, "// 1": "dependencies are used locally and by bazel", "dependencies": { - "@angular-devkit/build-angular": "15.2.0-next.4", + "@angular-devkit/build-angular": "https://github.com/angular/angular-devkit-build-angular-builds.git#5e54a29f44b099e58e63af002edc7975440aee8f", "@angular-devkit/core": "15.2.0-next.4", "@angular-devkit/schematics": "15.2.0-next.4", - "@angular/animations-12": "npm:@angular/animations@12.2.13", "@angular/cdk": "15.2.0-next.4", "@angular/cli": "15.2.0-next.4", - "@angular/common-12": "npm:@angular/common@12.2.13", - "@angular/core-12": "npm:@angular/core@12.2.13", - "@angular/forms-12": "npm:@angular/forms@12.2.13", "@angular/material": "15.2.0-next.4", - "@angular/platform-browser-12": "npm:@angular/platform-browser@12.2.13", - "@angular/platform-browser-dynamic-12": "npm:@angular/platform-browser-dynamic@12.2.13", - "@angular/platform-server-12": "npm:@angular/platform-server@12.2.13", - "@angular/router-12": "npm:@angular/router@12.2.13", "@babel/cli": "7.19.3", "@babel/core": "7.19.3", "@babel/generator": "7.19.5", @@ -123,7 +115,6 @@ "convert-source-map": "^1.5.1", "core-js-bundle": "^3.10.2", "d3": "^7.0.0", - "dependency-graph": "^0.11.0", "diff": "^5.0.0", "document-register-element": "^1.7.2", "domino": "~2.1.6", diff --git a/packages/common/BUILD.bazel b/packages/common/BUILD.bazel index 626e942398f..f91773fb352 100644 --- a/packages/common/BUILD.bazel +++ b/packages/common/BUILD.bazel @@ -52,7 +52,6 @@ ng_package( "//integration:__subpackages__", "//packages/bazel/test/ng_package:__pkg__", "//packages/compiler-cli/integrationtest:__pkg__", - "//packages/compiler-cli/ngcc/test:__pkg__", "//packages/compiler-cli/test:__pkg__", "//packages/compiler-cli/test/diagnostics:__pkg__", "//packages/compiler-cli/test/transformers:__pkg__", diff --git a/packages/compiler-cli/BUILD.bazel b/packages/compiler-cli/BUILD.bazel index fd4833fa90d..f937e51330b 100644 --- a/packages/compiler-cli/BUILD.bazel +++ b/packages/compiler-cli/BUILD.bazel @@ -7,7 +7,6 @@ package(default_visibility = ["//visibility:public"]) PUBLIC_TARGETS = [ ":compiler-cli", "//packages/compiler-cli/private", - "//packages/compiler-cli/ngcc", "//packages/compiler-cli/linker", "//packages/compiler-cli/linker/babel", ] @@ -23,8 +22,6 @@ esbuild( entry_points = [ ":index.ts", "//packages/compiler-cli:src/bin/ngc.ts", - "//packages/compiler-cli/ngcc:main-ngcc.ts", - "//packages/compiler-cli/ngcc:index.ts", "//packages/compiler-cli:src/bin/ng_xi18n.ts", "//packages/compiler-cli/linker:index.ts", "//packages/compiler-cli/linker/babel:index.ts", @@ -32,8 +29,6 @@ esbuild( "//packages/compiler-cli/private:localize.ts", "//packages/compiler-cli/private:migrations.ts", "//packages/compiler-cli/private:tooling.ts", - "//packages/compiler-cli/ngcc:src/locking/lock_file_with_child_process/ngcc_lock_unlocker.ts", - "//packages/compiler-cli/ngcc:src/execution/cluster/ngcc_cluster_worker.ts", ], external = [ "@angular/compiler", @@ -43,8 +38,6 @@ esbuild( "reflect-metadata", "chokidar", "convert-source-map", - "dependency-graph", - "magic-string", "semver", "@jridgewell/sourcemap-codec", "tslib", diff --git a/packages/compiler-cli/ngcc/BUILD.bazel b/packages/compiler-cli/ngcc/BUILD.bazel deleted file mode 100644 index 5d89fe76035..00000000000 --- a/packages/compiler-cli/ngcc/BUILD.bazel +++ /dev/null @@ -1,46 +0,0 @@ -load("//tools:defaults.bzl", "ts_library") - -package(default_visibility = ["//visibility:public"]) - -ts_library( - name = "ngcc", - srcs = glob([ - "*.ts", - "**/*.ts", - ]), - tsconfig = "//packages/compiler-cli:tsconfig", - deps = [ - "//packages:types", - "//packages/compiler", - "//packages/compiler-cli", - "//packages/compiler-cli/src/ngtsc/annotations", - "//packages/compiler-cli/src/ngtsc/annotations/common", - "//packages/compiler-cli/src/ngtsc/cycles", - "//packages/compiler-cli/src/ngtsc/diagnostics", - "//packages/compiler-cli/src/ngtsc/file_system", - "//packages/compiler-cli/src/ngtsc/imports", - "//packages/compiler-cli/src/ngtsc/incremental:api", - "//packages/compiler-cli/src/ngtsc/incremental/semantic_graph", - "//packages/compiler-cli/src/ngtsc/logging", - "//packages/compiler-cli/src/ngtsc/metadata", - "//packages/compiler-cli/src/ngtsc/partial_evaluator", - "//packages/compiler-cli/src/ngtsc/perf", - "//packages/compiler-cli/src/ngtsc/reflection", - "//packages/compiler-cli/src/ngtsc/scope", - "//packages/compiler-cli/src/ngtsc/shims", - "//packages/compiler-cli/src/ngtsc/sourcemaps", - "//packages/compiler-cli/src/ngtsc/transform", - "//packages/compiler-cli/src/ngtsc/translator", - "//packages/compiler-cli/src/ngtsc/ts_compatibility", - "//packages/compiler-cli/src/ngtsc/util", - "@npm//@types/convert-source-map", - "@npm//@types/node", - "@npm//@types/semver", - "@npm//@types/yargs", - "@npm//dependency-graph", - "@npm//magic-string", - "@npm//semver", - "@npm//typescript", - "@npm//yargs", - ], -) diff --git a/packages/compiler-cli/ngcc/README.md b/packages/compiler-cli/ngcc/README.md deleted file mode 100644 index f03e235a456..00000000000 --- a/packages/compiler-cli/ngcc/README.md +++ /dev/null @@ -1,30 +0,0 @@ -# Angular Compatibility Compiler (ngcc) - -This compiler will convert `node_modules` compiled with `ngc`, into `node_modules` which -appear to have been compiled with `ngtsc`. - -This conversion will allow such "legacy" packages to be used by the Ivy rendering engine. - -## Building - -The project is built using Bazel: - -```bash -yarn bazel build //packages/compiler-cli/ngcc -``` - -## Unit Testing - -The unit tests are built and run using Bazel: - -```bash -yarn bazel test //packages/compiler-cli/ngcc/test -``` - -## Integration Testing - -There are tests that check the behavior of the overall executable: - -```bash -yarn bazel test //packages/compiler-cli/ngcc/test:integration -``` diff --git a/packages/compiler-cli/ngcc/index.ts b/packages/compiler-cli/ngcc/index.ts deleted file mode 100644 index 8d9bb5a1cba..00000000000 --- a/packages/compiler-cli/ngcc/index.ts +++ /dev/null @@ -1,38 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ - -import {dirname, join} from 'path'; -import {fileURLToPath} from 'url'; - -import {NodeJSFileSystem, setFileSystem} from '../src/ngtsc/file_system'; - -import {mainNgcc} from './src/main'; -import {AsyncNgccOptions, SyncNgccOptions} from './src/ngcc_options'; - -export {ConsoleLogger, Logger, LogLevel} from '../src/ngtsc/logging'; -export {AsyncNgccOptions, clearTsConfigCache, NgccOptions, SyncNgccOptions} from './src/ngcc_options'; -export {PathMappings} from './src/path_mappings'; - -export function process(options: T): - T extends AsyncNgccOptions ? Promise: void; -export function process(options: AsyncNgccOptions|SyncNgccOptions): void|Promise { - setFileSystem(new NodeJSFileSystem()); - return mainNgcc(options); -} - -// The current script path is needed for providing an absolute path to the -// ngcc command line entry-point script (leveraged by the Angular CLI). -const containingDirPath = dirname(fileURLToPath(import.meta.url)); - -/** - * Absolute file path that points to the `ngcc` command line entry-point. - * - * This can be used by the Angular CLI to spawn a process running ngcc using - * command line options. - */ -export const ngccMainFilePath = join(containingDirPath, './main-ngcc.js'); diff --git a/packages/compiler-cli/ngcc/main-ngcc.ts b/packages/compiler-cli/ngcc/main-ngcc.ts deleted file mode 100644 index fdfbb77c143..00000000000 --- a/packages/compiler-cli/ngcc/main-ngcc.ts +++ /dev/null @@ -1,28 +0,0 @@ -#!/usr/bin/env node -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import {mainNgcc} from './src/main'; -import {parseCommandLineOptions} from './src/command_line_options'; - -// CLI entry point -process.title = 'ngcc'; -const startTime = Date.now(); -const options = parseCommandLineOptions(process.argv.slice(2)); -(async () => { - try { - await mainNgcc(options); - if (options.logger) { - const duration = Math.round((Date.now() - startTime) / 1000); - options.logger.debug(`Run ngcc in ${duration}s.`); - } - process.exitCode = 0; - } catch (e: any) { - console.error((e as Error).stack || (e as Error).message); - process.exit(typeof e.code === 'number' ? e.code : 1); - } -})(); diff --git a/packages/compiler-cli/ngcc/src/analysis/decoration_analyzer.ts b/packages/compiler-cli/ngcc/src/analysis/decoration_analyzer.ts deleted file mode 100644 index 7f017c6d96a..00000000000 --- a/packages/compiler-cli/ngcc/src/analysis/decoration_analyzer.ts +++ /dev/null @@ -1,271 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import {ConstantPool} from '@angular/compiler'; -import ts from 'typescript'; - -import {ParsedConfiguration} from '../../..'; -import {ComponentDecoratorHandler, DirectiveDecoratorHandler, InjectableDecoratorHandler, NgModuleDecoratorHandler, PipeDecoratorHandler, ReferencesRegistry, ResourceLoader} from '../../../src/ngtsc/annotations'; -import {InjectableClassRegistry} from '../../../src/ngtsc/annotations/common'; -import {CycleAnalyzer, CycleHandlingStrategy, ImportGraph} from '../../../src/ngtsc/cycles'; -import {isFatalDiagnosticError} from '../../../src/ngtsc/diagnostics'; -import {absoluteFromSourceFile, LogicalFileSystem, ReadonlyFileSystem} from '../../../src/ngtsc/file_system'; -import {AbsoluteModuleStrategy, LocalIdentifierStrategy, LogicalProjectStrategy, ModuleResolver, PrivateExportAliasingHost, Reexport, ReferenceEmitter} from '../../../src/ngtsc/imports'; -import {SemanticSymbol} from '../../../src/ngtsc/incremental/semantic_graph'; -import {CompoundMetadataReader, CompoundMetadataRegistry, DtsMetadataReader, HostDirectivesResolver, LocalMetadataRegistry, ResourceRegistry} from '../../../src/ngtsc/metadata'; -import {PartialEvaluator} from '../../../src/ngtsc/partial_evaluator'; -import {NOOP_PERF_RECORDER} from '../../../src/ngtsc/perf'; -import {LocalModuleScopeRegistry, MetadataDtsModuleScopeResolver, TypeCheckScopeRegistry} from '../../../src/ngtsc/scope'; -import {DecoratorHandler} from '../../../src/ngtsc/transform'; -import {NgccReflectionHost} from '../host/ngcc_host'; -import {Migration} from '../migrations/migration'; -import {MissingInjectableMigration} from '../migrations/missing_injectable_migration'; -import {UndecoratedChildMigration} from '../migrations/undecorated_child_migration'; -import {UndecoratedParentMigration} from '../migrations/undecorated_parent_migration'; -import {EntryPointBundle} from '../packages/entry_point_bundle'; - -import {DefaultMigrationHost} from './migration_host'; -import {NgccTraitCompiler} from './ngcc_trait_compiler'; -import {CompiledClass, CompiledFile, DecorationAnalyses} from './types'; -import {isWithinPackage, NOOP_DEPENDENCY_TRACKER} from './util'; - - - -/** - * Simple class that resolves and loads files directly from the filesystem. - */ -class NgccResourceLoader implements ResourceLoader { - constructor(private fs: ReadonlyFileSystem) {} - canPreload = false; - canPreprocess = false; - preload(): undefined|Promise { - throw new Error('Not implemented.'); - } - preprocessInline(): Promise { - throw new Error('Not implemented.'); - } - load(url: string): string { - return this.fs.readFile(this.fs.resolve(url)); - } - resolve(url: string, containingFile: string): string { - return this.fs.resolve(this.fs.dirname(containingFile), url); - } -} - -/** - * This Analyzer will analyze the files that have decorated classes that need to be transformed. - */ -export class DecorationAnalyzer { - private program = this.bundle.src.program; - private options = this.bundle.src.options; - private host = this.bundle.src.host; - private typeChecker = this.bundle.src.program.getTypeChecker(); - private rootDirs = this.bundle.rootDirs; - private packagePath = this.bundle.entryPoint.packagePath; - private isCore = this.bundle.isCore; - private compilerOptions = this.tsConfig !== null ? this.tsConfig.options : {}; - - moduleResolver = - new ModuleResolver(this.program, this.options, this.host, /* moduleResolutionCache */ null); - resourceManager = new NgccResourceLoader(this.fs); - metaRegistry = new LocalMetadataRegistry(); - dtsMetaReader = new DtsMetadataReader(this.typeChecker, this.reflectionHost); - fullMetaReader = new CompoundMetadataReader([this.metaRegistry, this.dtsMetaReader]); - refEmitter = new ReferenceEmitter([ - new LocalIdentifierStrategy(), - new AbsoluteModuleStrategy( - this.program, this.typeChecker, this.moduleResolver, this.reflectionHost), - // TODO(alxhub): there's no reason why ngcc needs the "logical file system" logic here, as ngcc - // projects only ever have one rootDir. Instead, ngcc should just switch its emitted import - // based on whether a bestGuessOwningModule is present in the Reference. - new LogicalProjectStrategy( - this.reflectionHost, new LogicalFileSystem(this.rootDirs, this.host)), - ]); - aliasingHost = this.bundle.entryPoint.generateDeepReexports ? - new PrivateExportAliasingHost(this.reflectionHost) : - null; - dtsModuleScopeResolver = - new MetadataDtsModuleScopeResolver(this.dtsMetaReader, this.aliasingHost); - scopeRegistry = new LocalModuleScopeRegistry( - this.metaRegistry, this.fullMetaReader, this.dtsModuleScopeResolver, this.refEmitter, - this.aliasingHost); - fullRegistry = new CompoundMetadataRegistry([this.metaRegistry, this.scopeRegistry]); - evaluator = - new PartialEvaluator(this.reflectionHost, this.typeChecker, /* dependencyTracker */ null); - importGraph = new ImportGraph(this.typeChecker, NOOP_PERF_RECORDER); - cycleAnalyzer = new CycleAnalyzer(this.importGraph); - injectableRegistry = new InjectableClassRegistry(this.reflectionHost, this.isCore); - hostDirectivesResolver = new HostDirectivesResolver(this.fullMetaReader); - typeCheckScopeRegistry = new TypeCheckScopeRegistry( - this.scopeRegistry, this.fullMetaReader, this.hostDirectivesResolver); - handlers: DecoratorHandler[] = [ - new ComponentDecoratorHandler( - this.reflectionHost, this.evaluator, this.fullRegistry, this.fullMetaReader, - this.scopeRegistry, this.dtsModuleScopeResolver, this.scopeRegistry, - this.typeCheckScopeRegistry, new ResourceRegistry(), this.isCore, - /* strictCtorDeps */ false, this.resourceManager, this.rootDirs, - !!this.compilerOptions.preserveWhitespaces, - /* i18nUseExternalIds */ true, this.bundle.enableI18nLegacyMessageIdFormat, - /* usePoisonedData */ false, - /* i18nNormalizeLineEndingsInICUs */ false, this.moduleResolver, this.cycleAnalyzer, - CycleHandlingStrategy.UseRemoteScoping, this.refEmitter, NOOP_DEPENDENCY_TRACKER, - this.injectableRegistry, - /* semanticDepGraphUpdater */ null, !!this.compilerOptions.annotateForClosureCompiler, - NOOP_PERF_RECORDER, this.hostDirectivesResolver), - - // See the note in ngtsc about why this cast is needed. - // clang-format off - new DirectiveDecoratorHandler( - this.reflectionHost, this.evaluator, this.fullRegistry, this.scopeRegistry, - this.fullMetaReader, this.injectableRegistry, this.refEmitter, this.isCore, /* strictCtorDeps */ false, - /* semanticDepGraphUpdater */ null, - !!this.compilerOptions.annotateForClosureCompiler, - // In ngcc we want to compile undecorated classes with Angular features. As of - // version 10, undecorated classes that use Angular features are no longer handled - // in ngtsc, but we want to ensure compatibility in ngcc for outdated libraries that - // have not migrated to explicit decorators. See: https://hackmd.io/@alx/ryfYYuvzH. - /* compileUndecoratedClassesWithAngularFeatures */ true, - NOOP_PERF_RECORDER, - ) as DecoratorHandler, - // clang-format on - // Pipe handler must be before injectable handler in list so pipe factories are printed - // before injectable factories (so injectable factories can delegate to them) - new PipeDecoratorHandler( - this.reflectionHost, this.evaluator, this.metaRegistry, this.scopeRegistry, - this.injectableRegistry, this.isCore, NOOP_PERF_RECORDER), - new InjectableDecoratorHandler( - this.reflectionHost, this.evaluator, this.isCore, - /* strictCtorDeps */ false, this.injectableRegistry, NOOP_PERF_RECORDER, - /* errorOnDuplicateProv */ false), - new NgModuleDecoratorHandler( - this.reflectionHost, this.evaluator, this.fullMetaReader, this.fullRegistry, - this.scopeRegistry, this.referencesRegistry, this.isCore, this.refEmitter, - /* factoryTracker */ null, !!this.compilerOptions.annotateForClosureCompiler, - /* onlyPublishPublicTypings */ false, this.injectableRegistry, NOOP_PERF_RECORDER), - ]; - compiler = new NgccTraitCompiler(this.handlers, this.reflectionHost); - migrations: Migration[] = [ - new UndecoratedParentMigration(), - new UndecoratedChildMigration(), - new MissingInjectableMigration(), - ]; - - constructor( - private fs: ReadonlyFileSystem, private bundle: EntryPointBundle, - private reflectionHost: NgccReflectionHost, private referencesRegistry: ReferencesRegistry, - private diagnosticHandler: (error: ts.Diagnostic) => void = () => {}, - private tsConfig: ParsedConfiguration|null = null) {} - - /** - * Analyze a program to find all the decorated files should be transformed. - * - * @returns a map of the source files to the analysis for those files. - */ - analyzeProgram(): DecorationAnalyses { - for (const sourceFile of this.program.getSourceFiles()) { - if (!sourceFile.isDeclarationFile && - isWithinPackage(this.packagePath, absoluteFromSourceFile(sourceFile))) { - this.compiler.analyzeFile(sourceFile); - } - } - - this.applyMigrations(); - - this.compiler.resolve(); - - this.reportDiagnostics(); - - const decorationAnalyses = new DecorationAnalyses(); - for (const analyzedFile of this.compiler.analyzedFiles) { - const compiledFile = this.compileFile(analyzedFile); - decorationAnalyses.set(compiledFile.sourceFile, compiledFile); - } - return decorationAnalyses; - } - - protected applyMigrations(): void { - const migrationHost = new DefaultMigrationHost( - this.reflectionHost, this.fullMetaReader, this.evaluator, this.compiler, - this.bundle.entryPoint.path); - - this.migrations.forEach(migration => { - this.compiler.analyzedFiles.forEach(analyzedFile => { - const records = this.compiler.recordsFor(analyzedFile); - if (records === null) { - throw new Error('Assertion error: file to migrate must have records.'); - } - - records.forEach(record => { - const addDiagnostic = (diagnostic: ts.Diagnostic) => { - if (record.metaDiagnostics === null) { - record.metaDiagnostics = []; - } - record.metaDiagnostics.push(diagnostic); - }; - - try { - const result = migration.apply(record.node, migrationHost); - if (result !== null) { - addDiagnostic(result); - } - } catch (e) { - if (isFatalDiagnosticError(e)) { - addDiagnostic(e.toDiagnostic()); - } else { - throw e; - } - } - }); - }); - }); - } - - protected reportDiagnostics() { - this.compiler.diagnostics.forEach(this.diagnosticHandler); - } - - protected compileFile(sourceFile: ts.SourceFile): CompiledFile { - const constantPool = new ConstantPool(); - const records = this.compiler.recordsFor(sourceFile); - if (records === null) { - throw new Error('Assertion error: file to compile must have records.'); - } - - const compiledClasses: CompiledClass[] = []; - - for (const record of records) { - const compilation = this.compiler.compile(record.node, constantPool); - if (compilation === null) { - continue; - } - - compiledClasses.push({ - name: record.node.name.text, - decorators: this.compiler.getAllDecorators(record.node), - declaration: record.node, - compilation - }); - } - - const reexports = this.getReexportsForSourceFile(sourceFile); - return {constantPool, sourceFile: sourceFile, compiledClasses, reexports}; - } - - private getReexportsForSourceFile(sf: ts.SourceFile): Reexport[] { - const exportStatements = this.compiler.exportStatements; - if (!exportStatements.has(sf.fileName)) { - return []; - } - const exports = exportStatements.get(sf.fileName)!; - - const reexports: Reexport[] = []; - exports.forEach(([fromModule, symbolName], asAlias) => { - reexports.push({asAlias, fromModule, symbolName}); - }); - return reexports; - } -} diff --git a/packages/compiler-cli/ngcc/src/analysis/migration_host.ts b/packages/compiler-cli/ngcc/src/analysis/migration_host.ts deleted file mode 100644 index c039700dd41..00000000000 --- a/packages/compiler-cli/ngcc/src/analysis/migration_host.ts +++ /dev/null @@ -1,94 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import ts from 'typescript'; - -import {absoluteFromSourceFile, AbsoluteFsPath} from '../../../src/ngtsc/file_system'; -import {MetadataReader} from '../../../src/ngtsc/metadata'; -import {PartialEvaluator} from '../../../src/ngtsc/partial_evaluator'; -import {ClassDeclaration, Decorator} from '../../../src/ngtsc/reflection'; -import {HandlerFlags, TraitState} from '../../../src/ngtsc/transform'; -import {NgccReflectionHost} from '../host/ngcc_host'; -import {MigrationHost} from '../migrations/migration'; - -import {NgccTraitCompiler} from './ngcc_trait_compiler'; -import {isWithinPackage} from './util'; - -/** - * The standard implementation of `MigrationHost`, which is created by the `DecorationAnalyzer`. - */ -export class DefaultMigrationHost implements MigrationHost { - constructor( - readonly reflectionHost: NgccReflectionHost, readonly metadata: MetadataReader, - readonly evaluator: PartialEvaluator, private compiler: NgccTraitCompiler, - private entryPointPath: AbsoluteFsPath) {} - - injectSyntheticDecorator(clazz: ClassDeclaration, decorator: Decorator, flags?: HandlerFlags): - void { - const migratedTraits = this.compiler.injectSyntheticDecorator(clazz, decorator, flags); - - for (const trait of migratedTraits) { - if ((trait.state === TraitState.Analyzed || trait.state === TraitState.Resolved) && - trait.analysisDiagnostics !== null) { - trait.analysisDiagnostics = trait.analysisDiagnostics.map( - diag => createMigrationDiagnostic(diag, clazz, decorator)); - } - if (trait.state === TraitState.Resolved && trait.resolveDiagnostics !== null) { - trait.resolveDiagnostics = - trait.resolveDiagnostics.map(diag => createMigrationDiagnostic(diag, clazz, decorator)); - } - } - } - - getAllDecorators(clazz: ClassDeclaration): Decorator[]|null { - return this.compiler.getAllDecorators(clazz); - } - - isInScope(clazz: ClassDeclaration): boolean { - return isWithinPackage(this.entryPointPath, absoluteFromSourceFile(clazz.getSourceFile())); - } -} - -/** - * Creates a diagnostic from another one, containing additional information about the synthetic - * decorator. - */ -function createMigrationDiagnostic( - diagnostic: ts.Diagnostic, source: ts.Node, decorator: Decorator): ts.Diagnostic { - const clone = {...diagnostic}; - - const chain: ts.DiagnosticMessageChain[] = [{ - messageText: `Occurs for @${decorator.name} decorator inserted by an automatic migration`, - category: ts.DiagnosticCategory.Message, - code: 0, - }]; - - if (decorator.args !== null) { - const args = decorator.args.map(arg => arg.getText()).join(', '); - chain.push({ - messageText: `@${decorator.name}(${args})`, - category: ts.DiagnosticCategory.Message, - code: 0, - }); - } - - if (typeof clone.messageText === 'string') { - clone.messageText = { - messageText: clone.messageText, - category: diagnostic.category, - code: diagnostic.code, - next: chain, - }; - } else { - if (clone.messageText.next === undefined) { - clone.messageText.next = chain; - } else { - clone.messageText.next.push(...chain); - } - } - return clone; -} diff --git a/packages/compiler-cli/ngcc/src/analysis/module_with_providers_analyzer.ts b/packages/compiler-cli/ngcc/src/analysis/module_with_providers_analyzer.ts deleted file mode 100644 index 081af69a4a0..00000000000 --- a/packages/compiler-cli/ngcc/src/analysis/module_with_providers_analyzer.ts +++ /dev/null @@ -1,231 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import ts from 'typescript'; - -import {ReferencesRegistry} from '../../../src/ngtsc/annotations'; -import {Reference} from '../../../src/ngtsc/imports'; -import {PartialEvaluator} from '../../../src/ngtsc/partial_evaluator'; -import {ClassDeclaration, DeclarationNode, isNamedClassDeclaration, isNamedVariableDeclaration} from '../../../src/ngtsc/reflection'; -import {NgccReflectionHost} from '../host/ngcc_host'; -import {hasNameIdentifier, isDefined} from '../utils'; - -/** - * A structure returned from `getModuleWithProvidersFunctions()` that describes functions - * that return ModuleWithProviders objects. - */ -export interface ModuleWithProvidersInfo { - /** - * The name of the declared function. - */ - name: string; - /** - * The declaration of the function that returns the `ModuleWithProviders` object. - */ - declaration: ts.SignatureDeclaration; - /** - * Declaration of the containing class (if this is a method) - */ - container: DeclarationNode|null; - /** - * The declaration of the class that the `ngModule` property on the `ModuleWithProviders` object - * refers to. - */ - ngModule: Reference; -} - -export type ModuleWithProvidersAnalyses = Map; -export const ModuleWithProvidersAnalyses = Map; - -export class ModuleWithProvidersAnalyzer { - private evaluator = new PartialEvaluator(this.host, this.typeChecker, null); - - constructor( - private host: NgccReflectionHost, private typeChecker: ts.TypeChecker, - private referencesRegistry: ReferencesRegistry, private processDts: boolean) {} - - analyzeProgram(program: ts.Program): ModuleWithProvidersAnalyses { - const analyses: ModuleWithProvidersAnalyses = new ModuleWithProvidersAnalyses(); - const rootFiles = this.getRootFiles(program); - rootFiles.forEach(f => { - const fns = this.getModuleWithProvidersFunctions(f); - fns && fns.forEach(fn => { - if (fn.ngModule.bestGuessOwningModule === null) { - // Record the usage of an internal module as it needs to become an exported symbol - this.referencesRegistry.add(fn.ngModule.node, new Reference(fn.ngModule.node)); - } - - // Only when processing the dts files do we need to determine which declaration to update. - if (this.processDts) { - const dtsFn = this.getDtsModuleWithProvidersFunction(fn); - const dtsFnType = dtsFn.declaration.type; - const typeParam = dtsFnType && ts.isTypeReferenceNode(dtsFnType) && - dtsFnType.typeArguments && dtsFnType.typeArguments[0] || - null; - if (!typeParam || isAnyKeyword(typeParam)) { - const dtsFile = dtsFn.declaration.getSourceFile(); - const analysis = analyses.has(dtsFile) ? analyses.get(dtsFile)! : []; - analysis.push(dtsFn); - analyses.set(dtsFile, analysis); - } - } - }); - }); - return analyses; - } - - private getRootFiles(program: ts.Program): ts.SourceFile[] { - return program.getRootFileNames().map(f => program.getSourceFile(f)).filter(isDefined); - } - - private getModuleWithProvidersFunctions(f: ts.SourceFile): ModuleWithProvidersInfo[] { - const exports = this.host.getExportsOfModule(f); - if (!exports) return []; - const infos: ModuleWithProvidersInfo[] = []; - exports.forEach((declaration) => { - if (declaration.node === null) { - return; - } - if (this.host.isClass(declaration.node)) { - this.host.getMembersOfClass(declaration.node).forEach(member => { - if (member.isStatic) { - const info = this.parseForModuleWithProviders( - member.name, member.node, member.implementation, declaration.node); - if (info) { - infos.push(info); - } - } - }); - } else { - if (hasNameIdentifier(declaration.node)) { - const info = - this.parseForModuleWithProviders(declaration.node.name.text, declaration.node); - if (info) { - infos.push(info); - } - } - } - }); - return infos; - } - - /** - * Parse a function/method node (or its implementation), to see if it returns a - * `ModuleWithProviders` object. - * @param name The name of the function. - * @param node the node to check - this could be a function, a method or a variable declaration. - * @param implementation the actual function expression if `node` is a variable declaration. - * @param container the class that contains the function, if it is a method. - * @returns info about the function if it does return a `ModuleWithProviders` object; `null` - * otherwise. - */ - private parseForModuleWithProviders( - name: string, node: ts.Node|null, implementation: ts.Node|null = node, - container: DeclarationNode|null = null): ModuleWithProvidersInfo|null { - if (implementation === null || - (!ts.isFunctionDeclaration(implementation) && !ts.isMethodDeclaration(implementation) && - !ts.isFunctionExpression(implementation))) { - return null; - } - const declaration = implementation; - const definition = this.host.getDefinitionOfFunction(declaration); - if (definition === null) { - return null; - } - - const body = definition.body; - if (body === null || body.length === 0) { - return null; - } - - // Get hold of the return statement expression for the function - const lastStatement = body[body.length - 1]; - if (!ts.isReturnStatement(lastStatement) || lastStatement.expression === undefined) { - return null; - } - - // Evaluate this expression and extract the `ngModule` reference - const result = this.evaluator.evaluate(lastStatement.expression); - if (!(result instanceof Map) || !result.has('ngModule')) { - return null; - } - - const ngModuleRef = result.get('ngModule')!; - if (!(ngModuleRef instanceof Reference)) { - return null; - } - - if (!isNamedClassDeclaration(ngModuleRef.node) && - !isNamedVariableDeclaration(ngModuleRef.node)) { - throw new Error(`The identity given by ${ngModuleRef.debugName} referenced in "${ - declaration!.getText()}" doesn't appear to be a "class" declaration.`); - } - - const ngModule = ngModuleRef as Reference; - return {name, ngModule, declaration, container}; - } - - private getDtsModuleWithProvidersFunction(fn: ModuleWithProvidersInfo): ModuleWithProvidersInfo { - let dtsFn: ts.Declaration|null = null; - const containerClass = fn.container && this.host.getClassSymbol(fn.container); - if (containerClass) { - const dtsClass = this.host.getDtsDeclaration(containerClass.declaration.valueDeclaration); - // Get the declaration of the matching static method - dtsFn = dtsClass && ts.isClassDeclaration(dtsClass) ? - dtsClass.members.find( - member => ts.isMethodDeclaration(member) && ts.isIdentifier(member.name) && - member.name.text === fn.name) as ts.Declaration : - null; - } else { - dtsFn = this.host.getDtsDeclaration(fn.declaration); - } - if (!dtsFn) { - throw new Error(`Matching type declaration for ${fn.declaration.getText()} is missing`); - } - if (!isFunctionOrMethod(dtsFn)) { - throw new Error(`Matching type declaration for ${ - fn.declaration.getText()} is not a function: ${dtsFn.getText()}`); - } - const container = containerClass ? containerClass.declaration.valueDeclaration : null; - const ngModule = this.resolveNgModuleReference(fn); - return {name: fn.name, container, declaration: dtsFn, ngModule}; - } - - private resolveNgModuleReference(fn: ModuleWithProvidersInfo): Reference { - const ngModule = fn.ngModule; - - // For external module references, use the declaration as is. - if (ngModule.bestGuessOwningModule !== null) { - return ngModule; - } - - // For internal (non-library) module references, redirect the module's value declaration - // to its type declaration. - const dtsNgModule = this.host.getDtsDeclaration(ngModule.node); - if (!dtsNgModule) { - throw new Error(`No typings declaration can be found for the referenced NgModule class in ${ - fn.declaration.getText()}.`); - } - if (!isNamedClassDeclaration(dtsNgModule)) { - throw new Error(`The referenced NgModule in ${ - fn.declaration - .getText()} is not a named class declaration in the typings program; instead we get ${ - dtsNgModule.getText()}`); - } - return new Reference(dtsNgModule, null); - } -} - - -function isFunctionOrMethod(declaration: ts.Declaration): declaration is ts.FunctionDeclaration| - ts.MethodDeclaration { - return ts.isFunctionDeclaration(declaration) || ts.isMethodDeclaration(declaration); -} - -function isAnyKeyword(typeParam: ts.TypeNode): typeParam is ts.KeywordTypeNode { - return typeParam.kind === ts.SyntaxKind.AnyKeyword; -} diff --git a/packages/compiler-cli/ngcc/src/analysis/ngcc_references_registry.ts b/packages/compiler-cli/ngcc/src/analysis/ngcc_references_registry.ts deleted file mode 100644 index 458bf946aa4..00000000000 --- a/packages/compiler-cli/ngcc/src/analysis/ngcc_references_registry.ts +++ /dev/null @@ -1,51 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ - -import ts from 'typescript'; -import {ReferencesRegistry} from '../../../src/ngtsc/annotations'; -import {Reference} from '../../../src/ngtsc/imports'; -import {Declaration, DeclarationNode, ReflectionHost} from '../../../src/ngtsc/reflection'; -import {hasNameIdentifier} from '../utils'; - -/** - * This is a place for DecoratorHandlers to register references that they - * find in their analysis of the code. - * - * This registry is used to ensure that these references are publicly exported - * from libraries that are compiled by ngcc. - */ -export class NgccReferencesRegistry implements ReferencesRegistry { - private map = new Map(); - - constructor(private host: ReflectionHost) {} - - /** - * Register one or more references in the registry. - * Only `ResolveReference` references are stored. Other types are ignored. - * @param references A collection of references to register. - */ - add(source: DeclarationNode, ...references: Reference[]): void { - references.forEach(ref => { - // Only store relative references. We are not interested in literals. - if (ref.bestGuessOwningModule === null && hasNameIdentifier(ref.node)) { - const declaration = this.host.getDeclarationOfIdentifier(ref.node.name); - if (declaration && hasNameIdentifier(declaration.node)) { - this.map.set(declaration.node.name, declaration); - } - } - }); - } - - /** - * Create and return a mapping for the registered resolved references. - * @returns A map of reference identifiers to reference declarations. - */ - getDeclarationMap(): Map { - return this.map; - } -} diff --git a/packages/compiler-cli/ngcc/src/analysis/ngcc_trait_compiler.ts b/packages/compiler-cli/ngcc/src/analysis/ngcc_trait_compiler.ts deleted file mode 100644 index 74a33c8a7a1..00000000000 --- a/packages/compiler-cli/ngcc/src/analysis/ngcc_trait_compiler.ts +++ /dev/null @@ -1,98 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import ts from 'typescript'; - -import {IncrementalBuild} from '../../../src/ngtsc/incremental/api'; -import {SemanticSymbol} from '../../../src/ngtsc/incremental/semantic_graph'; -import {NOOP_PERF_RECORDER} from '../../../src/ngtsc/perf'; -import {ClassDeclaration, Decorator} from '../../../src/ngtsc/reflection'; -import {isShim} from '../../../src/ngtsc/shims'; -import {CompilationMode, DecoratorHandler, DtsTransformRegistry, HandlerFlags, Trait, TraitCompiler} from '../../../src/ngtsc/transform'; -import {NgccReflectionHost} from '../host/ngcc_host'; -import {isDefined} from '../utils'; - -/** - * Specializes the `TraitCompiler` for ngcc purposes. Mainly, this includes an alternative way of - * scanning for classes to compile using the reflection host's `findClassSymbols`, together with - * support to inject synthetic decorators into the compilation for ad-hoc migrations that ngcc - * performs. - */ -export class NgccTraitCompiler extends TraitCompiler { - constructor( - handlers: DecoratorHandler[], - private ngccReflector: NgccReflectionHost) { - super( - handlers, ngccReflector, NOOP_PERF_RECORDER, new NoIncrementalBuild(), - /* compileNonExportedClasses */ true, CompilationMode.FULL, new DtsTransformRegistry(), - /* semanticDepGraphUpdater */ null, {isShim, isResource: () => false}); - } - - get analyzedFiles(): ts.SourceFile[] { - return Array.from(this.fileToClasses.keys()); - } - - /** - * Analyzes the source file in search for classes to process. For any class that is found in the - * file, a `ClassRecord` is created and the source file is included in the `analyzedFiles` array. - */ - analyzeFile(sf: ts.SourceFile): void { - const ngccClassSymbols = this.ngccReflector.findClassSymbols(sf); - for (const classSymbol of ngccClassSymbols) { - this.analyzeClass(classSymbol.declaration.valueDeclaration, null); - } - - return undefined; - } - - /** - * Associate a new synthesized decorator, which did not appear in the original source, with a - * given class. - * @param clazz the class to receive the new decorator. - * @param decorator the decorator to inject. - * @param flags optional bitwise flag to influence the compilation of the decorator. - */ - injectSyntheticDecorator(clazz: ClassDeclaration, decorator: Decorator, flags?: HandlerFlags): - Trait[] { - const migratedTraits = this.detectTraits(clazz, [decorator]); - if (migratedTraits === null) { - return []; - } - - for (const trait of migratedTraits) { - this.analyzeTrait(clazz, trait, flags); - } - - return migratedTraits; - } - - /** - * Returns all decorators that have been recognized for the provided class, including any - * synthetically injected decorators. - * @param clazz the declaration for which the decorators are returned. - */ - getAllDecorators(clazz: ClassDeclaration): Decorator[]|null { - const record = this.recordFor(clazz); - if (record === null) { - return null; - } - - return record.traits.map(trait => trait.detected.decorator).filter(isDefined); - } -} - -class NoIncrementalBuild implements IncrementalBuild { - priorAnalysisFor(sf: ts.SourceFile): any[]|null { - return null; - } - - priorTypeCheckingResultsFor(): null { - return null; - } - - recordSuccessfulTypeCheck(): void {} -} diff --git a/packages/compiler-cli/ngcc/src/analysis/private_declarations_analyzer.ts b/packages/compiler-cli/ngcc/src/analysis/private_declarations_analyzer.ts deleted file mode 100644 index eba3a0da726..00000000000 --- a/packages/compiler-cli/ngcc/src/analysis/private_declarations_analyzer.ts +++ /dev/null @@ -1,73 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import ts from 'typescript'; - -import {absoluteFromSourceFile, AbsoluteFsPath} from '../../../src/ngtsc/file_system'; -import {Declaration} from '../../../src/ngtsc/reflection'; -import {NgccReflectionHost} from '../host/ngcc_host'; -import {hasNameIdentifier, isDefined} from '../utils'; - -import {NgccReferencesRegistry} from './ngcc_references_registry'; - -export interface ExportInfo { - identifier: string; - from: AbsoluteFsPath; - dtsFrom?: AbsoluteFsPath|null; -} -export type PrivateDeclarationsAnalyses = ExportInfo[]; - -/** - * This class will analyze a program to find all the declared classes - * (i.e. on an NgModule) that are not publicly exported via an entry-point. - */ -export class PrivateDeclarationsAnalyzer { - constructor( - private host: NgccReflectionHost, private referencesRegistry: NgccReferencesRegistry) {} - - analyzeProgram(program: ts.Program): PrivateDeclarationsAnalyses { - const rootFiles = this.getRootFiles(program); - return this.getPrivateDeclarations(rootFiles, this.referencesRegistry.getDeclarationMap()); - } - - private getRootFiles(program: ts.Program): ts.SourceFile[] { - return program.getRootFileNames().map(f => program.getSourceFile(f)).filter(isDefined); - } - - private getPrivateDeclarations( - rootFiles: ts.SourceFile[], - declarations: Map): PrivateDeclarationsAnalyses { - const privateDeclarations: Map = new Map(declarations); - - rootFiles.forEach(f => { - const exports = this.host.getExportsOfModule(f); - if (exports) { - exports.forEach((declaration, exportedName) => { - if (declaration.node !== null && hasNameIdentifier(declaration.node)) { - if (privateDeclarations.has(declaration.node.name)) { - const privateDeclaration = privateDeclarations.get(declaration.node.name)!; - if (privateDeclaration.node !== declaration.node) { - throw new Error(`${declaration.node.name.text} is declared multiple times.`); - } - // This declaration is public so we can remove it from the list - privateDeclarations.delete(declaration.node.name); - } - } - }); - } - }); - - return Array.from(privateDeclarations.keys()).map(id => { - const from = absoluteFromSourceFile(id.getSourceFile()); - const declaration = privateDeclarations.get(id)!; - const dtsDeclaration = this.host.getDtsDeclaration(declaration.node); - const dtsFrom = dtsDeclaration && absoluteFromSourceFile(dtsDeclaration.getSourceFile()); - - return {identifier: id.text, from, dtsFrom}; - }); - } -} diff --git a/packages/compiler-cli/ngcc/src/analysis/types.ts b/packages/compiler-cli/ngcc/src/analysis/types.ts deleted file mode 100644 index cf6aa8add8a..00000000000 --- a/packages/compiler-cli/ngcc/src/analysis/types.ts +++ /dev/null @@ -1,33 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import {ConstantPool} from '@angular/compiler'; -import ts from 'typescript'; -import {Reexport} from '../../../src/ngtsc/imports'; -import {ClassDeclaration, Decorator} from '../../../src/ngtsc/reflection'; -import {CompileResult} from '../../../src/ngtsc/transform'; - -export interface CompiledClass { - name: string; - decorators: Decorator[]|null; - declaration: ClassDeclaration; - compilation: CompileResult[]; -} - -export interface CompiledFile { - compiledClasses: CompiledClass[]; - sourceFile: ts.SourceFile; - constantPool: ConstantPool; - - /** - * Any re-exports which should be added next to this class, both in .js and (if possible) .d.ts. - */ - reexports: Reexport[]; -} - -export type DecorationAnalyses = Map; -export const DecorationAnalyses = Map; diff --git a/packages/compiler-cli/ngcc/src/analysis/util.ts b/packages/compiler-cli/ngcc/src/analysis/util.ts deleted file mode 100644 index f5f5ee6e152..00000000000 --- a/packages/compiler-cli/ngcc/src/analysis/util.ts +++ /dev/null @@ -1,22 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import {AbsoluteFsPath, isLocalRelativePath, relative} from '../../../src/ngtsc/file_system'; -import {DependencyTracker} from '../../../src/ngtsc/incremental/api'; - -export function isWithinPackage(packagePath: AbsoluteFsPath, filePath: AbsoluteFsPath): boolean { - const relativePath = relative(packagePath, filePath); - return isLocalRelativePath(relativePath) && !relativePath.startsWith('node_modules/'); -} - -class NoopDependencyTracker implements DependencyTracker { - addDependency(): void {} - addResourceDependency(): void {} - recordDependencyAnalysisFailure(): void {} -} - -export const NOOP_DEPENDENCY_TRACKER: DependencyTracker = new NoopDependencyTracker(); diff --git a/packages/compiler-cli/ngcc/src/command_line_options.ts b/packages/compiler-cli/ngcc/src/command_line_options.ts deleted file mode 100644 index 5c8cb4c6fed..00000000000 --- a/packages/compiler-cli/ngcc/src/command_line_options.ts +++ /dev/null @@ -1,161 +0,0 @@ -#!/usr/bin/env node -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import yargs from 'yargs'; - -import {setFileSystem, NodeJSFileSystem} from '../../src/ngtsc/file_system'; -import {ConsoleLogger, LogLevel} from '../../src/ngtsc/logging'; -import {NgccOptions} from './ngcc_options'; - -export function parseCommandLineOptions(args: string[]): NgccOptions { - const options = - yargs(args) - .option('s', { - alias: 'source', - describe: - 'A path (relative to the working directory) of the `node_modules` folder to process.', - default: './node_modules', - type: 'string', - }) - .option('f', {alias: 'formats', hidden: true, array: true, type: 'string'}) - .option('p', { - alias: 'properties', - array: true, - describe: - 'An array of names of properties in package.json to compile (e.g. `module` or `main`)\n' + - 'Each of these properties should hold the path to a bundle-format.\n' + - 'If provided, only the specified properties are considered for processing.\n' + - 'If not provided, all the supported format properties (e.g. fesm2015, fesm5, es2015, esm2015, esm5, main, module) in the package.json are considered.', - type: 'string', - }) - .option('t', { - alias: 'target', - describe: - 'A relative path (from the `source` path) to a single entry-point to process (plus its dependencies).\n' + - 'If this property is provided then `error-on-failed-entry-point` is forced to true.\n' + - 'This option overrides the `--use-program-dependencies` option.', - type: 'string', - }) - .option('use-program-dependencies', { - type: 'boolean', - describe: - 'If this property is provided then the entry-points to process are parsed from the program defined by the loaded tsconfig.json. See `--tsconfig`.\n' + - 'This option is overridden by the `--target` option.', - }) - .option('first-only', { - describe: - 'If specified then only the first matching package.json property will be compiled.\n' + - 'This option is overridden by `--typings-only`.', - type: 'boolean', - }) - .option('typings-only', { - describe: - 'If specified then only the typings files are processed, and no JS source files will be modified.\n' + - 'Setting this option will force `--first-only` to be set, since only one format is needed to process the typings', - type: 'boolean', - }) - .option('create-ivy-entry-points', { - describe: - 'If specified then new `*_ivy_ngcc` entry-points will be added to package.json rather than modifying the ones in-place.\n' + - 'For this to work you need to have custom resolution set up (e.g. in webpack) to look for these new entry-points.\n' + - 'The Angular CLI does this already, so it is safe to use this option if the project is being built via the CLI.', - type: 'boolean', - }) - .option('legacy-message-ids', { - describe: 'Render `$localize` messages with legacy format ids.\n' + - 'The default value is `true`. Only set this to `false` if you do not want legacy message ids to\n' + - 'be rendered. For example, if you are not using legacy message ids in your translation files\n' + - 'AND are not doing compile-time inlining of translations, in which case the extra message ids\n' + - 'would add unwanted size to the final source bundle.\n' + - 'It is safe to leave this set to true if you are doing compile-time inlining because the extra\n' + - 'legacy message ids will all be stripped during translation.', - type: 'boolean', - default: true, - }) - .option('async', { - describe: - 'Whether to compile asynchronously. This is enabled by default as it allows compilations to be parallelized.\n' + - 'Disabling asynchronous compilation may be useful for debugging.', - type: 'boolean', - default: true, - }) - .option('l', { - alias: 'loglevel', - describe: 'The lowest severity logging message that should be output.', - choices: ['debug', 'info', 'warn', 'error'], - type: 'string', - }) - .option('invalidate-entry-point-manifest', { - describe: - 'If this is set then ngcc will not read an entry-point manifest file from disk.\n' + - 'Instead it will walk the directory tree as normal looking for entry-points, and then write a new manifest file.', - type: 'boolean', - default: false, - }) - .option('error-on-failed-entry-point', { - describe: - 'Set this option in order to terminate immediately with an error code if an entry-point fails to be processed.\n' + - 'If `-t`/`--target` is provided then this property is always true and cannot be changed. Otherwise the default is false.\n' + - 'When set to false, ngcc will continue to process entry-points after a failure. In which case it will log an error and resume processing other entry-points.', - type: 'boolean', - default: false, - }) - .option('tsconfig', { - describe: - 'A path to a tsconfig.json file that will be used to configure the Angular compiler and module resolution used by ngcc.\n' + - 'If not provided, ngcc will attempt to read a `tsconfig.json` file from the folder above that given by the `-s` option.\n' + - 'Set to false (via `--no-tsconfig`) if you do not want ngcc to use any `tsconfig.json` file.', - type: 'string', - }) - .strict() - .help() - .parseSync(); - - if (options.f?.length) { - console.error( - 'The formats option (-f/--formats) has been removed. Consider the properties option (-p/--properties) instead.'); - process.exit(1); - } - - const fs = new NodeJSFileSystem(); - setFileSystem(fs); - - const baseSourcePath = fs.resolve(options.s || './node_modules'); - const propertiesToConsider = options.p; - const targetEntryPointPath = options.t; - const compileAllFormats = !options['first-only']; - const typingsOnly = options['typings-only']; - const createNewEntryPointFormats = options['create-ivy-entry-points']; - const logLevel = options.l as keyof typeof LogLevel | undefined; - const enableI18nLegacyMessageIdFormat = options['legacy-message-ids']; - const invalidateEntryPointManifest = options['invalidate-entry-point-manifest']; - const errorOnFailedEntryPoint = options['error-on-failed-entry-point']; - const findEntryPointsFromTsConfigProgram = options['use-program-dependencies']; - // yargs is not so great at mixed string+boolean types, so we have to test tsconfig against a - // string "false" to capture the `tsconfig=false` option. - // And we have to convert the option to a string to handle `no-tsconfig`, which will be `false`. - const tsConfigPath = `${options.tsconfig}` === 'false' ? null : options.tsconfig; - - const logger = logLevel && new ConsoleLogger(LogLevel[logLevel]); - - return { - basePath: baseSourcePath, - propertiesToConsider, - targetEntryPointPath, - typingsOnly, - compileAllFormats, - createNewEntryPointFormats, - logger, - enableI18nLegacyMessageIdFormat, - async: options.async, - invalidateEntryPointManifest, - errorOnFailedEntryPoint, - tsConfigPath, - findEntryPointsFromTsConfigProgram, - }; -} diff --git a/packages/compiler-cli/ngcc/src/constants.ts b/packages/compiler-cli/ngcc/src/constants.ts deleted file mode 100644 index e188d56f430..00000000000 --- a/packages/compiler-cli/ngcc/src/constants.ts +++ /dev/null @@ -1,10 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ - -export const IMPORT_PREFIX = 'ɵngcc'; -export const NGCC_TIMED_OUT_EXIT_CODE = 177; diff --git a/packages/compiler-cli/ngcc/src/dependencies/commonjs_dependency_host.ts b/packages/compiler-cli/ngcc/src/dependencies/commonjs_dependency_host.ts deleted file mode 100644 index cbecba73888..00000000000 --- a/packages/compiler-cli/ngcc/src/dependencies/commonjs_dependency_host.ts +++ /dev/null @@ -1,93 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import ts from 'typescript'; - -import {AbsoluteFsPath} from '../../../src/ngtsc/file_system'; -import {isRequireCall, isWildcardReexportStatement, RequireCall} from '../host/commonjs_umd_utils'; - -import {DependencyHostBase} from './dependency_host'; - -/** - * Helper functions for computing dependencies. - */ -export class CommonJsDependencyHost extends DependencyHostBase { - protected override canSkipFile(fileContents: string): boolean { - return !hasRequireCalls(fileContents); - } - - protected override extractImports(file: AbsoluteFsPath, fileContents: string): Set { - // Parse the source into a TypeScript AST and then walk it looking for imports and re-exports. - const sf = - ts.createSourceFile(file, fileContents, ts.ScriptTarget.ES2015, false, ts.ScriptKind.JS); - const requireCalls: RequireCall[] = []; - - for (const stmt of sf.statements) { - if (ts.isVariableStatement(stmt)) { - // Regular import(s): - // `var foo = require('...')` or `var foo = require('...'), bar = require('...')` - const declarations = stmt.declarationList.declarations; - for (const declaration of declarations) { - if ((declaration.initializer !== undefined) && isRequireCall(declaration.initializer)) { - requireCalls.push(declaration.initializer); - } - } - } else if (ts.isExpressionStatement(stmt)) { - if (isRequireCall(stmt.expression)) { - // Import for the side-effects only: - // `require('...')` - requireCalls.push(stmt.expression); - } else if (isWildcardReexportStatement(stmt)) { - // Re-export in one of the following formats: - // - `__export(require('...'))` - // - `__export()` - // - `tslib_1.__exportStar(require('...'), exports)` - // - `tslib_1.__exportStar(, exports)` - const firstExportArg = stmt.expression.arguments[0]; - - if (isRequireCall(firstExportArg)) { - // Re-export with `require()` call: - // `__export(require('...'))` or `tslib_1.__exportStar(require('...'), exports)` - requireCalls.push(firstExportArg); - } - } else if ( - ts.isBinaryExpression(stmt.expression) && - (stmt.expression.operatorToken.kind === ts.SyntaxKind.EqualsToken)) { - if (isRequireCall(stmt.expression.right)) { - // Import with assignment. E.g.: - // `exports.foo = require('...')` - requireCalls.push(stmt.expression.right); - } else if (ts.isObjectLiteralExpression(stmt.expression.right)) { - // Import in object literal. E.g.: - // `module.exports = {foo: require('...')}` - stmt.expression.right.properties.forEach(prop => { - if (ts.isPropertyAssignment(prop) && isRequireCall(prop.initializer)) { - requireCalls.push(prop.initializer); - } - }); - } - } - } - } - - return new Set(requireCalls.map(call => call.arguments[0].text)); - } -} - -/** - * Check whether a source file needs to be parsed for imports. - * This is a performance short-circuit, which saves us from creating - * a TypeScript AST unnecessarily. - * - * @param source The content of the source file to check. - * - * @returns false if there are definitely no require calls - * in this file, true otherwise. - */ -export function hasRequireCalls(source: string): boolean { - return /require\(['"]/.test(source); -} diff --git a/packages/compiler-cli/ngcc/src/dependencies/dependency_host.ts b/packages/compiler-cli/ngcc/src/dependencies/dependency_host.ts deleted file mode 100644 index 8fdbb64169e..00000000000 --- a/packages/compiler-cli/ngcc/src/dependencies/dependency_host.ts +++ /dev/null @@ -1,143 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import {AbsoluteFsPath, PathSegment, ReadonlyFileSystem} from '../../../src/ngtsc/file_system'; -import {EntryPoint} from '../packages/entry_point'; -import {resolveFileWithPostfixes} from '../utils'; - -import {ModuleResolver, ResolvedDeepImport, ResolvedRelativeModule} from './module_resolver'; - -export interface DependencyHost { - collectDependencies( - entryPointPath: AbsoluteFsPath, {dependencies, missing, deepImports}: DependencyInfo): void; -} - -export interface DependencyInfo { - dependencies: Set; - missing: Set; - deepImports: Set; -} - -export interface EntryPointWithDependencies { - entryPoint: EntryPoint; - depInfo: DependencyInfo; -} - -export function createDependencyInfo(): DependencyInfo { - return {dependencies: new Set(), missing: new Set(), deepImports: new Set()}; -} - -export abstract class DependencyHostBase implements DependencyHost { - constructor(protected fs: ReadonlyFileSystem, protected moduleResolver: ModuleResolver) {} - - /** - * Find all the dependencies for the entry-point at the given path. - * - * @param entryPointPath The absolute path to the JavaScript file that represents an entry-point. - * @param dependencyInfo An object containing information about the dependencies of the - * entry-point, including those that were missing or deep imports into other entry-points. The - * sets in this object will be updated with new information about the entry-point's dependencies. - */ - collectDependencies( - entryPointPath: AbsoluteFsPath, {dependencies, missing, deepImports}: DependencyInfo): void { - const resolvedFile = - resolveFileWithPostfixes(this.fs, entryPointPath, this.moduleResolver.relativeExtensions); - if (resolvedFile !== null) { - const alreadySeen = new Set(); - this.recursivelyCollectDependencies( - resolvedFile, dependencies, missing, deepImports, alreadySeen); - } - } - - /** - * Find all the dependencies for the provided paths. - * - * @param files The list of absolute paths of JavaScript files to scan for dependencies. - * @param dependencyInfo An object containing information about the dependencies of the - * entry-point, including those that were missing or deep imports into other entry-points. The - * sets in this object will be updated with new information about the entry-point's dependencies. - */ - collectDependenciesInFiles( - files: AbsoluteFsPath[], {dependencies, missing, deepImports}: DependencyInfo): void { - const alreadySeen = new Set(); - for (const file of files) { - this.processFile(file, dependencies, missing, deepImports, alreadySeen); - } - } - - /** - * Compute the dependencies of the given file. - * - * @param file An absolute path to the file whose dependencies we want to get. - * @param dependencies A set that will have the absolute paths of resolved entry points added to - * it. - * @param missing A set that will have the dependencies that could not be found added to it. - * @param deepImports A set that will have the import paths that exist but cannot be mapped to - * entry-points, i.e. deep-imports. - * @param alreadySeen A set that is used to track internal dependencies to prevent getting stuck - * in a circular dependency loop. - */ - protected recursivelyCollectDependencies( - file: AbsoluteFsPath, dependencies: Set, missing: Set, - deepImports: Set, alreadySeen: Set): void { - const fromContents = this.fs.readFile(file); - if (this.canSkipFile(fromContents)) { - return; - } - const imports = this.extractImports(file, fromContents); - for (const importPath of imports) { - const resolved = - this.processImport(importPath, file, dependencies, missing, deepImports, alreadySeen); - if (!resolved) { - missing.add(importPath); - } - } - } - - protected abstract canSkipFile(fileContents: string): boolean; - protected abstract extractImports(file: AbsoluteFsPath, fileContents: string): Set; - - /** - * Resolve the given `importPath` from `file` and add it to the appropriate set. - * - * If the import is local to this package then follow it by calling - * `recursivelyCollectDependencies()`. - * - * @returns `true` if the import was resolved (to an entry-point, a local import, or a - * deep-import), `false` otherwise. - */ - protected processImport( - importPath: string, file: AbsoluteFsPath, dependencies: Set, - missing: Set, deepImports: Set, alreadySeen: Set): boolean { - const resolvedModule = this.moduleResolver.resolveModuleImport(importPath, file); - if (resolvedModule === null) { - return false; - } - if (resolvedModule instanceof ResolvedRelativeModule) { - this.processFile(resolvedModule.modulePath, dependencies, missing, deepImports, alreadySeen); - } else if (resolvedModule instanceof ResolvedDeepImport) { - deepImports.add(resolvedModule.importPath); - } else { - dependencies.add(resolvedModule.entryPointPath); - } - return true; - } - - /** - * Processes the file if it has not already been seen. This will also recursively process - * all files that are imported from the file, while taking the set of already seen files - * into account. - */ - protected processFile( - file: AbsoluteFsPath, dependencies: Set, missing: Set, - deepImports: Set, alreadySeen: Set): void { - if (!alreadySeen.has(file)) { - alreadySeen.add(file); - this.recursivelyCollectDependencies(file, dependencies, missing, deepImports, alreadySeen); - } - } -} diff --git a/packages/compiler-cli/ngcc/src/dependencies/dependency_resolver.ts b/packages/compiler-cli/ngcc/src/dependencies/dependency_resolver.ts deleted file mode 100644 index 69754de8ae3..00000000000 --- a/packages/compiler-cli/ngcc/src/dependencies/dependency_resolver.ts +++ /dev/null @@ -1,239 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ - -import {DepGraph} from 'dependency-graph'; -import module from 'module'; - -import {AbsoluteFsPath, ReadonlyFileSystem} from '../../../src/ngtsc/file_system'; -import {Logger} from '../../../src/ngtsc/logging'; -import {NgccConfiguration} from '../packages/configuration'; -import {EntryPoint, EntryPointFormat, getEntryPointFormat, SUPPORTED_FORMAT_PROPERTIES} from '../packages/entry_point'; -import {PartiallyOrderedList} from '../utils'; - -import {createDependencyInfo, DependencyHost, EntryPointWithDependencies} from './dependency_host'; - -const builtinNodeJsModules = new Set(module.builtinModules); - -/** - * Holds information about entry points that are removed because - * they have dependencies that are missing (directly or transitively). - * - * This might not be an error, because such an entry point might not actually be used - * in the application. If it is used then the `ngc` application compilation would - * fail also, so we don't need ngcc to catch this. - * - * For example, consider an application that uses the `@angular/router` package. - * This package includes an entry-point called `@angular/router/upgrade`, which has a dependency - * on the `@angular/upgrade` package. - * If the application never uses code from `@angular/router/upgrade` then there is no need for - * `@angular/upgrade` to be installed. - * In this case the ngcc tool should just ignore the `@angular/router/upgrade` end-point. - */ -export interface InvalidEntryPoint { - entryPoint: EntryPoint; - missingDependencies: string[]; -} - -/** - * Holds information about dependencies of an entry-point that do not need to be processed - * by the ngcc tool. - * - * For example, the `rxjs` package does not contain any Angular decorators that need to be - * compiled and so this can be safely ignored by ngcc. - */ -export interface IgnoredDependency { - entryPoint: EntryPoint; - dependencyPath: string; -} - -export interface DependencyDiagnostics { - invalidEntryPoints: InvalidEntryPoint[]; - ignoredDependencies: IgnoredDependency[]; -} - -/** - * Represents a partially ordered list of entry-points. - * - * The entry-points' order/precedence is such that dependent entry-points always come later than - * their dependencies in the list. - * - * See `DependencyResolver#sortEntryPointsByDependency()`. - */ -export type PartiallyOrderedEntryPoints = PartiallyOrderedList; - -/** - * A list of entry-points, sorted by their dependencies, and the dependency graph. - * - * The `entryPoints` array will be ordered so that no entry point depends upon an entry point that - * appears later in the array. - * - * Some entry points or their dependencies may have been ignored. These are captured for - * diagnostic purposes in `invalidEntryPoints` and `ignoredDependencies` respectively. - */ -export interface SortedEntryPointsInfo extends DependencyDiagnostics { - entryPoints: PartiallyOrderedEntryPoints; - graph: DepGraph; -} - -/** - * A class that resolves dependencies between entry-points. - */ -export class DependencyResolver { - constructor( - private fs: ReadonlyFileSystem, private logger: Logger, private config: NgccConfiguration, - private hosts: Partial>, - private typingsHost: DependencyHost) {} - /** - * Sort the array of entry points so that the dependant entry points always come later than - * their dependencies in the array. - * @param entryPoints An array entry points to sort. - * @param target If provided, only return entry-points depended on by this entry-point. - * @returns the result of sorting the entry points by dependency. - */ - sortEntryPointsByDependency(entryPoints: EntryPointWithDependencies[], target?: EntryPoint): - SortedEntryPointsInfo { - const {invalidEntryPoints, ignoredDependencies, graph} = - this.computeDependencyGraph(entryPoints); - - let sortedEntryPointNodes: string[]; - if (target) { - if (target.compiledByAngular && graph.hasNode(target.path)) { - sortedEntryPointNodes = graph.dependenciesOf(target.path); - sortedEntryPointNodes.push(target.path); - } else { - sortedEntryPointNodes = []; - } - } else { - sortedEntryPointNodes = graph.overallOrder(); - } - - return { - entryPoints: (sortedEntryPointNodes as PartiallyOrderedList) - .map(path => graph.getNodeData(path)), - graph, - invalidEntryPoints, - ignoredDependencies, - }; - } - - getEntryPointWithDependencies(entryPoint: EntryPoint): EntryPointWithDependencies { - const dependencies = createDependencyInfo(); - if (entryPoint.compiledByAngular) { - // Only bother to compute dependencies of entry-points that have been compiled by Angular - const formatInfo = this.getEntryPointFormatInfo(entryPoint); - const host = this.hosts[formatInfo.format]; - if (!host) { - throw new Error( - `Could not find a suitable format for computing dependencies of entry-point: '${ - entryPoint.path}'.`); - } - host.collectDependencies(formatInfo.path, dependencies); - this.typingsHost.collectDependencies(entryPoint.typings, dependencies); - } - return {entryPoint, depInfo: dependencies}; - } - - /** - * Computes a dependency graph of the given entry-points. - * - * The graph only holds entry-points that ngcc cares about and whose dependencies - * (direct and transitive) all exist. - */ - private computeDependencyGraph(entryPoints: EntryPointWithDependencies[]): DependencyGraph { - const invalidEntryPoints: InvalidEntryPoint[] = []; - const ignoredDependencies: IgnoredDependency[] = []; - const graph = new DepGraph(); - - const angularEntryPoints = entryPoints.filter(e => e.entryPoint.compiledByAngular); - - // Add the Angular compiled entry points to the graph as nodes - angularEntryPoints.forEach(e => graph.addNode(e.entryPoint.path, e.entryPoint)); - - // Now add the dependencies between them - angularEntryPoints.forEach(({entryPoint, depInfo: {dependencies, missing, deepImports}}) => { - const missingDependencies = Array.from(missing).filter(dep => !builtinNodeJsModules.has(dep)); - - if (missingDependencies.length > 0 && !entryPoint.ignoreMissingDependencies) { - // This entry point has dependencies that are missing - // so remove it from the graph. - removeNodes(entryPoint, missingDependencies); - } else { - dependencies.forEach(dependencyPath => { - if (!graph.hasNode(entryPoint.path)) { - // The entry-point has already been identified as invalid so we don't need - // to do any further work on it. - } else if (graph.hasNode(dependencyPath)) { - // The entry-point is still valid (i.e. has no missing dependencies) and - // the dependency maps to an entry point that exists in the graph so add it - graph.addDependency(entryPoint.path, dependencyPath); - } else if (invalidEntryPoints.some(i => i.entryPoint.path === dependencyPath)) { - // The dependency path maps to an entry-point that was previously removed - // from the graph, so remove this entry-point as well. - removeNodes(entryPoint, [dependencyPath]); - } else { - // The dependency path points to a package that ngcc does not care about. - ignoredDependencies.push({entryPoint, dependencyPath}); - } - }); - } - - if (deepImports.size > 0) { - const notableDeepImports = this.filterIgnorableDeepImports(entryPoint, deepImports); - if (notableDeepImports.length > 0) { - const imports = notableDeepImports.map(i => `'${i}'`).join(', '); - this.logger.warn( - `Entry point '${entryPoint.name}' contains deep imports into ${imports}. ` + - `This is probably not a problem, but may cause the compilation of entry points to be out of order.`); - } - } - }); - - return {invalidEntryPoints, ignoredDependencies, graph}; - - function removeNodes(entryPoint: EntryPoint, missingDependencies: string[]) { - const nodesToRemove = [entryPoint.path, ...graph.dependantsOf(entryPoint.path)]; - nodesToRemove.forEach(node => { - invalidEntryPoints.push({entryPoint: graph.getNodeData(node), missingDependencies}); - graph.removeNode(node); - }); - } - } - - private getEntryPointFormatInfo(entryPoint: EntryPoint): - {format: EntryPointFormat, path: AbsoluteFsPath} { - for (const property of SUPPORTED_FORMAT_PROPERTIES) { - const formatPath = entryPoint.packageJson[property]; - if (formatPath === undefined) continue; - - const format = getEntryPointFormat(this.fs, entryPoint, property); - if (format === undefined) continue; - - return {format, path: this.fs.resolve(entryPoint.path, formatPath)}; - } - - throw new Error( - `There is no appropriate source code format in '${entryPoint.path}' entry-point.`); - } - - /** - * Filter out the deepImports that can be ignored, according to this entryPoint's config. - */ - private filterIgnorableDeepImports(entryPoint: EntryPoint, deepImports: Set): - AbsoluteFsPath[] { - const version = (entryPoint.packageJson.version || null) as string | null; - const packageConfig = - this.config.getPackageConfig(entryPoint.packageName, entryPoint.packagePath, version); - const matchers = packageConfig.ignorableDeepImportMatchers; - return Array.from(deepImports) - .filter(deepImport => !matchers.some(matcher => matcher.test(deepImport))); - } -} - -interface DependencyGraph extends DependencyDiagnostics { - graph: DepGraph; -} diff --git a/packages/compiler-cli/ngcc/src/dependencies/dts_dependency_host.ts b/packages/compiler-cli/ngcc/src/dependencies/dts_dependency_host.ts deleted file mode 100644 index 9dff3d63bd9..00000000000 --- a/packages/compiler-cli/ngcc/src/dependencies/dts_dependency_host.ts +++ /dev/null @@ -1,33 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import {AbsoluteFsPath, ReadonlyFileSystem} from '../../../src/ngtsc/file_system'; -import {PathMappings} from '../path_mappings'; -import {EsmDependencyHost} from './esm_dependency_host'; -import {ModuleResolver} from './module_resolver'; - -/** - * Helper functions for computing dependencies via typings files. - */ -export class DtsDependencyHost extends EsmDependencyHost { - constructor(fs: ReadonlyFileSystem, pathMappings?: PathMappings) { - super( - fs, new ModuleResolver(fs, pathMappings, ['', '.d.ts', '/index.d.ts', '.js', '/index.js']), - false); - } - - /** - * Attempts to process the `importPath` directly and also inside `@types/...`. - */ - protected override processImport( - importPath: string, file: AbsoluteFsPath, dependencies: Set, - missing: Set, deepImports: Set, alreadySeen: Set): boolean { - return super.processImport(importPath, file, dependencies, missing, deepImports, alreadySeen) || - super.processImport( - `@types/${importPath}`, file, dependencies, missing, deepImports, alreadySeen); - } -} diff --git a/packages/compiler-cli/ngcc/src/dependencies/esm_dependency_host.ts b/packages/compiler-cli/ngcc/src/dependencies/esm_dependency_host.ts deleted file mode 100644 index eeebce495a8..00000000000 --- a/packages/compiler-cli/ngcc/src/dependencies/esm_dependency_host.ts +++ /dev/null @@ -1,310 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import ts from 'typescript'; -import {AbsoluteFsPath, ReadonlyFileSystem} from '../../../src/ngtsc/file_system'; -import {DependencyHostBase} from './dependency_host'; -import {ModuleResolver} from './module_resolver'; - -/** - * Helper functions for computing dependencies. - */ -export class EsmDependencyHost extends DependencyHostBase { - constructor( - fs: ReadonlyFileSystem, moduleResolver: ModuleResolver, - private scanImportExpressions = true) { - super(fs, moduleResolver); - } - // By skipping trivia here we don't have to account for it in the processing below - // It has no relevance to capturing imports. - private scanner = ts.createScanner(ts.ScriptTarget.Latest, /* skipTrivia */ true); - - protected override canSkipFile(fileContents: string): boolean { - return !hasImportOrReexportStatements(fileContents); - } - - /** - * Extract any import paths from imports found in the contents of this file. - * - * This implementation uses the TypeScript scanner, which tokenizes source code, - * to process the string. This is halfway between working with the string directly, - * which is too difficult due to corner cases, and parsing the string into a full - * TypeScript Abstract Syntax Tree (AST), which ends up doing more processing than - * is needed. - * - * The scanning is not trivial because we must hold state between each token since - * the context of the token affects how it should be scanned, and the scanner does - * not manage this for us. - * - * Specifically, backticked strings are particularly challenging since it is possible - * to recursively nest backticks and TypeScript expressions within each other. - */ - protected override extractImports(file: AbsoluteFsPath, fileContents: string): Set { - const imports = new Set(); - const templateStack: ts.SyntaxKind[] = []; - let lastToken: ts.SyntaxKind = ts.SyntaxKind.Unknown; - let currentToken: ts.SyntaxKind = ts.SyntaxKind.Unknown; - const stopAtIndex = findLastPossibleImportOrReexport(fileContents); - - this.scanner.setText(fileContents); - - while ((currentToken = this.scanner.scan()) !== ts.SyntaxKind.EndOfFileToken) { - if (this.scanner.getTokenPos() > stopAtIndex) { - break; - } - switch (currentToken) { - case ts.SyntaxKind.TemplateHead: - // TemplateHead indicates the beginning of a backticked string - // Capture this in the `templateStack` to indicate we are currently processing - // within the static text part of a backticked string. - templateStack.push(currentToken); - break; - case ts.SyntaxKind.OpenBraceToken: - if (templateStack.length > 0) { - // We are processing a backticked string. This indicates that we are either - // entering an interpolation expression or entering an object literal expression. - // We add it to the `templateStack` so we can track when we leave the interpolation or - // object literal. - templateStack.push(currentToken); - } - break; - case ts.SyntaxKind.CloseBraceToken: - if (templateStack.length > 0) { - // We are processing a backticked string then this indicates that we are either - // leaving an interpolation expression or leaving an object literal expression. - const templateToken = templateStack[templateStack.length - 1]; - if (templateToken === ts.SyntaxKind.TemplateHead) { - // We have hit a nested backticked string so we need to rescan it in that context - currentToken = this.scanner.reScanTemplateToken(/* isTaggedTemplate */ false); - if (currentToken === ts.SyntaxKind.TemplateTail) { - // We got to the end of the backticked string so pop the token that started it off - // the stack. - templateStack.pop(); - } - } else { - // We hit the end of an object-literal expression so pop the open-brace that started - // it off the stack. - templateStack.pop(); - } - } - break; - case ts.SyntaxKind.SlashToken: - case ts.SyntaxKind.SlashEqualsToken: - if (canPrecedeARegex(lastToken)) { - // We have hit a slash (`/`) in a context where it could be the start of a regular - // expression so rescan it in that context - currentToken = this.scanner.reScanSlashToken(); - } - break; - case ts.SyntaxKind.ImportKeyword: - const importPath = this.extractImportPath(); - if (importPath !== null) { - imports.add(importPath); - } - break; - case ts.SyntaxKind.ExportKeyword: - const reexportPath = this.extractReexportPath(); - if (reexportPath !== null) { - imports.add(reexportPath); - } - break; - } - lastToken = currentToken; - } - - // Clear the text from the scanner to avoid holding on to potentially large strings of source - // content after the scanning has completed. - this.scanner.setText(''); - - return imports; - } - - - /** - * We have found an `import` token so now try to identify the import path. - * - * This method will use the current state of `this.scanner` to extract a string literal module - * specifier. It expects that the current state of the scanner is that an `import` token has just - * been scanned. - * - * The following forms of import are matched: - * - * * `import "module-specifier";` - * * `import("module-specifier")` - * * `import defaultBinding from "module-specifier";` - * * `import defaultBinding, * as identifier from "module-specifier";` - * * `import defaultBinding, {...} from "module-specifier";` - * * `import * as identifier from "module-specifier";` - * * `import {...} from "module-specifier";` - * - * @returns the import path or null if there is no import or it is not a string literal. - */ - protected extractImportPath(): string|null { - // Check for side-effect import - let sideEffectImportPath = this.tryStringLiteral(); - if (sideEffectImportPath !== null) { - return sideEffectImportPath; - } - - let kind: ts.SyntaxKind|null = this.scanner.getToken(); - - // Check for dynamic import expression - if (kind === ts.SyntaxKind.OpenParenToken) { - return this.scanImportExpressions ? this.tryStringLiteral() : null; - } - - // Check for defaultBinding - if (kind === ts.SyntaxKind.Identifier) { - // Skip default binding - kind = this.scanner.scan(); - if (kind === ts.SyntaxKind.CommaToken) { - // Skip comma that indicates additional import bindings - kind = this.scanner.scan(); - } - } - - // Check for namespace import clause - if (kind === ts.SyntaxKind.AsteriskToken) { - kind = this.skipNamespacedClause(); - if (kind === null) { - return null; - } - } - // Check for named imports clause - else if (kind === ts.SyntaxKind.OpenBraceToken) { - kind = this.skipNamedClause(); - } - - // Expect a `from` clause, if not bail out - if (kind !== ts.SyntaxKind.FromKeyword) { - return null; - } - - return this.tryStringLiteral(); - } - - /** - * We have found an `export` token so now try to identify a re-export path. - * - * This method will use the current state of `this.scanner` to extract a string literal module - * specifier. It expects that the current state of the scanner is that an `export` token has - * just been scanned. - * - * There are three forms of re-export that are matched: - * - * * `export * from '...'; - * * `export * as alias from '...'; - * * `export {...} from '...'; - */ - protected extractReexportPath(): string|null { - // Skip the `export` keyword - let token: ts.SyntaxKind|null = this.scanner.scan(); - if (token === ts.SyntaxKind.AsteriskToken) { - token = this.skipNamespacedClause(); - if (token === null) { - return null; - } - } else if (token === ts.SyntaxKind.OpenBraceToken) { - token = this.skipNamedClause(); - } - // Expect a `from` clause, if not bail out - if (token !== ts.SyntaxKind.FromKeyword) { - return null; - } - return this.tryStringLiteral(); - } - - protected skipNamespacedClause(): ts.SyntaxKind|null { - // Skip past the `*` - let token = this.scanner.scan(); - // Check for a `* as identifier` alias clause - if (token === ts.SyntaxKind.AsKeyword) { - // Skip past the `as` keyword - token = this.scanner.scan(); - // Expect an identifier, if not bail out - if (token !== ts.SyntaxKind.Identifier) { - return null; - } - // Skip past the identifier - token = this.scanner.scan(); - } - return token; - } - - protected skipNamedClause(): ts.SyntaxKind { - let braceCount = 1; - // Skip past the initial opening brace `{` - let token = this.scanner.scan(); - // Search for the matching closing brace `}` - while (braceCount > 0 && token !== ts.SyntaxKind.EndOfFileToken) { - if (token === ts.SyntaxKind.OpenBraceToken) { - braceCount++; - } else if (token === ts.SyntaxKind.CloseBraceToken) { - braceCount--; - } - token = this.scanner.scan(); - } - return token; - } - - protected tryStringLiteral(): string|null { - return this.scanner.scan() === ts.SyntaxKind.StringLiteral ? this.scanner.getTokenValue() : - null; - } -} - -/** - * Check whether a source file needs to be parsed for imports. - * This is a performance short-circuit, which saves us from creating - * a TypeScript AST unnecessarily. - * - * @param source The content of the source file to check. - * - * @returns false if there are definitely no import or re-export statements - * in this file, true otherwise. - */ -export function hasImportOrReexportStatements(source: string): boolean { - return /(?:import|export)[\s\S]+?(["'])(?:\\\1|.)+?\1/.test(source); -} - -function findLastPossibleImportOrReexport(source: string): number { - return Math.max(source.lastIndexOf('import'), source.lastIndexOf(' from ')); -} - -/** - * Check whether the given statement is an import with a string literal module specifier. - * @param stmt the statement node to check. - * @returns true if the statement is an import with a string literal module specifier. - */ -export function isStringImportOrReexport(stmt: ts.Statement): stmt is ts.ImportDeclaration& - {moduleSpecifier: ts.StringLiteral} { - return ts.isImportDeclaration(stmt) || - ts.isExportDeclaration(stmt) && !!stmt.moduleSpecifier && - ts.isStringLiteral(stmt.moduleSpecifier); -} - - -function canPrecedeARegex(kind: ts.SyntaxKind): boolean { - switch (kind) { - case ts.SyntaxKind.Identifier: - case ts.SyntaxKind.StringLiteral: - case ts.SyntaxKind.NumericLiteral: - case ts.SyntaxKind.BigIntLiteral: - case ts.SyntaxKind.RegularExpressionLiteral: - case ts.SyntaxKind.ThisKeyword: - case ts.SyntaxKind.PlusPlusToken: - case ts.SyntaxKind.MinusMinusToken: - case ts.SyntaxKind.CloseParenToken: - case ts.SyntaxKind.CloseBracketToken: - case ts.SyntaxKind.CloseBraceToken: - case ts.SyntaxKind.TrueKeyword: - case ts.SyntaxKind.FalseKeyword: - return false; - default: - return true; - } -} diff --git a/packages/compiler-cli/ngcc/src/dependencies/module_resolver.ts b/packages/compiler-cli/ngcc/src/dependencies/module_resolver.ts deleted file mode 100644 index 05ef220f871..00000000000 --- a/packages/compiler-cli/ngcc/src/dependencies/module_resolver.ts +++ /dev/null @@ -1,287 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import {AbsoluteFsPath, ReadonlyFileSystem} from '../../../src/ngtsc/file_system'; -import {PathMappings} from '../path_mappings'; -import {isRelativePath, loadJson, loadSecondaryEntryPointInfoForApfV14, resolveFileWithPostfixes} from '../utils'; - -/** - * This is a very cut-down implementation of the TypeScript module resolution strategy. - * - * It is specific to the needs of ngcc and is not intended to be a drop-in replacement - * for the TS module resolver. It is used to compute the dependencies between entry-points - * that may be compiled by ngcc. - * - * The algorithm only finds `.js` files for internal/relative imports and paths to - * the folder containing the `package.json` of the entry-point for external imports. - * - * It can cope with nested `node_modules` folders and also supports `paths`/`baseUrl` - * configuration properties, as provided in a `ts.CompilerOptions` object. - */ -export class ModuleResolver { - private pathMappings: ProcessedPathMapping[]; - - constructor( - private fs: ReadonlyFileSystem, pathMappings?: PathMappings, - readonly relativeExtensions = ['', '.js', '/index.js']) { - this.pathMappings = pathMappings ? this.processPathMappings(pathMappings) : []; - } - - /** - * Resolve an absolute path for the `moduleName` imported into a file at `fromPath`. - * @param moduleName The name of the import to resolve. - * @param fromPath The path to the file containing the import. - * @returns A path to the resolved module or null if missing. - * Specifically: - * * the absolute path to the package.json of an external module - * * a JavaScript file of an internal module - * * null if none exists. - */ - resolveModuleImport(moduleName: string, fromPath: AbsoluteFsPath): ResolvedModule|null { - if (isRelativePath(moduleName)) { - return this.resolveAsRelativePath(moduleName, fromPath); - } else { - return this.pathMappings.length && this.resolveByPathMappings(moduleName, fromPath) || - this.resolveAsEntryPoint(moduleName, fromPath); - } - } - - /** - * Convert the `pathMappings` into a collection of `PathMapper` functions. - */ - private processPathMappings(pathMappings: PathMappings): ProcessedPathMapping[] { - const baseUrl = this.fs.resolve(pathMappings.baseUrl); - return Object.keys(pathMappings.paths).map(pathPattern => { - const matcher = splitOnStar(pathPattern); - const templates = pathMappings.paths[pathPattern].map(splitOnStar); - return {matcher, templates, baseUrl}; - }); - } - - /** - * Try to resolve a module name, as a relative path, from the `fromPath`. - * - * As it is relative, it only looks for files that end in one of the `relativeExtensions`. - * For example: `${moduleName}.js` or `${moduleName}/index.js`. - * If neither of these files exist then the method returns `null`. - */ - private resolveAsRelativePath(moduleName: string, fromPath: AbsoluteFsPath): ResolvedModule|null { - const resolvedPath = resolveFileWithPostfixes( - this.fs, this.fs.resolve(this.fs.dirname(fromPath), moduleName), this.relativeExtensions); - return resolvedPath && new ResolvedRelativeModule(resolvedPath); - } - - /** - * Try to resolve the `moduleName`, by applying the computed `pathMappings` and - * then trying to resolve the mapped path as a relative or external import. - * - * Whether the mapped path is relative is defined as it being "below the `fromPath`" and not - * containing `node_modules`. - * - * If the mapped path is not relative but does not resolve to an external entry-point, then we - * check whether it would have resolved to a relative path, in which case it is marked as a - * "deep-import". - */ - private resolveByPathMappings(moduleName: string, fromPath: AbsoluteFsPath): ResolvedModule|null { - const mappedPaths = this.findMappedPaths(moduleName); - if (mappedPaths.length > 0) { - const packagePath = this.findPackagePath(fromPath); - if (packagePath !== null) { - for (const mappedPath of mappedPaths) { - if (this.isEntryPoint(mappedPath)) { - return new ResolvedExternalModule(mappedPath); - } - const nonEntryPointImport = this.resolveAsRelativePath(mappedPath, fromPath); - if (nonEntryPointImport !== null) { - return isRelativeImport(packagePath, mappedPath) ? nonEntryPointImport : - new ResolvedDeepImport(mappedPath); - } - } - } - } - return null; - } - - /** - * Try to resolve the `moduleName` as an external entry-point by searching the `node_modules` - * folders up the tree for a matching `.../node_modules/${moduleName}`. - * - * If a folder is found but the path is not considered an entry-point (see `isEntryPoint()`) then - * it is marked as a "deep-import". - */ - private resolveAsEntryPoint(moduleName: string, fromPath: AbsoluteFsPath): ResolvedModule|null { - let folder = fromPath; - while (!this.fs.isRoot(folder)) { - folder = this.fs.dirname(folder); - if (folder.endsWith('node_modules')) { - // Skip up if the folder already ends in node_modules - folder = this.fs.dirname(folder); - } - const modulePath = this.fs.resolve(folder, 'node_modules', moduleName); - if (this.isEntryPoint(modulePath)) { - return new ResolvedExternalModule(modulePath); - } else if (this.resolveAsRelativePath(modulePath, fromPath)) { - return new ResolvedDeepImport(modulePath); - } - } - return null; - } - - - /** - * Can we consider the given path as an entry-point to a package? - * - * This is achieved by checking for the existence of `${modulePath}/package.json`. - * If there is no `package.json`, we check whether this is an APF v14+ secondary entry-point, - * which does not have its own `package.json` but has an `exports` entry in the package's primary - * `package.json`. - */ - private isEntryPoint(modulePath: AbsoluteFsPath): boolean { - if (this.fs.exists(this.fs.join(modulePath, 'package.json'))) { - return true; - } - - const packagePath = this.findPackagePath(modulePath); - if (packagePath === null) { - return false; - } - - const packagePackageJson = loadJson(this.fs, this.fs.join(packagePath, 'package.json')); - const entryPointInfoForApfV14 = - loadSecondaryEntryPointInfoForApfV14(this.fs, packagePackageJson, packagePath, modulePath); - - return entryPointInfoForApfV14 !== null; - } - - /** - * Apply the `pathMappers` to the `moduleName` and return all the possible - * paths that match. - * - * The mapped path is computed for each template in `mapping.templates` by - * replacing the `matcher.prefix` and `matcher.postfix` strings in `path with the - * `template.prefix` and `template.postfix` strings. - */ - private findMappedPaths(moduleName: string): AbsoluteFsPath[] { - const matches = this.pathMappings.map(mapping => this.matchMapping(moduleName, mapping)); - - let bestMapping: ProcessedPathMapping|undefined; - let bestMatch: string|undefined; - - for (let index = 0; index < this.pathMappings.length; index++) { - const mapping = this.pathMappings[index]; - const match = matches[index]; - if (match !== null) { - // If this mapping had no wildcard then this must be a complete match. - if (!mapping.matcher.hasWildcard) { - bestMatch = match; - bestMapping = mapping; - break; - } - // The best matched mapping is the one with the longest prefix. - if (!bestMapping || mapping.matcher.prefix > bestMapping.matcher.prefix) { - bestMatch = match; - bestMapping = mapping; - } - } - } - - return (bestMapping !== undefined && bestMatch !== undefined) ? - this.computeMappedTemplates(bestMapping, bestMatch) : - []; - } - - /** - * Attempt to find a mapped path for the given `path` and a `mapping`. - * - * The `path` matches the `mapping` if if it starts with `matcher.prefix` and ends with - * `matcher.postfix`. - * - * @returns the wildcard segment of a matched `path`, or `null` if no match. - */ - private matchMapping(path: string, mapping: ProcessedPathMapping): string|null { - const {prefix, postfix, hasWildcard} = mapping.matcher; - if (hasWildcard) { - return (path.startsWith(prefix) && path.endsWith(postfix)) ? - path.substring(prefix.length, path.length - postfix.length) : - null; - } else { - return (path === prefix) ? '' : null; - } - } - - /** - * Compute the candidate paths from the given mapping's templates using the matched - * string. - */ - private computeMappedTemplates(mapping: ProcessedPathMapping, match: string) { - return mapping.templates.map( - template => this.fs.resolve(mapping.baseUrl, template.prefix + match + template.postfix)); - } - - /** - * Search up the folder tree for the first folder that contains `package.json` - * or `null` if none is found. - */ - private findPackagePath(path: AbsoluteFsPath): AbsoluteFsPath|null { - let folder = path; - while (!this.fs.isRoot(folder)) { - folder = this.fs.dirname(folder); - if (this.fs.exists(this.fs.join(folder, 'package.json'))) { - return folder; - } - } - return null; - } -} - -/** The result of resolving an import to a module. */ -export type ResolvedModule = ResolvedExternalModule|ResolvedRelativeModule|ResolvedDeepImport; - -/** - * A module that is external to the package doing the importing. - * In this case we capture the folder containing the entry-point. - */ -export class ResolvedExternalModule { - constructor(public entryPointPath: AbsoluteFsPath) {} -} - -/** - * A module that is relative to the module doing the importing, and so internal to the - * source module's package. - */ -export class ResolvedRelativeModule { - constructor(public modulePath: AbsoluteFsPath) {} -} - -/** - * A module that is external to the package doing the importing but pointing to a - * module that is deep inside a package, rather than to an entry-point of the package. - */ -export class ResolvedDeepImport { - constructor(public importPath: AbsoluteFsPath) {} -} - -function splitOnStar(str: string): PathMappingPattern { - const [prefix, postfix] = str.split('*', 2); - return {prefix, postfix: postfix || '', hasWildcard: postfix !== undefined}; -} - -interface ProcessedPathMapping { - baseUrl: AbsoluteFsPath; - matcher: PathMappingPattern; - templates: PathMappingPattern[]; -} - -interface PathMappingPattern { - prefix: string; - postfix: string; - hasWildcard: boolean; -} - -function isRelativeImport(from: AbsoluteFsPath, to: AbsoluteFsPath) { - return to.startsWith(from) && !to.includes('node_modules'); -} diff --git a/packages/compiler-cli/ngcc/src/dependencies/umd_dependency_host.ts b/packages/compiler-cli/ngcc/src/dependencies/umd_dependency_host.ts deleted file mode 100644 index e5e4b21e025..00000000000 --- a/packages/compiler-cli/ngcc/src/dependencies/umd_dependency_host.ts +++ /dev/null @@ -1,41 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import ts from 'typescript'; - -import {AbsoluteFsPath} from '../../../src/ngtsc/file_system'; -import {getImportsOfUmdModule, parseStatementForUmdModule} from '../host/umd_host'; - -import {hasRequireCalls} from './commonjs_dependency_host'; -import {DependencyHostBase} from './dependency_host'; - -/** - * Helper functions for computing dependencies. - */ -export class UmdDependencyHost extends DependencyHostBase { - protected override canSkipFile(fileContents: string): boolean { - return !hasRequireCalls(fileContents); - } - - protected override extractImports(file: AbsoluteFsPath, fileContents: string): Set { - // Parse the source into a TypeScript AST and then walk it looking for imports and re-exports. - const sf = - ts.createSourceFile(file, fileContents, ts.ScriptTarget.ES2015, true, ts.ScriptKind.JS); - - if (sf.statements.length !== 1) { - return new Set(); - } - - const umdModule = parseStatementForUmdModule(sf.statements[0]); - const umdImports = umdModule && getImportsOfUmdModule(umdModule); - if (umdImports === null) { - return new Set(); - } - - return new Set(umdImports.map(i => i.path)); - } -} diff --git a/packages/compiler-cli/ngcc/src/entry_point_finder/directory_walker_entry_point_finder.ts b/packages/compiler-cli/ngcc/src/entry_point_finder/directory_walker_entry_point_finder.ts deleted file mode 100644 index cd368582de7..00000000000 --- a/packages/compiler-cli/ngcc/src/entry_point_finder/directory_walker_entry_point_finder.ts +++ /dev/null @@ -1,60 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import {AbsoluteFsPath} from '../../../src/ngtsc/file_system'; -import {Logger} from '../../../src/ngtsc/logging'; -import {EntryPointWithDependencies} from '../dependencies/dependency_host'; -import {DependencyResolver, SortedEntryPointsInfo} from '../dependencies/dependency_resolver'; -import {EntryPointManifest} from '../packages/entry_point_manifest'; -import {PathMappings} from '../path_mappings'; - -import {EntryPointCollector} from './entry_point_collector'; -import {EntryPointFinder} from './interface'; -import {getBasePaths, trackDuration} from './utils'; - -/** - * An EntryPointFinder that searches for all entry-points that can be found given a `basePath` and - * `pathMappings`. - */ -export class DirectoryWalkerEntryPointFinder implements EntryPointFinder { - private basePaths = getBasePaths(this.logger, this.sourceDirectory, this.pathMappings); - constructor( - private logger: Logger, private resolver: DependencyResolver, - private entryPointCollector: EntryPointCollector, - private entryPointManifest: EntryPointManifest, private sourceDirectory: AbsoluteFsPath, - private pathMappings: PathMappings|undefined) {} - - /** - * Search the `sourceDirectory`, and sub-directories, using `pathMappings` as necessary, to find - * all package entry-points. - */ - findEntryPoints(): SortedEntryPointsInfo { - const unsortedEntryPoints: EntryPointWithDependencies[] = []; - for (const basePath of this.basePaths) { - const entryPoints = this.entryPointManifest.readEntryPointsUsingManifest(basePath) || - this.walkBasePathForPackages(basePath); - entryPoints.forEach(e => unsortedEntryPoints.push(e)); - } - return this.resolver.sortEntryPointsByDependency(unsortedEntryPoints); - } - - /** - * Search the `basePath` for possible Angular packages and entry-points. - * - * @param basePath The path at which to start the search. - * @returns an array of `EntryPoint`s that were found within `basePath`. - */ - walkBasePathForPackages(basePath: AbsoluteFsPath): EntryPointWithDependencies[] { - this.logger.debug( - `No manifest found for ${basePath} so walking the directories for entry-points.`); - const entryPoints = trackDuration( - () => this.entryPointCollector.walkDirectoryForPackages(basePath), - duration => this.logger.debug(`Walking ${basePath} for entry-points took ${duration}s.`)); - this.entryPointManifest.writeEntryPointManifest(basePath, entryPoints); - return entryPoints; - } -} diff --git a/packages/compiler-cli/ngcc/src/entry_point_finder/entry_point_collector.ts b/packages/compiler-cli/ngcc/src/entry_point_finder/entry_point_collector.ts deleted file mode 100644 index 1d29a95c22e..00000000000 --- a/packages/compiler-cli/ngcc/src/entry_point_finder/entry_point_collector.ts +++ /dev/null @@ -1,153 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import {AbsoluteFsPath, PathSegment, ReadonlyFileSystem} from '../../../src/ngtsc/file_system'; -import {Logger} from '../../../src/ngtsc/logging'; -import {EntryPointWithDependencies} from '../dependencies/dependency_host'; -import {DependencyResolver} from '../dependencies/dependency_resolver'; -import {NgccConfiguration} from '../packages/configuration'; -import {getEntryPointInfo, IGNORED_ENTRY_POINT, INCOMPATIBLE_ENTRY_POINT, isEntryPoint, NO_ENTRY_POINT} from '../packages/entry_point'; -import {NGCC_DIRECTORY} from '../writing/new_entry_point_file_writer'; - -/** - * A class that traverses a file-tree, starting at a given path, looking for all entry-points, - * also capturing the dependencies of each entry-point that is found. - */ -export class EntryPointCollector { - constructor( - private fs: ReadonlyFileSystem, private config: NgccConfiguration, private logger: Logger, - private resolver: DependencyResolver) {} - - /** - * Look for Angular packages that need to be compiled, starting at the source directory. - * The function will recurse into directories that start with `@...`, e.g. `@angular/...`. - * - * @param sourceDirectory An absolute path to the root directory where searching begins. - * @returns an array of `EntryPoint`s that were found within `sourceDirectory`. - */ - walkDirectoryForPackages(sourceDirectory: AbsoluteFsPath): EntryPointWithDependencies[] { - // Try to get a primary entry point from this directory - const primaryEntryPoint = - getEntryPointInfo(this.fs, this.config, this.logger, sourceDirectory, sourceDirectory); - - // If there is an entry-point but it is not compatible with ngcc (it has a bad package.json or - // invalid typings) then exit. It is unlikely that such an entry point has a dependency on an - // Angular library. - if (primaryEntryPoint === INCOMPATIBLE_ENTRY_POINT) { - return []; - } - - const entryPoints: EntryPointWithDependencies[] = []; - if (primaryEntryPoint !== NO_ENTRY_POINT) { - if (primaryEntryPoint !== IGNORED_ENTRY_POINT) { - entryPoints.push(this.resolver.getEntryPointWithDependencies(primaryEntryPoint)); - } - this.collectSecondaryEntryPoints( - entryPoints, sourceDirectory, sourceDirectory, this.fs.readdir(sourceDirectory)); - - // Also check for any nested node_modules in this package but only if at least one of the - // entry-points was compiled by Angular. - if (entryPoints.some(e => e.entryPoint.compiledByAngular)) { - const nestedNodeModulesPath = this.fs.join(sourceDirectory, 'node_modules'); - if (this.fs.exists(nestedNodeModulesPath)) { - entryPoints.push(...this.walkDirectoryForPackages(nestedNodeModulesPath)); - } - } - - return entryPoints; - } - - // The `sourceDirectory` was not a package (i.e. there was no package.json) - // So search its sub-directories for Angular packages and entry-points - for (const path of this.fs.readdir(sourceDirectory)) { - if (isIgnorablePath(path)) { - // Ignore hidden files, node_modules and ngcc directory - continue; - } - - const absolutePath = this.fs.resolve(sourceDirectory, path); - const stat = this.fs.lstat(absolutePath); - if (stat.isSymbolicLink() || !stat.isDirectory()) { - // Ignore symbolic links and non-directories - continue; - } - - entryPoints.push(...this.walkDirectoryForPackages(this.fs.join(sourceDirectory, path))); - } - - return entryPoints; - } - - /** - * Search the `directory` looking for any secondary entry-points for a package, adding any that - * are found to the `entryPoints` array. - * - * @param entryPoints An array where we will add any entry-points found in this directory. - * @param packagePath The absolute path to the package that may contain entry-points. - * @param directory The current directory being searched. - * @param paths The paths contained in the current `directory`. - */ - private collectSecondaryEntryPoints( - entryPoints: EntryPointWithDependencies[], packagePath: AbsoluteFsPath, - directory: AbsoluteFsPath, paths: PathSegment[]): void { - for (const path of paths) { - if (isIgnorablePath(path)) { - // Ignore hidden files, node_modules and ngcc directory - continue; - } - - const absolutePath = this.fs.resolve(directory, path); - const stat = this.fs.lstat(absolutePath); - if (stat.isSymbolicLink()) { - // Ignore symbolic links - continue; - } - - const isDirectory = stat.isDirectory(); - if (!path.endsWith('.js') && !isDirectory) { - // Ignore files that do not end in `.js` - continue; - } - - // If the path is a JS file then strip its extension and see if we can match an - // entry-point (even if it is an ignored one). - const possibleEntryPointPath = isDirectory ? absolutePath : stripJsExtension(absolutePath); - const subEntryPoint = - getEntryPointInfo(this.fs, this.config, this.logger, packagePath, possibleEntryPointPath); - if (isEntryPoint(subEntryPoint)) { - entryPoints.push(this.resolver.getEntryPointWithDependencies(subEntryPoint)); - } - - if (!isDirectory) { - // This path is not a directory so we are done. - continue; - } - - // If not an entry-point itself, this directory may contain entry-points of its own. - const canContainEntryPoints = - subEntryPoint === NO_ENTRY_POINT || subEntryPoint === INCOMPATIBLE_ENTRY_POINT; - const childPaths = this.fs.readdir(absolutePath); - if (canContainEntryPoints && - childPaths.some( - childPath => childPath.endsWith('.js') && - this.fs.stat(this.fs.resolve(absolutePath, childPath)).isFile())) { - // We do not consider non-entry-point directories that contain JS files as they are very - // unlikely to be containers for sub-entry-points. - continue; - } - this.collectSecondaryEntryPoints(entryPoints, packagePath, absolutePath, childPaths); - } - } -} - -function stripJsExtension(filePath: T): T { - return filePath.replace(/\.js$/, '') as T; -} - -function isIgnorablePath(path: PathSegment): boolean { - return path.startsWith('.') || path === 'node_modules' || path === NGCC_DIRECTORY; -} diff --git a/packages/compiler-cli/ngcc/src/entry_point_finder/interface.ts b/packages/compiler-cli/ngcc/src/entry_point_finder/interface.ts deleted file mode 100644 index 17905a26fff..00000000000 --- a/packages/compiler-cli/ngcc/src/entry_point_finder/interface.ts +++ /dev/null @@ -1,15 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import {SortedEntryPointsInfo} from '../dependencies/dependency_resolver'; - -export interface EntryPointFinder { - /** - * Search for Angular package entry-points. - */ - findEntryPoints(): SortedEntryPointsInfo; -} diff --git a/packages/compiler-cli/ngcc/src/entry_point_finder/program_based_entry_point_finder.ts b/packages/compiler-cli/ngcc/src/entry_point_finder/program_based_entry_point_finder.ts deleted file mode 100644 index cb503e75c3f..00000000000 --- a/packages/compiler-cli/ngcc/src/entry_point_finder/program_based_entry_point_finder.ts +++ /dev/null @@ -1,121 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import {AbsoluteFsPath, ReadonlyFileSystem} from '../../../src/ngtsc/file_system'; -import {Logger} from '../../../src/ngtsc/logging'; -import {ParsedConfiguration} from '../../../src/perform_compile'; - -import {createDependencyInfo, EntryPointWithDependencies} from '../dependencies/dependency_host'; -import {DependencyResolver} from '../dependencies/dependency_resolver'; -import {EsmDependencyHost} from '../dependencies/esm_dependency_host'; -import {ModuleResolver} from '../dependencies/module_resolver'; -import {NgccConfiguration} from '../packages/configuration'; -import {EntryPointManifest} from '../packages/entry_point_manifest'; -import {getPathMappingsFromTsConfig} from '../path_mappings'; - -import {EntryPointCollector} from './entry_point_collector'; -import {TracingEntryPointFinder} from './tracing_entry_point_finder'; -import {trackDuration} from './utils'; - -/** - * An EntryPointFinder that starts from the files in the program defined by the given tsconfig.json - * and only returns entry-points that are dependencies of these files. - * - * This is faster than searching the entire file-system for all the entry-points, - * and is used primarily by the CLI integration. - */ -export class ProgramBasedEntryPointFinder extends TracingEntryPointFinder { - private entryPointsWithDependencies: Map|null = null; - - constructor( - fs: ReadonlyFileSystem, config: NgccConfiguration, logger: Logger, - resolver: DependencyResolver, private entryPointCollector: EntryPointCollector, - private entryPointManifest: EntryPointManifest, basePath: AbsoluteFsPath, - private tsConfig: ParsedConfiguration, projectPath: AbsoluteFsPath) { - super( - fs, config, logger, resolver, basePath, - getPathMappingsFromTsConfig(fs, tsConfig, projectPath)); - } - - /** - * Return an array containing the external import paths that were extracted from the source-files - * of the program defined by the tsconfig.json. - */ - protected override getInitialEntryPointPaths(): AbsoluteFsPath[] { - const moduleResolver = new ModuleResolver(this.fs, this.pathMappings, ['', '.ts', '/index.ts']); - const host = new EsmDependencyHost(this.fs, moduleResolver); - const dependencies = createDependencyInfo(); - const rootFiles = this.tsConfig.rootNames.map(rootName => this.fs.resolve(rootName)); - this.logger.debug( - `Using the program from ${this.tsConfig.project} to seed the entry-point finding.`); - this.logger.debug( - `Collecting dependencies from the following files:` + rootFiles.map(file => `\n- ${file}`)); - host.collectDependenciesInFiles(rootFiles, dependencies); - return Array.from(dependencies.dependencies); - } - - /** - * For the given `entryPointPath`, compute, or retrieve, the entry-point information, including - * paths to other entry-points that this entry-point depends upon. - * - * In this entry-point finder, we use the `EntryPointManifest` to avoid computing each - * entry-point's dependencies in the case that this had been done previously. - * - * @param entryPointPath the path to the entry-point whose information and dependencies are to be - * retrieved or computed. - * - * @returns the entry-point and its dependencies or `null` if the entry-point is not compiled by - * Angular or cannot be determined. - */ - protected override getEntryPointWithDeps(entryPointPath: AbsoluteFsPath): - EntryPointWithDependencies|null { - const entryPoints = this.findOrLoadEntryPoints(); - if (!entryPoints.has(entryPointPath)) { - return null; - } - const entryPointWithDeps = entryPoints.get(entryPointPath)!; - if (!entryPointWithDeps.entryPoint.compiledByAngular) { - return null; - } - return entryPointWithDeps; - } - - /** - * Walk the base paths looking for entry-points or load this information from an entry-point - * manifest, if available. - */ - private findOrLoadEntryPoints(): Map { - if (this.entryPointsWithDependencies === null) { - const entryPointsWithDependencies = this.entryPointsWithDependencies = - new Map(); - for (const basePath of this.getBasePaths()) { - const entryPoints = this.entryPointManifest.readEntryPointsUsingManifest(basePath) || - this.walkBasePathForPackages(basePath); - for (const e of entryPoints) { - entryPointsWithDependencies.set(e.entryPoint.path, e); - } - } - } - return this.entryPointsWithDependencies; - } - - /** - * Search the `basePath` for possible Angular packages and entry-points. - * - * @param basePath The path at which to start the search. - * @returns an array of `EntryPoint`s that were found within `basePath`. - */ - walkBasePathForPackages(basePath: AbsoluteFsPath): EntryPointWithDependencies[] { - this.logger.debug( - `No manifest found for ${basePath} so walking the directories for entry-points.`); - const entryPoints = trackDuration( - () => this.entryPointCollector.walkDirectoryForPackages(basePath), - duration => this.logger.debug(`Walking ${basePath} for entry-points took ${duration}s.`)); - this.entryPointManifest.writeEntryPointManifest(basePath, entryPoints); - return entryPoints; - } -} diff --git a/packages/compiler-cli/ngcc/src/entry_point_finder/targeted_entry_point_finder.ts b/packages/compiler-cli/ngcc/src/entry_point_finder/targeted_entry_point_finder.ts deleted file mode 100644 index c68e2317725..00000000000 --- a/packages/compiler-cli/ngcc/src/entry_point_finder/targeted_entry_point_finder.ts +++ /dev/null @@ -1,254 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import {AbsoluteFsPath, PathSegment, ReadonlyFileSystem} from '../../../src/ngtsc/file_system'; -import {Logger} from '../../../src/ngtsc/logging'; -import {EntryPointWithDependencies} from '../dependencies/dependency_host'; -import {DependencyResolver, SortedEntryPointsInfo} from '../dependencies/dependency_resolver'; -import {hasBeenProcessed} from '../packages/build_marker'; -import {NgccConfiguration} from '../packages/configuration'; -import {EntryPointJsonProperty, getEntryPointInfo, isEntryPoint} from '../packages/entry_point'; -import {PathMappings} from '../path_mappings'; - -import {TracingEntryPointFinder} from './tracing_entry_point_finder'; - -/** - * An EntryPointFinder that starts from a target entry-point and only finds - * entry-points that are dependencies of the target. - * - * This is faster than searching the entire file-system for all the entry-points, - * and is used primarily by the CLI integration. - */ -export class TargetedEntryPointFinder extends TracingEntryPointFinder { - constructor( - fs: ReadonlyFileSystem, config: NgccConfiguration, logger: Logger, - resolver: DependencyResolver, basePath: AbsoluteFsPath, pathMappings: PathMappings|undefined, - private targetPath: AbsoluteFsPath) { - super(fs, config, logger, resolver, basePath, pathMappings); - } - - /** - * Search for Angular entry-points that can be reached from the entry-point specified by the given - * `targetPath`. - */ - override findEntryPoints(): SortedEntryPointsInfo { - const entryPoints = super.findEntryPoints(); - - const invalidTarget = - entryPoints.invalidEntryPoints.find(i => i.entryPoint.path === this.targetPath); - if (invalidTarget !== undefined) { - throw new Error( - `The target entry-point "${invalidTarget.entryPoint.name}" has missing dependencies:\n` + - invalidTarget.missingDependencies.map(dep => ` - ${dep}\n`).join('')); - } - return entryPoints; - } - - /** - * Determine whether the entry-point at the given `targetPath` needs to be processed. - * - * @param propertiesToConsider the package.json properties that should be considered for - * processing. - * @param compileAllFormats true if all formats need to be processed, or false if it is enough for - * one of the formats covered by the `propertiesToConsider` is processed. - */ - targetNeedsProcessingOrCleaning( - propertiesToConsider: EntryPointJsonProperty[], compileAllFormats: boolean): boolean { - const entryPointWithDeps = this.getEntryPointWithDeps(this.targetPath); - if (entryPointWithDeps === null) { - return false; - } - - for (const property of propertiesToConsider) { - if (entryPointWithDeps.entryPoint.packageJson[property]) { - // Here is a property that should be processed. - if (!hasBeenProcessed(entryPointWithDeps.entryPoint.packageJson, property)) { - return true; - } - if (!compileAllFormats) { - // This property has been processed, and we only need one. - return false; - } - } - } - // All `propertiesToConsider` that appear in this entry-point have been processed. - // In other words, there were no properties that need processing. - return false; - } - - /** - * Return an array containing the `targetPath` from which to start the trace. - */ - protected override getInitialEntryPointPaths(): AbsoluteFsPath[] { - return [this.targetPath]; - } - - /** - * For the given `entryPointPath`, compute, or retrieve, the entry-point information, including - * paths to other entry-points that this entry-point depends upon. - * - * @param entryPointPath the path to the entry-point whose information and dependencies are to be - * retrieved or computed. - * - * @returns the entry-point and its dependencies or `null` if the entry-point is not compiled by - * Angular or cannot be determined. - */ - protected override getEntryPointWithDeps(entryPointPath: AbsoluteFsPath): - EntryPointWithDependencies|null { - const packagePath = this.computePackagePath(entryPointPath); - const entryPoint = - getEntryPointInfo(this.fs, this.config, this.logger, packagePath, entryPointPath); - if (!isEntryPoint(entryPoint) || !entryPoint.compiledByAngular) { - return null; - } - return this.resolver.getEntryPointWithDependencies(entryPoint); - } - - /** - * Compute the path to the package that contains the given entry-point. - * - * In this entry-point finder it is not trivial to find the containing package, since it is - * possible that this entry-point is not directly below the directory containing the package. - * Moreover, the import path could be affected by path-mapping. - * - * @param entryPointPath the path to the entry-point, whose package path we want to compute. - */ - private computePackagePath(entryPointPath: AbsoluteFsPath): AbsoluteFsPath { - // First try the main basePath, to avoid having to compute the other basePaths from the paths - // mappings, which can be computationally intensive. - if (this.isPathContainedBy(this.basePath, entryPointPath)) { - const packagePath = this.computePackagePathFromContainingPath(entryPointPath, this.basePath); - if (packagePath !== null) { - return packagePath; - } - } - - // The main `basePath` didn't work out so now we try the `basePaths` computed from the paths - // mappings in `tsconfig.json`. - for (const basePath of this.getBasePaths()) { - if (this.isPathContainedBy(basePath, entryPointPath)) { - const packagePath = this.computePackagePathFromContainingPath(entryPointPath, basePath); - if (packagePath !== null) { - return packagePath; - } - // If we got here then we couldn't find a `packagePath` for the current `basePath`. - // Since `basePath`s are guaranteed not to be a sub-directory of each other then no other - // `basePath` will match either. - break; - } - } - - // Finally, if we couldn't find a `packagePath` using `basePaths` then try to find the nearest - // `node_modules` that contains the `entryPointPath`, if there is one, and use it as a - // `basePath`. - return this.computePackagePathFromNearestNodeModules(entryPointPath); - } - - /** - * Compute whether the `test` path is contained within the `base` path. - * - * Note that this doesn't use a simple `startsWith()` since that would result in a false positive - * for `test` paths such as `a/b/c-x` when the `base` path is `a/b/c`. - * - * Since `fs.relative()` can be quite expensive we check the fast possibilities first. - */ - private isPathContainedBy(base: AbsoluteFsPath, test: AbsoluteFsPath): boolean { - return test === base || - (test.startsWith(base) && !this.fs.relative(base, test).startsWith('..')); - } - - /** - * Search down to the `entryPointPath` from the `containingPath` for the first `package.json` that - * we come to. This is the path to the entry-point's containing package. For example if - * `containingPath` is `/a/b/c` and `entryPointPath` is `/a/b/c/d/e` and there exists - * `/a/b/c/d/package.json` and `/a/b/c/d/e/package.json`, then we will return `/a/b/c/d`. - * - * To account for nested `node_modules` we actually start the search at the last `node_modules` in - * the `entryPointPath` that is below the `containingPath`. E.g. if `containingPath` is `/a/b/c` - * and `entryPointPath` is `/a/b/c/d/node_modules/x/y/z`, we start the search at - * `/a/b/c/d/node_modules`. - */ - private computePackagePathFromContainingPath( - entryPointPath: AbsoluteFsPath, containingPath: AbsoluteFsPath): AbsoluteFsPath|null { - let packagePath = containingPath; - const segments = this.splitPath(this.fs.relative(containingPath, entryPointPath)); - let nodeModulesIndex = segments.lastIndexOf('node_modules' as PathSegment); - - // If there are no `node_modules` in the relative path between the `basePath` and the - // `entryPointPath` then just try the `basePath` as the `packagePath`. - // (This can be the case with path-mapped entry-points.) - if (nodeModulesIndex === -1) { - if (this.fs.exists(this.fs.join(packagePath, 'package.json'))) { - return packagePath; - } - } - - // Start the search at the deepest nested `node_modules` folder that is below the `basePath` - // but above the `entryPointPath`, if there are any. - while (nodeModulesIndex >= 0) { - packagePath = this.fs.join(packagePath, segments.shift()!); - nodeModulesIndex--; - } - - // Note that we start at the folder below the current candidate `packagePath` because the - // initial candidate `packagePath` is either a `node_modules` folder or the `basePath` with - // no `package.json`. - for (const segment of segments) { - packagePath = this.fs.join(packagePath, segment); - if (this.fs.exists(this.fs.join(packagePath, 'package.json'))) { - return packagePath; - } - } - return null; - } - - /** - * Search up the directory tree from the `entryPointPath` looking for a `node_modules` directory - * that we can use as a potential starting point for computing the package path. - */ - private computePackagePathFromNearestNodeModules(entryPointPath: AbsoluteFsPath): AbsoluteFsPath { - let packagePath = entryPointPath; - let scopedPackagePath = packagePath; - let containerPath = this.fs.dirname(packagePath); - while (!this.fs.isRoot(containerPath) && !containerPath.endsWith('node_modules')) { - scopedPackagePath = packagePath; - packagePath = containerPath; - containerPath = this.fs.dirname(containerPath); - } - - if (this.fs.exists(this.fs.join(packagePath, 'package.json'))) { - // The directory directly below `node_modules` is a package - use it - return packagePath; - } else if ( - this.fs.basename(packagePath).startsWith('@') && - this.fs.exists(this.fs.join(scopedPackagePath, 'package.json'))) { - // The directory directly below the `node_modules` is a scope and the directory directly - // below that is a scoped package - use it - return scopedPackagePath; - } else { - // If we get here then none of the `basePaths` contained the `entryPointPath` and the - // `entryPointPath` contains no `node_modules` that contains a package or a scoped - // package. All we can do is assume that this entry-point is a primary entry-point to a - // package. - return entryPointPath; - } - } - - /** - * Split the given `path` into path segments using an FS independent algorithm. - */ - private splitPath(path: PathSegment|AbsoluteFsPath) { - const segments = []; - let container = this.fs.dirname(path); - while (path !== container) { - segments.unshift(this.fs.basename(path)); - path = container; - container = this.fs.dirname(container); - } - return segments; - } -} diff --git a/packages/compiler-cli/ngcc/src/entry_point_finder/tracing_entry_point_finder.ts b/packages/compiler-cli/ngcc/src/entry_point_finder/tracing_entry_point_finder.ts deleted file mode 100644 index a39d131638e..00000000000 --- a/packages/compiler-cli/ngcc/src/entry_point_finder/tracing_entry_point_finder.ts +++ /dev/null @@ -1,97 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import {AbsoluteFsPath, PathManipulation, ReadonlyFileSystem} from '../../../src/ngtsc/file_system'; -import {Logger} from '../../../src/ngtsc/logging'; -import {EntryPointWithDependencies} from '../dependencies/dependency_host'; -import {DependencyResolver, SortedEntryPointsInfo} from '../dependencies/dependency_resolver'; -import {NgccConfiguration} from '../packages/configuration'; -import {PathMappings} from '../path_mappings'; - -import {EntryPointFinder} from './interface'; -import {getBasePaths} from './utils'; - -/** - * An EntryPointFinder that starts from a set of initial files and only returns entry-points that - * are dependencies of these files. - * - * This is faster than processing all entry-points in the entire file-system, and is used primarily - * by the CLI integration. - * - * There are two concrete implementations of this class. - * - * * `TargetEntryPointFinder` - is given a single entry-point as the initial entry-point. This can - * be used in the synchronous CLI integration where the build tool has identified an external - * import to one of the source files being built. - * * `ProgramBasedEntryPointFinder` - computes the initial entry-points from the source files - * computed from a `tsconfig.json` file. This can be used in the asynchronous CLI integration - * where the `tsconfig.json` to be used to do the build is known. - */ -export abstract class TracingEntryPointFinder implements EntryPointFinder { - private basePaths: AbsoluteFsPath[]|null = null; - - constructor( - protected fs: ReadonlyFileSystem, protected config: NgccConfiguration, - protected logger: Logger, protected resolver: DependencyResolver, - protected basePath: AbsoluteFsPath, protected pathMappings: PathMappings|undefined) {} - - /** - * Search for Angular package entry-points. - */ - findEntryPoints(): SortedEntryPointsInfo { - const unsortedEntryPoints = new Map(); - const unprocessedPaths = this.getInitialEntryPointPaths(); - while (unprocessedPaths.length > 0) { - const path = unprocessedPaths.shift()!; - const entryPointWithDeps = this.getEntryPointWithDeps(path); - if (entryPointWithDeps === null) { - continue; - } - unsortedEntryPoints.set(entryPointWithDeps.entryPoint.path, entryPointWithDeps); - entryPointWithDeps.depInfo.dependencies.forEach(dep => { - if (!unsortedEntryPoints.has(dep)) { - unprocessedPaths.push(dep); - } - }); - } - return this.resolver.sortEntryPointsByDependency(Array.from(unsortedEntryPoints.values())); - } - - - /** - * Return an array of entry-point paths from which to start the trace. - */ - protected abstract getInitialEntryPointPaths(): AbsoluteFsPath[]; - - /** - * For the given `entryPointPath`, compute, or retrieve, the entry-point information, including - * paths to other entry-points that this entry-point depends upon. - * - * @param entryPointPath the path to the entry-point whose information and dependencies are to be - * retrieved or computed. - * - * @returns the entry-point and its dependencies or `null` if the entry-point is not compiled by - * Angular or cannot be determined. - */ - protected abstract getEntryPointWithDeps(entryPointPath: AbsoluteFsPath): - EntryPointWithDependencies|null; - - - /** - * Parse the path-mappings to compute the base-paths that need to be considered when finding - * entry-points. - * - * This processing can be time-consuming if the path-mappings are complex or extensive. - * So the result is cached locally once computed. - */ - protected getBasePaths() { - if (this.basePaths === null) { - this.basePaths = getBasePaths(this.logger, this.basePath, this.pathMappings); - } - return this.basePaths; - } -} diff --git a/packages/compiler-cli/ngcc/src/entry_point_finder/utils.ts b/packages/compiler-cli/ngcc/src/entry_point_finder/utils.ts deleted file mode 100644 index c74f5ce1def..00000000000 --- a/packages/compiler-cli/ngcc/src/entry_point_finder/utils.ts +++ /dev/null @@ -1,205 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import {AbsoluteFsPath, getFileSystem, PathManipulation, ReadonlyFileSystem} from '../../../src/ngtsc/file_system'; -import {Logger} from '../../../src/ngtsc/logging'; -import {PathMappings} from '../path_mappings'; - -/** - * Extract all the base-paths that we need to search for entry-points. - * - * This always contains the standard base-path (`sourceDirectory`). - * But it also parses the `paths` mappings object to guess additional base-paths. - * - * For example: - * - * ``` - * getBasePaths('/node_modules', {baseUrl: '/dist', paths: {'*': ['lib/*', 'lib/generated/*']}}) - * > ['/node_modules', '/dist/lib'] - * ``` - * - * Notice that `'/dist'` is not included as there is no `'*'` path, - * and `'/dist/lib/generated'` is not included as it is covered by `'/dist/lib'`. - * - * @param sourceDirectory The standard base-path (e.g. node_modules). - * @param pathMappings Path mapping configuration, from which to extract additional base-paths. - */ -export function getBasePaths( - logger: Logger, sourceDirectory: AbsoluteFsPath, - pathMappings: PathMappings|undefined): AbsoluteFsPath[] { - const fs = getFileSystem(); - const basePaths = [sourceDirectory]; - if (pathMappings) { - const baseUrl = fs.resolve(pathMappings.baseUrl); - if (fs.isRoot(baseUrl)) { - logger.warn( - `The provided pathMappings baseUrl is the root path ${baseUrl}.\n` + - `This is likely to mess up how ngcc finds entry-points and is probably not correct.\n` + - `Please check your path mappings configuration such as in the tsconfig.json file.`); - } - for (const paths of Object.values(pathMappings.paths)) { - for (const path of paths) { - let foundMatch = false; - - // We only want base paths that exist and are not files - const {prefix, hasWildcard} = extractPathPrefix(path); - let basePath = fs.resolve(baseUrl, prefix); - if (fs.exists(basePath) && fs.stat(basePath).isFile()) { - basePath = fs.dirname(basePath); - } - - if (fs.exists(basePath)) { - // The `basePath` is itself a directory - basePaths.push(basePath); - foundMatch = true; - } - - if (hasWildcard) { - // The path contains a wildcard (`*`) so also try searching for directories that start - // with the wildcard prefix path segment. - const wildcardContainer = fs.dirname(basePath); - const wildcardPrefix = fs.basename(basePath); - if (isExistingDirectory(fs, wildcardContainer)) { - const candidates = fs.readdir(wildcardContainer); - for (const candidate of candidates) { - if (candidate.startsWith(wildcardPrefix)) { - const candidatePath = fs.resolve(wildcardContainer, candidate); - if (isExistingDirectory(fs, candidatePath)) { - foundMatch = true; - basePaths.push(candidatePath); - } - } - } - } - } - - if (!foundMatch) { - // We neither found a direct match (i.e. `basePath` is an existing directory) nor a - // directory that starts with a wildcard prefix. - logger.debug( - `The basePath "${basePath}" computed from baseUrl "${baseUrl}" and path mapping "${ - path}" does not exist in the file-system.\n` + - `It will not be scanned for entry-points.`); - } - } - } - } - - const dedupedBasePaths = dedupePaths(fs, basePaths); - - // We want to ensure that the `sourceDirectory` is included when it is a node_modules folder. - // Otherwise our entry-point finding algorithm would fail to walk that folder. - if (fs.basename(sourceDirectory) === 'node_modules' && - !dedupedBasePaths.includes(sourceDirectory)) { - dedupedBasePaths.unshift(sourceDirectory); - } - - return dedupedBasePaths; -} - -function isExistingDirectory(fs: ReadonlyFileSystem, path: AbsoluteFsPath): boolean { - return fs.exists(path) && fs.stat(path).isDirectory(); -} - -/** - * Extract everything in the `path` up to the first `*`. - * @param path The path to parse. - * @returns The extracted prefix and a flag to indicate whether there was a wildcard `*`. - */ -function extractPathPrefix(path: string): {prefix: string, hasWildcard: boolean} { - const [prefix, rest] = path.split('*', 2); - return {prefix, hasWildcard: rest !== undefined}; -} - -/** - * Run a task and track how long it takes. - * - * @param task The task whose duration we are tracking. - * @param log The function to call with the duration of the task. - * @returns The result of calling `task`. - */ -export function trackDuration(task: () => T extends Promise? never : T, - log: (duration: number) => void): T { - const startTime = Date.now(); - const result = task(); - const duration = Math.round((Date.now() - startTime) / 100) / 10; - log(duration); - return result; -} - -/** - * Remove paths that are contained by other paths. - * - * For example: - * Given `['a/b/c', 'a/b/x', 'a/b', 'd/e', 'd/f']` we will end up with `['a/b', 'd/e', 'd/f]`. - * (Note that we do not get `d` even though `d/e` and `d/f` share a base directory, since `d` is not - * one of the base paths.) - */ -function dedupePaths(fs: PathManipulation, paths: AbsoluteFsPath[]): AbsoluteFsPath[] { - const root: Node = {children: new Map()}; - for (const path of paths) { - addPath(fs, root, path); - } - return flattenTree(root); -} - -/** - * Add a path (defined by the `segments`) to the current `node` in the tree. - */ -function addPath(fs: PathManipulation, root: Node, path: AbsoluteFsPath): void { - let node = root; - if (!fs.isRoot(path)) { - const segments = path.split('/'); - for (let index = 0; index < segments.length; index++) { - if (isLeaf(node)) { - // We hit a leaf so don't bother processing any more of the path - return; - } - // This is not the end of the path continue to process the rest of this path. - const next = segments[index]; - if (!node.children.has(next)) { - node.children.set(next, {children: new Map()}); - } - node = node.children.get(next)!; - } - } - // This path has finished so convert this node to a leaf - convertToLeaf(node, path); -} - -/** - * Flatten the tree of nodes back into an array of absolute paths. - */ -function flattenTree(root: Node): AbsoluteFsPath[] { - const paths: AbsoluteFsPath[] = []; - const nodes: Node[] = [root]; - for (let index = 0; index < nodes.length; index++) { - const node = nodes[index]; - if (isLeaf(node)) { - // We found a leaf so store the currentPath - paths.push(node.path); - } else { - node.children.forEach(value => nodes.push(value)); - } - } - return paths; -} - -function isLeaf(node: Node): node is Leaf { - return node.path !== undefined; -} - -function convertToLeaf(node: Node, path: AbsoluteFsPath) { - node.path = path; -} - -interface Node { - children: Map; - path?: AbsoluteFsPath; -} - -type Leaf = Required; diff --git a/packages/compiler-cli/ngcc/src/execution/analyze_entry_points.ts b/packages/compiler-cli/ngcc/src/execution/analyze_entry_points.ts deleted file mode 100644 index 1a876eb53db..00000000000 --- a/packages/compiler-cli/ngcc/src/execution/analyze_entry_points.ts +++ /dev/null @@ -1,182 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import {DepGraph} from 'dependency-graph'; - -import {FileSystem} from '../../../src/ngtsc/file_system'; -import {Logger} from '../../../src/ngtsc/logging'; -import {InvalidEntryPoint} from '../dependencies/dependency_resolver'; -import {EntryPointFinder} from '../entry_point_finder/interface'; -import {ParallelTaskQueue} from '../execution/tasks/queues/parallel_task_queue'; -import {SerialTaskQueue} from '../execution/tasks/queues/serial_task_queue'; -import {computeTaskDependencies} from '../execution/tasks/utils'; -import {hasBeenProcessed} from '../packages/build_marker'; -import {EntryPoint, EntryPointJsonProperty, EntryPointPackageJson, SUPPORTED_FORMAT_PROPERTIES} from '../packages/entry_point'; -import {cleanOutdatedPackages} from '../writing/cleaning/package_cleaner'; - -import {AnalyzeEntryPointsFn} from './api'; -import {DtsProcessing, PartiallyOrderedTasks, TaskQueue} from './tasks/api'; - -/** - * Create the function for performing the analysis of the entry-points. - */ -export function getAnalyzeEntryPointsFn( - logger: Logger, finder: EntryPointFinder, fileSystem: FileSystem, - supportedPropertiesToConsider: EntryPointJsonProperty[], typingsOnly: boolean, - compileAllFormats: boolean, propertiesToConsider: string[], - inParallel: boolean): AnalyzeEntryPointsFn { - return () => { - logger.debug('Analyzing entry-points...'); - const startTime = Date.now(); - - let entryPointInfo = finder.findEntryPoints(); - const cleaned = cleanOutdatedPackages(fileSystem, entryPointInfo.entryPoints); - if (cleaned) { - // If we had to clean up one or more packages then we must read in the entry-points again. - entryPointInfo = finder.findEntryPoints(); - } - - const {entryPoints, invalidEntryPoints, graph} = entryPointInfo; - logInvalidEntryPoints(logger, invalidEntryPoints); - - const unprocessableEntryPointPaths: string[] = []; - // The tasks are partially ordered by virtue of the entry-points being partially ordered too. - const tasks: PartiallyOrderedTasks = [] as any; - - for (const entryPoint of entryPoints) { - const packageJson = entryPoint.packageJson; - const {propertiesToProcess, equivalentPropertiesMap} = getPropertiesToProcess( - packageJson, supportedPropertiesToConsider, compileAllFormats, typingsOnly); - - if (propertiesToProcess.length === 0) { - // This entry-point is unprocessable (i.e. there is no format property that is of interest - // and can be processed). This will result in an error, but continue looping over - // entry-points in order to collect all unprocessable ones and display a more informative - // error. - unprocessableEntryPointPaths.push(entryPoint.path); - continue; - } - - const hasProcessedTypings = hasBeenProcessed(packageJson, 'typings'); - if (hasProcessedTypings && typingsOnly) { - // Typings for this entry-point have already been processed and we're in typings-only mode, - // so no task has to be created for this entry-point. - logger.debug(`Skipping ${entryPoint.name} : typings have already been processed.`); - continue; - } - let processDts = hasProcessedTypings ? DtsProcessing.No : - typingsOnly ? DtsProcessing.Only : DtsProcessing.Yes; - - for (const formatProperty of propertiesToProcess) { - if (hasBeenProcessed(entryPoint.packageJson, formatProperty)) { - // The format-path which the property maps to is already processed - nothing to do. - logger.debug(`Skipping ${entryPoint.name} : ${formatProperty} (already compiled).`); - continue; - } - - const formatPropertiesToMarkAsProcessed = equivalentPropertiesMap.get(formatProperty)!; - tasks.push({entryPoint, formatProperty, formatPropertiesToMarkAsProcessed, processDts}); - - // Only process typings for the first property (if not already processed). - processDts = DtsProcessing.No; - } - } - - // Check for entry-points for which we could not process any format at all. - if (unprocessableEntryPointPaths.length > 0) { - throw new Error( - 'Unable to process any formats for the following entry-points (tried ' + - `${propertiesToConsider.join(', ')}): ` + - unprocessableEntryPointPaths.map(path => `\n - ${path}`).join('')); - } - - const duration = Math.round((Date.now() - startTime) / 100) / 10; - logger.debug( - `Analyzed ${entryPoints.length} entry-points in ${duration}s. ` + - `(Total tasks: ${tasks.length})`); - - return getTaskQueue(logger, inParallel, tasks, graph); - }; -} - -function logInvalidEntryPoints(logger: Logger, invalidEntryPoints: InvalidEntryPoint[]): void { - invalidEntryPoints.forEach(invalidEntryPoint => { - logger.debug( - `Invalid entry-point ${invalidEntryPoint.entryPoint.path}.`, - `It is missing required dependencies:\n` + - invalidEntryPoint.missingDependencies.map(dep => ` - ${dep}`).join('\n')); - }); -} - -/** - * This function computes and returns the following: - * - `propertiesToProcess`: An (ordered) list of properties that exist and need to be processed, - * based on the provided `propertiesToConsider`, the properties in `package.json` and their - * corresponding format-paths. NOTE: Only one property per format-path needs to be processed. - * - `equivalentPropertiesMap`: A mapping from each property in `propertiesToProcess` to the list of - * other format properties in `package.json` that need to be marked as processed as soon as the - * former has been processed. - */ -function getPropertiesToProcess( - packageJson: EntryPointPackageJson, propertiesToConsider: EntryPointJsonProperty[], - compileAllFormats: boolean, typingsOnly: boolean): { - propertiesToProcess: EntryPointJsonProperty[]; - equivalentPropertiesMap: Map; -} { - const formatPathsToConsider = new Set(); - - const propertiesToProcess: EntryPointJsonProperty[] = []; - for (const prop of propertiesToConsider) { - const formatPath = packageJson[prop]; - - // Ignore properties that are not defined in `package.json`. - if (typeof formatPath !== 'string') continue; - - // Ignore properties that map to the same format-path as a preceding property. - if (formatPathsToConsider.has(formatPath)) continue; - - // Process this property, because it is the first one to map to this format-path. - formatPathsToConsider.add(formatPath); - propertiesToProcess.push(prop); - - // If we only need one format processed, there is no need to process any more properties. - if (!compileAllFormats) break; - } - - const formatPathToProperties: {[formatPath: string]: EntryPointJsonProperty[]} = {}; - for (const prop of SUPPORTED_FORMAT_PROPERTIES) { - const formatPath = packageJson[prop]; - - // Ignore properties that are not defined in `package.json`. - if (typeof formatPath !== 'string') continue; - - // Ignore properties that do not map to a format-path that will be considered. - if (!formatPathsToConsider.has(formatPath)) continue; - - // Add this property to the map. - const list = formatPathToProperties[formatPath] || (formatPathToProperties[formatPath] = []); - list.push(prop); - } - - const equivalentPropertiesMap = new Map(); - for (const prop of propertiesToConsider) { - const formatPath = packageJson[prop]!; - // If we are only processing typings then there should be no format properties to mark - const equivalentProperties = typingsOnly ? [] : formatPathToProperties[formatPath]; - equivalentPropertiesMap.set(prop, equivalentProperties); - } - - return {propertiesToProcess, equivalentPropertiesMap}; -} - -function getTaskQueue( - logger: Logger, inParallel: boolean, tasks: PartiallyOrderedTasks, - graph: DepGraph): TaskQueue { - const dependencies = computeTaskDependencies(tasks, graph); - return inParallel ? new ParallelTaskQueue(logger, tasks, dependencies) : - new SerialTaskQueue(logger, tasks, dependencies); -} diff --git a/packages/compiler-cli/ngcc/src/execution/api.ts b/packages/compiler-cli/ngcc/src/execution/api.ts deleted file mode 100644 index e613e9327e2..00000000000 --- a/packages/compiler-cli/ngcc/src/execution/api.ts +++ /dev/null @@ -1,34 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import {FileToWrite} from '../rendering/utils'; -import {Task, TaskCompletedCallback, TaskQueue} from './tasks/api'; - -/** - * The type of the function that analyzes entry-points and creates the list of tasks. - * - * @return A list of tasks that need to be executed in order to process the necessary format - * properties for all entry-points. - */ -export type AnalyzeEntryPointsFn = () => TaskQueue; - -/** The type of the function that can process/compile a task. */ -export type CompileFn = (task: Task) => void|T; - -/** The type of the function that creates the `CompileFn` function used to process tasks. */ -export type CreateCompileFn = >( - beforeWritingFiles: (transformedFiles: FileToWrite[]) => T, - onTaskCompleted: TaskCompletedCallback) => CompileFn; - -/** - * A class that orchestrates and executes the required work (i.e. analyzes the entry-points, - * processes the resulting tasks, does book-keeping and validates the final outcome). - */ -export interface Executor { - execute(analyzeEntryPoints: AnalyzeEntryPointsFn, createCompileFn: CreateCompileFn): - void|Promise; -} diff --git a/packages/compiler-cli/ngcc/src/execution/cluster/api.ts b/packages/compiler-cli/ngcc/src/execution/cluster/api.ts deleted file mode 100644 index e3e3b91d383..00000000000 --- a/packages/compiler-cli/ngcc/src/execution/cluster/api.ts +++ /dev/null @@ -1,61 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ - -import {AbsoluteFsPath} from '../../../../src/ngtsc/file_system'; -import {JsonObject} from '../../utils'; -import {PackageJsonChange} from '../../writing/package_json_updater'; -import {Task, TaskProcessingOutcome} from '../tasks/api'; - -/** A message reporting that the worker is ready for retrieving tasks. */ -export interface ReadyMessage extends JsonObject { - type: 'ready'; -} - -/** A message reporting that an unrecoverable error occurred. */ -export interface ErrorMessage extends JsonObject { - type: 'error'; - error: string; -} - -/** A message requesting the processing of a task. */ -export interface ProcessTaskMessage extends JsonObject { - type: 'process-task'; - task: Task; -} - -/** - * A message reporting the result of processing the currently assigned task. - * - * NOTE: To avoid the communication overhead, the task is not included in the message. Instead, the - * master is responsible for keeping a mapping of workers to their currently assigned tasks. - */ -export interface TaskCompletedMessage extends JsonObject { - type: 'task-completed'; - outcome: TaskProcessingOutcome; - message: string|null; -} - -/** A message listing the paths to transformed files about to be written to disk. */ -export interface TransformedFilesMessage extends JsonObject { - type: 'transformed-files'; - files: AbsoluteFsPath[]; -} - -/** A message requesting the update of a `package.json` file. */ -export interface UpdatePackageJsonMessage extends JsonObject { - type: 'update-package-json'; - packageJsonPath: AbsoluteFsPath; - changes: PackageJsonChange[]; -} - -/** The type of messages sent from cluster workers to the cluster master. */ -export type MessageFromWorker = - ReadyMessage|ErrorMessage|TaskCompletedMessage|TransformedFilesMessage|UpdatePackageJsonMessage; - -/** The type of messages sent from the cluster master to cluster workers. */ -export type MessageToWorker = ProcessTaskMessage; diff --git a/packages/compiler-cli/ngcc/src/execution/cluster/executor.ts b/packages/compiler-cli/ngcc/src/execution/cluster/executor.ts deleted file mode 100644 index 82c8bbcb2dc..00000000000 --- a/packages/compiler-cli/ngcc/src/execution/cluster/executor.ts +++ /dev/null @@ -1,40 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import {PathManipulation} from '../../../../src/ngtsc/file_system'; -import {Logger} from '../../../../src/ngtsc/logging'; -import {AsyncLocker} from '../../locking/async_locker'; -import {FileWriter} from '../../writing/file_writer'; -import {PackageJsonUpdater} from '../../writing/package_json_updater'; -import {AnalyzeEntryPointsFn, CreateCompileFn, Executor} from '../api'; -import {CreateTaskCompletedCallback} from '../tasks/api'; - -import {ClusterMaster} from './master'; - -/** - * An `Executor` that processes tasks in parallel (on multiple processes) and completes - * asynchronously. - */ -export class ClusterExecutor implements Executor { - constructor( - private workerCount: number, private fileSystem: PathManipulation, private logger: Logger, - private fileWriter: FileWriter, private pkgJsonUpdater: PackageJsonUpdater, - private lockFile: AsyncLocker, - private createTaskCompletedCallback: CreateTaskCompletedCallback) {} - - async execute(analyzeEntryPoints: AnalyzeEntryPointsFn, _createCompileFn: CreateCompileFn): - Promise { - return this.lockFile.lock(async () => { - this.logger.debug( - `Running ngcc on ${this.constructor.name} (using ${this.workerCount} worker processes).`); - const master = new ClusterMaster( - this.workerCount, this.fileSystem, this.logger, this.fileWriter, this.pkgJsonUpdater, - analyzeEntryPoints, this.createTaskCompletedCallback); - return await master.run(); - }); - } -} diff --git a/packages/compiler-cli/ngcc/src/execution/cluster/master.ts b/packages/compiler-cli/ngcc/src/execution/cluster/master.ts deleted file mode 100644 index 050aaffa949..00000000000 --- a/packages/compiler-cli/ngcc/src/execution/cluster/master.ts +++ /dev/null @@ -1,357 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ - -/// - -import cluster, {Worker} from 'cluster'; -import module from 'module'; - -import {AbsoluteFsPath, PathManipulation} from '../../../../src/ngtsc/file_system'; -import {Logger} from '../../../../src/ngtsc/logging'; -import {FileWriter} from '../../writing/file_writer'; -import {PackageJsonUpdater} from '../../writing/package_json_updater'; -import {AnalyzeEntryPointsFn} from '../api'; -import {CreateTaskCompletedCallback, Task, TaskCompletedCallback, TaskQueue} from '../tasks/api'; -import {stringifyTask} from '../tasks/utils'; - -import {MessageFromWorker, TaskCompletedMessage, TransformedFilesMessage, UpdatePackageJsonMessage} from './api'; -import {Deferred, sendMessageToWorker} from './utils'; - -/** - * The cluster master is responsible for analyzing all entry-points, planning the work that needs to - * be done, distributing it to worker-processes and collecting/post-processing the results. - */ -export class ClusterMaster { - private finishedDeferred = new Deferred(); - private processingStartTime: number = -1; - private taskAssignments = new Map(); - private taskQueue: TaskQueue; - private onTaskCompleted: TaskCompletedCallback; - private remainingRespawnAttempts = 3; - - constructor( - private maxWorkerCount: number, private fileSystem: PathManipulation, private logger: Logger, - private fileWriter: FileWriter, private pkgJsonUpdater: PackageJsonUpdater, - analyzeEntryPoints: AnalyzeEntryPointsFn, - createTaskCompletedCallback: CreateTaskCompletedCallback) { - if (!cluster.isMaster) { - throw new Error('Tried to instantiate `ClusterMaster` on a worker process.'); - } - - // Set the worker entry-point - cluster.setupMaster({exec: ClusterWorkerScriptResolver.resolve(fileSystem)}); - - this.taskQueue = analyzeEntryPoints(); - this.onTaskCompleted = createTaskCompletedCallback(this.taskQueue); - } - - run(): Promise { - if (this.taskQueue.allTasksCompleted) { - return Promise.resolve(); - } - - this.logger.info('Processing legacy "View Engine" libraries:'); - - // Set up listeners for worker events (emitted on `cluster`). - cluster.on( - 'message', this.wrapEventHandler((worker, msg) => this.onWorkerMessage(worker.id, msg))); - - cluster.on( - 'exit', - this.wrapEventHandler((worker, code, signal) => this.onWorkerExit(worker, code, signal))); - - // Since we have pending tasks at the very minimum we need a single worker. - cluster.fork(); - - return this.finishedDeferred.promise.then(() => this.stopWorkers(), err => { - this.stopWorkers(); - return Promise.reject(err); - }); - } - - /** Try to find available (idle) workers and assign them available (non-blocked) tasks. */ - private maybeDistributeWork(): void { - let isWorkerAvailable = false; - - // First, check whether all tasks have been completed. - if (this.taskQueue.allTasksCompleted) { - const duration = Math.round((Date.now() - this.processingStartTime) / 100) / 10; - this.logger.debug(`Processed tasks in ${duration}s.`); - this.logger.info('Encourage the library authors to publish an Ivy distribution.'); - - return this.finishedDeferred.resolve(); - } - - // Look for available workers and available tasks to assign to them. - for (const [workerId, assignedTask] of Array.from(this.taskAssignments)) { - if (assignedTask !== null) { - // This worker already has a job; check other workers. - continue; - } else { - // This worker is available. - isWorkerAvailable = true; - } - - // This worker needs a job. See if any are available. - const task = this.taskQueue.getNextTask(); - if (task === null) { - // No suitable work available right now. - break; - } - - // Process the next task on the worker. - this.taskAssignments.set(workerId, {task}); - sendMessageToWorker(workerId, {type: 'process-task', task}); - - isWorkerAvailable = false; - } - - if (!isWorkerAvailable) { - const spawnedWorkerCount = cluster.workers ? Object.keys(cluster.workers).length : 0; - if (spawnedWorkerCount < this.maxWorkerCount) { - this.logger.debug('Spawning another worker process as there is more work to be done.'); - cluster.fork(); - } else { - // If there are no available workers or no available tasks, log (for debugging purposes). - this.logger.debug( - `All ${spawnedWorkerCount} workers are currently busy and cannot take on more work.`); - } - } else { - const busyWorkers = Array.from(this.taskAssignments) - .filter(([_workerId, task]) => task !== null) - .map(([workerId]) => workerId); - const totalWorkerCount = this.taskAssignments.size; - const idleWorkerCount = totalWorkerCount - busyWorkers.length; - - this.logger.debug( - `No assignments for ${idleWorkerCount} idle (out of ${totalWorkerCount} total) ` + - `workers. Busy workers: ${busyWorkers.join(', ')}`); - - if (busyWorkers.length === 0) { - // This is a bug: - // All workers are idle (meaning no tasks are in progress) and `taskQueue.allTasksCompleted` - // is `false`, but there is still no assignable work. - throw new Error( - 'There are still unprocessed tasks in the queue and no tasks are currently in ' + - `progress, yet the queue did not return any available tasks: ${this.taskQueue}`); - } - } - } - - /** Handle a worker's exiting. (Might be intentional or not.) */ - private onWorkerExit(worker: Worker, code: number|null, signal: string|null): void { - // If the worker's exiting was intentional, nothing to do. - if (worker.exitedAfterDisconnect) return; - - // The worker exited unexpectedly: Determine it's status and take an appropriate action. - const assignment = this.taskAssignments.get(worker.id); - this.taskAssignments.delete(worker.id); - - this.logger.warn( - `Worker #${worker.id} exited unexpectedly (code: ${code} | signal: ${signal}).\n` + - ` Current task: ${(assignment == null) ? '-' : stringifyTask(assignment.task)}\n` + - ` Current phase: ${ - (assignment == null) ? '-' : - (assignment.files == null) ? 'compiling' : - 'writing files'}`); - - if (assignment == null) { - // The crashed worker process was not in the middle of a task: - // Just spawn another process. - this.logger.debug(`Spawning another worker process to replace #${worker.id}...`); - cluster.fork(); - } else { - const {task, files} = assignment; - - if (files != null) { - // The crashed worker process was in the middle of writing transformed files: - // Revert any changes before re-processing the task. - this.logger.debug(`Reverting ${files.length} transformed files...`); - this.fileWriter.revertBundle( - task.entryPoint, files, task.formatPropertiesToMarkAsProcessed); - } - - // The crashed worker process was in the middle of a task: - // Re-add the task back to the queue. - this.taskQueue.markAsUnprocessed(task); - - // The crashing might be a result of increased memory consumption by ngcc. - // Do not spawn another process, unless this was the last worker process. - const spawnedWorkerCount = cluster.workers ? Object.keys(cluster.workers).length : 0; - if (spawnedWorkerCount > 0) { - this.logger.debug(`Not spawning another worker process to replace #${ - worker.id}. Continuing with ${spawnedWorkerCount} workers...`); - this.maybeDistributeWork(); - } else if (this.remainingRespawnAttempts > 0) { - this.logger.debug(`Spawning another worker process to replace #${worker.id}...`); - this.remainingRespawnAttempts--; - cluster.fork(); - } else { - throw new Error( - 'All worker processes crashed and attempts to re-spawn them failed. ' + - 'Please check your system and ensure there is enough memory available.'); - } - } - } - - /** Handle a message from a worker. */ - private onWorkerMessage(workerId: number, msg: MessageFromWorker): void { - // A worker is now ready and can retrieve a processing task. - if (msg.type === 'ready') { - this.onWorkerReady(workerId); - return; - } - - // All other messages except for `ready` are unexpected if the worker has - // not notified the `ClusterMaster` about being ready. - if (!this.taskAssignments.has(workerId)) { - const knownWorkers = Array.from(this.taskAssignments.keys()); - throw new Error( - `Received message from unknown worker #${workerId} (known workers: ` + - `${knownWorkers.join(', ')}): ${JSON.stringify(msg)}`); - } - - switch (msg.type) { - case 'error': - throw new Error(`Error on worker #${workerId}: ${msg.error}`); - case 'task-completed': - return this.onWorkerTaskCompleted(workerId, msg); - case 'transformed-files': - return this.onWorkerTransformedFiles(workerId, msg); - case 'update-package-json': - return this.onWorkerUpdatePackageJson(workerId, msg); - default: - throw new Error( - `Invalid message received from worker #${workerId}: ${JSON.stringify(msg)}`); - } - } - - /** Handle a worker's coming online and ready for retrieving IPC messages. */ - private onWorkerReady(workerId: number): void { - if (this.taskAssignments.has(workerId)) { - throw new Error(`Invariant violated: Worker #${workerId} came online more than once.`); - } - - if (this.processingStartTime === -1) { - this.logger.debug('Processing tasks...'); - this.processingStartTime = Date.now(); - } - - this.taskAssignments.set(workerId, null); - this.maybeDistributeWork(); - } - - /** Handle a worker's having completed their assigned task. */ - private onWorkerTaskCompleted(workerId: number, msg: TaskCompletedMessage): void { - const assignment = this.taskAssignments.get(workerId) || null; - - if (assignment === null) { - throw new Error( - `Expected worker #${workerId} to have a task assigned, while handling message: ` + - JSON.stringify(msg)); - } - - this.onTaskCompleted(assignment.task, msg.outcome, msg.message); - - this.taskQueue.markAsCompleted(assignment.task); - this.taskAssignments.set(workerId, null); - this.maybeDistributeWork(); - } - - /** Handle a worker's message regarding the files transformed while processing its task. */ - private onWorkerTransformedFiles(workerId: number, msg: TransformedFilesMessage): void { - const assignment = this.taskAssignments.get(workerId) || null; - - if (assignment === null) { - throw new Error( - `Expected worker #${workerId} to have a task assigned, while handling message: ` + - JSON.stringify(msg)); - } - - const oldFiles = assignment.files; - const newFiles = msg.files; - - if (oldFiles !== undefined) { - throw new Error( - `Worker #${workerId} reported transformed files more than once.\n` + - ` Old files (${oldFiles.length}): [${oldFiles.join(', ')}]\n` + - ` New files (${newFiles.length}): [${newFiles.join(', ')}]\n`); - } - - assignment.files = newFiles; - } - - /** Handle a worker's request to update a `package.json` file. */ - private onWorkerUpdatePackageJson(workerId: number, msg: UpdatePackageJsonMessage): void { - const assignment = this.taskAssignments.get(workerId) || null; - - if (assignment === null) { - throw new Error( - `Expected worker #${workerId} to have a task assigned, while handling message: ` + - JSON.stringify(msg)); - } - - const entryPoint = assignment.task.entryPoint; - const expectedPackageJsonPath = this.fileSystem.resolve(entryPoint.path, 'package.json'); - - if (expectedPackageJsonPath !== msg.packageJsonPath) { - throw new Error( - `Received '${msg.type}' message from worker #${workerId} for '${msg.packageJsonPath}', ` + - `but was expecting '${expectedPackageJsonPath}' (based on task assignment).`); - } - - // NOTE: Although the change in the parsed `package.json` will be reflected in tasks objects - // locally and thus also in future `process-task` messages sent to worker processes, any - // processes already running and processing a task for the same entry-point will not get - // the change. - // Do not rely on having an up-to-date `package.json` representation in worker processes. - // In other words, task processing should only rely on the info that was there when the - // file was initially parsed (during entry-point analysis) and not on the info that might - // be added later (during task processing). - this.pkgJsonUpdater.writeChanges(msg.changes, msg.packageJsonPath, entryPoint.packageJson); - } - - /** Stop all workers and stop listening on cluster events. */ - private stopWorkers(): void { - if (cluster.workers) { - const workers = Object.values(cluster.workers) as Worker[]; - this.logger.debug(`Stopping ${workers.length} workers...`); - - cluster.removeAllListeners(); - workers.forEach(worker => worker.kill()); - } - } - - /** - * Wrap an event handler to ensure that `finishedDeferred` will be rejected on error (regardless - * if the handler completes synchronously or asynchronously). - */ - private wrapEventHandler(fn: (...args: Args) => void|Promise): - (...args: Args) => Promise { - return async (...args: Args) => { - try { - await fn(...args); - } catch (err) { - this.finishedDeferred.reject(err); - } - }; - } -} - -/** Wrapper for resolving the cluster worker script. Useful for test patching. */ -export class ClusterWorkerScriptResolver { - static resolve(fileSystem: PathManipulation): AbsoluteFsPath { - // NodeJS `import.meta.resolve` is experimental. We leverage `require`. - const requireFn = module.createRequire(import.meta.url); - // We resolve the worker script using module resolution as in the package output, - // the worker might be bundled but exposed through a subpath export mapping. - const workerScriptPath = - requireFn.resolve('@angular/compiler-cli/ngcc/src/execution/cluster/ngcc_cluster_worker'); - return fileSystem.resolve(workerScriptPath); - } -} diff --git a/packages/compiler-cli/ngcc/src/execution/cluster/ngcc_cluster_worker.ts b/packages/compiler-cli/ngcc/src/execution/cluster/ngcc_cluster_worker.ts deleted file mode 100644 index 2714ec6e88e..00000000000 --- a/packages/compiler-cli/ngcc/src/execution/cluster/ngcc_cluster_worker.ts +++ /dev/null @@ -1,46 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ - -import {parseCommandLineOptions} from '../../command_line_options'; -import {getSharedSetup} from '../../ngcc_options'; -import {getCreateCompileFn} from '../create_compile_function'; - -import {ClusterWorkerPackageJsonUpdater} from './package_json_updater'; -import {startWorker} from './worker'; - -// Cluster worker entry point -(async () => { - process.title = 'ngcc (worker)'; - - try { - const { - logger, - pathMappings, - enableI18nLegacyMessageIdFormat, - fileSystem, - tsConfig, - getFileWriter, - } = getSharedSetup(parseCommandLineOptions(process.argv.slice(2))); - - // NOTE: To avoid file corruption, `ngcc` invocation only creates _one_ instance of - // `PackageJsonUpdater` that actually writes to disk (across all processes). - // In cluster workers we use a `PackageJsonUpdater` that delegates to the cluster master. - const pkgJsonUpdater = new ClusterWorkerPackageJsonUpdater(); - const fileWriter = getFileWriter(pkgJsonUpdater); - - // The function for creating the `compile()` function. - const createCompileFn = getCreateCompileFn( - fileSystem, logger, fileWriter, enableI18nLegacyMessageIdFormat, tsConfig, pathMappings); - - await startWorker(logger, createCompileFn); - process.exitCode = 0; - } catch (e) { - console.error((e as Error).stack || (e as Error).message); - process.exit(1); - } -})(); diff --git a/packages/compiler-cli/ngcc/src/execution/cluster/package_json_updater.ts b/packages/compiler-cli/ngcc/src/execution/cluster/package_json_updater.ts deleted file mode 100644 index 625c9658d87..00000000000 --- a/packages/compiler-cli/ngcc/src/execution/cluster/package_json_updater.ts +++ /dev/null @@ -1,58 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ - -/// - -import cluster from 'cluster'; - -import {AbsoluteFsPath} from '../../../../src/ngtsc/file_system'; -import {JsonObject} from '../../utils'; -import {applyChange, PackageJsonChange, PackageJsonUpdate, PackageJsonUpdater} from '../../writing/package_json_updater'; - -import {sendMessageToMaster} from './utils'; - - -/** - * A `PackageJsonUpdater` for cluster workers that will send update changes to the master process so - * that it can safely handle update operations on multiple processes. - */ -export class ClusterWorkerPackageJsonUpdater implements PackageJsonUpdater { - constructor() { - if (cluster.isMaster) { - throw new Error('Tried to create cluster worker PackageJsonUpdater on the master process.'); - } - } - - createUpdate(): PackageJsonUpdate { - return new PackageJsonUpdate((...args) => this.writeChanges(...args)); - } - - /** - * Apply the changes in-memory (if necessary) and send a message to the master process. - */ - writeChanges( - changes: PackageJsonChange[], packageJsonPath: AbsoluteFsPath, - preExistingParsedJson?: JsonObject): void { - if (preExistingParsedJson) { - for (const [propPath, value] of changes) { - if (propPath.length === 0) { - throw new Error(`Missing property path for writing value to '${packageJsonPath}'.`); - } - - // No need to take property positioning into account for in-memory representations. - applyChange(preExistingParsedJson, propPath, value, 'unimportant'); - } - } - - sendMessageToMaster({ - type: 'update-package-json', - packageJsonPath, - changes, - }); - } -} diff --git a/packages/compiler-cli/ngcc/src/execution/cluster/utils.ts b/packages/compiler-cli/ngcc/src/execution/cluster/utils.ts deleted file mode 100644 index c9b4acb4212..00000000000 --- a/packages/compiler-cli/ngcc/src/execution/cluster/utils.ts +++ /dev/null @@ -1,87 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ - -/// - -import cluster from 'cluster'; - -import {MessageFromWorker, MessageToWorker} from './api'; - - - -/** Expose a `Promise` instance as well as APIs for resolving/rejecting it. */ -export class Deferred { - /** - * Resolve the associated promise with the specified value. - * If the value is a rejection (constructed with `Promise.reject()`), the promise will be rejected - * instead. - * - * @param value The value to resolve the promise with. - */ - resolve!: (value: T) => void; - - /** - * Rejects the associated promise with the specified reason. - * - * @param reason The rejection reason. - */ - reject!: (reason: any) => void; - - /** The `Promise` instance associated with this deferred. */ - promise = new Promise((resolve, reject) => { - this.resolve = resolve; - this.reject = reject; - }); -} - -/** - * Send a message to the cluster master. - * (This function should be invoked from cluster workers only.) - * - * @param msg The message to send to the cluster master. - * @return A promise that is resolved once the message has been sent. - */ -export const sendMessageToMaster = (msg: MessageFromWorker): Promise => { - if (cluster.isMaster) { - throw new Error('Unable to send message to the master process: Already on the master process.'); - } - - return new Promise((resolve, reject) => { - if (process.send === undefined) { - // Theoretically, this should never happen on a worker process. - throw new Error('Unable to send message to the master process: Missing `process.send()`.'); - } - - process.send(msg, (err: Error|null) => (err === null) ? resolve() : reject(err)); - }); -}; - -/** - * Send a message to a cluster worker. - * (This function should be invoked from the cluster master only.) - * - * @param workerId The ID of the recipient worker. - * @param msg The message to send to the worker. - * @return A promise that is resolved once the message has been sent. - */ -export const sendMessageToWorker = (workerId: number, msg: MessageToWorker): Promise => { - if (!cluster.isMaster) { - throw new Error('Unable to send message to worker process: Sender is not the master process.'); - } - - const worker = cluster.workers?.[workerId]; - - if (worker === undefined || worker.isDead() || !worker.isConnected()) { - throw new Error( - 'Unable to send message to worker process: Recipient does not exist or has disconnected.'); - } - - return new Promise((resolve, reject) => { - worker.send(msg, (err: Error|null) => (err === null) ? resolve() : reject(err)); - }); -}; diff --git a/packages/compiler-cli/ngcc/src/execution/cluster/worker.ts b/packages/compiler-cli/ngcc/src/execution/cluster/worker.ts deleted file mode 100644 index 30e7d08ac17..00000000000 --- a/packages/compiler-cli/ngcc/src/execution/cluster/worker.ts +++ /dev/null @@ -1,68 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -/// - -import cluster from 'cluster'; - -import {Logger} from '../../../../src/ngtsc/logging'; -import {CreateCompileFn} from '../api'; -import {stringifyTask} from '../tasks/utils'; - -import {MessageToWorker} from './api'; -import {sendMessageToMaster} from './utils'; - -export async function startWorker(logger: Logger, createCompileFn: CreateCompileFn): Promise { - if (cluster.isMaster || !cluster.worker) { - throw new Error('Tried to run cluster worker on the master process.'); - } - - const worker = cluster.worker; - const compile = createCompileFn( - transformedFiles => sendMessageToMaster({ - type: 'transformed-files', - files: transformedFiles.map(f => f.path), - }), - (_task, outcome, message) => sendMessageToMaster({type: 'task-completed', outcome, message})); - - // Listen for `ProcessTaskMessage`s and process tasks. - worker.on('message', async (msg: MessageToWorker) => { - try { - switch (msg.type) { - case 'process-task': - logger.debug(`[Worker #${worker.id}] Processing task: ${stringifyTask(msg.task)}`); - return await compile(msg.task); - default: - throw new Error( - `[Worker #${worker.id}] Invalid message received: ${JSON.stringify(msg)}`); - } - } catch (err: any) { - switch (err && err.code) { - case 'ENOMEM': - // Not being able to allocate enough memory is not necessarily a problem with processing - // the current task. It could just mean that there are too many tasks being processed - // simultaneously. - // - // Exit with an error and let the cluster master decide how to handle this. - logger.warn(`[Worker #${worker.id}] ${err.stack || err.message}`); - return process.exit(1); - default: - await sendMessageToMaster({ - type: 'error', - error: (err instanceof Error) ? (err.stack || err.message) : err, - }); - } - } - }); - - // Notify the master that the worker is now ready and can receive messages. - await sendMessageToMaster({type: 'ready'}); - - - // Return a promise that is never resolved. - return new Promise(() => undefined); -} diff --git a/packages/compiler-cli/ngcc/src/execution/create_compile_function.ts b/packages/compiler-cli/ngcc/src/execution/create_compile_function.ts deleted file mode 100644 index cbe2162b5b4..00000000000 --- a/packages/compiler-cli/ngcc/src/execution/create_compile_function.ts +++ /dev/null @@ -1,92 +0,0 @@ - -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import ts from 'typescript'; - -import {replaceTsWithNgInErrors} from '../../../src/ngtsc/diagnostics'; -import {FileSystem} from '../../../src/ngtsc/file_system'; -import {Logger} from '../../../src/ngtsc/logging'; -import {ParsedConfiguration} from '../../../src/perform_compile'; -import {getEntryPointFormat} from '../packages/entry_point'; -import {makeEntryPointBundle} from '../packages/entry_point_bundle'; -import {createModuleResolutionCache, SharedFileCache} from '../packages/source_file_cache'; -import {Transformer} from '../packages/transformer'; -import {PathMappings} from '../path_mappings'; -import {FileWriter} from '../writing/file_writer'; - -import {CreateCompileFn} from './api'; -import {Task, TaskProcessingOutcome} from './tasks/api'; - -/** - * The function for creating the `compile()` function. - */ -export function getCreateCompileFn( - fileSystem: FileSystem, logger: Logger, fileWriter: FileWriter, - enableI18nLegacyMessageIdFormat: boolean, tsConfig: ParsedConfiguration|null, - pathMappings: PathMappings|undefined): CreateCompileFn { - return (beforeWritingFiles, onTaskCompleted) => { - const transformer = new Transformer(fileSystem, logger, tsConfig); - const sharedFileCache = new SharedFileCache(fileSystem); - const moduleResolutionCache = createModuleResolutionCache(fileSystem); - - return (task: Task) => { - const {entryPoint, formatProperty, formatPropertiesToMarkAsProcessed, processDts} = task; - - const isCore = entryPoint.name === '@angular/core'; // Are we compiling the Angular core? - const packageJson = entryPoint.packageJson; - const formatPath = packageJson[formatProperty]; - const format = getEntryPointFormat(fileSystem, entryPoint, formatProperty); - - // All properties listed in `propertiesToProcess` are guaranteed to point to a format-path - // (i.e. they are defined in `entryPoint.packageJson`). Furthermore, they are also guaranteed - // to be among `SUPPORTED_FORMAT_PROPERTIES`. - // Based on the above, `formatPath` should always be defined and `getEntryPointFormat()` - // should always return a format here (and not `undefined`) unless `formatPath` points to a - // missing or empty file. - if (!formatPath || !format) { - onTaskCompleted( - task, TaskProcessingOutcome.Failed, - `property \`${formatProperty}\` pointing to a missing or empty file: ${formatPath}`); - return; - } - - logger.info( - `- ${entryPoint.name} [${formatProperty}/${format}] (${entryPoint.repositoryUrl})`); - - const bundle = makeEntryPointBundle( - fileSystem, entryPoint, sharedFileCache, moduleResolutionCache, formatPath, isCore, - format, processDts, pathMappings, true, enableI18nLegacyMessageIdFormat); - - const result = transformer.transform(bundle); - if (result.success) { - if (result.diagnostics.length > 0) { - logger.warn(replaceTsWithNgInErrors( - ts.formatDiagnosticsWithColorAndContext(result.diagnostics, bundle.src.host))); - } - - const writeBundle = () => { - fileWriter.writeBundle( - bundle, result.transformedFiles, formatPropertiesToMarkAsProcessed); - - logger.debug(` Successfully compiled ${entryPoint.name} : ${formatProperty}`); - onTaskCompleted(task, TaskProcessingOutcome.Processed, null); - }; - - const beforeWritingResult = beforeWritingFiles(result.transformedFiles); - - return (beforeWritingResult instanceof Promise) ? - beforeWritingResult.then(writeBundle) as ReturnType: - writeBundle(); - } else { - const errors = replaceTsWithNgInErrors( - ts.formatDiagnosticsWithColorAndContext(result.diagnostics, bundle.src.host)); - onTaskCompleted(task, TaskProcessingOutcome.Failed, `compilation errors:\n${errors}`); - } - }; - }; -} diff --git a/packages/compiler-cli/ngcc/src/execution/single_process_executor.ts b/packages/compiler-cli/ngcc/src/execution/single_process_executor.ts deleted file mode 100644 index 4f41ed3c97e..00000000000 --- a/packages/compiler-cli/ngcc/src/execution/single_process_executor.ts +++ /dev/null @@ -1,70 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ - -import {Logger} from '../../../src/ngtsc/logging'; -import {AsyncLocker} from '../locking/async_locker'; -import {SyncLocker} from '../locking/sync_locker'; - -import {AnalyzeEntryPointsFn, CreateCompileFn, Executor} from './api'; -import {CreateTaskCompletedCallback} from './tasks/api'; - -export abstract class SingleProcessorExecutorBase { - constructor( - private logger: Logger, private createTaskCompletedCallback: CreateTaskCompletedCallback) {} - - doExecute(analyzeEntryPoints: AnalyzeEntryPointsFn, createCompileFn: CreateCompileFn): - void|Promise { - this.logger.debug(`Running ngcc on ${this.constructor.name}.`); - - const taskQueue = analyzeEntryPoints(); - const onTaskCompleted = this.createTaskCompletedCallback(taskQueue); - const compile = createCompileFn(() => {}, onTaskCompleted); - - // Process all tasks. - this.logger.debug('Processing tasks...'); - const startTime = Date.now(); - - while (!taskQueue.allTasksCompleted) { - const task = taskQueue.getNextTask()!; - compile(task); - taskQueue.markAsCompleted(task); - } - - const duration = Math.round((Date.now() - startTime) / 1000); - this.logger.debug(`Processed tasks in ${duration}s.`); - } -} - -/** - * An `Executor` that processes all tasks serially and completes synchronously. - */ -export class SingleProcessExecutorSync extends SingleProcessorExecutorBase implements Executor { - constructor( - logger: Logger, private lockFile: SyncLocker, - createTaskCompletedCallback: CreateTaskCompletedCallback) { - super(logger, createTaskCompletedCallback); - } - execute(analyzeEntryPoints: AnalyzeEntryPointsFn, createCompileFn: CreateCompileFn): void { - this.lockFile.lock(() => this.doExecute(analyzeEntryPoints, createCompileFn)); - } -} - -/** - * An `Executor` that processes all tasks serially, but still completes asynchronously. - */ -export class SingleProcessExecutorAsync extends SingleProcessorExecutorBase implements Executor { - constructor( - logger: Logger, private lockFile: AsyncLocker, - createTaskCompletedCallback: CreateTaskCompletedCallback) { - super(logger, createTaskCompletedCallback); - } - async execute(analyzeEntryPoints: AnalyzeEntryPointsFn, createCompileFn: CreateCompileFn): - Promise { - await this.lockFile.lock(async () => this.doExecute(analyzeEntryPoints, createCompileFn)); - } -} diff --git a/packages/compiler-cli/ngcc/src/execution/tasks/api.ts b/packages/compiler-cli/ngcc/src/execution/tasks/api.ts deleted file mode 100644 index e5b3dad5c30..00000000000 --- a/packages/compiler-cli/ngcc/src/execution/tasks/api.ts +++ /dev/null @@ -1,158 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import {EntryPoint, EntryPointJsonProperty} from '../../packages/entry_point'; -import {JsonObject, PartiallyOrderedList} from '../../utils'; - -/** - * Represents a unit of work to be undertaken by an `Executor`. - * - * A task consists of processing a specific format property of an entry-point. - * This may or may not also include processing the typings for that entry-point, which only needs to - * happen once across all the formats. - */ -export interface Task extends JsonObject { - /** The `EntryPoint` which needs to be processed as part of the task. */ - entryPoint: EntryPoint; - - /** - * The `package.json` format property to process (i.e. the property which points to the file that - * is the program entry-point). - */ - formatProperty: EntryPointJsonProperty; - - /** - * The list of all format properties (including `task.formatProperty`) that should be marked as - * processed once the task has been completed, because they point to the format-path that will be - * processed as part of the task. - */ - formatPropertiesToMarkAsProcessed: EntryPointJsonProperty[]; - - /** - * Whether to process typings for this entry-point as part of the task. - */ - processDts: DtsProcessing; -} - -/** - * The options for processing Typescript typings (.d.ts) files. - */ -export enum DtsProcessing { - /** - * Yes, process the typings for this entry point as part of the task. - */ - Yes, - /** - * No, do not process the typings as part of this task - they must have already been processed by - * another task or previous ngcc process. - */ - No, - /** - * Only process the typings for this entry-point; do not render any JavaScript files for the - * `formatProperty` of this task. - */ - Only, -} - -/** - * Represents a partially ordered list of tasks. - * - * The ordering/precedence of tasks is determined by the inter-dependencies between their associated - * entry-points. Specifically, the tasks' order/precedence is such that tasks associated to - * dependent entry-points always come after tasks associated with their dependencies. - * - * As result of this ordering, it is guaranteed that - by processing tasks in the order in which - * they appear in the list - a task's dependencies will always have been processed before processing - * the task itself. - * - * See `DependencyResolver#sortEntryPointsByDependency()`. - */ -export type PartiallyOrderedTasks = PartiallyOrderedList; - -/** - * A mapping from Tasks to the Tasks that depend upon them (dependents). - */ -export type TaskDependencies = Map>; -export const TaskDependencies = Map; - -/** - * A function to create a TaskCompletedCallback function. - */ -export type CreateTaskCompletedCallback = (taskQueue: TaskQueue) => TaskCompletedCallback; - -/** - * A function to be called once a task has been processed. - */ -export type TaskCompletedCallback = - (task: Task, outcome: TaskProcessingOutcome, message: string|null) => void; - -/** - * Represents the outcome of processing a `Task`. - */ -export const enum TaskProcessingOutcome { - /** Successfully processed the target format property. */ - Processed, - /** Failed to process the target format. */ - Failed, -} - -/** - * A wrapper around a list of tasks and providing utility methods for getting the next task of - * interest and determining when all tasks have been completed. - * - * (This allows different implementations to impose different constraints on when a task's - * processing can start.) - */ -export interface TaskQueue { - /** Whether all tasks have been completed. */ - allTasksCompleted: boolean; - - /** - * Get the next task whose processing can start (if any). - * - * This implicitly marks the task as in-progress. - * (This information is used to determine whether all tasks have been completed.) - * - * @return The next task available for processing or `null`, if no task can be processed at the - * moment (including if there are no more unprocessed tasks). - */ - getNextTask(): Task|null; - - /** - * Mark a task as completed. - * - * This removes the task from the internal list of in-progress tasks. - * (This information is used to determine whether all tasks have been completed.) - * - * @param task The task to mark as completed. - */ - markAsCompleted(task: Task): void; - - /** - * Mark a task as failed. - * - * Do not process the tasks that depend upon the given task. - */ - markAsFailed(task: Task): void; - - /** - * Mark a task as not processed (i.e. add an in-progress task back to the queue). - * - * This removes the task from the internal list of in-progress tasks and adds it back to the list - * of pending tasks. - * - * @param task The task to mark as not processed. - */ - markAsUnprocessed(task: Task): void; - - /** - * Return a string representation of the task queue (for debugging purposes). - * - * @return A string representation of the task queue. - */ - toString(): string; -} diff --git a/packages/compiler-cli/ngcc/src/execution/tasks/completion.ts b/packages/compiler-cli/ngcc/src/execution/tasks/completion.ts deleted file mode 100644 index 0fa0081084d..00000000000 --- a/packages/compiler-cli/ngcc/src/execution/tasks/completion.ts +++ /dev/null @@ -1,90 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import {PathManipulation, ReadonlyFileSystem} from '../../../../src/ngtsc/file_system'; -import {Logger} from '../../../../src/ngtsc/logging'; -import {markAsProcessed} from '../../packages/build_marker'; -import {getEntryPointFormat, PackageJsonFormatProperties} from '../../packages/entry_point'; -import {PackageJsonUpdater} from '../../writing/package_json_updater'; - -import {DtsProcessing, Task, TaskCompletedCallback, TaskProcessingOutcome, TaskQueue} from './api'; - -/** - * A function that can handle a specific outcome of a task completion. - * - * These functions can be composed using the `composeTaskCompletedCallbacks()` - * to create a `TaskCompletedCallback` function that can be passed to an `Executor`. - */ -export type TaskCompletedHandler = (task: Task, message: string|null) => void; - -/** - * Compose a group of TaskCompletedHandlers into a single TaskCompletedCallback. - * - * The compose callback will receive an outcome and will delegate to the appropriate handler based - * on this outcome. - * - * @param callbacks a map of outcomes to handlers. - */ -export function composeTaskCompletedCallbacks( - callbacks: Record): TaskCompletedCallback { - return (task: Task, outcome: TaskProcessingOutcome, message: string|null): void => { - const callback = callbacks[outcome]; - if (callback === undefined) { - throw new Error(`Unknown task outcome: "${outcome}" - supported outcomes: ${ - JSON.stringify(Object.keys(callbacks))}`); - } - callback(task, message); - }; -} - -/** - * Create a handler that will mark the entry-points in a package as being processed. - * - * @param pkgJsonUpdater The service used to update the package.json - */ -export function createMarkAsProcessedHandler( - fs: PathManipulation, pkgJsonUpdater: PackageJsonUpdater): TaskCompletedHandler { - return (task: Task): void => { - const {entryPoint, formatPropertiesToMarkAsProcessed, processDts} = task; - const packageJsonPath = fs.resolve(entryPoint.path, 'package.json'); - const propsToMarkAsProcessed: PackageJsonFormatProperties[] = - [...formatPropertiesToMarkAsProcessed]; - if (processDts !== DtsProcessing.No) { - propsToMarkAsProcessed.push('typings'); - } - markAsProcessed( - pkgJsonUpdater, entryPoint.packageJson, packageJsonPath, propsToMarkAsProcessed); - }; -} - -/** - * Create a handler that will throw an error. - */ -export function createThrowErrorHandler(fs: ReadonlyFileSystem): TaskCompletedHandler { - return (task: Task, message: string|null): void => { - throw new Error(createErrorMessage(fs, task, message)); - }; -} - -/** - * Create a handler that logs an error and marks the task as failed. - */ -export function createLogErrorHandler( - logger: Logger, fs: ReadonlyFileSystem, taskQueue: TaskQueue): TaskCompletedHandler { - return (task: Task, message: string|null): void => { - taskQueue.markAsFailed(task); - logger.error(createErrorMessage(fs, task, message)); - }; -} - -function createErrorMessage(fs: ReadonlyFileSystem, task: Task, message: string|null): string { - const jsFormat = `\`${task.formatProperty}\` as ${ - getEntryPointFormat(fs, task.entryPoint, task.formatProperty) ?? 'unknown format'}`; - const format = task.typingsOnly ? `typings only using ${jsFormat}` : jsFormat; - message = message !== null ? ` due to ${message}` : ''; - return `Failed to compile entry-point ${task.entryPoint.name} (${format})` + message; -} diff --git a/packages/compiler-cli/ngcc/src/execution/tasks/queues/base_task_queue.ts b/packages/compiler-cli/ngcc/src/execution/tasks/queues/base_task_queue.ts deleted file mode 100644 index e142aa021d9..00000000000 --- a/packages/compiler-cli/ngcc/src/execution/tasks/queues/base_task_queue.ts +++ /dev/null @@ -1,103 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import {Logger} from '../../../../../src/ngtsc/logging'; -import {PartiallyOrderedTasks, Task, TaskDependencies, TaskQueue} from '../api'; -import {stringifyTask} from '../utils'; - - -/** - * A base `TaskQueue` implementation to be used as base for concrete implementations. - */ -export abstract class BaseTaskQueue implements TaskQueue { - get allTasksCompleted(): boolean { - return (this.tasks.length === 0) && (this.inProgressTasks.size === 0); - } - protected inProgressTasks = new Set(); - - /** - * A map of tasks that should be skipped, mapped to the task that caused them to be skipped. - */ - private tasksToSkip = new Map(); - - constructor( - protected logger: Logger, protected tasks: PartiallyOrderedTasks, - protected dependencies: TaskDependencies) {} - - protected abstract computeNextTask(): Task|null; - - getNextTask(): Task|null { - let nextTask = this.computeNextTask(); - while (nextTask !== null) { - if (!this.tasksToSkip.has(nextTask)) { - break; - } - // We are skipping this task so mark it as complete - this.markAsCompleted(nextTask); - const failedTask = this.tasksToSkip.get(nextTask)!; - this.logger.warn(`Skipping processing of ${nextTask.entryPoint.name} because its dependency ${ - failedTask.entryPoint.name} failed to compile.`); - nextTask = this.computeNextTask(); - } - return nextTask; - } - - markAsCompleted(task: Task): void { - if (!this.inProgressTasks.has(task)) { - throw new Error( - `Trying to mark task that was not in progress as completed: ${stringifyTask(task)}`); - } - - this.inProgressTasks.delete(task); - } - - markAsFailed(task: Task): void { - if (this.dependencies.has(task)) { - for (const dependentTask of this.dependencies.get(task)!) { - this.skipDependentTasks(dependentTask, task); - } - } - } - - markAsUnprocessed(task: Task): void { - if (!this.inProgressTasks.has(task)) { - throw new Error( - `Trying to mark task that was not in progress as unprocessed: ${stringifyTask(task)}`); - } - - this.inProgressTasks.delete(task); - this.tasks.unshift(task); - } - - toString(): string { - const inProgTasks = Array.from(this.inProgressTasks); - - return `${this.constructor.name}\n` + - ` All tasks completed: ${this.allTasksCompleted}\n` + - ` Unprocessed tasks (${this.tasks.length}): ${this.stringifyTasks(this.tasks, ' ')}\n` + - ` In-progress tasks (${inProgTasks.length}): ${this.stringifyTasks(inProgTasks, ' ')}`; - } - - /** - * Mark the given `task` as to be skipped, then recursive skip all its dependents. - * - * @param task The task to skip - * @param failedTask The task that failed, causing this task to be skipped - */ - protected skipDependentTasks(task: Task, failedTask: Task) { - this.tasksToSkip.set(task, failedTask); - if (this.dependencies.has(task)) { - for (const dependentTask of this.dependencies.get(task)!) { - this.skipDependentTasks(dependentTask, failedTask); - } - } - } - - protected stringifyTasks(tasks: Task[], indentation: string): string { - return tasks.map(task => `\n${indentation}- ${stringifyTask(task)}`).join(''); - } -} diff --git a/packages/compiler-cli/ngcc/src/execution/tasks/queues/parallel_task_queue.ts b/packages/compiler-cli/ngcc/src/execution/tasks/queues/parallel_task_queue.ts deleted file mode 100644 index d6bfa10d86a..00000000000 --- a/packages/compiler-cli/ngcc/src/execution/tasks/queues/parallel_task_queue.ts +++ /dev/null @@ -1,78 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import {Logger} from '../../../../../src/ngtsc/logging'; -import {PartiallyOrderedTasks, Task, TaskDependencies} from '../api'; -import {getBlockedTasks, sortTasksByPriority, stringifyTask} from '../utils'; -import {BaseTaskQueue} from './base_task_queue'; - -/** - * A `TaskQueue` implementation that assumes tasks are processed in parallel, thus has to ensure a - * task's dependencies have been processed before processing the task. - */ -export class ParallelTaskQueue extends BaseTaskQueue { - /** - * A map from Tasks to the Tasks that it depends upon. - * - * This is the reverse mapping of `TaskDependencies`. - */ - private blockedTasks: Map>; - - constructor(logger: Logger, tasks: PartiallyOrderedTasks, dependencies: TaskDependencies) { - super(logger, sortTasksByPriority(tasks, dependencies), dependencies); - this.blockedTasks = getBlockedTasks(dependencies); - } - - override computeNextTask(): Task|null { - // Look for the first available (i.e. not blocked) task. - // (NOTE: Since tasks are sorted by priority, the first available one is the best choice.) - const nextTaskIdx = this.tasks.findIndex(task => !this.blockedTasks.has(task)); - if (nextTaskIdx === -1) return null; - - // Remove the task from the list of available tasks and add it to the list of in-progress tasks. - const nextTask = this.tasks[nextTaskIdx]; - this.tasks.splice(nextTaskIdx, 1); - this.inProgressTasks.add(nextTask); - - return nextTask; - } - - override markAsCompleted(task: Task): void { - super.markAsCompleted(task); - - if (!this.dependencies.has(task)) { - return; - } - - // Unblock the tasks that are dependent upon `task` - for (const dependentTask of this.dependencies.get(task)!) { - if (this.blockedTasks.has(dependentTask)) { - const blockingTasks = this.blockedTasks.get(dependentTask)!; - // Remove the completed task from the lists of tasks blocking other tasks. - blockingTasks.delete(task); - if (blockingTasks.size === 0) { - // If the dependent task is not blocked any more, mark it for unblocking. - this.blockedTasks.delete(dependentTask); - } - } - } - } - - override toString(): string { - return `${super.toString()}\n` + - ` Blocked tasks (${this.blockedTasks.size}): ${this.stringifyBlockedTasks(' ')}`; - } - - private stringifyBlockedTasks(indentation: string): string { - return Array.from(this.blockedTasks) - .map( - ([task, blockingTasks]) => - `\n${indentation}- ${stringifyTask(task)} (${blockingTasks.size}): ` + - this.stringifyTasks(Array.from(blockingTasks), `${indentation} `)) - .join(''); - } -} diff --git a/packages/compiler-cli/ngcc/src/execution/tasks/queues/serial_task_queue.ts b/packages/compiler-cli/ngcc/src/execution/tasks/queues/serial_task_queue.ts deleted file mode 100644 index b0515c84924..00000000000 --- a/packages/compiler-cli/ngcc/src/execution/tasks/queues/serial_task_queue.ts +++ /dev/null @@ -1,37 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ - -import {Task} from '../api'; -import {stringifyTask} from '../utils'; - -import {BaseTaskQueue} from './base_task_queue'; - - -/** - * A `TaskQueue` implementation that assumes tasks are processed serially and each one is completed - * before requesting the next one. - */ -export class SerialTaskQueue extends BaseTaskQueue { - override computeNextTask(): Task|null { - const nextTask = this.tasks.shift() || null; - - if (nextTask) { - if (this.inProgressTasks.size > 0) { - // `SerialTaskQueue` can have max one in-progress task. - const inProgressTask = this.inProgressTasks.values().next().value; - throw new Error( - 'Trying to get next task, while there is already a task in progress: ' + - stringifyTask(inProgressTask)); - } - - this.inProgressTasks.add(nextTask); - } - - return nextTask; - } -} diff --git a/packages/compiler-cli/ngcc/src/execution/tasks/utils.ts b/packages/compiler-cli/ngcc/src/execution/tasks/utils.ts deleted file mode 100644 index 3ea7a8a7ea1..00000000000 --- a/packages/compiler-cli/ngcc/src/execution/tasks/utils.ts +++ /dev/null @@ -1,139 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import {DepGraph} from 'dependency-graph'; -import {EntryPoint} from '../../packages/entry_point'; -import {DtsProcessing, PartiallyOrderedTasks, Task, TaskDependencies} from './api'; - -/** Stringify a task for debugging purposes. */ -export const stringifyTask = (task: Task): string => - `{entryPoint: ${task.entryPoint.name}, formatProperty: ${task.formatProperty}, ` + - `processDts: ${DtsProcessing[task.processDts]}}`; - -/** - * Compute a mapping of tasks to the tasks that are dependent on them (if any). - * - * Task A can depend upon task B, if either: - * - * * A and B have the same entry-point _and_ B is generating the typings for that entry-point - * (i.e. has `processDts: true`). - * * A's entry-point depends on B's entry-point _and_ B is also generating typings. - * - * NOTE: If a task is not generating typings, then it cannot affect anything which depends on its - * entry-point, regardless of the dependency graph. To put this another way, only the task - * which produces the typings for a dependency needs to have been completed. - * - * As a performance optimization, we take into account the fact that `tasks` are sorted in such a - * way that a task can only depend on earlier tasks (i.e. dependencies always come before - * dependents in the list of tasks). - * - * @param tasks A (partially ordered) list of tasks. - * @param graph The dependency graph between entry-points. - * @return A map from each task to those tasks directly dependent upon it. - */ -export function computeTaskDependencies( - tasks: PartiallyOrderedTasks, graph: DepGraph): TaskDependencies { - const dependencies = new TaskDependencies(); - const candidateDependencies = new Map(); - - tasks.forEach(task => { - const entryPointPath = task.entryPoint.path; - - // Find the earlier tasks (`candidateDependencies`) that this task depends upon. - const deps = graph.dependenciesOf(entryPointPath); - const taskDependencies = deps.filter(dep => candidateDependencies.has(dep)) - .map(dep => candidateDependencies.get(dep)!); - - // If this task has dependencies, add it to the dependencies and dependents maps. - if (taskDependencies.length > 0) { - for (const dependency of taskDependencies) { - const taskDependents = getDependentsSet(dependencies, dependency); - taskDependents.add(task); - } - } - - if (task.processDts !== DtsProcessing.No) { - // SANITY CHECK: - // There should only be one task per entry-point that generates typings (and thus can be a - // dependency of other tasks), so the following should theoretically never happen, but check - // just in case. - if (candidateDependencies.has(entryPointPath)) { - const otherTask = candidateDependencies.get(entryPointPath)!; - throw new Error( - 'Invariant violated: Multiple tasks are assigned generating typings for ' + - `'${entryPointPath}':\n - ${stringifyTask(otherTask)}\n - ${stringifyTask(task)}`); - } - // This task can potentially be a dependency (i.e. it generates typings), so add it to the - // list of candidate dependencies for subsequent tasks. - candidateDependencies.set(entryPointPath, task); - } else { - // This task is not generating typings so we need to add it to the dependents of the task that - // does generate typings, if that exists - if (candidateDependencies.has(entryPointPath)) { - const typingsTask = candidateDependencies.get(entryPointPath)!; - const typingsTaskDependents = getDependentsSet(dependencies, typingsTask); - typingsTaskDependents.add(task); - } - } - }); - - return dependencies; -} - -export function getDependentsSet(map: TaskDependencies, task: Task): Set { - if (!map.has(task)) { - map.set(task, new Set()); - } - return map.get(task)!; -} - -/** - * Invert the given mapping of Task dependencies. - * - * @param dependencies The mapping of tasks to the tasks that depend upon them. - * @returns A mapping of tasks to the tasks that they depend upon. - */ -export function getBlockedTasks(dependencies: TaskDependencies): Map> { - const blockedTasks = new Map>(); - for (const [dependency, dependents] of dependencies) { - for (const dependent of dependents) { - const dependentSet = getDependentsSet(blockedTasks, dependent); - dependentSet.add(dependency); - } - } - return blockedTasks; -} - -/** - * Sort a list of tasks by priority. - * - * Priority is determined by the number of other tasks that a task is (transitively) blocking: - * The more tasks a task is blocking the higher its priority is, because processing it will - * potentially unblock more tasks. - * - * To keep the behavior predictable, if two tasks block the same number of other tasks, their - * relative order in the original `tasks` lists is preserved. - * - * @param tasks A (partially ordered) list of tasks. - * @param dependencies The mapping of tasks to the tasks that depend upon them. - * @return The list of tasks sorted by priority. - */ -export function sortTasksByPriority( - tasks: PartiallyOrderedTasks, dependencies: TaskDependencies): PartiallyOrderedTasks { - const priorityPerTask = new Map(); - const computePriority = (task: Task, idx: number): - [number, number] => [dependencies.has(task) ? dependencies.get(task)!.size : 0, idx]; - - tasks.forEach((task, i) => priorityPerTask.set(task, computePriority(task, i))); - - return tasks.slice().sort((task1, task2) => { - const [p1, idx1] = priorityPerTask.get(task1)!; - const [p2, idx2] = priorityPerTask.get(task2)!; - - return (p2 - p1) || (idx1 - idx2); - }); -} diff --git a/packages/compiler-cli/ngcc/src/host/commonjs_host.ts b/packages/compiler-cli/ngcc/src/host/commonjs_host.ts deleted file mode 100644 index 18a34459f4b..00000000000 --- a/packages/compiler-cli/ngcc/src/host/commonjs_host.ts +++ /dev/null @@ -1,256 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ - -import ts from 'typescript'; - -import {absoluteFrom} from '../../../src/ngtsc/file_system'; -import {Logger} from '../../../src/ngtsc/logging'; -import {Declaration, DeclarationKind, Import} from '../../../src/ngtsc/reflection'; -import {BundleProgram} from '../packages/bundle_program'; -import {FactoryMap, isDefined} from '../utils'; - -import {DefinePropertyReexportStatement, ExportDeclaration, ExportsStatement, extractGetterFnExpression, findNamespaceOfIdentifier, findRequireCallReference, isDefinePropertyReexportStatement, isExportsAssignment, isExportsStatement, isExternalImport, isRequireCall, isWildcardReexportStatement, RequireCall, skipAliases, WildcardReexportStatement} from './commonjs_umd_utils'; -import {getInnerClassDeclaration, getOuterNodeFromInnerDeclaration} from './esm2015_host'; -import {Esm5ReflectionHost} from './esm5_host'; -import {NgccClassSymbol} from './ngcc_host'; - -export class CommonJsReflectionHost extends Esm5ReflectionHost { - protected commonJsExports = new FactoryMap|null>( - sf => this.computeExportsOfCommonJsModule(sf)); - protected topLevelHelperCalls = - new FactoryMap>( - helperName => new FactoryMap( - sf => sf.statements.map(stmt => this.getHelperCall(stmt, [helperName])) - .filter(isDefined))); - protected program: ts.Program; - protected compilerHost: ts.CompilerHost; - constructor(logger: Logger, isCore: boolean, src: BundleProgram, dts: BundleProgram|null = null) { - super(logger, isCore, src, dts); - this.program = src.program; - this.compilerHost = src.host; - } - - override getImportOfIdentifier(id: ts.Identifier): Import|null { - const requireCall = this.findCommonJsImport(id); - if (requireCall === null) { - return null; - } - return {from: requireCall.arguments[0].text, name: id.text}; - } - - override getDeclarationOfIdentifier(id: ts.Identifier): Declaration|null { - return this.getCommonJsModuleDeclaration(id) || super.getDeclarationOfIdentifier(id); - } - - override getExportsOfModule(module: ts.Node): Map|null { - return super.getExportsOfModule(module) || this.commonJsExports.get(module.getSourceFile()); - } - - /** - * Search statements related to the given class for calls to the specified helper. - * - * In CommonJS these helper calls can be outside the class's IIFE at the top level of the - * source file. Searching the top level statements for helpers can be expensive, so we - * try to get helpers from the IIFE first and only fall back on searching the top level if - * no helpers are found. - * - * @param classSymbol the class whose helper calls we are interested in. - * @param helperNames the names of the helpers (e.g. `__decorate`) whose calls we are interested - * in. - * @returns an array of nodes of calls to the helper with the given name. - */ - protected override getHelperCallsForClass(classSymbol: NgccClassSymbol, helperNames: string[]): - ts.CallExpression[] { - const esm5HelperCalls = super.getHelperCallsForClass(classSymbol, helperNames); - if (esm5HelperCalls.length > 0) { - return esm5HelperCalls; - } else { - const sourceFile = classSymbol.declaration.valueDeclaration.getSourceFile(); - return this.getTopLevelHelperCalls(sourceFile, helperNames); - } - } - - /** - * Find all the helper calls at the top level of a source file. - * - * We cache the helper calls per source file so that we don't have to keep parsing the code for - * each class in a file. - * - * @param sourceFile the source who may contain helper calls. - * @param helperNames the names of the helpers (e.g. `__decorate`) whose calls we are interested - * in. - * @returns an array of nodes of calls to the helper with the given name. - */ - private getTopLevelHelperCalls(sourceFile: ts.SourceFile, helperNames: string[]): - ts.CallExpression[] { - const calls: ts.CallExpression[] = []; - helperNames.forEach(helperName => { - const helperCallsMap = this.topLevelHelperCalls.get(helperName); - calls.push(...helperCallsMap.get(sourceFile)); - }); - return calls; - } - - private computeExportsOfCommonJsModule(sourceFile: ts.SourceFile): Map { - const moduleMap = new Map(); - for (const statement of this.getModuleStatements(sourceFile)) { - if (isExportsStatement(statement)) { - const exportDeclaration = this.extractBasicCommonJsExportDeclaration(statement); - moduleMap.set(exportDeclaration.name, exportDeclaration.declaration); - } else if (isWildcardReexportStatement(statement)) { - const reexports = this.extractCommonJsWildcardReexports(statement, sourceFile); - for (const reexport of reexports) { - moduleMap.set(reexport.name, reexport.declaration); - } - } else if (isDefinePropertyReexportStatement(statement)) { - const exportDeclaration = this.extractCommonJsDefinePropertyExportDeclaration(statement); - if (exportDeclaration !== null) { - moduleMap.set(exportDeclaration.name, exportDeclaration.declaration); - } - } - } - return moduleMap; - } - - private extractBasicCommonJsExportDeclaration(statement: ExportsStatement): ExportDeclaration { - const exportExpression = skipAliases(statement.expression.right); - const node = statement.expression.left; - const declaration = this.getDeclarationOfExpression(exportExpression) ?? { - kind: DeclarationKind.Inline, - node, - implementation: exportExpression, - known: null, - viaModule: null, - }; - return {name: node.name.text, declaration}; - } - - private extractCommonJsWildcardReexports( - statement: WildcardReexportStatement, containingFile: ts.SourceFile): ExportDeclaration[] { - const reexportArg = statement.expression.arguments[0]; - - const requireCall = isRequireCall(reexportArg) ? - reexportArg : - ts.isIdentifier(reexportArg) ? findRequireCallReference(reexportArg, this.checker) : null; - if (requireCall === null) { - return []; - } - - const importPath = requireCall.arguments[0].text; - const importedFile = this.resolveModuleName(importPath, containingFile); - if (importedFile === undefined) { - return []; - } - - const importedExports = this.getExportsOfModule(importedFile); - if (importedExports === null) { - return []; - } - - const viaModule = isExternalImport(importPath) ? importPath : null; - const reexports: ExportDeclaration[] = []; - importedExports.forEach((declaration, name) => { - if (viaModule !== null && declaration.viaModule === null) { - declaration = {...declaration, viaModule}; - } - reexports.push({name, declaration}); - }); - return reexports; - } - - private extractCommonJsDefinePropertyExportDeclaration( - statement: DefinePropertyReexportStatement): ExportDeclaration|null { - const args = statement.expression.arguments; - const name = args[1].text; - const getterFnExpression = extractGetterFnExpression(statement); - if (getterFnExpression === null) { - return null; - } - - const declaration = this.getDeclarationOfExpression(getterFnExpression); - if (declaration !== null) { - return {name, declaration}; - } - - return { - name, - declaration: { - kind: DeclarationKind.Inline, - node: args[1], - implementation: getterFnExpression, - known: null, - viaModule: null, - }, - }; - } - - private findCommonJsImport(id: ts.Identifier): RequireCall|null { - // Is `id` a namespaced property access, e.g. `Directive` in `core.Directive`? - // If so capture the symbol of the namespace, e.g. `core`. - const nsIdentifier = findNamespaceOfIdentifier(id); - return nsIdentifier && findRequireCallReference(nsIdentifier, this.checker); - } - - /** - * Handle the case where the identifier represents a reference to a whole CommonJS - * module, i.e. the result of a call to `require(...)`. - * - * @param id the identifier whose declaration we are looking for. - * @returns a declaration if `id` refers to a CommonJS module, or `null` otherwise. - */ - private getCommonJsModuleDeclaration(id: ts.Identifier): Declaration|null { - const requireCall = findRequireCallReference(id, this.checker); - if (requireCall === null) { - return null; - } - const importPath = requireCall.arguments[0].text; - const module = this.resolveModuleName(importPath, id.getSourceFile()); - if (module === undefined) { - return null; - } - const viaModule = isExternalImport(importPath) ? importPath : null; - return {node: module, known: null, viaModule, identity: null, kind: DeclarationKind.Concrete}; - } - - /** - * If this is an IFE then try to grab the outer and inner classes otherwise fallback on the super - * class. - */ - protected override getDeclarationOfExpression(expression: ts.Expression): Declaration|null { - const inner = getInnerClassDeclaration(expression); - if (inner !== null) { - const outer = getOuterNodeFromInnerDeclaration(inner); - if (outer !== null && isExportsAssignment(outer)) { - return { - kind: DeclarationKind.Inline, - node: outer.left, - implementation: inner, - known: null, - viaModule: null, - }; - } - } - return super.getDeclarationOfExpression(expression); - } - - private resolveModuleName(moduleName: string, containingFile: ts.SourceFile): ts.SourceFile - |undefined { - if (this.compilerHost.resolveModuleNames) { - const moduleInfo = this.compilerHost.resolveModuleNames( - [moduleName], containingFile.fileName, undefined, undefined, - this.program.getCompilerOptions())[0]; - return moduleInfo && this.program.getSourceFile(absoluteFrom(moduleInfo.resolvedFileName)); - } else { - const moduleInfo = ts.resolveModuleName( - moduleName, containingFile.fileName, this.program.getCompilerOptions(), - this.compilerHost); - return moduleInfo.resolvedModule && - this.program.getSourceFile(absoluteFrom(moduleInfo.resolvedModule.resolvedFileName)); - } - } -} diff --git a/packages/compiler-cli/ngcc/src/host/commonjs_umd_utils.ts b/packages/compiler-cli/ngcc/src/host/commonjs_umd_utils.ts deleted file mode 100644 index 9c474c10ffe..00000000000 --- a/packages/compiler-cli/ngcc/src/host/commonjs_umd_utils.ts +++ /dev/null @@ -1,284 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ - -import ts from 'typescript'; - -import {Declaration} from '../../../src/ngtsc/reflection'; -import {isAssignment} from '../../../src/ngtsc/util/src/typescript'; - -export interface ExportDeclaration { - name: string; - declaration: Declaration; -} - -/** - * A CommonJS or UMD wildcard re-export statement. - * - * The CommonJS or UMD version of `export * from 'blah';`. - * - * These statements can have several forms (depending, for example, on whether - * the TypeScript helpers are imported or emitted inline). The expression can have one of the - * following forms: - * - `__export(firstArg)` - * - `__exportStar(firstArg)` - * - `tslib.__export(firstArg, exports)` - * - `tslib.__exportStar(firstArg, exports)` - * - * In all cases, we only care about `firstArg`, which is the first argument of the re-export call - * expression and can be either a `require('...')` call or an identifier (initialized via a - * `require('...')` call). - */ -export interface WildcardReexportStatement extends ts.ExpressionStatement { - expression: ts.CallExpression; -} - -/** - * A CommonJS or UMD re-export statement using an `Object.defineProperty()` call. - * For example: - * - * ``` - * Object.defineProperty(exports, "", - * { enumerable: true, get: function () { return ; } }); - * ``` - */ -export interface DefinePropertyReexportStatement extends ts.ExpressionStatement { - expression: ts.CallExpression& - {arguments: [ts.Identifier, ts.StringLiteral, ts.ObjectLiteralExpression]}; -} - -/** - * A call expression that has a string literal for its first argument. - */ -export interface RequireCall extends ts.CallExpression { - arguments: ts.CallExpression['arguments']&[ts.StringLiteral]; -} - - -/** - * Return the "namespace" of the specified `ts.Identifier` if the identifier is the RHS of a - * property access expression, i.e. an expression of the form `.` (in which case a - * `ts.Identifier` corresponding to `` will be returned). Otherwise return `null`. - */ -export function findNamespaceOfIdentifier(id: ts.Identifier): ts.Identifier|null { - return id.parent && ts.isPropertyAccessExpression(id.parent) && id.parent.name === id && - ts.isIdentifier(id.parent.expression) ? - id.parent.expression : - null; -} - -/** - * Return the `RequireCall` that is used to initialize the specified `ts.Identifier`, if the - * specified identifier was indeed initialized with a require call in a declaration of the form: - * `var = require('...')` - */ -export function findRequireCallReference(id: ts.Identifier, checker: ts.TypeChecker): RequireCall| - null { - const symbol = checker.getSymbolAtLocation(id) || null; - const declaration = symbol?.valueDeclaration ?? symbol?.declarations?.[0]; - const initializer = - declaration && ts.isVariableDeclaration(declaration) && declaration.initializer || null; - return initializer && isRequireCall(initializer) ? initializer : null; -} - -/** - * Check whether the specified `ts.Statement` is a wildcard re-export statement. - * I.E. an expression statement of one of the following forms: - * - `__export()` - * - `__exportStar()` - * - `tslib.__export(, exports)` - * - `tslib.__exportStar(, exports)` - */ -export function isWildcardReexportStatement(stmt: ts.Statement): stmt is WildcardReexportStatement { - // Ensure it is a call expression statement. - if (!ts.isExpressionStatement(stmt) || !ts.isCallExpression(stmt.expression)) { - return false; - } - - // Get the called function identifier. - // NOTE: Currently, it seems that `__export()` is used when emitting helpers inline and - // `__exportStar()` when importing them - // ([source](https://github.com/microsoft/TypeScript/blob/d7c83f023/src/compiler/transformers/module/module.ts#L1796-L1797)). - // So, theoretically, we only care about the formats `__export()` and - // `tslib.__exportStar(, exports)`. - // The current implementation accepts the other two formats (`__exportStar(...)` and - // `tslib.__export(...)`) as well to be more future-proof (given that it is unlikely that - // they will introduce false positives). - let fnName: string|null = null; - if (ts.isIdentifier(stmt.expression.expression)) { - // Statement of the form `someFn(...)`. - fnName = stmt.expression.expression.text; - } else if ( - ts.isPropertyAccessExpression(stmt.expression.expression) && - ts.isIdentifier(stmt.expression.expression.name)) { - // Statement of the form `tslib.someFn(...)`. - fnName = stmt.expression.expression.name.text; - } - - // Ensure the called function is either `__export()` or `__exportStar()`. - if ((fnName !== '__export') && (fnName !== '__exportStar')) { - return false; - } - - // Ensure there is at least one argument. - // (The first argument is the exported thing and there will be a second `exports` argument in the - // case of imported helpers). - return stmt.expression.arguments.length > 0; -} - - -/** - * Check whether the statement is a re-export of the form: - * - * ``` - * Object.defineProperty(exports, "", - * { enumerable: true, get: function () { return ; } }); - * ``` - */ -export function isDefinePropertyReexportStatement(stmt: ts.Statement): - stmt is DefinePropertyReexportStatement { - if (!ts.isExpressionStatement(stmt) || !ts.isCallExpression(stmt.expression)) { - return false; - } - - // Check for Object.defineProperty - if (!ts.isPropertyAccessExpression(stmt.expression.expression) || - !ts.isIdentifier(stmt.expression.expression.expression) || - stmt.expression.expression.expression.text !== 'Object' || - !ts.isIdentifier(stmt.expression.expression.name) || - stmt.expression.expression.name.text !== 'defineProperty') { - return false; - } - - const args = stmt.expression.arguments; - if (args.length !== 3) { - return false; - } - const exportsObject = args[0]; - if (!ts.isIdentifier(exportsObject) || exportsObject.text !== 'exports') { - return false; - } - - const propertyKey = args[1]; - if (!ts.isStringLiteral(propertyKey)) { - return false; - } - - const propertyDescriptor = args[2]; - if (!ts.isObjectLiteralExpression(propertyDescriptor)) { - return false; - } - - return (propertyDescriptor.properties.some( - prop => prop.name !== undefined && ts.isIdentifier(prop.name) && prop.name.text === 'get')); -} - -/** - * Extract the "value" of the getter in a `defineProperty` statement. - * - * This will return the `ts.Expression` value of a single `return` statement in the `get` method - * of the property definition object, or `null` if that is not possible. - */ -export function extractGetterFnExpression(statement: DefinePropertyReexportStatement): - ts.Expression|null { - const args = statement.expression.arguments; - const getterFn = args[2].properties.find( - prop => prop.name !== undefined && ts.isIdentifier(prop.name) && prop.name.text === 'get'); - if (getterFn === undefined || !ts.isPropertyAssignment(getterFn) || - !ts.isFunctionExpression(getterFn.initializer)) { - return null; - } - const returnStatement = getterFn.initializer.body.statements[0]; - if (!ts.isReturnStatement(returnStatement) || returnStatement.expression === undefined) { - return null; - } - return returnStatement.expression; -} - -/** - * Check whether the specified `ts.Node` represents a `require()` call, i.e. an call expression of - * the form: `require('')` - */ -export function isRequireCall(node: ts.Node): node is RequireCall { - return ts.isCallExpression(node) && ts.isIdentifier(node.expression) && - node.expression.text === 'require' && node.arguments.length === 1 && - ts.isStringLiteral(node.arguments[0]); -} - -/** - * Check whether the specified `path` is an "external" import. - * In other words, that it comes from a entry-point outside the current one. - */ -export function isExternalImport(path: string): boolean { - return !/^\.\.?(\/|$)/.test(path); -} - -/** - * A UMD/CommonJS style export declaration of the form `exports.`. - */ -export interface ExportsDeclaration extends ts.PropertyAccessExpression { - name: ts.Identifier; - expression: ts.Identifier; - parent: ExportsAssignment; -} - -/** - * Check whether the specified `node` is a property access expression of the form - * `exports.`. - */ -export function isExportsDeclaration(expr: ts.Node): expr is ExportsDeclaration { - return expr.parent && isExportsAssignment(expr.parent); -} - -/** - * A UMD/CommonJS style export assignment of the form `exports. = `. - */ -export interface ExportsAssignment extends ts.BinaryExpression { - left: ExportsDeclaration; -} - -/** - * Check whether the specified `node` is an assignment expression of the form - * `exports. = `. - */ -export function isExportsAssignment(expr: ts.Node): expr is ExportsAssignment { - return isAssignment(expr) && ts.isPropertyAccessExpression(expr.left) && - ts.isIdentifier(expr.left.expression) && expr.left.expression.text === 'exports' && - ts.isIdentifier(expr.left.name); -} - -/** - * An expression statement of the form `exports. = ;`. - */ -export interface ExportsStatement extends ts.ExpressionStatement { - expression: ExportsAssignment; -} - -/** - * Check whether the specified `stmt` is an expression statement of the form - * `exports. = ;`. - */ -export function isExportsStatement(stmt: ts.Node): stmt is ExportsStatement { - return ts.isExpressionStatement(stmt) && isExportsAssignment(stmt.expression); -} - -/** - * Find the far right hand side of a sequence of aliased assignments of the form - * - * ``` - * exports.MyClass = alias1 = alias2 = <> - * ``` - * - * @param node the expression to parse - * @returns the original `node` or the far right expression of a series of assignments. - */ -export function skipAliases(node: ts.Expression): ts.Expression { - while (isAssignment(node)) { - node = node.right; - } - return node; -} diff --git a/packages/compiler-cli/ngcc/src/host/delegating_host.ts b/packages/compiler-cli/ngcc/src/host/delegating_host.ts deleted file mode 100644 index ef0e5c515ab..00000000000 --- a/packages/compiler-cli/ngcc/src/host/delegating_host.ts +++ /dev/null @@ -1,164 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ - -import ts from 'typescript'; - -import {ClassDeclaration, ClassMember, CtorParameter, Declaration, DeclarationNode, Decorator, FunctionDefinition, Import, ReflectionHost} from '../../../src/ngtsc/reflection'; -import {isFromDtsFile} from '../../../src/ngtsc/util/src/typescript'; - -import {NgccClassSymbol, NgccReflectionHost} from './ngcc_host'; - -/** - * A reflection host implementation that delegates reflector queries depending on whether they - * reflect on declaration files (for dependent libraries) or source files within the entry-point - * that is being compiled. The first type of queries are handled by the regular TypeScript - * reflection host, whereas the other queries are handled by an `NgccReflectionHost` that is - * specific to the entry-point's format. - */ -export class DelegatingReflectionHost implements NgccReflectionHost { - constructor(private tsHost: ReflectionHost, private ngccHost: NgccReflectionHost) {} - - getConstructorParameters(clazz: ClassDeclaration): CtorParameter[]|null { - if (isFromDtsFile(clazz)) { - return this.tsHost.getConstructorParameters(clazz); - } - return this.ngccHost.getConstructorParameters(clazz); - } - - getDeclarationOfIdentifier(id: ts.Identifier): Declaration|null { - if (isFromDtsFile(id)) { - const declaration = this.tsHost.getDeclarationOfIdentifier(id); - return declaration !== null ? this.detectKnownDeclaration(declaration) : null; - } - return this.ngccHost.getDeclarationOfIdentifier(id); - } - - getDecoratorsOfDeclaration(declaration: DeclarationNode): Decorator[]|null { - if (isFromDtsFile(declaration)) { - return this.tsHost.getDecoratorsOfDeclaration(declaration); - } - return this.ngccHost.getDecoratorsOfDeclaration(declaration); - } - - getDefinitionOfFunction(fn: ts.Node): FunctionDefinition|null { - if (isFromDtsFile(fn)) { - return this.tsHost.getDefinitionOfFunction(fn); - } - return this.ngccHost.getDefinitionOfFunction(fn); - } - - getDtsDeclaration(declaration: DeclarationNode): ts.Declaration|null { - if (isFromDtsFile(declaration)) { - return this.tsHost.getDtsDeclaration(declaration); - } - return this.ngccHost.getDtsDeclaration(declaration); - } - - getExportsOfModule(module: ts.Node): Map|null { - if (isFromDtsFile(module)) { - const exportMap = this.tsHost.getExportsOfModule(module); - - if (exportMap !== null) { - exportMap.forEach(decl => this.detectKnownDeclaration(decl)); - } - - return exportMap; - } - return this.ngccHost.getExportsOfModule(module); - } - - getGenericArityOfClass(clazz: ClassDeclaration): number|null { - if (isFromDtsFile(clazz)) { - return this.tsHost.getGenericArityOfClass(clazz); - } - return this.ngccHost.getGenericArityOfClass(clazz); - } - - getImportOfIdentifier(id: ts.Identifier): Import|null { - if (isFromDtsFile(id)) { - return this.tsHost.getImportOfIdentifier(id); - } - return this.ngccHost.getImportOfIdentifier(id); - } - - getInternalNameOfClass(clazz: ClassDeclaration): ts.Identifier { - if (isFromDtsFile(clazz)) { - return this.tsHost.getInternalNameOfClass(clazz); - } - return this.ngccHost.getInternalNameOfClass(clazz); - } - - getAdjacentNameOfClass(clazz: ClassDeclaration): ts.Identifier { - if (isFromDtsFile(clazz)) { - return this.tsHost.getAdjacentNameOfClass(clazz); - } - return this.ngccHost.getAdjacentNameOfClass(clazz); - } - - getMembersOfClass(clazz: ClassDeclaration): ClassMember[] { - if (isFromDtsFile(clazz)) { - return this.tsHost.getMembersOfClass(clazz); - } - return this.ngccHost.getMembersOfClass(clazz); - } - - getVariableValue(declaration: ts.VariableDeclaration): ts.Expression|null { - if (isFromDtsFile(declaration)) { - return this.tsHost.getVariableValue(declaration); - } - return this.ngccHost.getVariableValue(declaration); - } - - hasBaseClass(clazz: ClassDeclaration): boolean { - if (isFromDtsFile(clazz)) { - return this.tsHost.hasBaseClass(clazz); - } - return this.ngccHost.hasBaseClass(clazz); - } - - getBaseClassExpression(clazz: ClassDeclaration): ts.Expression|null { - if (isFromDtsFile(clazz)) { - return this.tsHost.getBaseClassExpression(clazz); - } - return this.ngccHost.getBaseClassExpression(clazz); - } - - isClass(node: ts.Node): node is ClassDeclaration { - if (isFromDtsFile(node)) { - return this.tsHost.isClass(node); - } - return this.ngccHost.isClass(node); - } - - // Note: the methods below are specific to ngcc and the entry-point that is being compiled, so - // they don't take declaration files into account. - - findClassSymbols(sourceFile: ts.SourceFile): NgccClassSymbol[] { - return this.ngccHost.findClassSymbols(sourceFile); - } - - getClassSymbol(node: ts.Node): NgccClassSymbol|undefined { - return this.ngccHost.getClassSymbol(node); - } - - getDecoratorsOfSymbol(symbol: NgccClassSymbol): Decorator[]|null { - return this.ngccHost.getDecoratorsOfSymbol(symbol); - } - - getEndOfClass(classSymbol: NgccClassSymbol): ts.Node { - return this.ngccHost.getEndOfClass(classSymbol); - } - - detectKnownDeclaration(decl: T): T { - return this.ngccHost.detectKnownDeclaration(decl); - } - - isStaticallyExported(decl: ts.Node): boolean { - return this.ngccHost.isStaticallyExported(decl); - } -} diff --git a/packages/compiler-cli/ngcc/src/host/esm2015_host.ts b/packages/compiler-cli/ngcc/src/host/esm2015_host.ts deleted file mode 100644 index 37d009e1b8f..00000000000 --- a/packages/compiler-cli/ngcc/src/host/esm2015_host.ts +++ /dev/null @@ -1,2633 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ - -import ts from 'typescript'; - -import {absoluteFromSourceFile} from '../../../src/ngtsc/file_system'; -import {Logger} from '../../../src/ngtsc/logging'; -import {ClassDeclaration, ClassMember, ClassMemberKind, CtorParameter, Declaration, DeclarationNode, Decorator, EnumMember, Import, isConcreteDeclaration, isDecoratorIdentifier, isNamedClassDeclaration, isNamedFunctionDeclaration, isNamedVariableDeclaration, KnownDeclaration, reflectObjectLiteral, SpecialDeclarationKind, TypeScriptReflectionHost, TypeValueReference, TypeValueReferenceKind, ValueUnavailableKind} from '../../../src/ngtsc/reflection'; -import {isSymbolWithValueDeclaration, SymbolWithValueDeclaration} from '../../../src/ngtsc/util/src/typescript'; -import {isWithinPackage} from '../analysis/util'; -import {BundleProgram} from '../packages/bundle_program'; -import {getNameText, hasNameIdentifier, isDefined, stripDollarSuffix} from '../utils'; - -import {ClassSymbol, NgccClassSymbol, NgccReflectionHost} from './ngcc_host'; -import {stripParentheses} from './utils'; - -export const DECORATORS = 'decorators' as ts.__String; -export const PROP_DECORATORS = 'propDecorators' as ts.__String; -export const CONSTRUCTOR = '__constructor' as ts.__String; -export const CONSTRUCTOR_PARAMS = 'ctorParameters' as ts.__String; - -/** - * Esm2015 packages contain ECMAScript 2015 classes, etc. - * Decorators are defined via static properties on the class. For example: - * - * ``` - * class SomeDirective { - * } - * SomeDirective.decorators = [ - * { type: Directive, args: [{ selector: '[someDirective]' },] } - * ]; - * SomeDirective.ctorParameters = () => [ - * { type: ViewContainerRef, }, - * { type: TemplateRef, }, - * { type: undefined, decorators: [{ type: Inject, args: [INJECTED_TOKEN,] },] }, - * ]; - * SomeDirective.propDecorators = { - * "input1": [{ type: Input },], - * "input2": [{ type: Input },], - * }; - * ``` - * - * * Classes are decorated if they have a static property called `decorators`. - * * Members are decorated if there is a matching key on a static property - * called `propDecorators`. - * * Constructor parameters decorators are found on an object returned from - * a static method called `ctorParameters`. - */ -export class Esm2015ReflectionHost extends TypeScriptReflectionHost implements NgccReflectionHost { - /** - * A mapping from source declarations to typings declarations, which are both publicly exported. - * - * There should be one entry for every public export visible from the root file of the source - * tree. Note that by definition the key and value declarations will not be in the same TS - * program. - */ - protected publicDtsDeclarationMap: Map|null = null; - /** - * A mapping from source declarations to typings declarations, which are not publicly exported. - * - * This mapping is a best guess between declarations that happen to be exported from their file by - * the same name in both the source and the dts file. Note that by definition the key and value - * declarations will not be in the same TS program. - */ - protected privateDtsDeclarationMap: Map|null = null; - - /** - * The set of source files that have already been preprocessed. - */ - protected preprocessedSourceFiles = new Set(); - - /** - * In ES2015, class declarations may have been down-leveled into variable declarations, - * initialized using a class expression. In certain scenarios, an additional variable - * is introduced that represents the class so that results in code such as: - * - * ``` - * let MyClass_1; let MyClass = MyClass_1 = class MyClass {}; - * ``` - * - * This map tracks those aliased variables to their original identifier, i.e. the key - * corresponds with the declaration of `MyClass_1` and its value becomes the `MyClass` identifier - * of the variable declaration. - * - * This map is populated during the preprocessing of each source file. - */ - protected aliasedClassDeclarations = new Map(); - - /** - * Caches the information of the decorators on a class, as the work involved with extracting - * decorators is complex and frequently used. - * - * This map is lazily populated during the first call to `acquireDecoratorInfo` for a given class. - */ - protected decoratorCache = new Map(); - - constructor( - protected logger: Logger, protected isCore: boolean, protected src: BundleProgram, - protected dts: BundleProgram|null = null) { - super(src.program.getTypeChecker()); - } - - /** - * Find a symbol for a node that we think is a class. - * Classes should have a `name` identifier, because they may need to be referenced in other parts - * of the program. - * - * In ES2015, a class may be declared using a variable declaration of the following structures: - * - * ``` - * var MyClass = MyClass_1 = class MyClass {}; - * ``` - * - * or - * - * ``` - * var MyClass = MyClass_1 = (() => { class MyClass {} ... return MyClass; })() - * ``` - * - * Here, the intermediate `MyClass_1` assignment is optional. In the above example, the - * `class MyClass {}` node is returned as declaration of `MyClass`. - * - * @param declaration the declaration node whose symbol we are finding. - * @returns the symbol for the node or `undefined` if it is not a "class" or has no symbol. - */ - getClassSymbol(declaration: ts.Node): NgccClassSymbol|undefined { - const symbol = this.getClassSymbolFromOuterDeclaration(declaration); - if (symbol !== undefined) { - return symbol; - } - const innerDeclaration = this.getInnerDeclarationFromAliasOrInner(declaration); - return this.getClassSymbolFromInnerDeclaration(innerDeclaration); - } - - /** - * Examine a declaration (for example, of a class or function) and return metadata about any - * decorators present on the declaration. - * - * @param declaration a TypeScript node representing the class or function over which to reflect. - * For example, if the intent is to reflect the decorators of a class and the source is in ES6 - * format, this will be a `ts.ClassDeclaration` node. If the source is in ES5 format, this - * might be a `ts.VariableDeclaration` as classes in ES5 are represented as the result of an - * IIFE execution. - * - * @returns an array of `Decorator` metadata if decorators are present on the declaration, or - * `null` if either no decorators were present or if the declaration is not of a decoratable - * type. - */ - override getDecoratorsOfDeclaration(declaration: DeclarationNode): Decorator[]|null { - const symbol = this.getClassSymbol(declaration); - if (!symbol) { - return null; - } - return this.getDecoratorsOfSymbol(symbol); - } - - /** - * Examine a declaration which should be of a class, and return metadata about the members of the - * class. - * - * @param clazz a `ClassDeclaration` representing the class over which to reflect. - * - * @returns an array of `ClassMember` metadata representing the members of the class. - * - * @throws if `declaration` does not resolve to a class declaration. - */ - override getMembersOfClass(clazz: ClassDeclaration): ClassMember[] { - const classSymbol = this.getClassSymbol(clazz); - if (!classSymbol) { - throw new Error(`Attempted to get members of a non-class: "${clazz.getText()}"`); - } - - return this.getMembersOfSymbol(classSymbol); - } - - /** - * Reflect over the constructor of a class and return metadata about its parameters. - * - * This method only looks at the constructor of a class directly and not at any inherited - * constructors. - * - * @param clazz a `ClassDeclaration` representing the class over which to reflect. - * - * @returns an array of `Parameter` metadata representing the parameters of the constructor, if - * a constructor exists. If the constructor exists and has 0 parameters, this array will be empty. - * If the class has no constructor, this method returns `null`. - * - * @throws if `declaration` does not resolve to a class declaration. - */ - override getConstructorParameters(clazz: ClassDeclaration): CtorParameter[]|null { - const classSymbol = this.getClassSymbol(clazz); - if (!classSymbol) { - throw new Error( - `Attempted to get constructor parameters of a non-class: "${clazz.getText()}"`); - } - const parameterNodes = this.getConstructorParameterDeclarations(classSymbol); - if (parameterNodes) { - return this.getConstructorParamInfo(classSymbol, parameterNodes); - } - return null; - } - - override getBaseClassExpression(clazz: ClassDeclaration): ts.Expression|null { - // First try getting the base class from an ES2015 class declaration - const superBaseClassIdentifier = super.getBaseClassExpression(clazz); - if (superBaseClassIdentifier) { - return superBaseClassIdentifier; - } - - // That didn't work so now try getting it from the "inner" declaration. - const classSymbol = this.getClassSymbol(clazz); - if (classSymbol?.implementation.valueDeclaration === undefined || - !isNamedDeclaration(classSymbol.implementation.valueDeclaration)) { - return null; - } - return super.getBaseClassExpression(classSymbol.implementation.valueDeclaration); - } - - override getInternalNameOfClass(clazz: ClassDeclaration): ts.Identifier { - const classSymbol = this.getClassSymbol(clazz); - if (classSymbol === undefined) { - throw new Error(`getInternalNameOfClass() called on a non-class: expected ${ - clazz.name.text} to be a class declaration.`); - } - return this.getNameFromClassSymbolDeclaration( - classSymbol, classSymbol.implementation.valueDeclaration); - } - - override getAdjacentNameOfClass(clazz: ClassDeclaration): ts.Identifier { - const classSymbol = this.getClassSymbol(clazz); - if (classSymbol === undefined) { - throw new Error(`getAdjacentNameOfClass() called on a non-class: expected ${ - clazz.name.text} to be a class declaration.`); - } - - return this.getAdjacentNameOfClassSymbol(classSymbol); - } - - private getNameFromClassSymbolDeclaration( - classSymbol: NgccClassSymbol, declaration: ts.Declaration|undefined): ts.Identifier { - if (declaration === undefined) { - throw new Error( - `getInternalNameOfClass() called on a class with an undefined internal declaration. External class name: ${ - classSymbol.name}; internal class name: ${classSymbol.implementation.name}.`); - } - if (!isNamedDeclaration(declaration)) { - throw new Error( - `getInternalNameOfClass() called on a class with an anonymous inner declaration: expected a name on:\n${ - declaration.getText()}`); - } - return declaration.name; - } - - /** - * Check whether the given node actually represents a class. - */ - override isClass(node: ts.Node): node is ClassDeclaration { - return super.isClass(node) || this.getClassSymbol(node) !== undefined; - } - - /** - * Trace an identifier to its declaration, if possible. - * - * This method attempts to resolve the declaration of the given identifier, tracing back through - * imports and re-exports until the original declaration statement is found. A `Declaration` - * object is returned if the original declaration is found, or `null` is returned otherwise. - * - * In ES2015, we need to account for identifiers that refer to aliased class declarations such as - * `MyClass_1`. Since such declarations are only available within the module itself, we need to - * find the original class declaration, e.g. `MyClass`, that is associated with the aliased one. - * - * @param id a TypeScript `ts.Identifier` to trace back to a declaration. - * - * @returns metadata about the `Declaration` if the original declaration is found, or `null` - * otherwise. - */ - override getDeclarationOfIdentifier(id: ts.Identifier): Declaration|null { - const superDeclaration = super.getDeclarationOfIdentifier(id); - - // If no declaration was found, return. - if (superDeclaration === null) { - return superDeclaration; - } - - // If the declaration already has traits assigned to it, return as is. - if (superDeclaration.known !== null || - isConcreteDeclaration(superDeclaration) && superDeclaration.identity !== null) { - return superDeclaration; - } - - let declarationNode: ts.Node = superDeclaration.node; - if (isNamedVariableDeclaration(superDeclaration.node) && !isTopLevel(superDeclaration.node)) { - const variableValue = this.getVariableValue(superDeclaration.node); - if (variableValue !== null && ts.isClassExpression(variableValue)) { - declarationNode = getContainingStatement(variableValue); - } - } - - const outerNode = getOuterNodeFromInnerDeclaration(declarationNode); - const declaration = outerNode !== null && isNamedVariableDeclaration(outerNode) ? - this.getDeclarationOfIdentifier(outerNode.name) : - superDeclaration; - if (declaration === null || declaration.known !== null || - isConcreteDeclaration(declaration) && declaration.identity !== null) { - return declaration; - } - - // The identifier may have been of an additional class assignment such as `MyClass_1` that was - // present as alias for `MyClass`. If so, resolve such aliases to their original declaration. - const aliasedIdentifier = this.resolveAliasedClassIdentifier(declaration.node); - if (aliasedIdentifier !== null) { - return this.getDeclarationOfIdentifier(aliasedIdentifier); - } - - // Variable declarations may represent an enum declaration, so attempt to resolve its members. - if (isConcreteDeclaration(declaration) && ts.isVariableDeclaration(declaration.node)) { - const enumMembers = this.resolveEnumMembers(declaration.node); - if (enumMembers !== null) { - declaration.identity = {kind: SpecialDeclarationKind.DownleveledEnum, enumMembers}; - } - } - - return declaration; - } - - /** - * Gets all decorators of the given class symbol. Any decorator that have been synthetically - * injected by a migration will not be present in the returned collection. - */ - getDecoratorsOfSymbol(symbol: NgccClassSymbol): Decorator[]|null { - const {classDecorators} = this.acquireDecoratorInfo(symbol); - if (classDecorators === null) { - return null; - } - - // Return a clone of the array to prevent consumers from mutating the cache. - return Array.from(classDecorators); - } - - override getVariableValue(declaration: ts.VariableDeclaration): ts.Expression|null { - const value = super.getVariableValue(declaration); - if (value) { - return value; - } - - // We have a variable declaration that has no initializer. For example: - // - // ``` - // var HttpClientXsrfModule_1; - // ``` - // - // So look for the special scenario where the variable is being assigned in - // a nearby statement to the return value of a call to `__decorate`. - // Then find the 2nd argument of that call, the "target", which will be the - // actual class identifier. For example: - // - // ``` - // HttpClientXsrfModule = HttpClientXsrfModule_1 = tslib_1.__decorate([ - // NgModule({ - // providers: [], - // }) - // ], HttpClientXsrfModule); - // ``` - // - // And finally, find the declaration of the identifier in that argument. - // Note also that the assignment can occur within another assignment. - // - const block = declaration.parent.parent.parent; - const symbol = this.checker.getSymbolAtLocation(declaration.name); - if (symbol && (ts.isBlock(block) || ts.isSourceFile(block))) { - const decorateCall = this.findDecoratedVariableValue(block, symbol); - const target = decorateCall && decorateCall.arguments[1]; - if (target && ts.isIdentifier(target)) { - const targetSymbol = this.checker.getSymbolAtLocation(target); - const targetDeclaration = targetSymbol && targetSymbol.valueDeclaration; - if (targetDeclaration) { - if (ts.isClassDeclaration(targetDeclaration) || - ts.isFunctionDeclaration(targetDeclaration)) { - // The target is just a function or class declaration - // so return its identifier as the variable value. - return targetDeclaration.name || null; - } else if (ts.isVariableDeclaration(targetDeclaration)) { - // The target is a variable declaration, so find the far right expression, - // in the case of multiple assignments (e.g. `var1 = var2 = value`). - let targetValue = targetDeclaration.initializer; - while (targetValue && isAssignment(targetValue)) { - targetValue = targetValue.right; - } - if (targetValue) { - return targetValue; - } - } - } - } - } - return null; - } - - /** - * Find all top-level class symbols in the given file. - * @param sourceFile The source file to search for classes. - * @returns An array of class symbols. - */ - findClassSymbols(sourceFile: ts.SourceFile): NgccClassSymbol[] { - const classes = new Map(); - this.getModuleStatements(sourceFile) - .forEach(statement => this.addClassSymbolsFromStatement(classes, statement)); - return Array.from(classes.values()); - } - - /** - * Get the number of generic type parameters of a given class. - * - * @param clazz a `ClassDeclaration` representing the class over which to reflect. - * - * @returns the number of type parameters of the class, if known, or `null` if the declaration - * is not a class or has an unknown number of type parameters. - */ - override getGenericArityOfClass(clazz: ClassDeclaration): number|null { - const dtsDeclaration = this.getDtsDeclaration(clazz); - if (dtsDeclaration && ts.isClassDeclaration(dtsDeclaration)) { - return dtsDeclaration.typeParameters ? dtsDeclaration.typeParameters.length : 0; - } - return null; - } - - /** - * Take an exported declaration of a class (maybe down-leveled to a variable) and look up the - * declaration of its type in a separate .d.ts tree. - * - * This function is allowed to return `null` if the current compilation unit does not have a - * separate .d.ts tree. When compiling TypeScript code this is always the case, since .d.ts files - * are produced only during the emit of such a compilation. When compiling .js code, however, - * there is frequently a parallel .d.ts tree which this method exposes. - * - * Note that the `ts.ClassDeclaration` returned from this function may not be from the same - * `ts.Program` as the input declaration. - */ - override getDtsDeclaration(declaration: DeclarationNode): ts.Declaration|null { - if (this.dts === null) { - return null; - } - if (!isNamedDeclaration(declaration)) { - throw new Error(`Cannot get the dts file for a declaration that has no name: ${ - declaration.getText()} in ${declaration.getSourceFile().fileName}`); - } - - const decl = this.getDeclarationOfIdentifier(declaration.name); - if (decl === null) { - throw new Error( - `Cannot get the dts file for a node that cannot be associated with a declaration ${ - declaration.getText()} in ${declaration.getSourceFile().fileName}`); - } - - // Try to retrieve the dts declaration from the public map - if (this.publicDtsDeclarationMap === null) { - this.publicDtsDeclarationMap = this.computePublicDtsDeclarationMap(this.src, this.dts); - } - if (this.publicDtsDeclarationMap.has(decl.node)) { - return this.publicDtsDeclarationMap.get(decl.node)!; - } - - // No public export, try the private map - if (this.privateDtsDeclarationMap === null) { - this.privateDtsDeclarationMap = this.computePrivateDtsDeclarationMap(this.src, this.dts); - } - if (this.privateDtsDeclarationMap.has(decl.node)) { - return this.privateDtsDeclarationMap.get(decl.node)!; - } - - // No declaration found at all - return null; - } - - getEndOfClass(classSymbol: NgccClassSymbol): ts.Node { - const implementation = classSymbol.implementation; - let last: ts.Node = implementation.valueDeclaration; - const implementationStatement = getContainingStatement(last); - if (implementationStatement === null) return last; - - const container = implementationStatement.parent; - if (ts.isBlock(container)) { - // Assume that the implementation is inside an IIFE - const returnStatementIndex = container.statements.findIndex(ts.isReturnStatement); - if (returnStatementIndex === -1) { - throw new Error( - `Compiled class wrapper IIFE does not have a return statement: ${classSymbol.name} in ${ - classSymbol.declaration.valueDeclaration.getSourceFile().fileName}`); - } - - // Return the statement before the IIFE return statement - last = container.statements[returnStatementIndex - 1]; - } else if (ts.isSourceFile(container)) { - // If there are static members on this class then find the last one - if (implementation.exports !== undefined) { - implementation.exports.forEach(exportSymbol => { - if (exportSymbol.valueDeclaration === undefined) { - return; - } - const exportStatement = getContainingStatement(exportSymbol.valueDeclaration); - if (exportStatement !== null && last.getEnd() < exportStatement.getEnd()) { - last = exportStatement; - } - }); - } - - // If there are helper calls for this class then find the last one - const helpers = this.getHelperCallsForClass( - classSymbol, ['__decorate', '__extends', '__param', '__metadata']); - helpers.forEach(helper => { - const helperStatement = getContainingStatement(helper); - if (helperStatement !== null && last.getEnd() < helperStatement.getEnd()) { - last = helperStatement; - } - }); - } - return last; - } - - /** - * Check whether a `Declaration` corresponds with a known declaration, such as `Object`, and set - * its `known` property to the appropriate `KnownDeclaration`. - * - * @param decl The `Declaration` to check. - * @return The passed in `Declaration` (potentially enhanced with a `KnownDeclaration`). - */ - detectKnownDeclaration(decl: T): T { - if (decl.known === null && this.isJavaScriptObjectDeclaration(decl)) { - // If the identifier resolves to the global JavaScript `Object`, update the declaration to - // denote it as the known `JsGlobalObject` declaration. - decl.known = KnownDeclaration.JsGlobalObject; - } - return decl; - } - - - ///////////// Protected Helpers ///////////// - - /** - * Extract all the "classes" from the `statement` and add them to the `classes` map. - */ - protected addClassSymbolsFromStatement( - classes: Map, statement: ts.Statement): void { - if (ts.isVariableStatement(statement)) { - statement.declarationList.declarations.forEach(declaration => { - const classSymbol = this.getClassSymbol(declaration); - if (classSymbol) { - classes.set(classSymbol.implementation, classSymbol); - } - }); - } else if (ts.isClassDeclaration(statement)) { - const classSymbol = this.getClassSymbol(statement); - if (classSymbol) { - classes.set(classSymbol.implementation, classSymbol); - } - } - } - - /** - * Compute the inner declaration node of a "class" from the given `declaration` node. - * - * @param declaration a node that is either an inner declaration or an alias of a class. - */ - protected getInnerDeclarationFromAliasOrInner(declaration: ts.Node): ts.Node { - if (declaration.parent !== undefined && isNamedVariableDeclaration(declaration.parent)) { - const variableValue = this.getVariableValue(declaration.parent); - if (variableValue !== null) { - declaration = variableValue; - } - } - return declaration; - } - - /** - * A class may be declared as a top level class declaration: - * - * ``` - * class OuterClass { ... } - * ``` - * - * or in a variable declaration to a class expression: - * - * ``` - * var OuterClass = ClassAlias = class InnerClass {}; - * ``` - * - * or in a variable declaration to an IIFE containing a class declaration - * - * ``` - * var OuterClass = ClassAlias = (() => { - * class InnerClass {} - * ... - * return InnerClass; - * })() - * ``` - * - * or in a variable declaration to an IIFE containing a function declaration - * - * ``` - * var OuterClass = ClassAlias = (() => { - * function InnerClass() {} - * ... - * return InnerClass; - * })() - * ``` - * - * This method returns an `NgccClassSymbol` when provided with one of these cases. - * - * @param declaration the declaration whose symbol we are finding. - * @returns the symbol for the class or `undefined` if `declaration` does not represent an outer - * declaration of a class. - */ - protected getClassSymbolFromOuterDeclaration(declaration: ts.Node): NgccClassSymbol|undefined { - // Return a class symbol without an inner declaration if it is a regular "top level" class - if (isNamedClassDeclaration(declaration) && isTopLevel(declaration)) { - return this.createClassSymbol(declaration.name, null); - } - - // Otherwise, an outer class declaration must be an initialized variable declaration: - if (!isInitializedVariableClassDeclaration(declaration)) { - return undefined; - } - - const innerDeclaration = getInnerClassDeclaration(skipClassAliases(declaration)); - if (innerDeclaration === null) { - return undefined; - } - - return this.createClassSymbol(declaration.name, innerDeclaration); - } - - /** - * In ES2015, a class may be declared using a variable declaration of the following structures: - * - * ``` - * let MyClass = MyClass_1 = class MyClass {}; - * ``` - * - * or - * - * ``` - * let MyClass = MyClass_1 = (() => { class MyClass {} ... return MyClass; })() - * ``` - * - * or - * - * ``` - * let MyClass = MyClass_1 = (() => { let MyClass = class MyClass {}; ... return MyClass; })() - * ``` - * - * This method extracts the `NgccClassSymbol` for `MyClass` when provided with the - * `class MyClass {}` declaration node. When the `var MyClass` node or any other node is given, - * this method will return undefined instead. - * - * @param declaration the declaration whose symbol we are finding. - * @returns the symbol for the node or `undefined` if it does not represent an inner declaration - * of a class. - */ - protected getClassSymbolFromInnerDeclaration(declaration: ts.Node): NgccClassSymbol|undefined { - let outerDeclaration: ts.ClassDeclaration|ts.VariableDeclaration|undefined = undefined; - - if (ts.isClassExpression(declaration) && hasNameIdentifier(declaration)) { - // Handle `let MyClass = MyClass_1 = class MyClass {};` - outerDeclaration = getFarLeftHandSideOfAssignment(declaration); - - // Handle this being in an IIFE - if (outerDeclaration !== undefined && !isTopLevel(outerDeclaration)) { - outerDeclaration = getContainingVariableDeclaration(outerDeclaration); - } - } else if (isNamedClassDeclaration(declaration)) { - // Handle `class MyClass {}` statement - if (isTopLevel(declaration)) { - // At the top level - outerDeclaration = declaration; - } else { - // Or inside an IIFE - outerDeclaration = getContainingVariableDeclaration(declaration); - } - } - - if (outerDeclaration === undefined || !hasNameIdentifier(outerDeclaration)) { - return undefined; - } - - return this.createClassSymbol(outerDeclaration.name, declaration); - } - - /** - * Creates an `NgccClassSymbol` from an outer and inner declaration. If a class only has an outer - * declaration, the "implementation" symbol of the created `NgccClassSymbol` will be set equal to - * the "declaration" symbol. - * - * @param outerDeclaration The outer declaration node of the class. - * @param innerDeclaration The inner declaration node of the class, or undefined if no inner - * declaration is present. - * @returns the `NgccClassSymbol` representing the class, or undefined if a `ts.Symbol` for any of - * the declarations could not be resolved. - */ - protected createClassSymbol(outerDeclaration: ts.Identifier, innerDeclaration: ts.Node|null): - NgccClassSymbol|undefined { - const declarationSymbol = - this.checker.getSymbolAtLocation(outerDeclaration) as ClassSymbol | undefined; - if (declarationSymbol === undefined) { - return undefined; - } - - let implementationSymbol: ts.Symbol|undefined = declarationSymbol; - if (innerDeclaration !== null && isNamedDeclaration(innerDeclaration)) { - implementationSymbol = this.checker.getSymbolAtLocation(innerDeclaration.name); - } - - if (!isSymbolWithValueDeclaration(implementationSymbol)) { - return undefined; - } - - const classSymbol: NgccClassSymbol = { - name: declarationSymbol.name, - declaration: declarationSymbol, - implementation: implementationSymbol, - adjacent: this.getAdjacentSymbol(declarationSymbol, implementationSymbol), - }; - - return classSymbol; - } - - private getAdjacentSymbol( - declarationSymbol: ClassSymbol, - implementationSymbol: SymbolWithValueDeclaration): SymbolWithValueDeclaration|undefined { - if (declarationSymbol === implementationSymbol) { - return undefined; - } - const innerDeclaration = implementationSymbol.valueDeclaration; - if (!ts.isClassExpression(innerDeclaration) && !ts.isFunctionExpression(innerDeclaration)) { - return undefined; - } - // Deal with the inner class looking like this inside an IIFE: - // `let MyClass = class MyClass {};` or `var MyClass = function MyClass() {};` - const adjacentDeclaration = getFarLeftHandSideOfAssignment(innerDeclaration); - if (adjacentDeclaration === undefined || !isNamedVariableDeclaration(adjacentDeclaration)) { - return undefined; - } - const adjacentSymbol = this.checker.getSymbolAtLocation(adjacentDeclaration.name); - if (adjacentSymbol === declarationSymbol || adjacentSymbol === implementationSymbol || - !isSymbolWithValueDeclaration(adjacentSymbol)) { - return undefined; - } - return adjacentSymbol; - } - - /** - * Resolve a `ts.Symbol` to its declaration and detect whether it corresponds with a known - * declaration. - */ - protected override getDeclarationOfSymbol(symbol: ts.Symbol, originalId: ts.Identifier|null): - Declaration|null { - const declaration = super.getDeclarationOfSymbol(symbol, originalId); - if (declaration === null) { - return null; - } - return this.detectKnownDeclaration(declaration); - } - - /** - * Finds the identifier of the actual class declaration for a potentially aliased declaration of a - * class. - * - * If the given declaration is for an alias of a class, this function will determine an identifier - * to the original declaration that represents this class. - * - * @param declaration The declaration to resolve. - * @returns The original identifier that the given class declaration resolves to, or `undefined` - * if the declaration does not represent an aliased class. - */ - protected resolveAliasedClassIdentifier(declaration: DeclarationNode): ts.Identifier|null { - this.ensurePreprocessed(declaration.getSourceFile()); - return this.aliasedClassDeclarations.has(declaration) ? - this.aliasedClassDeclarations.get(declaration)! : - null; - } - - /** - * Ensures that the source file that `node` is part of has been preprocessed. - * - * During preprocessing, all statements in the source file will be visited such that certain - * processing steps can be done up-front and cached for subsequent usages. - * - * @param sourceFile The source file that needs to have gone through preprocessing. - */ - protected ensurePreprocessed(sourceFile: ts.SourceFile): void { - if (!this.preprocessedSourceFiles.has(sourceFile)) { - this.preprocessedSourceFiles.add(sourceFile); - - for (const statement of this.getModuleStatements(sourceFile)) { - this.preprocessStatement(statement); - } - } - } - - /** - * Analyzes the given statement to see if it corresponds with a variable declaration like - * `let MyClass = MyClass_1 = class MyClass {};`. If so, the declaration of `MyClass_1` - * is associated with the `MyClass` identifier. - * - * @param statement The statement that needs to be preprocessed. - */ - protected preprocessStatement(statement: ts.Statement): void { - if (!ts.isVariableStatement(statement)) { - return; - } - - const declarations = statement.declarationList.declarations; - if (declarations.length !== 1) { - return; - } - - const declaration = declarations[0]; - const initializer = declaration.initializer; - if (!ts.isIdentifier(declaration.name) || !initializer || !isAssignment(initializer) || - !ts.isIdentifier(initializer.left) || !this.isClass(declaration)) { - return; - } - - const aliasedIdentifier = initializer.left; - - const aliasedDeclaration = this.getDeclarationOfIdentifier(aliasedIdentifier); - if (aliasedDeclaration === null) { - throw new Error( - `Unable to locate declaration of ${aliasedIdentifier.text} in "${statement.getText()}"`); - } - this.aliasedClassDeclarations.set(aliasedDeclaration.node, declaration.name); - } - - /** - * Get the top level statements for a module. - * - * In ES5 and ES2015 this is just the top level statements of the file. - * @param sourceFile The module whose statements we want. - * @returns An array of top level statements for the given module. - */ - protected getModuleStatements(sourceFile: ts.SourceFile): ts.Statement[] { - return Array.from(sourceFile.statements); - } - - /** - * Walk the AST looking for an assignment to the specified symbol. - * @param node The current node we are searching. - * @returns an expression that represents the value of the variable, or undefined if none can be - * found. - */ - protected findDecoratedVariableValue(node: ts.Node|undefined, symbol: ts.Symbol): - ts.CallExpression|null { - if (!node) { - return null; - } - if (ts.isBinaryExpression(node) && node.operatorToken.kind === ts.SyntaxKind.EqualsToken) { - const left = node.left; - const right = node.right; - if (ts.isIdentifier(left) && this.checker.getSymbolAtLocation(left) === symbol) { - return (ts.isCallExpression(right) && getCalleeName(right) === '__decorate') ? right : null; - } - return this.findDecoratedVariableValue(right, symbol); - } - return node.forEachChild(node => this.findDecoratedVariableValue(node, symbol)) || null; - } - - /** - * Try to retrieve the symbol of a static property on a class. - * - * In some cases, a static property can either be set on the inner (implementation or adjacent) - * declaration inside the class' IIFE, or it can be set on the outer variable declaration. - * Therefore, the host checks all places, first looking up the property on the inner symbols, and - * if the property is not found it will fall back to looking up the property on the outer symbol. - * - * @param symbol the class whose property we are interested in. - * @param propertyName the name of static property. - * @returns the symbol if it is found or `undefined` if not. - */ - protected getStaticProperty(symbol: NgccClassSymbol, propertyName: ts.__String): ts.Symbol - |undefined { - return symbol.implementation.exports?.get(propertyName) || - symbol.adjacent?.exports?.get(propertyName) || - symbol.declaration.exports?.get(propertyName); - } - - /** - * This is the main entry-point for obtaining information on the decorators of a given class. This - * information is computed either from static properties if present, or using `tslib.__decorate` - * helper calls otherwise. The computed result is cached per class. - * - * @param classSymbol the class for which decorators should be acquired. - * @returns all information of the decorators on the class. - */ - protected acquireDecoratorInfo(classSymbol: NgccClassSymbol): DecoratorInfo { - const decl = classSymbol.declaration.valueDeclaration; - if (this.decoratorCache.has(decl)) { - return this.decoratorCache.get(decl)!; - } - - // Extract decorators from static properties and `__decorate` helper calls, then merge them - // together where the information from the static properties is preferred. - const staticProps = this.computeDecoratorInfoFromStaticProperties(classSymbol); - const helperCalls = this.computeDecoratorInfoFromHelperCalls(classSymbol); - - const decoratorInfo: DecoratorInfo = { - classDecorators: staticProps.classDecorators || helperCalls.classDecorators, - memberDecorators: staticProps.memberDecorators || helperCalls.memberDecorators, - constructorParamInfo: staticProps.constructorParamInfo || helperCalls.constructorParamInfo, - }; - - this.decoratorCache.set(decl, decoratorInfo); - return decoratorInfo; - } - - /** - * Attempts to compute decorator information from static properties "decorators", "propDecorators" - * and "ctorParameters" on the class. If neither of these static properties is present the - * library is likely not compiled using tsickle for usage with Closure compiler, in which case - * `null` is returned. - * - * @param classSymbol The class symbol to compute the decorators information for. - * @returns All information on the decorators as extracted from static properties, or `null` if - * none of the static properties exist. - */ - protected computeDecoratorInfoFromStaticProperties(classSymbol: NgccClassSymbol): { - classDecorators: Decorator[]|null; memberDecorators: Map| null; - constructorParamInfo: ParamInfo[] | null; - } { - let classDecorators: Decorator[]|null = null; - let memberDecorators: Map|null = null; - let constructorParamInfo: ParamInfo[]|null = null; - - const decoratorsProperty = this.getStaticProperty(classSymbol, DECORATORS); - if (decoratorsProperty !== undefined) { - classDecorators = this.getClassDecoratorsFromStaticProperty(decoratorsProperty); - } - - const propDecoratorsProperty = this.getStaticProperty(classSymbol, PROP_DECORATORS); - if (propDecoratorsProperty !== undefined) { - memberDecorators = this.getMemberDecoratorsFromStaticProperty(propDecoratorsProperty); - } - - const constructorParamsProperty = this.getStaticProperty(classSymbol, CONSTRUCTOR_PARAMS); - if (constructorParamsProperty !== undefined) { - constructorParamInfo = this.getParamInfoFromStaticProperty(constructorParamsProperty); - } - - return {classDecorators, memberDecorators, constructorParamInfo}; - } - - /** - * Get all class decorators for the given class, where the decorators are declared - * via a static property. For example: - * - * ``` - * class SomeDirective {} - * SomeDirective.decorators = [ - * { type: Directive, args: [{ selector: '[someDirective]' },] } - * ]; - * ``` - * - * @param decoratorsSymbol the property containing the decorators we want to get. - * @returns an array of decorators or null if none where found. - */ - protected getClassDecoratorsFromStaticProperty(decoratorsSymbol: ts.Symbol): Decorator[]|null { - const decoratorsIdentifier = decoratorsSymbol.valueDeclaration; - if (decoratorsIdentifier && decoratorsIdentifier.parent) { - if (ts.isBinaryExpression(decoratorsIdentifier.parent) && - decoratorsIdentifier.parent.operatorToken.kind === ts.SyntaxKind.EqualsToken) { - // AST of the array of decorator values - const decoratorsArray = decoratorsIdentifier.parent.right; - return this.reflectDecorators(decoratorsArray) - .filter(decorator => this.isFromCore(decorator)); - } - } - return null; - } - - /** - * Examine a symbol which should be of a class, and return metadata about its members. - * - * @param symbol the `ClassSymbol` representing the class over which to reflect. - * @returns an array of `ClassMember` metadata representing the members of the class. - */ - protected getMembersOfSymbol(symbol: NgccClassSymbol): ClassMember[] { - const members: ClassMember[] = []; - - // The decorators map contains all the properties that are decorated - const {memberDecorators} = this.acquireDecoratorInfo(symbol); - - // Make a copy of the decorators as successfully reflected members delete themselves from the - // map, so that any leftovers can be easily dealt with. - const decoratorsMap = new Map(memberDecorators); - - // The member map contains all the method (instance and static); and any instance properties - // that are initialized in the class. - if (symbol.implementation.members) { - symbol.implementation.members.forEach((value, key) => { - const decorators = decoratorsMap.get(key as string); - const reflectedMembers = this.reflectMembers(value, decorators); - if (reflectedMembers) { - decoratorsMap.delete(key as string); - members.push(...reflectedMembers); - } - }); - } - - // The static property map contains all the static properties - if (symbol.implementation.exports) { - symbol.implementation.exports.forEach((value, key) => { - const decorators = decoratorsMap.get(key as string); - const reflectedMembers = this.reflectMembers(value, decorators, true); - if (reflectedMembers) { - decoratorsMap.delete(key as string); - members.push(...reflectedMembers); - } - }); - } - - // If this class was declared as a VariableDeclaration then it may have static properties - // attached to the variable rather than the class itself - // For example: - // ``` - // let MyClass = class MyClass { - // // no static properties here! - // } - // MyClass.staticProperty = ...; - // ``` - if (ts.isVariableDeclaration(symbol.declaration.valueDeclaration)) { - if (symbol.declaration.exports) { - symbol.declaration.exports.forEach((value, key) => { - const decorators = decoratorsMap.get(key as string); - const reflectedMembers = this.reflectMembers(value, decorators, true); - if (reflectedMembers) { - decoratorsMap.delete(key as string); - members.push(...reflectedMembers); - } - }); - } - } - - // If this class was declared as a VariableDeclaration inside an IIFE, then it may have static - // properties attached to the variable rather than the class itself. - // - // For example: - // ``` - // let OuterClass = (() => { - // let AdjacentClass = class InternalClass { - // // no static properties here! - // } - // AdjacentClass.staticProperty = ...; - // })(); - // ``` - if (symbol.adjacent !== undefined) { - if (ts.isVariableDeclaration(symbol.adjacent.valueDeclaration)) { - if (symbol.adjacent.exports !== undefined) { - symbol.adjacent.exports.forEach((value, key) => { - const decorators = decoratorsMap.get(key as string); - const reflectedMembers = this.reflectMembers(value, decorators, true); - if (reflectedMembers) { - decoratorsMap.delete(key as string); - members.push(...reflectedMembers); - } - }); - } - } - } - - // Deal with any decorated properties that were not initialized in the class - decoratorsMap.forEach((value, key) => { - members.push({ - implementation: null, - decorators: value, - isStatic: false, - kind: ClassMemberKind.Property, - name: key, - nameNode: null, - node: null, - type: null, - value: null - }); - }); - - return members; - } - - /** - * Member decorators may be declared as static properties of the class: - * - * ``` - * SomeDirective.propDecorators = { - * "ngForOf": [{ type: Input },], - * "ngForTrackBy": [{ type: Input },], - * "ngForTemplate": [{ type: Input },], - * }; - * ``` - * - * @param decoratorsProperty the class whose member decorators we are interested in. - * @returns a map whose keys are the name of the members and whose values are collections of - * decorators for the given member. - */ - protected getMemberDecoratorsFromStaticProperty(decoratorsProperty: ts.Symbol): - Map { - const memberDecorators = new Map(); - // Symbol of the identifier for `SomeDirective.propDecorators`. - const propDecoratorsMap = getPropertyValueFromSymbol(decoratorsProperty); - if (propDecoratorsMap && ts.isObjectLiteralExpression(propDecoratorsMap)) { - const propertiesMap = reflectObjectLiteral(propDecoratorsMap); - propertiesMap.forEach((value, name) => { - const decorators = - this.reflectDecorators(value).filter(decorator => this.isFromCore(decorator)); - if (decorators.length) { - memberDecorators.set(name, decorators); - } - }); - } - return memberDecorators; - } - - /** - * For a given class symbol, collects all decorator information from tslib helper methods, as - * generated by TypeScript into emitted JavaScript files. - * - * Class decorators are extracted from calls to `tslib.__decorate` that look as follows: - * - * ``` - * let SomeDirective = class SomeDirective {} - * SomeDirective = __decorate([ - * Directive({ selector: '[someDirective]' }), - * ], SomeDirective); - * ``` - * - * The extraction of member decorators is similar, with the distinction that its 2nd and 3rd - * argument correspond with a "prototype" target and the name of the member to which the - * decorators apply. - * - * ``` - * __decorate([ - * Input(), - * __metadata("design:type", String) - * ], SomeDirective.prototype, "input1", void 0); - * ``` - * - * @param classSymbol The class symbol for which decorators should be extracted. - * @returns All information on the decorators of the class. - */ - protected computeDecoratorInfoFromHelperCalls(classSymbol: NgccClassSymbol): DecoratorInfo { - let classDecorators: Decorator[]|null = null; - const memberDecorators = new Map(); - const constructorParamInfo: ParamInfo[] = []; - - const getConstructorParamInfo = (index: number) => { - let param = constructorParamInfo[index]; - if (param === undefined) { - param = constructorParamInfo[index] = {decorators: null, typeExpression: null}; - } - return param; - }; - - // All relevant information can be extracted from calls to `__decorate`, obtain these first. - // Note that although the helper calls are retrieved using the class symbol, the result may - // contain helper calls corresponding with unrelated classes. Therefore, each helper call still - // has to be checked to actually correspond with the class symbol. - const helperCalls = this.getHelperCallsForClass(classSymbol, ['__decorate']); - - const outerDeclaration = classSymbol.declaration.valueDeclaration; - const innerDeclaration = classSymbol.implementation.valueDeclaration; - const adjacentDeclaration = this.getAdjacentNameOfClassSymbol(classSymbol).parent; - const matchesClass = (identifier: ts.Identifier) => { - const decl = this.getDeclarationOfIdentifier(identifier); - return decl !== null && - (decl.node === adjacentDeclaration || decl.node === outerDeclaration || - decl.node === innerDeclaration); - }; - - for (const helperCall of helperCalls) { - if (isClassDecorateCall(helperCall, matchesClass)) { - // This `__decorate` call is targeting the class itself. - const helperArgs = helperCall.arguments[0]; - - for (const element of helperArgs.elements) { - const entry = this.reflectDecorateHelperEntry(element); - if (entry === null) { - continue; - } - - if (entry.type === 'decorator') { - // The helper arg was reflected to represent an actual decorator - if (this.isFromCore(entry.decorator)) { - (classDecorators || (classDecorators = [])).push(entry.decorator); - } - } else if (entry.type === 'param:decorators') { - // The helper arg represents a decorator for a parameter. Since it's applied to the - // class, it corresponds with a constructor parameter of the class. - const param = getConstructorParamInfo(entry.index); - (param.decorators || (param.decorators = [])).push(entry.decorator); - } else if (entry.type === 'params') { - // The helper arg represents the types of the parameters. Since it's applied to the - // class, it corresponds with the constructor parameters of the class. - entry.types.forEach( - (type, index) => getConstructorParamInfo(index).typeExpression = type); - } - } - } else if (isMemberDecorateCall(helperCall, matchesClass)) { - // The `__decorate` call is targeting a member of the class - const helperArgs = helperCall.arguments[0]; - const memberName = helperCall.arguments[2].text; - - for (const element of helperArgs.elements) { - const entry = this.reflectDecorateHelperEntry(element); - if (entry === null) { - continue; - } - - if (entry.type === 'decorator') { - // The helper arg was reflected to represent an actual decorator. - if (this.isFromCore(entry.decorator)) { - const decorators = - memberDecorators.has(memberName) ? memberDecorators.get(memberName)! : []; - decorators.push(entry.decorator); - memberDecorators.set(memberName, decorators); - } - } else { - // Information on decorated parameters is not interesting for ngcc, so it's ignored. - } - } - } - } - - return {classDecorators, memberDecorators, constructorParamInfo}; - } - - /** - * Extract the details of an entry within a `__decorate` helper call. For example, given the - * following code: - * - * ``` - * __decorate([ - * Directive({ selector: '[someDirective]' }), - * tslib_1.__param(2, Inject(INJECTED_TOKEN)), - * tslib_1.__metadata("design:paramtypes", [ViewContainerRef, TemplateRef, String]) - * ], SomeDirective); - * ``` - * - * it can be seen that there are calls to regular decorators (the `Directive`) and calls into - * `tslib` functions which have been inserted by TypeScript. Therefore, this function classifies - * a call to correspond with - * 1. a real decorator like `Directive` above, or - * 2. a decorated parameter, corresponding with `__param` calls from `tslib`, or - * 3. the type information of parameters, corresponding with `__metadata` call from `tslib` - * - * @param expression the expression that needs to be reflected into a `DecorateHelperEntry` - * @returns an object that indicates which of the three categories the call represents, together - * with the reflected information of the call, or null if the call is not a valid decorate call. - */ - protected reflectDecorateHelperEntry(expression: ts.Expression): DecorateHelperEntry|null { - // We only care about those elements that are actual calls - if (!ts.isCallExpression(expression)) { - return null; - } - const call = expression; - - const helperName = getCalleeName(call); - if (helperName === '__metadata') { - // This is a `tslib.__metadata` call, reflect to arguments into a `ParameterTypes` object - // if the metadata key is "design:paramtypes". - const key = call.arguments[0]; - if (key === undefined || !ts.isStringLiteral(key) || key.text !== 'design:paramtypes') { - return null; - } - - const value = call.arguments[1]; - if (value === undefined || !ts.isArrayLiteralExpression(value)) { - return null; - } - - return { - type: 'params', - types: Array.from(value.elements), - }; - } - - if (helperName === '__param') { - // This is a `tslib.__param` call that is reflected into a `ParameterDecorators` object. - const indexArg = call.arguments[0]; - const index = indexArg && ts.isNumericLiteral(indexArg) ? parseInt(indexArg.text, 10) : NaN; - if (isNaN(index)) { - return null; - } - - const decoratorCall = call.arguments[1]; - if (decoratorCall === undefined || !ts.isCallExpression(decoratorCall)) { - return null; - } - - const decorator = this.reflectDecoratorCall(decoratorCall); - if (decorator === null) { - return null; - } - - return { - type: 'param:decorators', - index, - decorator, - }; - } - - // Otherwise attempt to reflect it as a regular decorator. - const decorator = this.reflectDecoratorCall(call); - if (decorator === null) { - return null; - } - return { - type: 'decorator', - decorator, - }; - } - - protected reflectDecoratorCall(call: ts.CallExpression): Decorator|null { - const decoratorExpression = call.expression; - if (!isDecoratorIdentifier(decoratorExpression)) { - return null; - } - - // We found a decorator! - const decoratorIdentifier = - ts.isIdentifier(decoratorExpression) ? decoratorExpression : decoratorExpression.name; - - return { - name: decoratorIdentifier.text, - identifier: decoratorExpression, - import: this.getImportOfIdentifier(decoratorIdentifier), - node: call, - args: Array.from(call.arguments), - }; - } - - /** - * Check the given statement to see if it is a call to any of the specified helper functions or - * null if not found. - * - * Matching statements will look like: `tslib_1.__decorate(...);`. - * @param statement the statement that may contain the call. - * @param helperNames the names of the helper we are looking for. - * @returns the node that corresponds to the `__decorate(...)` call or null if the statement - * does not match. - */ - protected getHelperCall(statement: ts.Statement, helperNames: string[]): ts.CallExpression|null { - if ((ts.isExpressionStatement(statement) || ts.isReturnStatement(statement)) && - statement.expression) { - let expression = statement.expression; - while (isAssignment(expression)) { - expression = expression.right; - } - if (ts.isCallExpression(expression)) { - const calleeName = getCalleeName(expression); - if (calleeName !== null && helperNames.includes(calleeName)) { - return expression; - } - } - } - return null; - } - - - /** - * Reflect over the given array node and extract decorator information from each element. - * - * This is used for decorators that are defined in static properties. For example: - * - * ``` - * SomeDirective.decorators = [ - * { type: Directive, args: [{ selector: '[someDirective]' },] } - * ]; - * ``` - * - * @param decoratorsArray an expression that contains decorator information. - * @returns an array of decorator info that was reflected from the array node. - */ - protected reflectDecorators(decoratorsArray: ts.Expression): Decorator[] { - const decorators: Decorator[] = []; - - if (ts.isArrayLiteralExpression(decoratorsArray)) { - // Add each decorator that is imported from `@angular/core` into the `decorators` array - decoratorsArray.elements.forEach(node => { - // If the decorator is not an object literal expression then we are not interested - if (ts.isObjectLiteralExpression(node)) { - // We are only interested in objects of the form: `{ type: DecoratorType, args: [...] }` - const decorator = reflectObjectLiteral(node); - - // Is the value of the `type` property an identifier? - if (decorator.has('type')) { - let decoratorType = decorator.get('type')!; - if (isDecoratorIdentifier(decoratorType)) { - const decoratorIdentifier = - ts.isIdentifier(decoratorType) ? decoratorType : decoratorType.name; - decorators.push({ - name: decoratorIdentifier.text, - identifier: decoratorType, - import: this.getImportOfIdentifier(decoratorIdentifier), - node, - args: getDecoratorArgs(node), - }); - } - } - } - }); - } - return decorators; - } - - /** - * Reflect over a symbol and extract the member information, combining it with the - * provided decorator information, and whether it is a static member. - * - * A single symbol may represent multiple class members in the case of accessors; - * an equally named getter/setter accessor pair is combined into a single symbol. - * When the symbol is recognized as representing an accessor, its declarations are - * analyzed such that both the setter and getter accessor are returned as separate - * class members. - * - * One difference wrt the TypeScript host is that in ES2015, we cannot see which - * accessor originally had any decorators applied to them, as decorators are applied - * to the property descriptor in general, not a specific accessor. If an accessor - * has both a setter and getter, any decorators are only attached to the setter member. - * - * @param symbol the symbol for the member to reflect over. - * @param decorators an array of decorators associated with the member. - * @param isStatic true if this member is static, false if it is an instance property. - * @returns the reflected member information, or null if the symbol is not a member. - */ - protected reflectMembers(symbol: ts.Symbol, decorators?: Decorator[], isStatic?: boolean): - ClassMember[]|null { - if (symbol.flags & ts.SymbolFlags.Accessor) { - const members: ClassMember[] = []; - const setter = symbol.declarations && symbol.declarations.find(ts.isSetAccessor); - const getter = symbol.declarations && symbol.declarations.find(ts.isGetAccessor); - - const setterMember = - setter && this.reflectMember(setter, ClassMemberKind.Setter, decorators, isStatic); - if (setterMember) { - members.push(setterMember); - - // Prevent attaching the decorators to a potential getter. In ES2015, we can't tell where - // the decorators were originally attached to, however we only want to attach them to a - // single `ClassMember` as otherwise ngtsc would handle the same decorators twice. - decorators = undefined; - } - - const getterMember = - getter && this.reflectMember(getter, ClassMemberKind.Getter, decorators, isStatic); - if (getterMember) { - members.push(getterMember); - } - - return members; - } - - let kind: ClassMemberKind|null = null; - if (symbol.flags & ts.SymbolFlags.Method) { - kind = ClassMemberKind.Method; - } else if (symbol.flags & ts.SymbolFlags.Property) { - kind = ClassMemberKind.Property; - } - - const node = symbol.valueDeclaration || symbol.declarations && symbol.declarations[0]; - if (!node) { - // If the symbol has been imported from a TypeScript typings file then the compiler - // may pass the `prototype` symbol as an export of the class. - // But this has no declaration. In this case we just quietly ignore it. - return null; - } - - const member = this.reflectMember(node, kind, decorators, isStatic); - if (!member) { - return null; - } - - return [member]; - } - - /** - * Reflect over a symbol and extract the member information, combining it with the - * provided decorator information, and whether it is a static member. - * @param node the declaration node for the member to reflect over. - * @param kind the assumed kind of the member, may become more accurate during reflection. - * @param decorators an array of decorators associated with the member. - * @param isStatic true if this member is static, false if it is an instance property. - * @returns the reflected member information, or null if the symbol is not a member. - */ - protected reflectMember( - node: ts.Declaration, kind: ClassMemberKind|null, decorators?: Decorator[], - isStatic?: boolean): ClassMember|null { - let value: ts.Expression|null = null; - let name: string|null = null; - let nameNode: ts.Identifier|null = null; - - if (!isClassMemberType(node)) { - return null; - } - - if (isStatic && isPropertyAccess(node)) { - name = node.name.text; - value = kind === ClassMemberKind.Property ? node.parent.right : null; - } else if (isThisAssignment(node)) { - kind = ClassMemberKind.Property; - name = node.left.name.text; - value = node.right; - isStatic = false; - } else if (ts.isConstructorDeclaration(node)) { - kind = ClassMemberKind.Constructor; - name = 'constructor'; - isStatic = false; - } - - if (kind === null) { - this.logger.warn(`Unknown member type: "${node.getText()}`); - return null; - } - - if (!name) { - if (isNamedDeclaration(node)) { - name = node.name.text; - nameNode = node.name; - } else { - return null; - } - } - - // If we have still not determined if this is a static or instance member then - // look for the `static` keyword on the declaration - if (isStatic === undefined) { - const modifiers = ts.canHaveModifiers(node) ? ts.getModifiers(node) : undefined; - isStatic = modifiers !== undefined && - modifiers.some(mod => mod.kind === ts.SyntaxKind.StaticKeyword); - } - - const type: ts.TypeNode = (node as any).type || null; - return { - node, - implementation: node, - kind, - type, - name, - nameNode, - value, - isStatic, - decorators: decorators || [] - }; - } - - /** - * Find the declarations of the constructor parameters of a class identified by its symbol. - * @param classSymbol the class whose parameters we want to find. - * @returns an array of `ts.ParameterDeclaration` objects representing each of the parameters in - * the class's constructor or null if there is no constructor. - */ - protected getConstructorParameterDeclarations(classSymbol: NgccClassSymbol): - ts.ParameterDeclaration[]|null { - const members = classSymbol.implementation.members; - if (members && members.has(CONSTRUCTOR)) { - const constructorSymbol = members.get(CONSTRUCTOR)!; - // For some reason the constructor does not have a `valueDeclaration` ?!? - const constructor = constructorSymbol.declarations && - constructorSymbol.declarations[0] as ts.ConstructorDeclaration | undefined; - if (!constructor) { - return []; - } - if (constructor.parameters.length > 0) { - return Array.from(constructor.parameters); - } - if (isSynthesizedConstructor(constructor)) { - return null; - } - return []; - } - return null; - } - - /** - * Get the parameter decorators of a class constructor. - * - * @param classSymbol the class whose parameter info we want to get. - * @param parameterNodes the array of TypeScript parameter nodes for this class's constructor. - * @returns an array of constructor parameter info objects. - */ - protected getConstructorParamInfo( - classSymbol: NgccClassSymbol, parameterNodes: ts.ParameterDeclaration[]): CtorParameter[] { - const {constructorParamInfo} = this.acquireDecoratorInfo(classSymbol); - - return parameterNodes.map((node, index) => { - const {decorators, typeExpression} = constructorParamInfo[index] ? - constructorParamInfo[index] : - {decorators: null, typeExpression: null}; - const nameNode = node.name; - const typeValueReference = this.typeToValue(typeExpression); - - return { - name: getNameText(nameNode), - nameNode, - typeValueReference, - typeNode: null, - decorators - }; - }); - } - - /** - * Compute the `TypeValueReference` for the given `typeExpression`. - * - * Although `typeExpression` is a valid `ts.Expression` that could be emitted directly into the - * generated code, ngcc still needs to resolve the declaration and create an `IMPORTED` type - * value reference as the compiler has specialized handling for some symbols, for example - * `ChangeDetectorRef` from `@angular/core`. Such an `IMPORTED` type value reference will result - * in a newly generated namespace import, instead of emitting the original `typeExpression` as is. - */ - private typeToValue(typeExpression: ts.Expression|null): TypeValueReference { - if (typeExpression === null) { - return { - kind: TypeValueReferenceKind.UNAVAILABLE, - reason: {kind: ValueUnavailableKind.MISSING_TYPE}, - }; - } - - const imp = this.getImportOfExpression(typeExpression); - const decl = this.getDeclarationOfExpression(typeExpression); - if (imp === null || decl === null) { - return { - kind: TypeValueReferenceKind.LOCAL, - expression: typeExpression, - defaultImportStatement: null, - }; - } - - return { - kind: TypeValueReferenceKind.IMPORTED, - valueDeclaration: decl.node, - moduleName: imp.from, - importedName: imp.name, - nestedPath: null, - }; - } - - /** - * Determines where the `expression` is imported from. - * - * @param expression the expression to determine the import details for. - * @returns the `Import` for the expression, or `null` if the expression is not imported or the - * expression syntax is not supported. - */ - private getImportOfExpression(expression: ts.Expression): Import|null { - if (ts.isIdentifier(expression)) { - return this.getImportOfIdentifier(expression); - } else if (ts.isPropertyAccessExpression(expression) && ts.isIdentifier(expression.name)) { - return this.getImportOfIdentifier(expression.name); - } else { - return null; - } - } - - /** - * Get the parameter type and decorators for the constructor of a class, - * where the information is stored on a static property of the class. - * - * Note that in ESM2015, the property is defined an array, or by an arrow function that returns - * an array, of decorator and type information. - * - * For example, - * - * ``` - * SomeDirective.ctorParameters = () => [ - * {type: ViewContainerRef}, - * {type: TemplateRef}, - * {type: undefined, decorators: [{ type: Inject, args: [INJECTED_TOKEN]}]}, - * ]; - * ``` - * - * or - * - * ``` - * SomeDirective.ctorParameters = [ - * {type: ViewContainerRef}, - * {type: TemplateRef}, - * {type: undefined, decorators: [{type: Inject, args: [INJECTED_TOKEN]}]}, - * ]; - * ``` - * - * @param paramDecoratorsProperty the property that holds the parameter info we want to get. - * @returns an array of objects containing the type and decorators for each parameter. - */ - protected getParamInfoFromStaticProperty(paramDecoratorsProperty: ts.Symbol): ParamInfo[]|null { - const paramDecorators = getPropertyValueFromSymbol(paramDecoratorsProperty); - if (paramDecorators) { - // The decorators array may be wrapped in an arrow function. If so unwrap it. - const container = - ts.isArrowFunction(paramDecorators) ? paramDecorators.body : paramDecorators; - if (ts.isArrayLiteralExpression(container)) { - const elements = container.elements; - return elements - .map( - element => - ts.isObjectLiteralExpression(element) ? reflectObjectLiteral(element) : null) - .map(paramInfo => { - const typeExpression = - paramInfo && paramInfo.has('type') ? paramInfo.get('type')! : null; - const decoratorInfo = - paramInfo && paramInfo.has('decorators') ? paramInfo.get('decorators')! : null; - const decorators = decoratorInfo && - this.reflectDecorators(decoratorInfo) - .filter(decorator => this.isFromCore(decorator)); - return {typeExpression, decorators}; - }); - } else if (paramDecorators !== undefined) { - this.logger.warn( - 'Invalid constructor parameter decorator in ' + - paramDecorators.getSourceFile().fileName + ':\n', - paramDecorators.getText()); - } - } - return null; - } - - /** - * Search statements related to the given class for calls to the specified helper. - * @param classSymbol the class whose helper calls we are interested in. - * @param helperNames the names of the helpers (e.g. `__decorate`) whose calls we are interested - * in. - * @returns an array of CallExpression nodes for each matching helper call. - */ - protected getHelperCallsForClass(classSymbol: NgccClassSymbol, helperNames: string[]): - ts.CallExpression[] { - return this.getStatementsForClass(classSymbol) - .map(statement => this.getHelperCall(statement, helperNames)) - .filter(isDefined); - } - - /** - * Find statements related to the given class that may contain calls to a helper. - * - * In ESM2015 code the helper calls are in the top level module, so we have to consider - * all the statements in the module. - * - * @param classSymbol the class whose helper calls we are interested in. - * @returns an array of statements that may contain helper calls. - */ - protected getStatementsForClass(classSymbol: NgccClassSymbol): ts.Statement[] { - const classNode = classSymbol.implementation.valueDeclaration; - if (isTopLevel(classNode)) { - return this.getModuleStatements(classNode.getSourceFile()); - } - const statement = getContainingStatement(classNode); - if (ts.isBlock(statement.parent)) { - return Array.from(statement.parent.statements); - } - // We should never arrive here - throw new Error(`Unable to find adjacent statements for ${classSymbol.name}`); - } - - /** - * Test whether a decorator was imported from `@angular/core`. - * - * Is the decorator: - * * externally imported from `@angular/core`? - * * the current hosted program is actually `@angular/core` and - * - relatively internally imported; or - * - not imported, from the current file. - * - * @param decorator the decorator to test. - */ - protected isFromCore(decorator: Decorator): boolean { - if (this.isCore) { - return !decorator.import || /^\./.test(decorator.import.from); - } else { - return !!decorator.import && decorator.import.from === '@angular/core'; - } - } - - /** - * Create a mapping between the public exports in a src program and the public exports of a dts - * program. - * - * @param src the program bundle containing the source files. - * @param dts the program bundle containing the typings files. - * @returns a map of source declarations to typings declarations. - */ - protected computePublicDtsDeclarationMap(src: BundleProgram, dts: BundleProgram): - Map { - const declarationMap = new Map(); - const dtsDeclarationMap = new Map(); - const rootDts = getRootFileOrFail(dts); - this.collectDtsExportedDeclarations(dtsDeclarationMap, rootDts, dts.program.getTypeChecker()); - const rootSrc = getRootFileOrFail(src); - this.collectSrcExportedDeclarations(declarationMap, dtsDeclarationMap, rootSrc); - return declarationMap; - } - - /** - * Create a mapping between the "private" exports in a src program and the "private" exports of a - * dts program. These exports may be exported from individual files in the src or dts programs, - * but not exported from the root file (i.e publicly from the entry-point). - * - * This mapping is a "best guess" since we cannot guarantee that two declarations that happen to - * be exported from a file with the same name are actually equivalent. But this is a reasonable - * estimate for the purposes of ngcc. - * - * @param src the program bundle containing the source files. - * @param dts the program bundle containing the typings files. - * @returns a map of source declarations to typings declarations. - */ - protected computePrivateDtsDeclarationMap(src: BundleProgram, dts: BundleProgram): - Map { - const declarationMap = new Map(); - const dtsDeclarationMap = new Map(); - const typeChecker = dts.program.getTypeChecker(); - - const dtsFiles = getNonRootPackageFiles(dts); - for (const dtsFile of dtsFiles) { - this.collectDtsExportedDeclarations(dtsDeclarationMap, dtsFile, typeChecker); - } - - const srcFiles = getNonRootPackageFiles(src); - for (const srcFile of srcFiles) { - this.collectSrcExportedDeclarations(declarationMap, dtsDeclarationMap, srcFile); - } - return declarationMap; - } - - /** - * Collect mappings between names of exported declarations in a file and its actual declaration. - * - * Any new mappings are added to the `dtsDeclarationMap`. - */ - protected collectDtsExportedDeclarations( - dtsDeclarationMap: Map, srcFile: ts.SourceFile, - checker: ts.TypeChecker): void { - const srcModule = srcFile && checker.getSymbolAtLocation(srcFile); - const moduleExports = srcModule && checker.getExportsOfModule(srcModule); - if (moduleExports) { - moduleExports.forEach(exportedSymbol => { - const name = exportedSymbol.name; - if (exportedSymbol.flags & ts.SymbolFlags.Alias) { - exportedSymbol = checker.getAliasedSymbol(exportedSymbol); - } - const declaration = exportedSymbol.valueDeclaration; - if (declaration && !dtsDeclarationMap.has(name)) { - dtsDeclarationMap.set(name, declaration); - } - }); - } - } - - - protected collectSrcExportedDeclarations( - declarationMap: Map, - dtsDeclarationMap: Map, srcFile: ts.SourceFile): void { - const fileExports = this.getExportsOfModule(srcFile); - if (fileExports !== null) { - for (const [exportName, {node: declarationNode}] of fileExports) { - if (dtsDeclarationMap.has(exportName)) { - declarationMap.set(declarationNode, dtsDeclarationMap.get(exportName)!); - } - } - } - } - - protected getDeclarationOfExpression(expression: ts.Expression): Declaration|null { - if (ts.isIdentifier(expression)) { - return this.getDeclarationOfIdentifier(expression); - } - - if (!ts.isPropertyAccessExpression(expression) || !ts.isIdentifier(expression.expression)) { - return null; - } - - const namespaceDecl = this.getDeclarationOfIdentifier(expression.expression); - if (!namespaceDecl || !ts.isSourceFile(namespaceDecl.node)) { - return null; - } - - const namespaceExports = this.getExportsOfModule(namespaceDecl.node); - if (namespaceExports === null) { - return null; - } - - if (!namespaceExports.has(expression.name.text)) { - return null; - } - - const exportDecl = namespaceExports.get(expression.name.text)!; - return {...exportDecl, viaModule: namespaceDecl.viaModule}; - } - - /** Checks if the specified declaration resolves to the known JavaScript global `Object`. */ - protected isJavaScriptObjectDeclaration(decl: Declaration): boolean { - const node = decl.node; - // The default TypeScript library types the global `Object` variable through - // a variable declaration with a type reference resolving to `ObjectConstructor`. - if (!ts.isVariableDeclaration(node) || !ts.isIdentifier(node.name) || - node.name.text !== 'Object' || node.type === undefined) { - return false; - } - const typeNode = node.type; - // If the variable declaration does not have a type resolving to `ObjectConstructor`, - // we cannot guarantee that the declaration resolves to the global `Object` variable. - if (!ts.isTypeReferenceNode(typeNode) || !ts.isIdentifier(typeNode.typeName) || - typeNode.typeName.text !== 'ObjectConstructor') { - return false; - } - // Finally, check if the type definition for `Object` originates from a default library - // definition file. This requires default types to be enabled for the host program. - return this.src.program.isSourceFileDefaultLibrary(node.getSourceFile()); - } - - /** - * In JavaScript, enum declarations are emitted as a regular variable declaration followed by an - * IIFE in which the enum members are assigned. - * - * export var Enum; - * (function (Enum) { - * Enum["a"] = "A"; - * Enum["b"] = "B"; - * })(Enum || (Enum = {})); - * - * @param declaration A variable declaration that may represent an enum - * @returns An array of enum members if the variable declaration is followed by an IIFE that - * declares the enum members, or null otherwise. - */ - protected resolveEnumMembers(declaration: ts.VariableDeclaration): EnumMember[]|null { - // Initialized variables don't represent enum declarations. - if (declaration.initializer !== undefined) return null; - - const variableStmt = declaration.parent.parent; - if (!ts.isVariableStatement(variableStmt)) return null; - - const block = variableStmt.parent; - if (!ts.isBlock(block) && !ts.isSourceFile(block)) return null; - - const declarationIndex = block.statements.findIndex(statement => statement === variableStmt); - if (declarationIndex === -1 || declarationIndex === block.statements.length - 1) return null; - - const subsequentStmt = block.statements[declarationIndex + 1]; - if (!ts.isExpressionStatement(subsequentStmt)) return null; - - const iife = stripParentheses(subsequentStmt.expression); - if (!ts.isCallExpression(iife) || !isEnumDeclarationIife(iife)) return null; - - const fn = stripParentheses(iife.expression); - if (!ts.isFunctionExpression(fn)) return null; - - return this.reflectEnumMembers(fn); - } - - /** - * Attempts to extract all `EnumMember`s from a function that is according to the JavaScript emit - * format for enums: - * - * function (Enum) { - * Enum["MemberA"] = "a"; - * Enum["MemberB"] = "b"; - * } - * - * @param fn The function expression that is assumed to contain enum members. - * @returns All enum members if the function is according to the correct syntax, null otherwise. - */ - private reflectEnumMembers(fn: ts.FunctionExpression): EnumMember[]|null { - if (fn.parameters.length !== 1) return null; - - const enumName = fn.parameters[0].name; - if (!ts.isIdentifier(enumName)) return null; - - const enumMembers: EnumMember[] = []; - for (const statement of fn.body.statements) { - const enumMember = this.reflectEnumMember(enumName, statement); - if (enumMember === null) { - return null; - } - enumMembers.push(enumMember); - } - return enumMembers; - } - - /** - * Attempts to extract a single `EnumMember` from a statement in the following syntax: - * - * Enum["MemberA"] = "a"; - * - * or, for enum member with numeric values: - * - * Enum[Enum["MemberA"] = 0] = "MemberA"; - * - * @param enumName The identifier of the enum that the members should be set on. - * @param statement The statement to inspect. - * @returns An `EnumMember` if the statement is according to the expected syntax, null otherwise. - */ - protected reflectEnumMember(enumName: ts.Identifier, statement: ts.Statement): EnumMember|null { - if (!ts.isExpressionStatement(statement)) return null; - - const expression = statement.expression; - - // Check for the `Enum[X] = Y;` case. - if (!isEnumAssignment(enumName, expression)) { - return null; - } - const assignment = reflectEnumAssignment(expression); - if (assignment != null) { - return assignment; - } - - // Check for the `Enum[Enum[X] = Y] = ...;` case. - const innerExpression = expression.left.argumentExpression; - if (!isEnumAssignment(enumName, innerExpression)) { - return null; - } - return reflectEnumAssignment(innerExpression); - } - - private getAdjacentNameOfClassSymbol(classSymbol: NgccClassSymbol): ts.Identifier { - if (classSymbol.adjacent !== undefined) { - return this.getNameFromClassSymbolDeclaration( - classSymbol, classSymbol.adjacent.valueDeclaration); - } else { - return this.getNameFromClassSymbolDeclaration( - classSymbol, classSymbol.implementation.valueDeclaration); - } - } -} - -///////////// Exported Helpers ///////////// - -/** - * Checks whether the iife has the following call signature: - * - * (Enum || (Enum = {}) - * - * Note that the `Enum` identifier is not checked, as it could also be something - * like `exports.Enum`. Instead, only the structure of binary operators is checked. - * - * @param iife The call expression to check. - * @returns true if the iife has a call signature that corresponds with a potential - * enum declaration. - */ -function isEnumDeclarationIife(iife: ts.CallExpression): boolean { - if (iife.arguments.length !== 1) return false; - - const arg = iife.arguments[0]; - if (!ts.isBinaryExpression(arg) || arg.operatorToken.kind !== ts.SyntaxKind.BarBarToken || - !ts.isParenthesizedExpression(arg.right)) { - return false; - } - - const right = arg.right.expression; - if (!ts.isBinaryExpression(right) || right.operatorToken.kind !== ts.SyntaxKind.EqualsToken) { - return false; - } - - if (!ts.isObjectLiteralExpression(right.right) || right.right.properties.length !== 0) { - return false; - } - - return true; -} - -/** - * An enum member assignment that looks like `Enum[X] = Y;`. - */ -export type EnumMemberAssignment = ts.BinaryExpression&{left: ts.ElementAccessExpression}; - -/** - * Checks whether the expression looks like an enum member assignment targeting `Enum`: - * - * Enum[X] = Y; - * - * Here, X and Y can be any expression. - * - * @param enumName The identifier of the enum that the members should be set on. - * @param expression The expression that should be checked to conform to the above form. - * @returns true if the expression is of the correct form, false otherwise. - */ -function isEnumAssignment( - enumName: ts.Identifier, expression: ts.Expression): expression is EnumMemberAssignment { - if (!ts.isBinaryExpression(expression) || - expression.operatorToken.kind !== ts.SyntaxKind.EqualsToken || - !ts.isElementAccessExpression(expression.left)) { - return false; - } - - // Verify that the outer assignment corresponds with the enum declaration. - const enumIdentifier = expression.left.expression; - return ts.isIdentifier(enumIdentifier) && enumIdentifier.text === enumName.text; -} - -/** - * Attempts to create an `EnumMember` from an expression that is believed to represent an enum - * assignment. - * - * @param expression The expression that is believed to be an enum assignment. - * @returns An `EnumMember` or null if the expression did not represent an enum member after all. - */ -function reflectEnumAssignment(expression: EnumMemberAssignment): EnumMember|null { - const memberName = expression.left.argumentExpression; - if (!ts.isPropertyName(memberName)) return null; - - return {name: memberName, initializer: expression.right}; -} - -export type ParamInfo = { - decorators: Decorator[]|null, - typeExpression: ts.Expression|null -}; - -/** - * Represents a call to `tslib.__metadata` as present in `tslib.__decorate` calls. This is a - * synthetic decorator inserted by TypeScript that contains reflection information about the - * target of the decorator, i.e. the class or property. - */ -export interface ParameterTypes { - type: 'params'; - types: ts.Expression[]; -} - -/** - * Represents a call to `tslib.__param` as present in `tslib.__decorate` calls. This contains - * information on any decorators were applied to a certain parameter. - */ -export interface ParameterDecorators { - type: 'param:decorators'; - index: number; - decorator: Decorator; -} - -/** - * Represents a call to a decorator as it was present in the original source code, as present in - * `tslib.__decorate` calls. - */ -export interface DecoratorCall { - type: 'decorator'; - decorator: Decorator; -} - -/** - * Represents the different kinds of decorate helpers that may be present as first argument to - * `tslib.__decorate`, as follows: - * - * ``` - * __decorate([ - * Directive({ selector: '[someDirective]' }), - * tslib_1.__param(2, Inject(INJECTED_TOKEN)), - * tslib_1.__metadata("design:paramtypes", [ViewContainerRef, TemplateRef, String]) - * ], SomeDirective); - * ``` - */ -export type DecorateHelperEntry = ParameterTypes|ParameterDecorators|DecoratorCall; - -/** - * The recorded decorator information of a single class. This information is cached in the host. - */ -interface DecoratorInfo { - /** - * All decorators that were present on the class. If no decorators were present, this is `null` - */ - classDecorators: Decorator[]|null; - - /** - * All decorators per member of the class they were present on. - */ - memberDecorators: Map; - - /** - * Represents the constructor parameter information, such as the type of a parameter and all - * decorators for a certain parameter. Indices in this array correspond with the parameter's - * index in the constructor. Note that this array may be sparse, i.e. certain constructor - * parameters may not have any info recorded. - */ - constructorParamInfo: ParamInfo[]; -} - -/** - * A statement node that represents an assignment. - */ -export type AssignmentStatement = - ts.ExpressionStatement&{expression: {left: ts.Identifier, right: ts.Expression}}; - -/** - * Test whether a statement node is an assignment statement. - * @param statement the statement to test. - */ -export function isAssignmentStatement(statement: ts.Statement): statement is AssignmentStatement { - return ts.isExpressionStatement(statement) && isAssignment(statement.expression) && - ts.isIdentifier(statement.expression.left); -} - -/** - * Parse the `expression` that is believed to be an IIFE and return the AST node that corresponds to - * the body of the IIFE. - * - * The expression may be wrapped in parentheses, which are stripped off. - * - * If the IIFE is an arrow function then its body could be a `ts.Expression` rather than a - * `ts.FunctionBody`. - * - * @param expression the expression to parse. - * @returns the `ts.Expression` or `ts.FunctionBody` that holds the body of the IIFE or `undefined` - * if the `expression` did not have the correct shape. - */ -export function getIifeBody(expression: ts.Expression): ts.ConciseBody|undefined { - const call = stripParentheses(expression); - if (!ts.isCallExpression(call)) { - return undefined; - } - - const fn = stripParentheses(call.expression); - if (!ts.isFunctionExpression(fn) && !ts.isArrowFunction(fn)) { - return undefined; - } - - return fn.body; -} - -/** - * Returns true if the `node` is an assignment of the form `a = b`. - * - * @param node The AST node to check. - */ -export function isAssignment(node: ts.Node): node is ts.AssignmentExpression { - return ts.isBinaryExpression(node) && node.operatorToken.kind === ts.SyntaxKind.EqualsToken; -} - -/** - * Tests whether the provided call expression targets a class, by verifying its arguments are - * according to the following form: - * - * ``` - * __decorate([], SomeDirective); - * ``` - * - * @param call the call expression that is tested to represent a class decorator call. - * @param matches predicate function to test whether the call is associated with the desired class. - */ -export function isClassDecorateCall( - call: ts.CallExpression, matches: (identifier: ts.Identifier) => boolean): - call is ts.CallExpression&{arguments: [ts.ArrayLiteralExpression, ts.Expression]} { - const helperArgs = call.arguments[0]; - if (helperArgs === undefined || !ts.isArrayLiteralExpression(helperArgs)) { - return false; - } - - const target = call.arguments[1]; - return target !== undefined && ts.isIdentifier(target) && matches(target); -} - -/** - * Tests whether the provided call expression targets a member of the class, by verifying its - * arguments are according to the following form: - * - * ``` - * __decorate([], SomeDirective.prototype, "member", void 0); - * ``` - * - * @param call the call expression that is tested to represent a member decorator call. - * @param matches predicate function to test whether the call is associated with the desired class. - */ -export function isMemberDecorateCall( - call: ts.CallExpression, matches: (identifier: ts.Identifier) => boolean): - call is ts.CallExpression& - {arguments: [ts.ArrayLiteralExpression, ts.StringLiteral, ts.StringLiteral]} { - const helperArgs = call.arguments[0]; - if (helperArgs === undefined || !ts.isArrayLiteralExpression(helperArgs)) { - return false; - } - - const target = call.arguments[1]; - if (target === undefined || !ts.isPropertyAccessExpression(target) || - !ts.isIdentifier(target.expression) || !matches(target.expression) || - target.name.text !== 'prototype') { - return false; - } - - const memberName = call.arguments[2]; - return memberName !== undefined && ts.isStringLiteral(memberName); -} - -/** - * Helper method to extract the value of a property given the property's "symbol", - * which is actually the symbol of the identifier of the property. - */ -export function getPropertyValueFromSymbol(propSymbol: ts.Symbol): ts.Expression|undefined { - const propIdentifier = propSymbol.valueDeclaration; - const parent = propIdentifier && propIdentifier.parent; - return parent && ts.isBinaryExpression(parent) ? parent.right : undefined; -} - -/** - * A callee could be one of: `__decorate(...)` or `tslib_1.__decorate`. - */ -function getCalleeName(call: ts.CallExpression): string|null { - if (ts.isIdentifier(call.expression)) { - return stripDollarSuffix(call.expression.text); - } - if (ts.isPropertyAccessExpression(call.expression)) { - return stripDollarSuffix(call.expression.name.text); - } - return null; -} - -///////////// Internal Helpers ///////////// - -type InitializedVariableClassDeclaration = - ClassDeclaration&{initializer: ts.Expression}; - -function isInitializedVariableClassDeclaration(node: ts.Node): - node is InitializedVariableClassDeclaration { - return isNamedVariableDeclaration(node) && node.initializer !== undefined; -} - -/** - * Handle a variable declaration of the form - * - * ``` - * var MyClass = alias1 = alias2 = <> - * ``` - * - * @param node the LHS of a variable declaration. - * @returns the original AST node or the RHS of a series of assignments in a variable - * declaration. - */ -export function skipClassAliases(node: InitializedVariableClassDeclaration): ts.Expression { - let expression = node.initializer; - while (isAssignment(expression)) { - expression = expression.right; - } - return expression; -} - -/** - * This expression could either be a class expression - * - * ``` - * class MyClass {}; - * ``` - * - * or an IIFE wrapped class expression - * - * ``` - * (() => { - * class MyClass {} - * ... - * return MyClass; - * })() - * ``` - * - * or an IIFE wrapped aliased class expression - * - * ``` - * (() => { - * let MyClass = class MyClass {} - * ... - * return MyClass; - * })() - * ``` - * - * or an IFFE wrapped ES5 class function - * - * ``` - * (function () { - * function MyClass() {} - * ... - * return MyClass - * })() - * ``` - * - * @param expression the node that represents the class whose declaration we are finding. - * @returns the declaration of the class or `null` if it is not a "class". - */ -export function getInnerClassDeclaration(expression: ts.Expression): - ClassDeclaration|null { - if (ts.isClassExpression(expression) && hasNameIdentifier(expression)) { - return expression; - } - - const iifeBody = getIifeBody(expression); - if (iifeBody === undefined) { - return null; - } - - if (!ts.isBlock(iifeBody)) { - // Handle the fat arrow expression case: `() => ClassExpression` - return ts.isClassExpression(iifeBody) && isNamedDeclaration(iifeBody) ? iifeBody : null; - } else { - // Handle the case of a normal or fat-arrow function with a body. - // Return the first ClassDeclaration/VariableDeclaration inside the body - for (const statement of iifeBody.statements) { - if (isNamedClassDeclaration(statement) || isNamedFunctionDeclaration(statement)) { - return statement; - } - if (ts.isVariableStatement(statement)) { - for (const declaration of statement.declarationList.declarations) { - if (isInitializedVariableClassDeclaration(declaration)) { - const expression = skipClassAliases(declaration); - if (ts.isClassExpression(expression) && hasNameIdentifier(expression)) { - return expression; - } - } - } - } - } - } - - return null; -} - -function getDecoratorArgs(node: ts.ObjectLiteralExpression): ts.Expression[] { - // The arguments of a decorator are held in the `args` property of its declaration object. - const argsProperty = node.properties.filter(ts.isPropertyAssignment) - .find(property => getNameText(property.name) === 'args'); - const argsExpression = argsProperty && argsProperty.initializer; - return argsExpression && ts.isArrayLiteralExpression(argsExpression) ? - Array.from(argsExpression.elements) : - []; -} - -function isPropertyAccess(node: ts.Node): node is ts.PropertyAccessExpression& - {parent: ts.BinaryExpression} { - return !!node.parent && ts.isBinaryExpression(node.parent) && ts.isPropertyAccessExpression(node); -} - -function isThisAssignment(node: ts.Declaration): node is ts.BinaryExpression& - {left: ts.PropertyAccessExpression} { - return ts.isBinaryExpression(node) && ts.isPropertyAccessExpression(node.left) && - node.left.expression.kind === ts.SyntaxKind.ThisKeyword; -} - -function isNamedDeclaration(node: ts.Node): node is ts.NamedDeclaration&{name: ts.Identifier} { - const anyNode: any = node; - return !!anyNode.name && ts.isIdentifier(anyNode.name); -} - - -function isClassMemberType(node: ts.Declaration): node is ts.ClassElement| - ts.PropertyAccessExpression|ts.BinaryExpression { - return (ts.isClassElement(node) || isPropertyAccess(node) || ts.isBinaryExpression(node)) && - // Additionally, ensure `node` is not an index signature, for example on an abstract class: - // `abstract class Foo { [key: string]: any; }` - !ts.isIndexSignatureDeclaration(node); -} - -/** - * Attempt to resolve the variable declaration that the given declaration is assigned to. - * For example, for the following code: - * - * ``` - * var MyClass = MyClass_1 = class MyClass {}; - * ``` - * - * or - * - * ``` - * var MyClass = MyClass_1 = (() => { - * class MyClass {} - * ... - * return MyClass; - * })() - ``` - * - * and the provided declaration being `class MyClass {}`, this will return the `var MyClass` - * declaration. - * - * @param declaration The declaration for which any variable declaration should be obtained. - * @returns the outer variable declaration if found, undefined otherwise. - */ -function getFarLeftHandSideOfAssignment(declaration: ts.Declaration): ts.VariableDeclaration| - undefined { - let node = declaration.parent; - - // Detect an intermediary variable assignment and skip over it. - if (isAssignment(node) && ts.isIdentifier(node.left)) { - node = node.parent; - } - - return ts.isVariableDeclaration(node) ? node : undefined; -} - -function getContainingVariableDeclaration(node: ts.Node): ClassDeclaration| - undefined { - node = node.parent; - while (node !== undefined) { - if (isNamedVariableDeclaration(node)) { - return node; - } - node = node.parent; - } - return undefined; -} - -/** - * A constructor function may have been "synthesized" by TypeScript during JavaScript emit, - * in the case no user-defined constructor exists and e.g. property initializers are used. - * Those initializers need to be emitted into a constructor in JavaScript, so the TypeScript - * compiler generates a synthetic constructor. - * - * We need to identify such constructors as ngcc needs to be able to tell if a class did - * originally have a constructor in the TypeScript source. When a class has a superclass, - * a synthesized constructor must not be considered as a user-defined constructor as that - * prevents a base factory call from being created by ngtsc, resulting in a factory function - * that does not inject the dependencies of the superclass. Hence, we identify a default - * synthesized super call in the constructor body, according to the structure that TypeScript - * emits during JavaScript emit: - * https://github.com/Microsoft/TypeScript/blob/v3.2.2/src/compiler/transformers/ts.ts#L1068-L1082 - * - * @param constructor a constructor function to test - * @returns true if the constructor appears to have been synthesized - */ -function isSynthesizedConstructor(constructor: ts.ConstructorDeclaration): boolean { - if (!constructor.body) return false; - - const firstStatement = constructor.body.statements[0]; - if (!firstStatement || !ts.isExpressionStatement(firstStatement)) return false; - - return isSynthesizedSuperCall(firstStatement.expression); -} - -/** - * Tests whether the expression appears to have been synthesized by TypeScript, i.e. whether - * it is of the following form: - * - * ``` - * super(...arguments); - * ``` - * - * @param expression the expression that is to be tested - * @returns true if the expression appears to be a synthesized super call - */ -function isSynthesizedSuperCall(expression: ts.Expression): boolean { - if (!ts.isCallExpression(expression)) return false; - if (expression.expression.kind !== ts.SyntaxKind.SuperKeyword) return false; - if (expression.arguments.length !== 1) return false; - - const argument = expression.arguments[0]; - return ts.isSpreadElement(argument) && ts.isIdentifier(argument.expression) && - argument.expression.text === 'arguments'; -} - -/** - * Find the statement that contains the given node - * @param node a node whose containing statement we wish to find - */ -export function getContainingStatement(node: ts.Node): ts.Statement { - while (node.parent) { - if (ts.isBlock(node.parent) || ts.isSourceFile(node.parent)) { - break; - } - node = node.parent; - } - return node as ts.Statement; -} - -function getRootFileOrFail(bundle: BundleProgram): ts.SourceFile { - const rootFile = bundle.program.getSourceFile(bundle.path); - if (rootFile === undefined) { - throw new Error(`The given rootPath ${rootFile} is not a file of the program.`); - } - return rootFile; -} - -function getNonRootPackageFiles(bundle: BundleProgram): ts.SourceFile[] { - const rootFile = bundle.program.getSourceFile(bundle.path); - return bundle.program.getSourceFiles().filter( - f => (f !== rootFile) && isWithinPackage(bundle.package, absoluteFromSourceFile(f))); -} - -function isTopLevel(node: ts.Node): boolean { - while (node = node.parent) { - if (ts.isBlock(node)) { - return false; - } - } - return true; -} - -/** - * Get a node that represents the actual (outer) declaration of a class from its implementation. - * - * Sometimes, the implementation of a class is an expression that is hidden inside an IIFE and - * assigned to a variable outside the IIFE, which is what the rest of the program interacts with. - * For example, - * - * ``` - * OuterNode = Alias = (function() { function InnerNode() {} return InnerNode; })(); - * ``` - * - * @param node a node that could be the implementation inside an IIFE. - * @returns a node that represents the outer declaration, or `null` if it is does not match the IIFE - * format shown above. - */ -export function getOuterNodeFromInnerDeclaration(node: ts.Node): ts.Node|null { - if (!ts.isFunctionDeclaration(node) && !ts.isClassDeclaration(node) && - !ts.isVariableStatement(node)) { - return null; - } - - // It might be the function expression inside the IIFE. We need to go 5 levels up... - - // - IIFE body. - let outerNode = node.parent; - if (!outerNode || !ts.isBlock(outerNode)) return null; - - // - IIFE function expression. - outerNode = outerNode.parent; - if (!outerNode || (!ts.isFunctionExpression(outerNode) && !ts.isArrowFunction(outerNode))) { - return null; - } - outerNode = outerNode.parent; - - // - Parenthesis inside IIFE. - if (outerNode && ts.isParenthesizedExpression(outerNode)) outerNode = outerNode.parent; - - // - IIFE call expression. - if (!outerNode || !ts.isCallExpression(outerNode)) return null; - outerNode = outerNode.parent; - - // - Parenthesis around IIFE. - if (outerNode && ts.isParenthesizedExpression(outerNode)) outerNode = outerNode.parent; - - // - Skip any aliases between the IIFE and the far left hand side of any assignments. - while (isAssignment(outerNode.parent)) { - outerNode = outerNode.parent; - } - - return outerNode; -} diff --git a/packages/compiler-cli/ngcc/src/host/esm5_host.ts b/packages/compiler-cli/ngcc/src/host/esm5_host.ts deleted file mode 100644 index 9d2ad9358f7..00000000000 --- a/packages/compiler-cli/ngcc/src/host/esm5_host.ts +++ /dev/null @@ -1,803 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ - -import ts from 'typescript'; - -import {ClassDeclaration, ClassMember, ClassMemberKind, Declaration, DeclarationKind, Decorator, FunctionDefinition, isNamedFunctionDeclaration, KnownDeclaration, Parameter, reflectObjectLiteral} from '../../../src/ngtsc/reflection'; -import {getTsHelperFnFromDeclaration, getTsHelperFnFromIdentifier, hasNameIdentifier} from '../utils'; - -import {Esm2015ReflectionHost, getOuterNodeFromInnerDeclaration, getPropertyValueFromSymbol, isAssignmentStatement, ParamInfo} from './esm2015_host'; -import {NgccClassSymbol} from './ngcc_host'; - - -/** - * ESM5 packages contain ECMAScript IIFE functions that act like classes. For example: - * - * ``` - * var CommonModule = (function () { - * function CommonModule() { - * } - * CommonModule.decorators = [ ... ]; - * return CommonModule; - * ``` - * - * * "Classes" are decorated if they have a static property called `decorators`. - * * Members are decorated if there is a matching key on a static property - * called `propDecorators`. - * * Constructor parameters decorators are found on an object returned from - * a static method called `ctorParameters`. - * - */ -export class Esm5ReflectionHost extends Esm2015ReflectionHost { - override getBaseClassExpression(clazz: ClassDeclaration): ts.Expression|null { - const superBaseClassExpression = super.getBaseClassExpression(clazz); - if (superBaseClassExpression !== null) { - return superBaseClassExpression; - } - - const iife = getIifeFn(this.getClassSymbol(clazz)); - if (iife === null) return null; - - if (iife.parameters.length !== 1 || !isSuperIdentifier(iife.parameters[0].name)) { - return null; - } - - if (!ts.isCallExpression(iife.parent)) { - return null; - } - - return iife.parent.arguments[0]; - } - - /** - * Trace an identifier to its declaration, if possible. - * - * This method attempts to resolve the declaration of the given identifier, tracing back through - * imports and re-exports until the original declaration statement is found. A `Declaration` - * object is returned if the original declaration is found, or `null` is returned otherwise. - * - * In ES5, the implementation of a class is a function expression that is hidden inside an IIFE. - * If we are looking for the declaration of the identifier of the inner function expression, we - * will get hold of the outer "class" variable declaration and return its identifier instead. See - * `getClassDeclarationFromInnerFunctionDeclaration()` for more info. - * - * @param id a TypeScript `ts.Identifier` to trace back to a declaration. - * - * @returns metadata about the `Declaration` if the original declaration is found, or `null` - * otherwise. - */ - override getDeclarationOfIdentifier(id: ts.Identifier): Declaration|null { - const declaration = super.getDeclarationOfIdentifier(id); - - if (declaration === null) { - const nonEmittedNorImportedTsHelperDeclaration = getTsHelperFnFromIdentifier(id); - if (nonEmittedNorImportedTsHelperDeclaration !== null) { - // No declaration could be found for this identifier and its name matches a known TS helper - // function. This can happen if a package is compiled with `noEmitHelpers: true` and - // `importHelpers: false` (the default). This is, for example, the case with - // `@nativescript/angular@9.0.0-next-2019-11-12-155500-01`. - return { - kind: DeclarationKind.Inline, - node: id, - known: nonEmittedNorImportedTsHelperDeclaration, - viaModule: null, - }; - } - } - - if (declaration === null || declaration.node === null || declaration.known !== null) { - return declaration; - } - - if (!ts.isVariableDeclaration(declaration.node) || declaration.node.initializer !== undefined || - // VariableDeclaration => VariableDeclarationList => VariableStatement => IIFE Block - !ts.isBlock(declaration.node.parent.parent.parent)) { - return declaration; - } - - // We might have an alias to another variable declaration. - // Search the containing iife body for it. - const block = declaration.node.parent.parent.parent; - const aliasSymbol = this.checker.getSymbolAtLocation(declaration.node.name); - for (let i = 0; i < block.statements.length; i++) { - const statement = block.statements[i]; - // Looking for statement that looks like: `AliasedVariable = OriginalVariable;` - if (isAssignmentStatement(statement) && ts.isIdentifier(statement.expression.left) && - ts.isIdentifier(statement.expression.right) && - this.checker.getSymbolAtLocation(statement.expression.left) === aliasSymbol) { - return this.getDeclarationOfIdentifier(statement.expression.right); - } - } - - return declaration; - } - - /** - * Parse a function declaration to find the relevant metadata about it. - * - * In ESM5 we need to do special work with optional arguments to the function, since they get - * their own initializer statement that needs to be parsed and then not included in the "body" - * statements of the function. - * - * @param node the function declaration to parse. - * @returns an object containing the node, statements and parameters of the function. - */ - override getDefinitionOfFunction(node: ts.Node): FunctionDefinition|null { - const definition = super.getDefinitionOfFunction(node); - if (definition === null) { - return null; - } - - // Filter out and capture parameter initializers - if (definition.body !== null) { - let lookingForInitializers = true; - const statements = definition.body.filter(s => { - lookingForInitializers = - lookingForInitializers && captureParamInitializer(s, definition.parameters); - // If we are no longer looking for parameter initializers then we include this statement - return !lookingForInitializers; - }); - definition.body = statements; - } - - return definition; - } - - /** - * Check whether a `Declaration` corresponds with a known declaration, such as a TypeScript helper - * function, and set its `known` property to the appropriate `KnownDeclaration`. - * - * @param decl The `Declaration` to check. - * @return The passed in `Declaration` (potentially enhanced with a `KnownDeclaration`). - */ - override detectKnownDeclaration(decl: T): T { - decl = super.detectKnownDeclaration(decl); - - // Also check for TS helpers - if (decl.known === null && decl.node !== null) { - decl.known = getTsHelperFnFromDeclaration(decl.node); - } - - return decl; - } - - - ///////////// Protected Helpers ///////////// - - /** - * In ES5, the implementation of a class is a function expression that is hidden inside an IIFE, - * whose value is assigned to a variable (which represents the class to the rest of the program). - * So we might need to dig around to get hold of the "class" declaration. - * - * This method extracts a `NgccClassSymbol` if `declaration` is the function declaration inside - * the IIFE. Otherwise, undefined is returned. - * - * @param declaration the declaration whose symbol we are finding. - * @returns the symbol for the node or `undefined` if it is not a "class" or has no symbol. - */ - protected override getClassSymbolFromInnerDeclaration(declaration: ts.Node): NgccClassSymbol - |undefined { - const classSymbol = super.getClassSymbolFromInnerDeclaration(declaration); - if (classSymbol !== undefined) { - return classSymbol; - } - - if (!isNamedFunctionDeclaration(declaration)) { - return undefined; - } - - const outerNode = getOuterNodeFromInnerDeclaration(declaration); - if (outerNode === null || !hasNameIdentifier(outerNode)) { - return undefined; - } - - return this.createClassSymbol(outerNode.name, declaration); - } - - /** - * Find the declarations of the constructor parameters of a class identified by its symbol. - * - * In ESM5, there is no "class" so the constructor that we want is actually the inner function - * declaration inside the IIFE, whose return value is assigned to the outer variable declaration - * (that represents the class to the rest of the program). - * - * @param classSymbol the symbol of the class (i.e. the outer variable declaration) whose - * parameters we want to find. - * @returns an array of `ts.ParameterDeclaration` objects representing each of the parameters in - * the class's constructor or `null` if there is no constructor. - */ - protected override getConstructorParameterDeclarations(classSymbol: NgccClassSymbol): - ts.ParameterDeclaration[]|null { - const constructor = classSymbol.implementation.valueDeclaration; - if (!ts.isFunctionDeclaration(constructor)) return null; - - if (constructor.parameters.length > 0) { - return Array.from(constructor.parameters); - } - - if (this.isSynthesizedConstructor(constructor)) { - return null; - } - - return []; - } - - /** - * Get the parameter type and decorators for the constructor of a class, - * where the information is stored on a static method of the class. - * - * In this case the decorators are stored in the body of a method - * (`ctorParameters`) attached to the constructor function. - * - * Note that unlike ESM2015 this is a function expression rather than an arrow - * function: - * - * ``` - * SomeDirective.ctorParameters = function() { return [ - * { type: ViewContainerRef, }, - * { type: TemplateRef, }, - * { type: IterableDiffers, }, - * { type: undefined, decorators: [{ type: Inject, args: [INJECTED_TOKEN,] },] }, - * ]; }; - * ``` - * - * @param paramDecoratorsProperty the property that holds the parameter info we want to get. - * @returns an array of objects containing the type and decorators for each parameter. - */ - protected override getParamInfoFromStaticProperty(paramDecoratorsProperty: ts.Symbol): - ParamInfo[]|null { - const paramDecorators = getPropertyValueFromSymbol(paramDecoratorsProperty); - // The decorators array may be wrapped in a function. If so unwrap it. - const returnStatement = getReturnStatement(paramDecorators); - const expression = returnStatement ? returnStatement.expression : paramDecorators; - if (expression && ts.isArrayLiteralExpression(expression)) { - const elements = expression.elements; - return elements.map(reflectArrayElement).map(paramInfo => { - const typeExpression = paramInfo && paramInfo.has('type') ? paramInfo.get('type')! : null; - const decoratorInfo = - paramInfo && paramInfo.has('decorators') ? paramInfo.get('decorators')! : null; - const decorators = decoratorInfo && this.reflectDecorators(decoratorInfo); - return {typeExpression, decorators}; - }); - } else if (paramDecorators !== undefined) { - this.logger.warn( - 'Invalid constructor parameter decorator in ' + paramDecorators.getSourceFile().fileName + - ':\n', - paramDecorators.getText()); - } - return null; - } - - /** - * Reflect over a symbol and extract the member information, combining it with the - * provided decorator information, and whether it is a static member. - * - * If a class member uses accessors (e.g getters and/or setters) then it gets downleveled - * in ES5 to a single `Object.defineProperty()` call. In that case we must parse this - * call to extract the one or two ClassMember objects that represent the accessors. - * - * @param symbol the symbol for the member to reflect over. - * @param decorators an array of decorators associated with the member. - * @param isStatic true if this member is static, false if it is an instance property. - * @returns the reflected member information, or null if the symbol is not a member. - */ - protected override reflectMembers( - symbol: ts.Symbol, decorators?: Decorator[], isStatic?: boolean): ClassMember[]|null { - const node = symbol.valueDeclaration || symbol.declarations && symbol.declarations[0]; - const propertyDefinition = node && getPropertyDefinition(node); - if (propertyDefinition) { - const members: ClassMember[] = []; - if (propertyDefinition.setter) { - members.push({ - node: node!, - implementation: propertyDefinition.setter, - kind: ClassMemberKind.Setter, - type: null, - name: symbol.name, - nameNode: null, - value: null, - isStatic: isStatic || false, - decorators: decorators || [], - }); - - // Prevent attaching the decorators to a potential getter. In ES5, we can't tell where the - // decorators were originally attached to, however we only want to attach them to a single - // `ClassMember` as otherwise ngtsc would handle the same decorators twice. - decorators = undefined; - } - if (propertyDefinition.getter) { - members.push({ - node: node!, - implementation: propertyDefinition.getter, - kind: ClassMemberKind.Getter, - type: null, - name: symbol.name, - nameNode: null, - value: null, - isStatic: isStatic || false, - decorators: decorators || [], - }); - } - return members; - } - - const members = super.reflectMembers(symbol, decorators, isStatic); - members && members.forEach(member => { - if (member && member.kind === ClassMemberKind.Method && member.isStatic && member.node && - ts.isPropertyAccessExpression(member.node) && member.node.parent && - ts.isBinaryExpression(member.node.parent) && - ts.isFunctionExpression(member.node.parent.right)) { - // Recompute the implementation for this member: - // ES5 static methods are variable declarations so the declaration is actually the - // initializer of the variable assignment - member.implementation = member.node.parent.right; - } - }); - return members; - } - - /** - * Find statements related to the given class that may contain calls to a helper. - * - * In ESM5 code the helper calls are hidden inside the class's IIFE. - * - * @param classSymbol the class whose helper calls we are interested in. We expect this symbol - * to reference the inner identifier inside the IIFE. - * @returns an array of statements that may contain helper calls. - */ - protected override getStatementsForClass(classSymbol: NgccClassSymbol): ts.Statement[] { - const classDeclarationParent = classSymbol.implementation.valueDeclaration.parent; - return ts.isBlock(classDeclarationParent) ? Array.from(classDeclarationParent.statements) : []; - } - - ///////////// Host Private Helpers ///////////// - - /** - * A constructor function may have been "synthesized" by TypeScript during JavaScript emit, - * in the case no user-defined constructor exists and e.g. property initializers are used. - * Those initializers need to be emitted into a constructor in JavaScript, so the TypeScript - * compiler generates a synthetic constructor. - * - * We need to identify such constructors as ngcc needs to be able to tell if a class did - * originally have a constructor in the TypeScript source. For ES5, we can not tell an - * empty constructor apart from a synthesized constructor, but fortunately that does not - * matter for the code generated by ngtsc. - * - * When a class has a superclass however, a synthesized constructor must not be considered - * as a user-defined constructor as that prevents a base factory call from being created by - * ngtsc, resulting in a factory function that does not inject the dependencies of the - * superclass. Hence, we identify a default synthesized super call in the constructor body, - * according to the structure that TypeScript's ES2015 to ES5 transformer generates in - * https://github.com/Microsoft/TypeScript/blob/v3.2.2/src/compiler/transformers/es2015.ts#L1082-L1098 - * - * Additionally, we handle synthetic delegate constructors that are emitted when TypeScript - * downlevel's ES2015 synthetically generated to ES5. These vary slightly from the default - * structure mentioned above because the ES2015 output uses a spread operator, for delegating - * to the parent constructor, that is preserved through a TypeScript helper in ES5. e.g. - * - * ``` - * return _super.apply(this, tslib.__spread(arguments)) || this; - * ``` - * - * or, since TypeScript 4.2 it would be - * - * ``` - * return _super.apply(this, tslib.__spreadArray([], tslib.__read(arguments))) || this; - * ``` - * - * Such constructs can be still considered as synthetic delegate constructors as they are - * the product of a common TypeScript to ES5 synthetic constructor, just being downleveled - * to ES5 using `tsc`. See: https://github.com/angular/angular/issues/38453. - * - * - * @param constructor a constructor function to test - * @returns true if the constructor appears to have been synthesized - */ - private isSynthesizedConstructor(constructor: ts.FunctionDeclaration): boolean { - if (!constructor.body) return false; - - const firstStatement = constructor.body.statements[0]; - if (!firstStatement) return false; - - return this.isSynthesizedSuperThisAssignment(firstStatement) || - this.isSynthesizedSuperReturnStatement(firstStatement); - } - - /** - * Identifies synthesized super calls which pass-through function arguments directly and are - * being assigned to a common `_this` variable. The following patterns we intend to match: - * - * 1. Delegate call emitted by TypeScript when it emits ES5 directly. - * ``` - * var _this = _super !== null && _super.apply(this, arguments) || this; - * ``` - * - * 2. Delegate call emitted by TypeScript when it downlevel's ES2015 to ES5. - * ``` - * var _this = _super.apply(this, tslib.__spread(arguments)) || this; - * ``` - * or using the syntax emitted since TypeScript 4.2: - * ``` - * return _super.apply(this, tslib.__spreadArray([], tslib.__read(arguments))) || this; - * ``` - * - * @param statement a statement that may be a synthesized super call - * @returns true if the statement looks like a synthesized super call - */ - private isSynthesizedSuperThisAssignment(statement: ts.Statement): boolean { - if (!ts.isVariableStatement(statement)) return false; - - const variableDeclarations = statement.declarationList.declarations; - if (variableDeclarations.length !== 1) return false; - - const variableDeclaration = variableDeclarations[0]; - if (!ts.isIdentifier(variableDeclaration.name) || - !variableDeclaration.name.text.startsWith('_this')) - return false; - - const initializer = variableDeclaration.initializer; - if (!initializer) return false; - - return this.isSynthesizedDefaultSuperCall(initializer); - } - /** - * Identifies synthesized super calls which pass-through function arguments directly and - * are being returned. The following patterns correspond to synthetic super return calls: - * - * 1. Delegate call emitted by TypeScript when it emits ES5 directly. - * ``` - * return _super !== null && _super.apply(this, arguments) || this; - * ``` - * - * 2. Delegate call emitted by TypeScript when it downlevel's ES2015 to ES5. - * ``` - * return _super.apply(this, tslib.__spread(arguments)) || this; - * ``` - * or using the syntax emitted since TypeScript 4.2: - * ``` - * return _super.apply(this, tslib.__spreadArray([], tslib.__read(arguments))) || this; - * ``` - * - * @param statement a statement that may be a synthesized super call - * @returns true if the statement looks like a synthesized super call - */ - private isSynthesizedSuperReturnStatement(statement: ts.Statement): boolean { - if (!ts.isReturnStatement(statement)) return false; - - const expression = statement.expression; - if (!expression) return false; - - return this.isSynthesizedDefaultSuperCall(expression); - } - - /** - * Identifies synthesized super calls which pass-through function arguments directly. The - * synthetic delegate super call match the following patterns we intend to match: - * - * 1. Delegate call emitted by TypeScript when it emits ES5 directly. - * ``` - * _super !== null && _super.apply(this, arguments) || this; - * ``` - * - * 2. Delegate call emitted by TypeScript when it downlevel's ES2015 to ES5. - * ``` - * _super.apply(this, tslib.__spread(arguments)) || this; - * ``` - * or using the syntax emitted since TypeScript 4.2: - * ``` - * return _super.apply(this, tslib.__spreadArray([], tslib.__read(arguments))) || this; - * ``` - * - * @param expression an expression that may represent a default super call - * @returns true if the expression corresponds with the above form - */ - private isSynthesizedDefaultSuperCall(expression: ts.Expression): boolean { - if (!isBinaryExpr(expression, ts.SyntaxKind.BarBarToken)) return false; - if (expression.right.kind !== ts.SyntaxKind.ThisKeyword) return false; - - const left = expression.left; - if (isBinaryExpr(left, ts.SyntaxKind.AmpersandAmpersandToken)) { - return isSuperNotNull(left.left) && this.isSuperApplyCall(left.right); - } else { - return this.isSuperApplyCall(left); - } - } - - /** - * Tests whether the expression corresponds to a `super` call passing through - * function arguments without any modification. e.g. - * - * ``` - * _super !== null && _super.apply(this, arguments) || this; - * ``` - * - * This structure is generated by TypeScript when transforming ES2015 to ES5, see - * https://github.com/Microsoft/TypeScript/blob/v3.2.2/src/compiler/transformers/es2015.ts#L1148-L1163 - * - * Additionally, we also handle cases where `arguments` are wrapped by a TypeScript spread - * helper. - * This can happen if ES2015 class output contain auto-generated constructors due to class - * members. The ES2015 output will be using `super(...arguments)` to delegate to the superclass, - * but once downleveled to ES5, the spread operator will be persisted through a TypeScript spread - * helper. For example: - * - * ``` - * _super.apply(this, __spread(arguments)) || this; - * ``` - * - * or, since TypeScript 4.2 it would be - * - * ``` - * _super.apply(this, tslib.__spreadArray([], tslib.__read(arguments))) || this; - * ``` - * - * More details can be found in: https://github.com/angular/angular/issues/38453. - * - * @param expression an expression that may represent a default super call - * @returns true if the expression corresponds with the above form - */ - private isSuperApplyCall(expression: ts.Expression): boolean { - if (!ts.isCallExpression(expression) || expression.arguments.length !== 2) return false; - - const targetFn = expression.expression; - if (!ts.isPropertyAccessExpression(targetFn)) return false; - if (!isSuperIdentifier(targetFn.expression)) return false; - if (targetFn.name.text !== 'apply') return false; - - const thisArgument = expression.arguments[0]; - if (thisArgument.kind !== ts.SyntaxKind.ThisKeyword) return false; - - const argumentsExpr = expression.arguments[1]; - - // If the super is directly invoked with `arguments`, return `true`. This represents the - // common TypeScript output where the delegate constructor super call matches the following - // pattern: `super.apply(this, arguments)`. - if (isArgumentsIdentifier(argumentsExpr)) { - return true; - } - - // The other scenario we intend to detect: The `arguments` variable might be wrapped with the - // TypeScript spread helper (either through tslib or inlined). This can happen if an explicit - // delegate constructor uses `super(...arguments)` in ES2015 and is downleveled to ES5 using - // `--downlevelIteration`. - return this.isSpreadArgumentsExpression(argumentsExpr); - } - - /** - * Determines if the provided expression is one of the following call expressions: - * - * 1. `__spread(arguments)` - * 2. `__spreadArray([], __read(arguments))` - * 3. `__spreadArray([], __read(arguments), false)` - * - * The tslib helpers may have been emitted inline as in the above example, or they may be read - * from a namespace import. - */ - private isSpreadArgumentsExpression(expression: ts.Expression): boolean { - const call = this.extractKnownHelperCall(expression); - if (call === null) { - return false; - } - - if (call.helper === KnownDeclaration.TsHelperSpread) { - // `__spread(arguments)` - return call.args.length === 1 && isArgumentsIdentifier(call.args[0]); - } else if (call.helper === KnownDeclaration.TsHelperSpreadArray) { - // `__spreadArray([], __read(arguments), false)` - if (call.args.length !== 2 && call.args.length !== 3) { - return false; - } - - const firstArg = call.args[0]; - if (!ts.isArrayLiteralExpression(firstArg) || firstArg.elements.length !== 0) { - return false; - } - - const secondArg = this.extractKnownHelperCall(call.args[1]); - if (secondArg === null || secondArg.helper !== KnownDeclaration.TsHelperRead) { - return false; - } - - return secondArg.args.length === 1 && isArgumentsIdentifier(secondArg.args[0]); - } else { - return false; - } - } - - /** - * Inspects the provided expression and determines if it corresponds with a known helper function - * as receiver expression. - */ - private extractKnownHelperCall(expression: ts.Expression): - {helper: KnownDeclaration, args: ts.NodeArray}|null { - if (!ts.isCallExpression(expression)) { - return null; - } - - const receiverExpr = expression.expression; - - // The helper could be globally available, or accessed through a namespaced import. Hence we - // support a property access here as long as it resolves to the actual known TypeScript helper. - let receiver: Declaration|null = null; - if (ts.isIdentifier(receiverExpr)) { - receiver = this.getDeclarationOfIdentifier(receiverExpr); - } else if (ts.isPropertyAccessExpression(receiverExpr) && ts.isIdentifier(receiverExpr.name)) { - receiver = this.getDeclarationOfIdentifier(receiverExpr.name); - } - - if (receiver === null || receiver.known === null) { - return null; - } - - return { - helper: receiver.known, - args: expression.arguments, - }; - } -} - -///////////// Internal Helpers ///////////// - -/** - * Represents the details about property definitions that were set using `Object.defineProperty`. - */ -interface PropertyDefinition { - setter: ts.FunctionExpression|null; - getter: ts.FunctionExpression|null; -} - -/** - * In ES5, getters and setters have been downleveled into call expressions of - * `Object.defineProperty`, such as - * - * ``` - * Object.defineProperty(Clazz.prototype, "property", { - * get: function () { - * return 'value'; - * }, - * set: function (value) { - * this.value = value; - * }, - * enumerable: true, - * configurable: true - * }); - * ``` - * - * This function inspects the given node to determine if it corresponds with such a call, and if so - * extracts the `set` and `get` function expressions from the descriptor object, if they exist. - * - * @param node The node to obtain the property definition from. - * @returns The property definition if the node corresponds with accessor, null otherwise. - */ -function getPropertyDefinition(node: ts.Node): PropertyDefinition|null { - if (!ts.isCallExpression(node)) return null; - - const fn = node.expression; - if (!ts.isPropertyAccessExpression(fn) || !ts.isIdentifier(fn.expression) || - fn.expression.text !== 'Object' || fn.name.text !== 'defineProperty') - return null; - - const descriptor = node.arguments[2]; - if (!descriptor || !ts.isObjectLiteralExpression(descriptor)) return null; - - return { - setter: readPropertyFunctionExpression(descriptor, 'set'), - getter: readPropertyFunctionExpression(descriptor, 'get'), - }; -} - -function readPropertyFunctionExpression(object: ts.ObjectLiteralExpression, name: string) { - const property = object.properties.find( - (p): p is ts.PropertyAssignment => - ts.isPropertyAssignment(p) && ts.isIdentifier(p.name) && p.name.text === name); - - return property && ts.isFunctionExpression(property.initializer) && property.initializer || null; -} - -function getReturnStatement(declaration: ts.Expression|undefined): ts.ReturnStatement|undefined { - return declaration && ts.isFunctionExpression(declaration) ? - declaration.body.statements.find(ts.isReturnStatement) : - undefined; -} - -function reflectArrayElement(element: ts.Expression) { - return ts.isObjectLiteralExpression(element) ? reflectObjectLiteral(element) : null; -} - -function isArgumentsIdentifier(expression: ts.Expression): boolean { - return ts.isIdentifier(expression) && expression.text === 'arguments'; -} - -function isSuperNotNull(expression: ts.Expression): boolean { - return isBinaryExpr(expression, ts.SyntaxKind.ExclamationEqualsEqualsToken) && - isSuperIdentifier(expression.left); -} - -function isBinaryExpr( - expression: ts.Expression, operator: ts.BinaryOperator): expression is ts.BinaryExpression { - return ts.isBinaryExpression(expression) && expression.operatorToken.kind === operator; -} - -function isSuperIdentifier(node: ts.Node): boolean { - // Verify that the identifier is prefixed with `_super`. We don't test for equivalence - // as TypeScript may have suffixed the name, e.g. `_super_1` to avoid name conflicts. - // Requiring only a prefix should be sufficiently accurate. - return ts.isIdentifier(node) && node.text.startsWith('_super'); -} - -/** - * Parse the statement to extract the ESM5 parameter initializer if there is one. - * If one is found, add it to the appropriate parameter in the `parameters` collection. - * - * The form we are looking for is: - * - * ``` - * if (arg === void 0) { arg = initializer; } - * ``` - * - * @param statement a statement that may be initializing an optional parameter - * @param parameters the collection of parameters that were found in the function definition - * @returns true if the statement was a parameter initializer - */ -function captureParamInitializer(statement: ts.Statement, parameters: Parameter[]) { - if (ts.isIfStatement(statement) && isUndefinedComparison(statement.expression) && - ts.isBlock(statement.thenStatement) && statement.thenStatement.statements.length === 1) { - const ifStatementComparison = statement.expression; // (arg === void 0) - const thenStatement = statement.thenStatement.statements[0]; // arg = initializer; - if (isAssignmentStatement(thenStatement)) { - const comparisonName = ifStatementComparison.left.text; - const assignmentName = thenStatement.expression.left.text; - if (comparisonName === assignmentName) { - const parameter = parameters.find(p => p.name === comparisonName); - if (parameter) { - parameter.initializer = thenStatement.expression.right; - return true; - } - } - } - } - return false; -} - -function isUndefinedComparison(expression: ts.Expression): expression is ts.Expression& - {left: ts.Identifier, right: ts.Expression} { - return ts.isBinaryExpression(expression) && - expression.operatorToken.kind === ts.SyntaxKind.EqualsEqualsEqualsToken && - ts.isVoidExpression(expression.right) && ts.isIdentifier(expression.left); -} - -/** - * Parse the declaration of the given `classSymbol` to find the IIFE wrapper function. - * - * This function may accept a `_super` argument if there is a base class. - * - * ``` - * var TestClass = (function (_super) { - * __extends(TestClass, _super); - * function TestClass() {} - * return TestClass; - * }(BaseClass)); - * ``` - * - * @param classSymbol the class whose iife wrapper function we want to get. - * @returns the IIFE function or null if it could not be parsed. - */ -function getIifeFn(classSymbol: NgccClassSymbol|undefined): ts.FunctionExpression|null { - if (classSymbol === undefined) { - return null; - } - - const innerDeclaration = classSymbol.implementation.valueDeclaration; - const iifeBody = innerDeclaration.parent; - if (!ts.isBlock(iifeBody)) { - return null; - } - - const iifeWrapper = iifeBody.parent; - return iifeWrapper && ts.isFunctionExpression(iifeWrapper) ? iifeWrapper : null; -} diff --git a/packages/compiler-cli/ngcc/src/host/ngcc_host.ts b/packages/compiler-cli/ngcc/src/host/ngcc_host.ts deleted file mode 100644 index b2ee41dfdf0..00000000000 --- a/packages/compiler-cli/ngcc/src/host/ngcc_host.ts +++ /dev/null @@ -1,96 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import ts from 'typescript'; - -import {ClassDeclaration, Declaration, Decorator, ReflectionHost} from '../../../src/ngtsc/reflection'; -import {SymbolWithValueDeclaration} from '../../../src/ngtsc/util/src/typescript'; - -/** - * The symbol corresponding to a "class" declaration. I.e. a `ts.Symbol` whose `valueDeclaration` is - * a `ClassDeclaration`. - */ -export type ClassSymbol = ts.Symbol&{valueDeclaration: ClassDeclaration}; - -/** - * A representation of a class that accounts for the potential existence of two `ClassSymbol`s for a - * given class, as the compiled JavaScript bundles that ngcc reflects on can have two declarations. - */ -export interface NgccClassSymbol { - /** - * The name of the class. - */ - name: string; - - /** - * Represents the symbol corresponding with the outer declaration of the class. This should be - * considered the public class symbol, i.e. its declaration is visible to the rest of the program. - */ - declaration: ClassSymbol; - - /** - * Represents the symbol corresponding with the inner declaration of the class, referred to as its - * "implementation". This is not necessarily a `ClassSymbol` but rather just a `ts.Symbol`, as the - * inner declaration does not need to satisfy the requirements imposed on a publicly visible class - * declaration. - */ - implementation: SymbolWithValueDeclaration; - - /** - * Represents the symbol corresponding to a variable within a class IIFE that may be used to - * attach static properties or decorated. - */ - adjacent?: SymbolWithValueDeclaration; -} - -/** - * A reflection host that has extra methods for looking at non-Typescript package formats - */ -export interface NgccReflectionHost extends ReflectionHost { - /** - * Find a symbol for a declaration that we think is a class. - * @param declaration The declaration whose symbol we are finding - * @returns the symbol for the declaration or `undefined` if it is not - * a "class" or has no symbol. - */ - getClassSymbol(declaration: ts.Node): NgccClassSymbol|undefined; - - /** - * Retrieves all decorators of a given class symbol. - * @param symbol Class symbol that can refer to a declaration which can hold decorators. - * @returns An array of decorators or null if none are declared. - */ - getDecoratorsOfSymbol(symbol: NgccClassSymbol): Decorator[]|null; - - /** - * Retrieves all class symbols of a given source file. - * @param sourceFile The source file to search for classes. - * @returns An array of found class symbols. - */ - findClassSymbols(sourceFile: ts.SourceFile): NgccClassSymbol[]; - - /** - * Find the last node that is relevant to the specified class. - * - * As well as the main declaration, classes can have additional statements such as static - * properties (`SomeClass.staticProp = ...;`) and decorators (`__decorate(SomeClass, ...);`). - * It is useful to know exactly where the class "ends" so that we can inject additional - * statements after that point. - * - * @param classSymbol The class whose statements we want. - */ - getEndOfClass(classSymbol: NgccClassSymbol): ts.Node; - - /** - * Check whether a `Declaration` corresponds with a known declaration and set its `known` property - * to the appropriate `KnownDeclaration`. - * - * @param decl The `Declaration` to check. - * @return The passed in `Declaration` (potentially enhanced with a `KnownDeclaration`). - */ - detectKnownDeclaration(decl: T): T; -} diff --git a/packages/compiler-cli/ngcc/src/host/umd_host.ts b/packages/compiler-cli/ngcc/src/host/umd_host.ts deleted file mode 100644 index c48b191c6db..00000000000 --- a/packages/compiler-cli/ngcc/src/host/umd_host.ts +++ /dev/null @@ -1,885 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ - -import ts from 'typescript'; - -import {absoluteFrom} from '../../../src/ngtsc/file_system'; -import {Logger} from '../../../src/ngtsc/logging'; -import {Declaration, DeclarationKind, Import, isNamedFunctionDeclaration} from '../../../src/ngtsc/reflection'; -import {BundleProgram} from '../packages/bundle_program'; -import {FactoryMap, getTsHelperFnFromIdentifier, stripExtension} from '../utils'; - -import {DefinePropertyReexportStatement, ExportDeclaration, ExportsStatement, extractGetterFnExpression, findNamespaceOfIdentifier, findRequireCallReference, isDefinePropertyReexportStatement, isExportsAssignment, isExportsDeclaration, isExportsStatement, isExternalImport, isRequireCall, isWildcardReexportStatement, skipAliases, WildcardReexportStatement} from './commonjs_umd_utils'; -import {getInnerClassDeclaration, getOuterNodeFromInnerDeclaration, isAssignment} from './esm2015_host'; -import {Esm5ReflectionHost} from './esm5_host'; -import {NgccClassSymbol} from './ngcc_host'; -import {stripParentheses} from './utils'; - -export class UmdReflectionHost extends Esm5ReflectionHost { - protected umdModules = - new FactoryMap(sf => this.computeUmdModule(sf)); - protected umdExports = new FactoryMap|null>( - sf => this.computeExportsOfUmdModule(sf)); - protected umdImportPaths = - new FactoryMap(param => this.computeImportPath(param)); - protected program: ts.Program; - protected compilerHost: ts.CompilerHost; - - constructor(logger: Logger, isCore: boolean, src: BundleProgram, dts: BundleProgram|null = null) { - super(logger, isCore, src, dts); - this.program = src.program; - this.compilerHost = src.host; - } - - override getImportOfIdentifier(id: ts.Identifier): Import|null { - // Is `id` a namespaced property access, e.g. `Directive` in `core.Directive`? - // If so capture the symbol of the namespace, e.g. `core`. - const nsIdentifier = findNamespaceOfIdentifier(id); - const importParameter = nsIdentifier && this.findUmdImportParameter(nsIdentifier); - const from = importParameter && this.getUmdImportPath(importParameter); - return from !== null ? {from, name: id.text} : null; - } - - override getDeclarationOfIdentifier(id: ts.Identifier): Declaration|null { - // First we try one of the following: - // 1. The `exports` identifier - referring to the current file/module. - // 2. An identifier (e.g. `foo`) that refers to an imported UMD module. - // 3. A UMD style export identifier (e.g. the `foo` of `exports.foo`). - const declaration = this.getExportsDeclaration(id) || this.getUmdModuleDeclaration(id) || - this.getUmdDeclaration(id); - if (declaration !== null) { - return declaration; - } - - // Try to get the declaration using the super class. - const superDeclaration = super.getDeclarationOfIdentifier(id); - if (superDeclaration === null) { - return null; - } - - // Check to see if the declaration is the inner node of a declaration IIFE. - const outerNode = getOuterNodeFromInnerDeclaration(superDeclaration.node); - if (outerNode === null) { - return superDeclaration; - } - - // We are only interested if the outer declaration is of the form - // `exports. = `. - if (!isExportsAssignment(outerNode)) { - return superDeclaration; - } - - return { - kind: DeclarationKind.Inline, - node: outerNode.left, - implementation: outerNode.right, - known: null, - viaModule: null, - }; - } - - override getExportsOfModule(module: ts.Node): Map|null { - return super.getExportsOfModule(module) || this.umdExports.get(module.getSourceFile()); - } - - getUmdModule(sourceFile: ts.SourceFile): UmdModule|null { - if (sourceFile.isDeclarationFile) { - return null; - } - - return this.umdModules.get(sourceFile); - } - - getUmdImportPath(importParameter: ts.ParameterDeclaration): string|null { - return this.umdImportPaths.get(importParameter); - } - - /** - * Get the top level statements for a module. - * - * In UMD modules these are the body of the UMD factory function. - * - * @param sourceFile The module whose statements we want. - * @returns An array of top level statements for the given module. - */ - protected override getModuleStatements(sourceFile: ts.SourceFile): ts.Statement[] { - const umdModule = this.getUmdModule(sourceFile); - return umdModule !== null ? Array.from(umdModule.factoryFn.body.statements) : []; - } - - protected override getClassSymbolFromOuterDeclaration(declaration: ts.Node): NgccClassSymbol - |undefined { - const superSymbol = super.getClassSymbolFromOuterDeclaration(declaration); - if (superSymbol) { - return superSymbol; - } - - if (!isExportsDeclaration(declaration)) { - return undefined; - } - - let initializer = skipAliases(declaration.parent.right); - - if (ts.isIdentifier(initializer)) { - const implementation = this.getDeclarationOfIdentifier(initializer); - if (implementation !== null) { - const implementationSymbol = this.getClassSymbol(implementation.node); - if (implementationSymbol !== null) { - return implementationSymbol; - } - } - } - - const innerDeclaration = getInnerClassDeclaration(initializer); - if (innerDeclaration !== null) { - return this.createClassSymbol(declaration.name, innerDeclaration); - } - - return undefined; - } - - - protected override getClassSymbolFromInnerDeclaration(declaration: ts.Node): NgccClassSymbol - |undefined { - const superClassSymbol = super.getClassSymbolFromInnerDeclaration(declaration); - if (superClassSymbol !== undefined) { - return superClassSymbol; - } - - if (!isNamedFunctionDeclaration(declaration)) { - return undefined; - } - - const outerNode = getOuterNodeFromInnerDeclaration(declaration); - if (outerNode === null || !isExportsAssignment(outerNode)) { - return undefined; - } - - return this.createClassSymbol(outerNode.left.name, declaration); - } - - /** - * Extract all "classes" from the `statement` and add them to the `classes` map. - */ - protected override addClassSymbolsFromStatement( - classes: Map, statement: ts.Statement): void { - super.addClassSymbolsFromStatement(classes, statement); - - // Also check for exports of the form: `exports. = ;` - if (isExportsStatement(statement)) { - const classSymbol = this.getClassSymbol(statement.expression.left); - if (classSymbol) { - classes.set(classSymbol.implementation, classSymbol); - } - } - } - - /** - * Analyze the given statement to see if it corresponds with an exports declaration like - * `exports.MyClass = MyClass_1 = ;`. If so, the declaration of `MyClass_1` - * is associated with the `MyClass` identifier. - * - * @param statement The statement that needs to be preprocessed. - */ - protected override preprocessStatement(statement: ts.Statement): void { - super.preprocessStatement(statement); - - if (!isExportsStatement(statement)) { - return; - } - - const declaration = statement.expression.left; - const initializer = statement.expression.right; - if (!isAssignment(initializer) || !ts.isIdentifier(initializer.left) || - !this.isClass(declaration)) { - return; - } - - const aliasedIdentifier = initializer.left; - - const aliasedDeclaration = this.getDeclarationOfIdentifier(aliasedIdentifier); - if (aliasedDeclaration === null || aliasedDeclaration.node === null) { - throw new Error( - `Unable to locate declaration of ${aliasedIdentifier.text} in "${statement.getText()}"`); - } - this.aliasedClassDeclarations.set(aliasedDeclaration.node, declaration.name); - } - - private computeUmdModule(sourceFile: ts.SourceFile): UmdModule|null { - if (sourceFile.statements.length !== 1) { - throw new Error( - `Expected UMD module file (${sourceFile.fileName}) to contain exactly one statement, ` + - `but found ${sourceFile.statements.length}.`); - } - - return parseStatementForUmdModule(sourceFile.statements[0]); - } - - private computeExportsOfUmdModule(sourceFile: ts.SourceFile): Map|null { - const moduleMap = new Map(); - for (const statement of this.getModuleStatements(sourceFile)) { - if (isExportsStatement(statement)) { - const exportDeclaration = this.extractBasicUmdExportDeclaration(statement); - if (!moduleMap.has(exportDeclaration.name)) { - // We assume that the first `exports.` is the actual declaration, and that any - // subsequent statements that match are decorating the original declaration. - // For example: - // ``` - // exports.foo = ; - // exports.foo = __decorate(, exports.foo); - // ``` - // The declaration is the first line not the second. - moduleMap.set(exportDeclaration.name, exportDeclaration.declaration); - } - } else if (isWildcardReexportStatement(statement)) { - const reexports = this.extractUmdWildcardReexports(statement, sourceFile); - for (const reexport of reexports) { - moduleMap.set(reexport.name, reexport.declaration); - } - } else if (isDefinePropertyReexportStatement(statement)) { - const exportDeclaration = this.extractUmdDefinePropertyExportDeclaration(statement); - if (exportDeclaration !== null) { - moduleMap.set(exportDeclaration.name, exportDeclaration.declaration); - } - } - } - return moduleMap; - } - - private computeImportPath(param: ts.ParameterDeclaration): string|null { - const umdModule = this.getUmdModule(param.getSourceFile()); - if (umdModule === null) { - return null; - } - - const imports = getImportsOfUmdModule(umdModule); - if (imports === null) { - return null; - } - - let importPath: string|null = null; - - for (const i of imports) { - // Add all imports to the map to speed up future look ups. - this.umdImportPaths.set(i.parameter, i.path); - if (i.parameter === param) { - importPath = i.path; - } - } - - return importPath; - } - - private extractBasicUmdExportDeclaration(statement: ExportsStatement): ExportDeclaration { - const name = statement.expression.left.name.text; - const exportExpression = skipAliases(statement.expression.right); - const declaration = this.getDeclarationOfExpression(exportExpression) ?? { - kind: DeclarationKind.Inline, - node: statement.expression.left, - implementation: statement.expression.right, - known: null, - viaModule: null, - }; - return {name, declaration}; - } - - private extractUmdWildcardReexports( - statement: WildcardReexportStatement, containingFile: ts.SourceFile): ExportDeclaration[] { - const reexportArg = statement.expression.arguments[0]; - - const requireCall = isRequireCall(reexportArg) ? reexportArg : - ts.isIdentifier(reexportArg) ? findRequireCallReference(reexportArg, this.checker) : - null; - - let importPath: string|null = null; - - if (requireCall !== null) { - importPath = requireCall.arguments[0].text; - } else if (ts.isIdentifier(reexportArg)) { - const importParameter = this.findUmdImportParameter(reexportArg); - importPath = importParameter && this.getUmdImportPath(importParameter); - } - - if (importPath === null) { - return []; - } - - const importedFile = this.resolveModuleName(importPath, containingFile); - if (importedFile === undefined) { - return []; - } - - const importedExports = this.getExportsOfModule(importedFile); - if (importedExports === null) { - return []; - } - - const viaModule = stripExtension(importedFile.fileName); - const reexports: ExportDeclaration[] = []; - importedExports.forEach( - (decl, name) => reexports.push({name, declaration: {...decl, viaModule}})); - return reexports; - } - - private extractUmdDefinePropertyExportDeclaration(statement: DefinePropertyReexportStatement): - ExportDeclaration|null { - const args = statement.expression.arguments; - const name = args[1].text; - const getterFnExpression = extractGetterFnExpression(statement); - if (getterFnExpression === null) { - return null; - } - - const declaration = this.getDeclarationOfExpression(getterFnExpression); - if (declaration !== null) { - return {name, declaration}; - } - - return { - name, - declaration: { - kind: DeclarationKind.Inline, - node: args[1], - implementation: getterFnExpression, - known: null, - viaModule: null, - }, - }; - } - - /** - * Is the identifier a parameter on a UMD factory function, e.g. `function factory(this, core)`? - * If so then return its declaration. - */ - private findUmdImportParameter(id: ts.Identifier): ts.ParameterDeclaration|null { - const symbol = id && this.checker.getSymbolAtLocation(id) || null; - const declaration = symbol && symbol.valueDeclaration; - return declaration && ts.isParameter(declaration) ? declaration : null; - } - - private getUmdDeclaration(id: ts.Identifier): Declaration|null { - const nsIdentifier = findNamespaceOfIdentifier(id); - if (nsIdentifier === null) { - return null; - } - - if (nsIdentifier.parent.parent && isExportsAssignment(nsIdentifier.parent.parent)) { - const initializer = nsIdentifier.parent.parent.right; - if (ts.isIdentifier(initializer)) { - return this.getDeclarationOfIdentifier(initializer); - } - return this.detectKnownDeclaration({ - kind: DeclarationKind.Inline, - node: nsIdentifier.parent.parent.left, - implementation: skipAliases(nsIdentifier.parent.parent.right), - viaModule: null, - known: null, - }); - } - - const moduleDeclaration = this.getUmdModuleDeclaration(nsIdentifier); - if (moduleDeclaration === null || moduleDeclaration.node === null || - !ts.isSourceFile(moduleDeclaration.node)) { - return null; - } - - const moduleExports = this.getExportsOfModule(moduleDeclaration.node); - if (moduleExports === null) { - return null; - } - - // We need to compute the `viaModule` because the `getExportsOfModule()` call - // did not know that we were importing the declaration. - const declaration = moduleExports.get(id.text)!; - - if (!moduleExports.has(id.text)) { - return null; - } - - // We need to compute the `viaModule` because the `getExportsOfModule()` call - // did not know that we were importing the declaration. - const viaModule = - declaration.viaModule === null ? moduleDeclaration.viaModule : declaration.viaModule; - - return {...declaration, viaModule, known: getTsHelperFnFromIdentifier(id)}; - } - - private getExportsDeclaration(id: ts.Identifier): Declaration|null { - if (!isExportsIdentifier(id)) { - return null; - } - - // Sadly, in the case of `exports.foo = bar`, we can't use `this.findUmdImportParameter(id)` - // to check whether this `exports` is from the IIFE body arguments, because - // `this.checker.getSymbolAtLocation(id)` will return the symbol for the `foo` identifier - // rather than the `exports` identifier. - // - // Instead we search the symbols in the current local scope. - const exportsSymbol = this.checker.getSymbolsInScope(id, ts.SymbolFlags.Variable) - .find(symbol => symbol.name === 'exports'); - - const node = exportsSymbol?.valueDeclaration !== undefined && - !ts.isFunctionExpression(exportsSymbol.valueDeclaration.parent) ? - // There is a locally defined `exports` variable that is not a function parameter. - // So this `exports` identifier must be a local variable and does not represent the module. - exportsSymbol.valueDeclaration : - // There is no local symbol or it is a parameter of an IIFE. - // So this `exports` represents the current "module". - id.getSourceFile(); - - return { - kind: DeclarationKind.Concrete, - node, - viaModule: null, - known: null, - identity: null, - }; - } - - private getUmdModuleDeclaration(id: ts.Identifier): Declaration|null { - const importPath = this.getImportPathFromParameter(id) || this.getImportPathFromRequireCall(id); - if (importPath === null) { - return null; - } - - const module = this.resolveModuleName(importPath, id.getSourceFile()); - if (module === undefined) { - return null; - } - - const viaModule = isExternalImport(importPath) ? importPath : null; - return {kind: DeclarationKind.Concrete, node: module, viaModule, known: null, identity: null}; - } - - private getImportPathFromParameter(id: ts.Identifier): string|null { - const importParameter = this.findUmdImportParameter(id); - if (importParameter === null) { - return null; - } - return this.getUmdImportPath(importParameter); - } - - private getImportPathFromRequireCall(id: ts.Identifier): string|null { - const requireCall = findRequireCallReference(id, this.checker); - if (requireCall === null) { - return null; - } - return requireCall.arguments[0].text; - } - - /** - * If this is an IIFE then try to grab the outer and inner classes otherwise fallback on the super - * class. - */ - protected override getDeclarationOfExpression(expression: ts.Expression): Declaration|null { - const inner = getInnerClassDeclaration(expression); - if (inner !== null) { - const outer = getOuterNodeFromInnerDeclaration(inner); - if (outer !== null && isExportsAssignment(outer)) { - return { - kind: DeclarationKind.Inline, - node: outer.left, - implementation: inner, - known: null, - viaModule: null, - }; - } - } - return super.getDeclarationOfExpression(expression); - } - - private resolveModuleName(moduleName: string, containingFile: ts.SourceFile): ts.SourceFile - |undefined { - if (this.compilerHost.resolveModuleNames) { - const moduleInfo = this.compilerHost.resolveModuleNames( - [moduleName], containingFile.fileName, undefined, undefined, - this.program.getCompilerOptions())[0]; - return moduleInfo && this.program.getSourceFile(absoluteFrom(moduleInfo.resolvedFileName)); - } else { - const moduleInfo = ts.resolveModuleName( - moduleName, containingFile.fileName, this.program.getCompilerOptions(), - this.compilerHost); - return moduleInfo.resolvedModule && - this.program.getSourceFile(absoluteFrom(moduleInfo.resolvedModule.resolvedFileName)); - } - } -} - -export function parseStatementForUmdModule(statement: ts.Statement): UmdModule|null { - const wrapper = getUmdWrapper(statement); - if (wrapper === null) return null; - - const factoryFnParamIndex = wrapper.fn.parameters.findIndex( - parameter => ts.isIdentifier(parameter.name) && parameter.name.text === 'factory'); - if (factoryFnParamIndex === -1) return null; - - const factoryFn = stripParentheses(wrapper.call.arguments[factoryFnParamIndex]); - if (!factoryFn || !ts.isFunctionExpression(factoryFn)) return null; - - let factoryCalls: UmdModule['factoryCalls']|null = null; - - return { - wrapperFn: wrapper.fn, - factoryFn, - // Compute `factoryCalls` lazily, because in some cases they might not be needed for the task at - // hand. - // For example, if we just want to determine if an entry-point is in CommonJS or UMD format, - // trying to parse the wrapper function could potentially throw a (premature) error. By making - // the computation of `factoryCalls` lazy, such an error would be thrown later (during an - // operation where the format of the wrapper function does actually matter) or potentially not - // at all (if we end up not having to process that entry-point). - get factoryCalls() { - if (factoryCalls === null) { - factoryCalls = parseUmdWrapperFunction(this.wrapperFn); - } - return factoryCalls; - }, - }; -} - -function getUmdWrapper(statement: ts.Statement): - {call: ts.CallExpression, fn: ts.FunctionExpression}|null { - if (!ts.isExpressionStatement(statement)) return null; - - if (ts.isParenthesizedExpression(statement.expression) && - ts.isCallExpression(statement.expression.expression) && - ts.isFunctionExpression(statement.expression.expression.expression)) { - // (function () { ... } (...) ); - const call = statement.expression.expression; - const fn = statement.expression.expression.expression; - return {call, fn}; - } - if (ts.isCallExpression(statement.expression) && - ts.isParenthesizedExpression(statement.expression.expression) && - ts.isFunctionExpression(statement.expression.expression.expression)) { - // (function () { ... }) (...); - const call = statement.expression; - const fn = statement.expression.expression.expression; - return {call, fn}; - } - return null; -} - -/** - * Parse the wrapper function of a UMD module and extract info about the factory function calls for - * the various formats (CommonJS, CommonJS2, AMD, global). - * - * NOTE: - * For more info on the distinction between CommonJS and CommonJS2 see - * https://github.com/webpack/webpack/issues/1114. - * - * The supported format for the UMD wrapper function body is a single statement which is either a - * `ts.ConditionalExpression` (i.e. using a ternary operator) (typically emitted by Rollup) or a - * `ts.IfStatement` (typically emitted by Webpack). For example: - * - * ```js - * // Using a conditional expression: - * (function (global, factory) { - * typeof exports === 'object' && typeof module !== 'undefined' ? - * // CommonJS2 factory call. - * factory(exports, require('foo'), require('bar')) : - * typeof define === 'function' && define.amd ? - * // AMD factory call. - * define(['exports', 'foo', 'bar'], factory) : - * // Global factory call. - * (factory((global['my-lib'] = {}), global.foo, global.bar)); - * }(this, (function (exports, foo, bar) { - * // ... - * })); - * ``` - * - * or - * - * ```js - * // Using an `if` statement: - * (function (root, factory) { - * if (typeof exports === 'object' && typeof module === 'object') - * // CommonJS2 factory call. - * module.exports = factory(require('foo'), require('bar')); - * else if (typeof define === 'function' && define.amd) - * // AMD factory call. - * define(['foo', 'bar'], factory); - * else if (typeof exports === 'object') - * // CommonJS factory call. - * exports['my-lib'] = factory(require('foo'), require('bar')); - * else - * // Global factory call. - * root['my-lib'] = factory(root['foo'], root['bar']); - * })(global, function (foo, bar) { - * // ... - * }); - * ``` - */ -function parseUmdWrapperFunction(wrapperFn: ts.FunctionExpression): UmdModule['factoryCalls'] { - const stmt = wrapperFn.body.statements[0]; - let conditionalFactoryCalls: UmdConditionalFactoryCall[]; - - if (ts.isExpressionStatement(stmt) && ts.isConditionalExpression(stmt.expression)) { - conditionalFactoryCalls = extractFactoryCallsFromConditionalExpression(stmt.expression); - } else if (ts.isIfStatement(stmt)) { - conditionalFactoryCalls = extractFactoryCallsFromIfStatement(stmt); - } else { - throw new Error( - 'UMD wrapper body is not in a supported format (expected a conditional expression or if ' + - 'statement):\n' + wrapperFn.body.getText()); - } - - const amdDefine = getAmdDefineCall(conditionalFactoryCalls); - const commonJs = getCommonJsFactoryCall(conditionalFactoryCalls); - const commonJs2 = getCommonJs2FactoryCall(conditionalFactoryCalls); - const global = getGlobalFactoryCall(conditionalFactoryCalls); - const cjsCallForImports = commonJs2 || commonJs; - - if (cjsCallForImports === null) { - throw new Error( - 'Unable to find a CommonJS or CommonJS2 factory call inside the UMD wrapper function:\n' + - stmt.getText()); - } - - return {amdDefine, commonJs, commonJs2, global, cjsCallForImports}; -} - -/** - * Extract `UmdConditionalFactoryCall`s from a `ts.ConditionalExpression` of the form: - * - * ```js - * typeof exports === 'object' && typeof module !== 'undefined' ? - * // CommonJS2 factory call. - * factory(exports, require('foo'), require('bar')) : - * typeof define === 'function' && define.amd ? - * // AMD factory call. - * define(['exports', 'foo', 'bar'], factory) : - * // Global factory call. - * (factory((global['my-lib'] = {}), global.foo, global.bar)); - * ``` - */ -function extractFactoryCallsFromConditionalExpression(node: ts.ConditionalExpression): - UmdConditionalFactoryCall[] { - const factoryCalls: UmdConditionalFactoryCall[] = []; - let currentNode: ts.Expression = node; - - while (ts.isConditionalExpression(currentNode)) { - if (!ts.isBinaryExpression(currentNode.condition)) { - throw new Error( - 'Condition inside UMD wrapper is not a binary expression:\n' + - currentNode.condition.getText()); - } - - factoryCalls.push({ - condition: currentNode.condition, - factoryCall: getFunctionCallFromExpression(currentNode.whenTrue), - }); - - currentNode = currentNode.whenFalse; - } - - factoryCalls.push({ - condition: null, - factoryCall: getFunctionCallFromExpression(currentNode), - }); - - return factoryCalls; -} - -/** - * Extract `UmdConditionalFactoryCall`s from a `ts.IfStatement` of the form: - * - * ```js - * if (typeof exports === 'object' && typeof module === 'object') - * // CommonJS2 factory call. - * module.exports = factory(require('foo'), require('bar')); - * else if (typeof define === 'function' && define.amd) - * // AMD factory call. - * define(['foo', 'bar'], factory); - * else if (typeof exports === 'object') - * // CommonJS factory call. - * exports['my-lib'] = factory(require('foo'), require('bar')); - * else - * // Global factory call. - * root['my-lib'] = factory(root['foo'], root['bar']); - * ``` - */ -function extractFactoryCallsFromIfStatement(node: ts.IfStatement): UmdConditionalFactoryCall[] { - const factoryCalls: UmdConditionalFactoryCall[] = []; - let currentNode: ts.Statement|undefined = node; - - while (currentNode && ts.isIfStatement(currentNode)) { - if (!ts.isBinaryExpression(currentNode.expression)) { - throw new Error( - 'Condition inside UMD wrapper is not a binary expression:\n' + - currentNode.expression.getText()); - } - if (!ts.isExpressionStatement(currentNode.thenStatement)) { - throw new Error( - 'Then-statement inside UMD wrapper is not an expression statement:\n' + - currentNode.thenStatement.getText()); - } - - factoryCalls.push({ - condition: currentNode.expression, - factoryCall: getFunctionCallFromExpression(currentNode.thenStatement.expression), - }); - - currentNode = currentNode.elseStatement; - } - - if (currentNode) { - if (!ts.isExpressionStatement(currentNode)) { - throw new Error( - 'Else-statement inside UMD wrapper is not an expression statement:\n' + - currentNode.getText()); - } - - factoryCalls.push({ - condition: null, - factoryCall: getFunctionCallFromExpression(currentNode.expression), - }); - } - - return factoryCalls; -} - -function getFunctionCallFromExpression(node: ts.Expression): ts.CallExpression { - // Be resilient to `node` being inside parenthesis. - if (ts.isParenthesizedExpression(node)) { - // NOTE: - // Since we are going further down the AST, there is no risk of infinite recursion. - return getFunctionCallFromExpression(node.expression); - } - - // Be resilient to `node` being part of an assignment or comma expression. - if (ts.isBinaryExpression(node) && - [ts.SyntaxKind.CommaToken, ts.SyntaxKind.EqualsToken].includes(node.operatorToken.kind)) { - // NOTE: - // Since we are going further down the AST, there is no risk of infinite recursion. - return getFunctionCallFromExpression(node.right); - } - - if (!ts.isCallExpression(node)) { - throw new Error('Expression inside UMD wrapper is not a call expression:\n' + node.getText()); - } - - return node; -} - -/** - * Get the `define` call for setting up the AMD dependencies in the UMD wrapper. - */ -function getAmdDefineCall(calls: UmdConditionalFactoryCall[]): ts.CallExpression|null { - // The `define` call for AMD dependencies is the one that is guarded with a `&&` expression whose - // one side is a `typeof define` condition. - const amdConditionalCall = calls.find( - call => call.condition?.operatorToken.kind === ts.SyntaxKind.AmpersandAmpersandToken && - oneOfBinaryConditions(call.condition, exp => isTypeOf(exp, 'define')) && - ts.isIdentifier(call.factoryCall.expression) && - call.factoryCall.expression.text === 'define'); - - return amdConditionalCall?.factoryCall ?? null; -} - -/** - * Get the factory call for setting up the CommonJS dependencies in the UMD wrapper. - */ -function getCommonJsFactoryCall(calls: UmdConditionalFactoryCall[]): ts.CallExpression|null { - // The factory call for CommonJS dependencies is the one that is guarded with a `typeof exports` - // condition. - const cjsConditionalCall = calls.find( - call => call.condition?.operatorToken.kind === ts.SyntaxKind.EqualsEqualsEqualsToken && - isTypeOf(call.condition, 'exports') && ts.isIdentifier(call.factoryCall.expression) && - call.factoryCall.expression.text === 'factory'); - - return cjsConditionalCall?.factoryCall ?? null; -} - -/** - * Get the factory call for setting up the CommonJS2 dependencies in the UMD wrapper. - */ -function getCommonJs2FactoryCall(calls: UmdConditionalFactoryCall[]): ts.CallExpression|null { - // The factory call for CommonJS2 dependencies is the one that is guarded with a `&&` expression - // whose one side is a `typeof exports` or `typeof module` condition. - const cjs2ConditionalCall = calls.find( - call => call.condition?.operatorToken.kind === ts.SyntaxKind.AmpersandAmpersandToken && - oneOfBinaryConditions(call.condition, exp => isTypeOf(exp, 'exports', 'module')) && - ts.isIdentifier(call.factoryCall.expression) && - call.factoryCall.expression.text === 'factory'); - - return cjs2ConditionalCall?.factoryCall ?? null; -} - -/** - * Get the factory call for setting up the global dependencies in the UMD wrapper. - */ -function getGlobalFactoryCall(calls: UmdConditionalFactoryCall[]): ts.CallExpression|null { - // The factory call for global dependencies is the one that is the final else-case (i.e. the one - // that has `condition: null`). - const globalConditionalCall = calls.find(call => call.condition === null); - - return globalConditionalCall?.factoryCall ?? null; -} - -function oneOfBinaryConditions( - node: ts.BinaryExpression, test: (expression: ts.Expression) => boolean) { - return test(node.left) || test(node.right); -} - -function isTypeOf(node: ts.Expression, ...types: string[]): boolean { - return ts.isBinaryExpression(node) && ts.isTypeOfExpression(node.left) && - ts.isIdentifier(node.left.expression) && types.includes(node.left.expression.text); -} - -export function getImportsOfUmdModule(umdModule: UmdModule): - {parameter: ts.ParameterDeclaration, path: string}[] { - const imports: {parameter: ts.ParameterDeclaration, path: string}[] = []; - const factoryFnParams = umdModule.factoryFn.parameters; - const cjsFactoryCallArgs = umdModule.factoryCalls.cjsCallForImports.arguments; - - for (let i = 0; i < factoryFnParams.length; i++) { - const arg = cjsFactoryCallArgs[i]; - - // In some UMD formats, the CommonJS factory call may include arguments that are not `require()` - // calls (such as an `exports` argument). Also, since a previous ngcc invocation may have added - // new imports (and thus new `require()` call arguments), these non-`require()` arguments can be - // interspersed among the `require()` calls. - // To remain robust against various UMD formats, we ignore arguments that are not `require()` - // calls when looking for imports. - if (arg !== undefined && isRequireCall(arg)) { - imports.push({ - parameter: factoryFnParams[i], - path: arg.arguments[0].text, - }); - } - } - - return imports; -} - -interface UmdModule { - wrapperFn: ts.FunctionExpression; - factoryFn: ts.FunctionExpression; - factoryCalls: Record<'amdDefine'|'commonJs'|'commonJs2'|'global', ts.CallExpression|null>&{ - cjsCallForImports: ts.CallExpression; - }; -} - -/** - * Represents a factory call found inside the UMD wrapper function. - * - * Each factory call corresponds to a format (such as AMD, CommonJS, etc.) and is guarded by a - * condition (except for the last factory call, which is reached when all other conditions fail). - */ -interface UmdConditionalFactoryCall { - condition: ts.BinaryExpression|null; - factoryCall: ts.CallExpression; -} - -/** - * Is the `node` an identifier with the name "exports"? - */ -function isExportsIdentifier(node: ts.Node): node is ts.Identifier { - return ts.isIdentifier(node) && node.text === 'exports'; -} diff --git a/packages/compiler-cli/ngcc/src/host/utils.ts b/packages/compiler-cli/ngcc/src/host/utils.ts deleted file mode 100644 index 5a8cf1e183a..00000000000 --- a/packages/compiler-cli/ngcc/src/host/utils.ts +++ /dev/null @@ -1,12 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import ts from 'typescript'; - -export function stripParentheses(node: ts.Node): ts.Node { - return ts.isParenthesizedExpression(node) ? node.expression : node; -} diff --git a/packages/compiler-cli/ngcc/src/locking/async_locker.ts b/packages/compiler-cli/ngcc/src/locking/async_locker.ts deleted file mode 100644 index 23261503a4e..00000000000 --- a/packages/compiler-cli/ngcc/src/locking/async_locker.ts +++ /dev/null @@ -1,81 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import {Logger} from '../../../src/ngtsc/logging'; -import {NGCC_TIMED_OUT_EXIT_CODE} from '../constants'; - -import {LockFile} from './lock_file'; - -class TimeoutError extends Error { - code = NGCC_TIMED_OUT_EXIT_CODE; -} - -/** - * AsyncLocker is used to prevent more than one instance of ngcc executing at the same time, - * when being called in an asynchronous context. - * - * * When ngcc starts executing, it creates a file in the `compiler-cli/ngcc` folder. - * * If it finds one is already there then it pauses and waits for the file to be removed by the - * other process. If the file is not removed within a set timeout period given by - * `retryDelay*retryAttempts` an error is thrown with a suitable error message. - * * If the process locking the file changes, then we restart the timeout. - * * When ngcc completes executing, it removes the file so that future ngcc executions can start. - */ -export class AsyncLocker { - constructor( - private lockFile: LockFile, protected logger: Logger, private retryDelay: number, - private retryAttempts: number) {} - - /** - * Run a function guarded by the lock file. - * - * @param fn The function to run. - */ - async lock(fn: () => Promise): Promise { - await this.create(); - try { - return await fn(); - } finally { - this.lockFile.remove(); - } - } - - protected async create() { - let pid: string = ''; - for (let attempts = 0; attempts < this.retryAttempts; attempts++) { - try { - return this.lockFile.write(); - } catch (e: any) { - if (e.code !== 'EEXIST') { - throw e; - } - const newPid = this.lockFile.read(); - if (newPid !== pid) { - // The process locking the file has changed, so restart the timeout - attempts = 0; - pid = newPid; - } - if (attempts === 0) { - this.logger.info( - `Another process, with id ${pid}, is currently running ngcc.\n` + - `Waiting up to ${this.retryDelay * this.retryAttempts / 1000}s for it to finish.\n` + - `(If you are sure no ngcc process is running then you should delete the lock-file at ${ - this.lockFile.path}.)`); - } - // The file is still locked by another process so wait for a bit and retry - await new Promise(resolve => setTimeout(resolve, this.retryDelay)); - } - } - // If we fall out of the loop then we ran out of retry attempts - throw new TimeoutError( - `Timed out waiting ${ - this.retryAttempts * this.retryDelay / - 1000}s for another ngcc process, with id ${pid}, to complete.\n` + - `(If you are sure no ngcc process is running then you should delete the lock-file at ${ - this.lockFile.path}.)`); - } -} diff --git a/packages/compiler-cli/ngcc/src/locking/lock_file.ts b/packages/compiler-cli/ngcc/src/locking/lock_file.ts deleted file mode 100644 index 4ffb94daaf3..00000000000 --- a/packages/compiler-cli/ngcc/src/locking/lock_file.ts +++ /dev/null @@ -1,47 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ - -import module from 'module'; - -import {AbsoluteFsPath, PathManipulation} from '../../../src/ngtsc/file_system'; - -/** Wrapper for resolving the lcok file path. Useful for test patching. */ -export class LockFilePathResolver { - static resolve(fs: PathManipulation): AbsoluteFsPath { - // NodeJS `import.meta.resolve` is experimental. We leverage `require`. - const requireFn = module.createRequire(import.meta.url); - // The lock file location is resolved based on the location of the `ngcc` entry-point as this - // allows us to have a consistent position for the lock file to reside. We are unable to rely - // on `__dirname` (or equivalent) as this code is being bundled and different entry-points - // will have dedicated bundles where the lock file location would differ then. - const ngccEntryPointFile = requireFn.resolve('@angular/compiler-cli/package.json'); - return fs.resolve(ngccEntryPointFile, '../../../.ngcc_lock_file'); - } -} - -export interface LockFile { - path: AbsoluteFsPath; - /** - * Write a lock file to disk containing the PID of the current process. - */ - write(): void; - - /** - * Read the PID, of the process holding the lock, from the lock-file. - * - * It is feasible that the lock-file was removed between the call to `write()` that effectively - * checks for existence and this attempt to read the file. If so then this method should just - * gracefully return `"{unknown}"`. - */ - read(): string; - - /** - * Remove the lock file from disk, whether or not it exists. - */ - remove(): void; -} diff --git a/packages/compiler-cli/ngcc/src/locking/lock_file_with_child_process/index.ts b/packages/compiler-cli/ngcc/src/locking/lock_file_with_child_process/index.ts deleted file mode 100644 index 59ca5187d25..00000000000 --- a/packages/compiler-cli/ngcc/src/locking/lock_file_with_child_process/index.ts +++ /dev/null @@ -1,103 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import {ChildProcess, fork} from 'child_process'; -import module from 'module'; - -import {AbsoluteFsPath, FileSystem} from '../../../../src/ngtsc/file_system'; -import {Logger, LogLevel} from '../../../../src/ngtsc/logging'; -import {LockFile, LockFilePathResolver} from '../lock_file'; - -import {removeLockFile} from './util'; - -/// - -/** - * This `LockFile` implementation uses a child-process to remove the lock file when the main process - * exits (for whatever reason). - * - * There are a few milliseconds between the child-process being forked and it registering its - * `disconnect` event, which is responsible for tidying up the lock-file in the event that the main - * process exits unexpectedly. - * - * We eagerly create the unlocker child-process so that it maximizes the time before the lock-file - * is actually written, which makes it very unlikely that the unlocker would not be ready in the - * case that the developer hits Ctrl-C or closes the terminal within a fraction of a second of the - * lock-file being created. - * - * The worst case scenario is that ngcc is killed too quickly and leaves behind an orphaned - * lock-file. In which case the next ngcc run will display a helpful error message about deleting - * the lock-file. - */ -export class LockFileWithChildProcess implements LockFile { - path: AbsoluteFsPath; - private unlocker: ChildProcess|null; - - constructor(protected fs: FileSystem, protected logger: Logger) { - this.path = LockFilePathResolver.resolve(fs); - this.unlocker = this.createUnlocker(this.path); - } - - - write(): void { - if (this.unlocker === null) { - // In case we already disconnected the previous unlocker child-process, perhaps by calling - // `remove()`. Normally the LockFile should only be used once per instance. - this.unlocker = this.createUnlocker(this.path); - } - this.logger.debug(`Attemping to write lock-file at ${this.path} with PID ${process.pid}`); - // To avoid race conditions, check for existence of the lock-file by trying to create it. - // This will throw an error if the file already exists. - this.fs.writeFile(this.path, process.pid.toString(), /* exclusive */ true); - this.logger.debug(`Written lock-file at ${this.path} with PID ${process.pid}`); - } - - read(): string { - try { - return this.fs.readFile(this.path); - } catch { - return '{unknown}'; - } - } - - remove() { - removeLockFile(this.fs, this.logger, this.path, process.pid.toString()); - if (this.unlocker !== null) { - // If there is an unlocker child-process then disconnect from it so that it can exit itself. - this.unlocker.disconnect(); - this.unlocker = null; - } - } - - protected createUnlocker(path: AbsoluteFsPath): ChildProcess { - this.logger.debug('Forking unlocker child-process'); - const logLevel = - this.logger.level !== undefined ? this.logger.level.toString() : LogLevel.info.toString(); - const isWindows = process.platform === 'win32'; - const unlocker = fork( - LockFileUnlockerScriptResolver.resolve(this.fs), [path, logLevel], - {detached: true, stdio: isWindows ? 'pipe' : 'inherit'}); - if (isWindows) { - unlocker.stdout?.on('data', process.stdout.write.bind(process.stdout)); - unlocker.stderr?.on('data', process.stderr.write.bind(process.stderr)); - } - return unlocker; - } -} - -/** Wrapper for resolving the lock file unlocker script. Useful for test patching. */ -export class LockFileUnlockerScriptResolver { - static resolve(fs: FileSystem): AbsoluteFsPath { - // NodeJS `import.meta.resolve` is experimental. We leverage `require`. - const requireFn = module.createRequire(import.meta.url); - // We resolve the worker script using module resolution as in the package output, - // the worker might be bundled but exposed through a subpath export mapping. - const unlockerScriptPath = requireFn.resolve( - '@angular/compiler-cli/ngcc/src/locking/lock_file_with_child_process/ngcc_lock_unlocker'); - return fs.resolve(unlockerScriptPath); - } -} diff --git a/packages/compiler-cli/ngcc/src/locking/lock_file_with_child_process/ngcc_lock_unlocker.ts b/packages/compiler-cli/ngcc/src/locking/lock_file_with_child_process/ngcc_lock_unlocker.ts deleted file mode 100644 index 4e732ab7cfc..00000000000 --- a/packages/compiler-cli/ngcc/src/locking/lock_file_with_child_process/ngcc_lock_unlocker.ts +++ /dev/null @@ -1,41 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import {NodeJSFileSystem} from '../../../../src/ngtsc/file_system'; -import {ConsoleLogger} from '../../../../src/ngtsc/logging'; -import {removeLockFile} from './util'; - -/// - -// This file is an entry-point for the child-process that is started by `LockFileWithChildProcess` -// to ensure that the lock-file is removed when the primary process exits unexpectedly. - -// We have no choice but to use the node.js file-system here since we are in a separate process -// from the main ngcc run, which may be running a mock file-system from within a test. -const fs = new NodeJSFileSystem(); - -// We create a logger that has the same logging level as the parent process, since it should have -// been passed through as one of the args -const logLevel = parseInt(process.argv.pop()!, 10); -const logger = new ConsoleLogger(logLevel); - -// We must store the parent PID now as it changes if the parent process is killed early -const ppid = process.ppid.toString(); - -// The path to the lock-file to remove should have been passed as one of the args -const lockFilePath = fs.resolve(process.argv.pop()!); - -logger.debug(`Starting unlocker at process ${process.pid} on behalf of process ${ppid}`); -logger.debug(`The lock-file path is ${lockFilePath}`); - -/** - * When the parent process exits (for whatever reason) remove the loc-file if it exists and as long - * as it was one that was created by the parent process. - */ -process.on('disconnect', () => { - removeLockFile(fs, logger, lockFilePath, ppid); -}); diff --git a/packages/compiler-cli/ngcc/src/locking/lock_file_with_child_process/util.ts b/packages/compiler-cli/ngcc/src/locking/lock_file_with_child_process/util.ts deleted file mode 100644 index a80c85a77f6..00000000000 --- a/packages/compiler-cli/ngcc/src/locking/lock_file_with_child_process/util.ts +++ /dev/null @@ -1,37 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import {AbsoluteFsPath, FileSystem} from '../../../../src/ngtsc/file_system'; -import {Logger} from '../../../../src/ngtsc/logging'; - -/** - * Remove the lock-file at the provided `lockFilePath` from the given file-system. - * - * It only removes the file if the pid stored in the file matches the provided `pid`. - * The provided `pid` is of the process that is exiting and so no longer needs to hold the lock. - */ -export function removeLockFile( - fs: FileSystem, logger: Logger, lockFilePath: AbsoluteFsPath, pid: string) { - try { - logger.debug(`Attempting to remove lock-file at ${lockFilePath}.`); - const lockFilePid = fs.readFile(lockFilePath); - if (lockFilePid === pid) { - logger.debug(`PIDs match (${pid}), so removing ${lockFilePath}.`); - fs.removeFile(lockFilePath); - } else { - logger.debug( - `PIDs do not match (${pid} and ${lockFilePid}), so not removing ${lockFilePath}.`); - } - } catch (e: any) { - if (e.code === 'ENOENT') { - logger.debug(`The lock-file at ${lockFilePath} was already removed.`); - // File already removed so quietly exit - } else { - throw e; - } - } -} diff --git a/packages/compiler-cli/ngcc/src/locking/sync_locker.ts b/packages/compiler-cli/ngcc/src/locking/sync_locker.ts deleted file mode 100644 index 51459fbad05..00000000000 --- a/packages/compiler-cli/ngcc/src/locking/sync_locker.ts +++ /dev/null @@ -1,61 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import {LockFile} from './lock_file'; - -/** - * SyncLocker is used to prevent more than one instance of ngcc executing at the same time, - * when being called in a synchronous context. - * - * * When ngcc starts executing, it creates a file in the `compiler-cli/ngcc` folder. - * * If it finds one is already there then it fails with a suitable error message. - * * When ngcc completes executing, it removes the file so that future ngcc executions can start. - */ -export class SyncLocker { - constructor(private lockFile: LockFile) {} - - /** - * Run the given function guarded by the lock file. - * - * @param fn the function to run. - * @returns the value returned from the `fn` call. - */ - lock(fn: () => T): T { - this.create(); - try { - return fn(); - } finally { - this.lockFile.remove(); - } - } - - /** - * Write a lock file to disk, or error if there is already one there. - */ - protected create(): void { - try { - this.lockFile.write(); - } catch (e: any) { - if (e.code !== 'EEXIST') { - throw e; - } - this.handleExistingLockFile(); - } - } - - /** - * The lock-file already exists so raise a helpful error. - */ - protected handleExistingLockFile(): void { - const pid = this.lockFile.read(); - throw new Error( - `ngcc is already running at process with id ${pid}.\n` + - `If you are running multiple builds in parallel then you might try pre-processing your node_modules via the command line ngcc tool before starting the builds.\n` + - `(If you are sure no ngcc process is running then you should delete the lock-file at ${ - this.lockFile.path}.)`); - } -} diff --git a/packages/compiler-cli/ngcc/src/main.ts b/packages/compiler-cli/ngcc/src/main.ts deleted file mode 100644 index 99f155ee92e..00000000000 --- a/packages/compiler-cli/ngcc/src/main.ts +++ /dev/null @@ -1,217 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ - -/// - -import {AbsoluteFsPath, FileSystem, ReadonlyFileSystem} from '../../src/ngtsc/file_system'; -import {Logger} from '../../src/ngtsc/logging'; -import {ParsedConfiguration} from '../../src/perform_compile'; - -import {CommonJsDependencyHost} from './dependencies/commonjs_dependency_host'; -import {DependencyResolver} from './dependencies/dependency_resolver'; -import {DtsDependencyHost} from './dependencies/dts_dependency_host'; -import {EsmDependencyHost} from './dependencies/esm_dependency_host'; -import {ModuleResolver} from './dependencies/module_resolver'; -import {UmdDependencyHost} from './dependencies/umd_dependency_host'; -import {DirectoryWalkerEntryPointFinder} from './entry_point_finder/directory_walker_entry_point_finder'; -import {EntryPointCollector} from './entry_point_finder/entry_point_collector'; -import {EntryPointFinder} from './entry_point_finder/interface'; -import {ProgramBasedEntryPointFinder} from './entry_point_finder/program_based_entry_point_finder'; -import {TargetedEntryPointFinder} from './entry_point_finder/targeted_entry_point_finder'; -import {getAnalyzeEntryPointsFn} from './execution/analyze_entry_points'; -import {Executor} from './execution/api'; -import {ClusterExecutor} from './execution/cluster/executor'; -import {getCreateCompileFn} from './execution/create_compile_function'; -import {SingleProcessExecutorAsync, SingleProcessExecutorSync} from './execution/single_process_executor'; -import {CreateTaskCompletedCallback, TaskProcessingOutcome} from './execution/tasks/api'; -import {composeTaskCompletedCallbacks, createLogErrorHandler, createMarkAsProcessedHandler, createThrowErrorHandler} from './execution/tasks/completion'; -import {AsyncLocker} from './locking/async_locker'; -import {LockFileWithChildProcess} from './locking/lock_file_with_child_process'; -import {SyncLocker} from './locking/sync_locker'; -import {AsyncNgccOptions, getMaxNumberOfWorkers, getSharedSetup, SyncNgccOptions} from './ngcc_options'; -import {NgccConfiguration} from './packages/configuration'; -import {EntryPointJsonProperty, SUPPORTED_FORMAT_PROPERTIES} from './packages/entry_point'; -import {EntryPointManifest, InvalidatingEntryPointManifest} from './packages/entry_point_manifest'; -import {PathMappings} from './path_mappings'; -import {FileWriter} from './writing/file_writer'; -import {DirectPackageJsonUpdater, PackageJsonUpdater} from './writing/package_json_updater'; - -/** - * This is the main entry-point into ngcc (aNGular Compatibility Compiler). - * - * You can call this function to process one or more npm packages, to ensure - * that they are compatible with the ivy compiler (ngtsc). - * - * @param options The options telling ngcc what to compile and how. - */ -export function mainNgcc(options: T): - T extends AsyncNgccOptions ? Promise: void; -export function mainNgcc(options: AsyncNgccOptions|SyncNgccOptions): void|Promise { - const { - basePath, - targetEntryPointPath, - propertiesToConsider, - typingsOnly, - compileAllFormats, - logger, - pathMappings, - async, - errorOnFailedEntryPoint, - enableI18nLegacyMessageIdFormat, - invalidateEntryPointManifest, - fileSystem, - absBasePath, - projectPath, - tsConfig, - getFileWriter, - } = getSharedSetup(options); - - const config = new NgccConfiguration(fileSystem, projectPath); - const dependencyResolver = getDependencyResolver(fileSystem, logger, config, pathMappings); - const entryPointManifest = invalidateEntryPointManifest ? - new InvalidatingEntryPointManifest(fileSystem, config, logger) : - new EntryPointManifest(fileSystem, config, logger); - - // Bail out early if the work is already done. - const supportedPropertiesToConsider = ensureSupportedProperties(propertiesToConsider); - const absoluteTargetEntryPointPath = targetEntryPointPath !== undefined ? - fileSystem.resolve(basePath, targetEntryPointPath) : - null; - const finder = getEntryPointFinder( - fileSystem, logger, dependencyResolver, config, entryPointManifest, absBasePath, - absoluteTargetEntryPointPath, pathMappings, - options.findEntryPointsFromTsConfigProgram ? tsConfig : null, projectPath); - if (finder instanceof TargetedEntryPointFinder && - !finder.targetNeedsProcessingOrCleaning(supportedPropertiesToConsider, compileAllFormats)) { - logger.debug('The target entry-point has already been processed'); - return; - } - - // Determine the number of workers to use and whether ngcc should run in parallel. - const workerCount = async ? getMaxNumberOfWorkers() : 1; - const inParallel = workerCount > 1; - - const analyzeEntryPoints = getAnalyzeEntryPointsFn( - logger, finder, fileSystem, supportedPropertiesToConsider, typingsOnly, compileAllFormats, - propertiesToConsider, inParallel); - - // Create an updater that will actually write to disk. - const pkgJsonUpdater = new DirectPackageJsonUpdater(fileSystem); - const fileWriter = getFileWriter(pkgJsonUpdater); - - // The function for creating the `compile()` function. - const createCompileFn = getCreateCompileFn( - fileSystem, logger, fileWriter, enableI18nLegacyMessageIdFormat, tsConfig, pathMappings); - - // The executor for actually planning and getting the work done. - const createTaskCompletedCallback = - getCreateTaskCompletedCallback(pkgJsonUpdater, errorOnFailedEntryPoint, logger, fileSystem); - const executor = getExecutor( - async, workerCount, logger, fileWriter, pkgJsonUpdater, fileSystem, config, - createTaskCompletedCallback); - - return executor.execute(analyzeEntryPoints, createCompileFn); -} - -function ensureSupportedProperties(properties: string[]): EntryPointJsonProperty[] { - // Short-circuit the case where `properties` has fallen back to the default value: - // `SUPPORTED_FORMAT_PROPERTIES` - if (properties === SUPPORTED_FORMAT_PROPERTIES) return SUPPORTED_FORMAT_PROPERTIES; - - const supportedProperties: EntryPointJsonProperty[] = []; - - for (const prop of properties as EntryPointJsonProperty[]) { - if (SUPPORTED_FORMAT_PROPERTIES.indexOf(prop) !== -1) { - supportedProperties.push(prop); - } - } - - if (supportedProperties.length === 0) { - throw new Error( - `No supported format property to consider among [${properties.join(', ')}]. ` + - `Supported properties: ${SUPPORTED_FORMAT_PROPERTIES.join(', ')}`); - } - - return supportedProperties; -} - -function getCreateTaskCompletedCallback( - pkgJsonUpdater: PackageJsonUpdater, errorOnFailedEntryPoint: boolean, logger: Logger, - fileSystem: ReadonlyFileSystem): CreateTaskCompletedCallback { - return taskQueue => composeTaskCompletedCallbacks({ - [TaskProcessingOutcome.Processed]: - createMarkAsProcessedHandler(fileSystem, pkgJsonUpdater), - [TaskProcessingOutcome.Failed]: - errorOnFailedEntryPoint ? createThrowErrorHandler(fileSystem) : - createLogErrorHandler(logger, fileSystem, taskQueue), - }); -} - -function getExecutor( - async: boolean, workerCount: number, logger: Logger, fileWriter: FileWriter, - pkgJsonUpdater: PackageJsonUpdater, fileSystem: FileSystem, config: NgccConfiguration, - createTaskCompletedCallback: CreateTaskCompletedCallback): Executor { - const lockFile = new LockFileWithChildProcess(fileSystem, logger); - if (async) { - // Execute asynchronously (either serially or in parallel) - const {retryAttempts, retryDelay} = config.getLockingConfig(); - const locker = new AsyncLocker(lockFile, logger, retryDelay, retryAttempts); - if (workerCount > 1) { - // Execute in parallel. - return new ClusterExecutor( - workerCount, fileSystem, logger, fileWriter, pkgJsonUpdater, locker, - createTaskCompletedCallback); - } else { - // Execute serially, on a single thread (async). - return new SingleProcessExecutorAsync(logger, locker, createTaskCompletedCallback); - } - } else { - // Execute serially, on a single thread (sync). - return new SingleProcessExecutorSync( - logger, new SyncLocker(lockFile), createTaskCompletedCallback); - } -} - -function getDependencyResolver( - fileSystem: ReadonlyFileSystem, logger: Logger, config: NgccConfiguration, - pathMappings: PathMappings|undefined): DependencyResolver { - const moduleResolver = new ModuleResolver(fileSystem, pathMappings); - const esmDependencyHost = new EsmDependencyHost(fileSystem, moduleResolver); - const umdDependencyHost = new UmdDependencyHost(fileSystem, moduleResolver); - const commonJsDependencyHost = new CommonJsDependencyHost(fileSystem, moduleResolver); - const dtsDependencyHost = new DtsDependencyHost(fileSystem, pathMappings); - return new DependencyResolver( - fileSystem, logger, config, { - esm5: esmDependencyHost, - esm2015: esmDependencyHost, - umd: umdDependencyHost, - commonjs: commonJsDependencyHost - }, - dtsDependencyHost); -} - -function getEntryPointFinder( - fs: ReadonlyFileSystem, logger: Logger, resolver: DependencyResolver, config: NgccConfiguration, - entryPointManifest: EntryPointManifest, basePath: AbsoluteFsPath, - absoluteTargetEntryPointPath: AbsoluteFsPath|null, pathMappings: PathMappings|undefined, - tsConfig: ParsedConfiguration|null, projectPath: AbsoluteFsPath): EntryPointFinder { - if (absoluteTargetEntryPointPath !== null) { - return new TargetedEntryPointFinder( - fs, config, logger, resolver, basePath, pathMappings, absoluteTargetEntryPointPath); - } else { - const entryPointCollector = new EntryPointCollector(fs, config, logger, resolver); - if (tsConfig !== null) { - return new ProgramBasedEntryPointFinder( - fs, config, logger, resolver, entryPointCollector, entryPointManifest, basePath, tsConfig, - projectPath); - } else { - return new DirectoryWalkerEntryPointFinder( - logger, resolver, entryPointCollector, entryPointManifest, basePath, pathMappings); - } - } -} diff --git a/packages/compiler-cli/ngcc/src/migrations/README.md b/packages/compiler-cli/ngcc/src/migrations/README.md deleted file mode 100644 index 62b2a0e0570..00000000000 --- a/packages/compiler-cli/ngcc/src/migrations/README.md +++ /dev/null @@ -1,18 +0,0 @@ -# ngcc migrations - -There are some cases where source code needs to be migrated before ngtsc can compile it correctly. - -For example, there are cases where ngtsc expects directives need to be explicitly attached to -classes, whereas previously they were not required. - -There are two ways this can happen: - -1) in a project being developed, the code can be migrated via a CLI schematic. -2) in a package already published to npm, the code can be migrated as part of the ngcc compilation. - -To create one of these migrations for ngcc, you should implement the `Migration` interface and add -an instance of the class to the `DecorationAnalyzer.migrations` collection. - -This folder is where we keep the `Migration` interface and the implemented migrations. - -Each migration should have a unit test stored in the `../../test/migrations` directory. \ No newline at end of file diff --git a/packages/compiler-cli/ngcc/src/migrations/migration.ts b/packages/compiler-cli/ngcc/src/migrations/migration.ts deleted file mode 100644 index 9da6386fc39..00000000000 --- a/packages/compiler-cli/ngcc/src/migrations/migration.ts +++ /dev/null @@ -1,66 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import ts from 'typescript'; - -import {MetadataReader} from '../../../src/ngtsc/metadata'; -import {PartialEvaluator} from '../../../src/ngtsc/partial_evaluator'; -import {ClassDeclaration, Decorator} from '../../../src/ngtsc/reflection'; -import {HandlerFlags} from '../../../src/ngtsc/transform'; -import {NgccReflectionHost} from '../host/ngcc_host'; - - -/** - * Implement this interface and add it to the `DecorationAnalyzer.migrations` collection to get ngcc - * to modify the analysis of the decorators in the program in order to migrate older code to work - * with Ivy. - * - * `Migration.apply()` is called for every class in the program being compiled by ngcc. - * - * Note that the underlying program could be in a variety of different formats, e.g. ES2015, ES5, - * UMD, CommonJS etc. This means that an author of a `Migration` should not attempt to navigate and - * manipulate the AST nodes directly. Instead, the `MigrationHost` interface, passed to the - * `Migration`, provides access to a `MetadataReader`, `ReflectionHost` and `PartialEvaluator` - * interfaces, which should be used. - */ -export interface Migration { - apply(clazz: ClassDeclaration, host: MigrationHost): ts.Diagnostic|null; -} - -export interface MigrationHost { - /** Provides access to the decorator information associated with classes. */ - readonly metadata: MetadataReader; - /** Provides access to navigate the AST in a format-agnostic manner. */ - readonly reflectionHost: NgccReflectionHost; - /** Enables expressions to be statically evaluated in the context of the program. */ - readonly evaluator: PartialEvaluator; - /** - * Associate a new synthesized decorator, which did not appear in the original source, with a - * given class. - * @param clazz the class to receive the new decorator. - * @param decorator the decorator to inject. - * @param flags optional bitwise flag to influence the compilation of the decorator. - */ - injectSyntheticDecorator(clazz: ClassDeclaration, decorator: Decorator, flags?: HandlerFlags): - void; - - /** - * Retrieves all decorators that are associated with the class, including synthetic decorators - * that have been injected before. - * @param clazz the class for which all decorators are retrieved. - * @returns the list of the decorators, or null if the class was not decorated. - */ - getAllDecorators(clazz: ClassDeclaration): Decorator[]|null; - - /** - * Determines whether the provided class in within scope of the entry-point that is currently - * being compiled. - * @param clazz the class for which to determine whether it is within the current entry-point. - * @returns true if the file is part of the compiled entry-point, false otherwise. - */ - isInScope(clazz: ClassDeclaration): boolean; -} diff --git a/packages/compiler-cli/ngcc/src/migrations/missing_injectable_migration.ts b/packages/compiler-cli/ngcc/src/migrations/missing_injectable_migration.ts deleted file mode 100644 index b5d3c4e7ceb..00000000000 --- a/packages/compiler-cli/ngcc/src/migrations/missing_injectable_migration.ts +++ /dev/null @@ -1,192 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import ts from 'typescript'; - -import {forwardRefResolver} from '../../../src/ngtsc/annotations'; -import {Reference} from '../../../src/ngtsc/imports'; -import {ResolvedValue, ResolvedValueMap} from '../../../src/ngtsc/partial_evaluator'; -import {ClassDeclaration, Decorator} from '../../../src/ngtsc/reflection'; - -import {Migration, MigrationHost} from './migration'; -import {createInjectableDecorator, isClassDeclaration} from './utils'; - -/** - * Ensures that classes that are provided as an Angular service in either `NgModule.providers` or - * `Directive.providers`/`Component.viewProviders` are decorated with one of the `@Injectable`, - * `@Directive`, `@Component` or `@Pipe` decorators, adding an `@Injectable()` decorator when none - * are present. - * - * At least one decorator is now mandatory, as otherwise the compiler would not compile an - * injectable definition for the service. This is unlike View Engine, where having just an unrelated - * decorator may have been sufficient for the service to become injectable. - * - * In essence, this migration operates on classes that are themselves an NgModule, Directive or - * Component. Their metadata is statically evaluated so that their "providers"/"viewProviders" - * properties can be analyzed. For any provider that refers to an undecorated class, the class will - * be migrated to have an `@Injectable()` decorator. - * - * This implementation mirrors the "missing-injectable" schematic. - */ -export class MissingInjectableMigration implements Migration { - apply(clazz: ClassDeclaration, host: MigrationHost): ts.Diagnostic|null { - const decorators = host.reflectionHost.getDecoratorsOfDeclaration(clazz); - if (decorators === null) { - return null; - } - - for (const decorator of decorators) { - const name = getAngularCoreDecoratorName(decorator); - if (name === 'NgModule') { - migrateNgModuleProviders(decorator, host); - } else if (name === 'Directive') { - migrateDirectiveProviders(decorator, host, /* isComponent */ false); - } else if (name === 'Component') { - migrateDirectiveProviders(decorator, host, /* isComponent */ true); - } - } - - return null; - } -} - -/** - * Iterates through all `NgModule.providers` and adds the `@Injectable()` decorator to any provider - * that is not otherwise decorated. - */ -function migrateNgModuleProviders(decorator: Decorator, host: MigrationHost): void { - if (decorator.args === null || decorator.args.length !== 1) { - return; - } - - const metadata = host.evaluator.evaluate(decorator.args[0], forwardRefResolver); - if (!(metadata instanceof Map)) { - return; - } - - migrateProviders(metadata, 'providers', host); - // TODO(alxhub): we should probably also check for `ModuleWithProviders` here. -} - -/** - * Iterates through all `Directive.providers` and if `isComponent` is set to true also - * `Component.viewProviders` and adds the `@Injectable()` decorator to any provider that is not - * otherwise decorated. - */ -function migrateDirectiveProviders( - decorator: Decorator, host: MigrationHost, isComponent: boolean): void { - if (decorator.args === null || decorator.args.length !== 1) { - return; - } - - const metadata = host.evaluator.evaluate(decorator.args[0], forwardRefResolver); - if (!(metadata instanceof Map)) { - return; - } - - migrateProviders(metadata, 'providers', host); - if (isComponent) { - migrateProviders(metadata, 'viewProviders', host); - } -} - -/** - * Given an object with decorator metadata, iterates through the list of providers to add - * `@Injectable()` to any provider that is not otherwise decorated. - */ -function migrateProviders(metadata: ResolvedValueMap, field: string, host: MigrationHost): void { - if (!metadata.has(field)) { - return; - } - const providers = metadata.get(field)!; - if (!Array.isArray(providers)) { - return; - } - - for (const provider of providers) { - migrateProvider(provider, host); - } -} - -/** - * Analyzes a single provider entry and determines the class that is required to have an - * `@Injectable()` decorator. - */ -function migrateProvider(provider: ResolvedValue, host: MigrationHost): void { - if (provider instanceof Map) { - if (!provider.has('provide') || provider.has('useValue') || provider.has('useFactory') || - provider.has('useExisting')) { - return; - } - if (provider.has('useClass')) { - // {provide: ..., useClass: SomeClass, deps: [...]} does not require a decorator on SomeClass, - // as the provider itself configures 'deps'. Only if 'deps' is missing will this require a - // factory to exist on SomeClass. - if (!provider.has('deps')) { - migrateProviderClass(provider.get('useClass')!, host); - } - } else { - migrateProviderClass(provider.get('provide')!, host); - } - } else if (Array.isArray(provider)) { - for (const v of provider) { - migrateProvider(v, host); - } - } else { - migrateProviderClass(provider, host); - } -} - -/** - * Given a provider class, adds the `@Injectable()` decorator if no other relevant Angular decorator - * is present on the class. - */ -function migrateProviderClass(provider: ResolvedValue, host: MigrationHost): void { - // Providers that do not refer to a class cannot be migrated. - if (!(provider instanceof Reference)) { - return; - } - - const clazz = provider.node; - if (isClassDeclaration(clazz) && host.isInScope(clazz) && needsInjectableDecorator(clazz, host)) { - host.injectSyntheticDecorator(clazz, createInjectableDecorator(clazz)); - } -} - -const NO_MIGRATE_DECORATORS = new Set(['Injectable', 'Directive', 'Component', 'Pipe']); - -/** - * Determines if the given class needs to be decorated with `@Injectable()` based on whether it - * already has an Angular decorator applied. - */ -function needsInjectableDecorator(clazz: ClassDeclaration, host: MigrationHost): boolean { - const decorators = host.getAllDecorators(clazz); - if (decorators === null) { - return true; - } - - for (const decorator of decorators) { - const name = getAngularCoreDecoratorName(decorator); - if (name !== null && NO_MIGRATE_DECORATORS.has(name)) { - return false; - } - } - - return true; -} - -/** - * Determines the original name of a decorator if it is from '@angular/core'. For other decorators, - * null is returned. - */ -export function getAngularCoreDecoratorName(decorator: Decorator): string|null { - if (decorator.import === null || decorator.import.from !== '@angular/core') { - return null; - } - - return decorator.import.name; -} diff --git a/packages/compiler-cli/ngcc/src/migrations/undecorated_child_migration.ts b/packages/compiler-cli/ngcc/src/migrations/undecorated_child_migration.ts deleted file mode 100644 index a5b79e96765..00000000000 --- a/packages/compiler-cli/ngcc/src/migrations/undecorated_child_migration.ts +++ /dev/null @@ -1,75 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ - -import ts from 'typescript'; - -import {readBaseClass} from '../../../src/ngtsc/annotations/common'; -import {Reference} from '../../../src/ngtsc/imports'; -import {ClassDeclaration} from '../../../src/ngtsc/reflection'; -import {HandlerFlags} from '../../../src/ngtsc/transform'; - -import {Migration, MigrationHost} from './migration'; -import {createComponentDecorator, createDirectiveDecorator, hasDirectiveDecorator, hasPipeDecorator} from './utils'; - -export class UndecoratedChildMigration implements Migration { - apply(clazz: ClassDeclaration, host: MigrationHost): ts.Diagnostic|null { - // This migration looks at NgModules and considers the directives (and pipes) it declares. - // It verifies that these classes have decorators. - const moduleMeta = host.metadata.getNgModuleMetadata(new Reference(clazz)); - if (moduleMeta === null) { - // Not an NgModule; don't care. - return null; - } - - // Examine each of the declarations to see if it needs to be migrated. - for (const decl of moduleMeta.declarations) { - this.maybeMigrate(decl, host); - } - - return null; - } - - maybeMigrate(ref: Reference, host: MigrationHost): void { - if (hasDirectiveDecorator(host, ref.node) || hasPipeDecorator(host, ref.node)) { - // Stop if one of the classes in the chain is actually decorated with @Directive, @Component, - // or @Pipe. - return; - } - - const baseRef = readBaseClass(ref.node, host.reflectionHost, host.evaluator); - if (baseRef === null) { - // Stop: can't migrate a class with no parent. - return; - } else if (baseRef === 'dynamic') { - // Stop: can't migrate a class with an indeterminate parent. - return; - } - - // Apply the migration recursively, to handle inheritance chains. - this.maybeMigrate(baseRef, host); - - // After the above call, `host.metadata` should have metadata for the base class, if indeed this - // is a directive inheritance chain. - const baseMeta = host.metadata.getDirectiveMetadata(baseRef); - if (baseMeta === null) { - // Stop: this isn't a directive inheritance chain after all. - return; - } - - // Otherwise, decorate the class with @Component() or @Directive(), as appropriate. - if (baseMeta.isComponent) { - host.injectSyntheticDecorator( - ref.node, createComponentDecorator(ref.node, baseMeta), HandlerFlags.FULL_INHERITANCE); - } else { - host.injectSyntheticDecorator( - ref.node, createDirectiveDecorator(ref.node, baseMeta), HandlerFlags.FULL_INHERITANCE); - } - - // Success! - } -} diff --git a/packages/compiler-cli/ngcc/src/migrations/undecorated_parent_migration.ts b/packages/compiler-cli/ngcc/src/migrations/undecorated_parent_migration.ts deleted file mode 100644 index 90d0bf95a05..00000000000 --- a/packages/compiler-cli/ngcc/src/migrations/undecorated_parent_migration.ts +++ /dev/null @@ -1,109 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import ts from 'typescript'; - -import {Reference} from '../../../src/ngtsc/imports'; -import {ClassDeclaration} from '../../../src/ngtsc/reflection'; - -import {Migration, MigrationHost} from './migration'; -import {createDirectiveDecorator, hasConstructor, hasDirectiveDecorator, isClassDeclaration} from './utils'; - - -/** - * Ensure that the parents of directives and components that have no constructor are also decorated - * as a `Directive`. - * - * Example: - * - * ``` - * export class BasePlain { - * constructor(private vcr: ViewContainerRef) {} - * } - * - * @Directive({selector: '[blah]'}) - * export class DerivedDir extends BasePlain {} - * ``` - * - * When compiling `DerivedDir` which extends the undecorated `BasePlain` class, the compiler needs - * to generate a directive def (`ɵdir`) for `DerivedDir`. In particular, it needs to generate a - * factory function that creates instances of `DerivedDir`. - * - * As `DerivedDir` has no constructor, the factory function for `DerivedDir` must delegate to the - * factory function for `BasePlain`. But for this to work, `BasePlain` must have a factory function, - * itself. - * - * This migration adds a `Directive` decorator to such undecorated parent classes, to ensure that - * the compiler will create the necessary factory function. - * - * The resulting code looks like: - * - * ``` - * @Directive() - * export class BasePlain { - * constructor(private vcr: ViewContainerRef) {} - * } - * - * @Directive({selector: '[blah]'}) - * export class DerivedDir extends BasePlain {} - * ``` - */ -export class UndecoratedParentMigration implements Migration { - apply(clazz: ClassDeclaration, host: MigrationHost): ts.Diagnostic|null { - // Only interested in `clazz` if it is a `Component` or a `Directive`, - // and it has no constructor of its own. - if (!hasDirectiveDecorator(host, clazz) || hasConstructor(host, clazz)) { - return null; - } - - // Only interested in `clazz` if it inherits from a base class. - let baseClazzRef = determineBaseClass(clazz, host); - while (baseClazzRef !== null) { - const baseClazz = baseClazzRef.node; - - // Do not proceed if the base class already has a decorator, or is not in scope of the - // entry-point that is currently being compiled. - if (hasDirectiveDecorator(host, baseClazz) || !host.isInScope(baseClazz)) { - break; - } - - // Inject an `@Directive()` decorator for the base class. - host.injectSyntheticDecorator(baseClazz, createDirectiveDecorator(baseClazz)); - - // If the base class has a constructor, there's no need to continue walking up the - // inheritance chain. The injected decorator ensures that a factory is generated that does - // not delegate to the base class. - if (hasConstructor(host, baseClazz)) { - break; - } - - // Continue with another level of class inheritance. - baseClazzRef = determineBaseClass(baseClazz, host); - } - - return null; - } -} - -/** - * Computes a reference to the base class, or `null` if the class has no base class or if it could - * not be statically determined. - */ -function determineBaseClass( - clazz: ClassDeclaration, host: MigrationHost): Reference|null { - const baseClassExpr = host.reflectionHost.getBaseClassExpression(clazz); - if (baseClassExpr === null) { - return null; - } - - const baseClass = host.evaluator.evaluate(baseClassExpr); - if (!(baseClass instanceof Reference) || !isClassDeclaration(baseClass.node)) { - return null; - } - - return baseClass as Reference; -} diff --git a/packages/compiler-cli/ngcc/src/migrations/utils.ts b/packages/compiler-cli/ngcc/src/migrations/utils.ts deleted file mode 100644 index 1a1c4f376cb..00000000000 --- a/packages/compiler-cli/ngcc/src/migrations/utils.ts +++ /dev/null @@ -1,134 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import ts from 'typescript'; - -import {Reference} from '../../../src/ngtsc/imports'; -import {ClassDeclaration, Decorator, isNamedClassDeclaration, isNamedFunctionDeclaration, isNamedVariableDeclaration} from '../../../src/ngtsc/reflection'; - -import {MigrationHost} from './migration'; - -export function isClassDeclaration(clazz: ts.Node): clazz is ClassDeclaration { - return isNamedClassDeclaration(clazz) || isNamedFunctionDeclaration(clazz) || - isNamedVariableDeclaration(clazz); -} - -/** - * Returns true if the `clazz` is decorated as a `Directive` or `Component`. - */ -export function hasDirectiveDecorator(host: MigrationHost, clazz: ClassDeclaration): boolean { - const ref = new Reference(clazz); - return host.metadata.getDirectiveMetadata(ref) !== null; -} - -/** - * Returns true if the `clazz` is decorated as a `Pipe`. - */ -export function hasPipeDecorator(host: MigrationHost, clazz: ClassDeclaration): boolean { - const ref = new Reference(clazz); - return host.metadata.getPipeMetadata(ref) !== null; -} - -/** - * Returns true if the `clazz` has its own constructor function. - */ -export function hasConstructor(host: MigrationHost, clazz: ClassDeclaration): boolean { - return host.reflectionHost.getConstructorParameters(clazz) !== null; -} - -/** - * Create an empty `Directive` decorator that will be associated with the `clazz`. - */ -export function createDirectiveDecorator( - clazz: ClassDeclaration, - metadata?: {selector: string|null, exportAs: string[]|null}): Decorator { - const args: ts.Expression[] = []; - if (metadata !== undefined) { - const metaArgs: ts.PropertyAssignment[] = []; - if (metadata.selector !== null) { - metaArgs.push(property('selector', metadata.selector)); - } - if (metadata.exportAs !== null) { - metaArgs.push(property('exportAs', metadata.exportAs.join(', '))); - } - args.push(reifySourceFile(ts.factory.createObjectLiteralExpression(metaArgs))); - } - return { - name: 'Directive', - identifier: null, - import: {name: 'Directive', from: '@angular/core'}, - node: null, - synthesizedFor: clazz.name, - args, - }; -} - -/** - * Create an empty `Component` decorator that will be associated with the `clazz`. - */ -export function createComponentDecorator( - clazz: ClassDeclaration, - metadata: {selector: string|null, exportAs: string[]|null}): Decorator { - const metaArgs: ts.PropertyAssignment[] = [ - property('template', ''), - ]; - if (metadata.selector !== null) { - metaArgs.push(property('selector', metadata.selector)); - } - if (metadata.exportAs !== null) { - metaArgs.push(property('exportAs', metadata.exportAs.join(', '))); - } - return { - name: 'Component', - identifier: null, - import: {name: 'Component', from: '@angular/core'}, - node: null, - synthesizedFor: clazz.name, - args: [ - reifySourceFile(ts.factory.createObjectLiteralExpression(metaArgs)), - ], - }; -} - -/** - * Create an empty `Injectable` decorator that will be associated with the `clazz`. - */ -export function createInjectableDecorator(clazz: ClassDeclaration): Decorator { - return { - name: 'Injectable', - identifier: null, - import: {name: 'Injectable', from: '@angular/core'}, - node: null, - synthesizedFor: clazz.name, - args: [], - }; -} - -function property(name: string, value: string): ts.PropertyAssignment { - return ts.factory.createPropertyAssignment(name, ts.factory.createStringLiteral(value)); -} - -const EMPTY_SF = ts.createSourceFile('(empty)', '', ts.ScriptTarget.Latest); - -/** - * Takes a `ts.Expression` and returns the same `ts.Expression`, but with an associated - * `ts.SourceFile`. - * - * This transformation is necessary to use synthetic `ts.Expression`s with the `PartialEvaluator`, - * and many decorator arguments are interpreted in this way. - */ -function reifySourceFile(expr: ts.Expression): ts.Expression { - const printer = ts.createPrinter(); - const exprText = printer.printNode(ts.EmitHint.Unspecified, expr, EMPTY_SF); - const sf = ts.createSourceFile( - '(synthetic)', `const expr = ${exprText};`, ts.ScriptTarget.Latest, true, ts.ScriptKind.JS); - const stmt = sf.statements[0]; - if (!ts.isVariableStatement(stmt)) { - throw new Error(`Expected VariableStatement, got ${ts.SyntaxKind[stmt.kind]}`); - } - return stmt.declarationList.declarations[0].initializer!; -} diff --git a/packages/compiler-cli/ngcc/src/ngcc_options.ts b/packages/compiler-cli/ngcc/src/ngcc_options.ts deleted file mode 100644 index 4c078977334..00000000000 --- a/packages/compiler-cli/ngcc/src/ngcc_options.ts +++ /dev/null @@ -1,301 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import os from 'os'; - -import {absoluteFrom, AbsoluteFsPath, FileSystem, getFileSystem, PathManipulation} from '../../src/ngtsc/file_system'; -import {ConsoleLogger, Logger, LogLevel} from '../../src/ngtsc/logging'; -import {ParsedConfiguration, readConfiguration} from '../../src/perform_compile'; - -import {SUPPORTED_FORMAT_PROPERTIES} from './packages/entry_point'; -import {getPathMappingsFromTsConfig, PathMappings} from './path_mappings'; -import {FileWriter} from './writing/file_writer'; -import {InPlaceFileWriter} from './writing/in_place_file_writer'; -import {NewEntryPointFileWriter} from './writing/new_entry_point_file_writer'; -import {PackageJsonUpdater} from './writing/package_json_updater'; - -/** - * The options to configure the ngcc compiler for synchronous execution. - */ -export interface SyncNgccOptions { - /** The absolute path to the `node_modules` folder that contains the packages to process. */ - basePath: string; - - /** - * The path to the primary package to be processed. If not absolute then it must be relative to - * `basePath`. - * - * All its dependencies will need to be processed too. - * - * If this property is provided then `errorOnFailedEntryPoint` is forced to true. - */ - targetEntryPointPath?: string; - - /** - * Which entry-point properties in the package.json to consider when processing an entry-point. - * Each property should hold a path to the particular bundle format for the entry-point. - * Defaults to all the properties in the package.json. - */ - propertiesToConsider?: string[]; - - /** - * Whether to only process the typings files for this entry-point. - * - * This is useful when running ngcc only to provide typings files to downstream tooling such as - * the Angular Language Service or ng-packagr. Defaults to `false`. - * - * If this is set to `true` then `compileAllFormats` is forced to `false`. - */ - typingsOnly?: boolean; - - /** - * Whether to process all formats specified by (`propertiesToConsider`) or to stop processing - * this entry-point at the first matching format. - * - * Defaults to `true`, but is forced to `false` if `typingsOnly` is `true`. - */ - compileAllFormats?: boolean; - - /** - * Whether to create new entry-points bundles rather than overwriting the original files. - */ - createNewEntryPointFormats?: boolean; - - /** - * Provide a logger that will be called with log messages. - */ - logger?: Logger; - - /** - * Paths mapping configuration (`paths` and `baseUrl`), as found in `ts.CompilerOptions`. - * These are used to resolve paths to locally built Angular libraries. - * - * Note that `pathMappings` specified here take precedence over any `pathMappings` loaded from a - * TS config file. - */ - pathMappings?: PathMappings; - - /** - * Provide a file-system service that will be used by ngcc for all file interactions. - */ - fileSystem?: FileSystem; - - /** - * Whether the compilation should run and return asynchronously. Allowing asynchronous execution - * may speed up the compilation by utilizing multiple CPU cores (if available). - * - * Default: `false` (i.e. run synchronously) - */ - async?: false; - - /** - * Set to true in order to terminate immediately with an error code if an entry-point fails to be - * processed. - * - * If `targetEntryPointPath` is provided then this property is always true and cannot be - * changed. Otherwise the default is false. - * - * When set to false, ngcc will continue to process entry-points after a failure. In which case it - * will log an error and resume processing other entry-points. - */ - errorOnFailedEntryPoint?: boolean; - - /** - * Render `$localize` messages with legacy format ids. - * - * The default value is `true`. Only set this to `false` if you do not want legacy message ids to - * be rendered. For example, if you are not using legacy message ids in your translation files - * AND are not doing compile-time inlining of translations, in which case the extra message ids - * would add unwanted size to the final source bundle. - * - * It is safe to leave this set to true if you are doing compile-time inlining because the extra - * legacy message ids will all be stripped during translation. - */ - enableI18nLegacyMessageIdFormat?: boolean; - - /** - * Whether to invalidate any entry-point manifest file that is on disk. Instead, walk the - * directory tree looking for entry-points, and then write a new entry-point manifest, if - * possible. - * - * Default: `false` (i.e. the manifest will be used if available) - */ - invalidateEntryPointManifest?: boolean; - - /** - * An absolute path to a TS config file (e.g. `tsconfig.json`) or a directory containing one, that - * will be used to configure module resolution with things like path mappings, if not specified - * explicitly via the `pathMappings` property to `mainNgcc`. - * - * If `undefined`, ngcc will attempt to load a `tsconfig.json` file from the directory above the - * `basePath`. - * - * If `null`, ngcc will not attempt to load any TS config file at all. - */ - tsConfigPath?: string|null; - - /** - * Use the program defined in the loaded tsconfig.json (if available - see - * `tsConfigPath` option) to identify the entry-points that should be processed. - * If this is set to `true` then only the entry-points reachable from the given - * program (and their dependencies) will be processed. - */ - findEntryPointsFromTsConfigProgram?: boolean; -} - -/** - * The options to configure the ngcc compiler for asynchronous execution. - */ -export type AsyncNgccOptions = Omit&{async: true}; - -/** - * The options to configure the ngcc compiler. - */ -export type NgccOptions = AsyncNgccOptions|SyncNgccOptions; - -export type OptionalNgccOptionKeys = - 'targetEntryPointPath'|'tsConfigPath'|'pathMappings'|'findEntryPointsFromTsConfigProgram'; -export type RequiredNgccOptions = Required>; -export type OptionalNgccOptions = Pick; -export type SharedSetup = { - fileSystem: FileSystem; absBasePath: AbsoluteFsPath; projectPath: AbsoluteFsPath; - tsConfig: ParsedConfiguration | null; - getFileWriter(pkgJsonUpdater: PackageJsonUpdater): FileWriter; -}; - -/** - * Instantiate common utilities that are always used and fix up options with defaults, as necessary. - * - * NOTE: Avoid eagerly instantiating anything that might not be used when running sync/async. - */ -export function getSharedSetup(options: NgccOptions): SharedSetup&RequiredNgccOptions& - OptionalNgccOptions { - const fileSystem = getFileSystem(); - const absBasePath = absoluteFrom(options.basePath); - const projectPath = fileSystem.dirname(absBasePath); - const tsConfig = - options.tsConfigPath !== null ? getTsConfig(options.tsConfigPath || projectPath) : null; - - let { - basePath, - targetEntryPointPath, - propertiesToConsider = SUPPORTED_FORMAT_PROPERTIES, - typingsOnly = false, - compileAllFormats = true, - createNewEntryPointFormats = false, - logger = new ConsoleLogger(LogLevel.info), - pathMappings = getPathMappingsFromTsConfig(fileSystem, tsConfig, projectPath), - async = false, - errorOnFailedEntryPoint = false, - enableI18nLegacyMessageIdFormat = true, - invalidateEntryPointManifest = false, - tsConfigPath, - } = options; - - if (!!targetEntryPointPath) { - // targetEntryPointPath forces us to error if an entry-point fails. - errorOnFailedEntryPoint = true; - } - - if (typingsOnly) { - // If we only want to process the typings then we do not want to waste time trying to process - // multiple JS formats. - compileAllFormats = false; - } - - checkForSolutionStyleTsConfig(fileSystem, logger, projectPath, options.tsConfigPath, tsConfig); - - return { - basePath, - targetEntryPointPath, - propertiesToConsider, - typingsOnly, - compileAllFormats, - createNewEntryPointFormats, - logger, - pathMappings, - async, - errorOnFailedEntryPoint, - enableI18nLegacyMessageIdFormat, - invalidateEntryPointManifest, - tsConfigPath, - fileSystem, - absBasePath, - projectPath, - tsConfig, - getFileWriter: (pkgJsonUpdater: PackageJsonUpdater) => createNewEntryPointFormats ? - new NewEntryPointFileWriter(fileSystem, logger, errorOnFailedEntryPoint, pkgJsonUpdater) : - new InPlaceFileWriter(fileSystem, logger, errorOnFailedEntryPoint), - }; -} - -let tsConfigCache: ParsedConfiguration|null = null; -let tsConfigPathCache: string|null = null; - -/** - * Get the parsed configuration object for the given `tsConfigPath`. - * - * This function will cache the previous parsed configuration object to avoid unnecessary processing - * of the tsconfig.json in the case that it is requested repeatedly. - * - * This makes the assumption, which is true as of writing, that the contents of tsconfig.json and - * its dependencies will not change during the life of the process running ngcc. - */ -function getTsConfig(tsConfigPath: string): ParsedConfiguration|null { - if (tsConfigPath !== tsConfigPathCache) { - tsConfigPathCache = tsConfigPath; - tsConfigCache = readConfiguration(tsConfigPath); - } - return tsConfigCache; -} - -export function clearTsConfigCache() { - tsConfigPathCache = null; - tsConfigCache = null; -} - -function checkForSolutionStyleTsConfig( - fileSystem: PathManipulation, logger: Logger, projectPath: AbsoluteFsPath, - tsConfigPath: string|null|undefined, tsConfig: ParsedConfiguration|null): void { - if (tsConfigPath !== null && !tsConfigPath && tsConfig !== null && - tsConfig.rootNames.length === 0 && tsConfig.projectReferences !== undefined && - tsConfig.projectReferences.length > 0) { - logger.warn( - `The inferred tsconfig file "${tsConfig.project}" appears to be "solution-style" ` + - `since it contains no root files but does contain project references.\n` + - `This is probably not wanted, since ngcc is unable to infer settings like "paths" mappings from such a file.\n` + - `Perhaps you should have explicitly specified one of the referenced projects using the --tsconfig option. For example:\n\n` + - tsConfig.projectReferences.map(ref => ` ngcc ... --tsconfig "${ref.originalPath}"\n`) - .join('') + - `\nFind out more about solution-style tsconfig at https://devblogs.microsoft.com/typescript/announcing-typescript-3-9/#solution-style-tsconfig.\n` + - `If you did intend to use this file, then you can hide this warning by providing it explicitly:\n\n` + - ` ngcc ... --tsconfig "${fileSystem.relative(projectPath, tsConfig.project)}"`); - } -} - -/** - * Determines the maximum number of workers to use for parallel execution. This can be set using the - * NGCC_MAX_WORKERS environment variable, or is computed based on the number of available CPUs. One - * CPU core is always reserved for the master process, so we take the number of CPUs minus one, with - * a maximum of 4 workers. We don't scale the number of workers beyond 4 by default, as it takes - * considerably more memory and CPU cycles while not offering a substantial improvement in time. - */ -export function getMaxNumberOfWorkers(): number { - const maxWorkers = process.env.NGCC_MAX_WORKERS; - if (maxWorkers === undefined) { - // Use up to 4 CPU cores for workers, always reserving one for master. - return Math.max(1, Math.min(4, os.cpus().length - 1)); - } - - const numericMaxWorkers = +maxWorkers.trim(); - if (!Number.isInteger(numericMaxWorkers)) { - throw new Error('NGCC_MAX_WORKERS should be an integer.'); - } else if (numericMaxWorkers < 1) { - throw new Error('NGCC_MAX_WORKERS should be at least 1.'); - } - return numericMaxWorkers; -} diff --git a/packages/compiler-cli/ngcc/src/packages/adjust_cjs_umd_exports.ts b/packages/compiler-cli/ngcc/src/packages/adjust_cjs_umd_exports.ts deleted file mode 100644 index ccc854ba482..00000000000 --- a/packages/compiler-cli/ngcc/src/packages/adjust_cjs_umd_exports.ts +++ /dev/null @@ -1,82 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import ts from 'typescript'; - -/** - * UMD/CJS bundles generated by Rollup may be using an element access expression like - * `exports["ɵa"]` for declaring and exporting classes, instead of a property access like - * `exports.ɵa`. The element access syntax introduces a problem for ngcc, where it wouldn't consider - * such export as class declaration, causing them to be skipped. The ngtsc compiler is implemented - * with the assumption that all class declarations use a `ts.Identifier` as name, whereas the - * element access is using a string literal for the declared name. This makes it troublesome for - * ngcc to support this syntax form in UMD bundles. - * - * To work around the problem, this function transforms these access expressions into regular - * property accesses. The source text is parsed to an AST to allow finding the element accesses in a - * robust way, after which the affected text ranges are replaced with property accesses in the - * original source text. These replacement operations are faster than applying an AST transform and - * going through a `ts.Printer` to print the text back out. - */ -export function adjustElementAccessExports(sourceText: string): string { - // Do a quick check to avoid a full parse if the source cannot contain an element access - // expression. Note that this check results in a false negative if trivia (whitespace/comments) is - // present, but this should be good enough for our use-case. - if (!/exports\[["']/.test(sourceText)) { - return sourceText; - } - - const replacements: {start: number; end: number; identifier: string}[] = []; - - const sf = ts.createSourceFile( - 'input.js', sourceText, ts.ScriptTarget.ES5, /* setParentNodes */ false, ts.ScriptKind.JS); - ts.forEachChild(sf, function visitNode(node: ts.Node) { - if (ts.isElementAccessExpression(node) && isExportsIdentifier(node.expression) && - ts.isStringLiteral(node.argumentExpression) && - isJsIdentifier(node.argumentExpression.text)) { - replacements.push({ - start: node.getStart(sf), - end: node.getEnd(), - identifier: node.argumentExpression.text, - }); - } - ts.forEachChild(node, visitNode); - }); - - // Sort the replacements in reverse order, such that applying the replacements does not invalidate - // the captured positions of other replacements. - replacements.sort((a, b) => a.start - b.start); - - const parts: string[] = []; - let currentIndex = 0; - for (const replacement of replacements) { - parts.push(sourceText.substring(currentIndex, replacement.start)); - // The whitespace is inserted such that the identifier aligns with the original string literal - // text, which means that source maps continue to be accurate. - parts.push(`exports. ${replacement.identifier} `); - currentIndex = replacement.end; - } - parts.push(sourceText.substring(currentIndex)); - return parts.join(''); -} - -function isExportsIdentifier(expr: ts.Expression): boolean { - return ts.isIdentifier(expr) && expr.text === 'exports'; -} - -/** - * An element access using a string literal can only be converted into a property access if the - * string is a valid JS identifier, i.e. not a reserved keyword. - * - * This regex is taken from {@link https://stackoverflow.com/a/9392578/171436}. - */ -const identifierPattern = - /^(?!(?:do|if|in|for|let|new|try|var|case|else|enum|eval|false|null|this|true|void|with|break|catch|class|const|super|throw|while|yield|delete|export|import|public|return|static|switch|typeof|default|extends|finally|package|private|continue|debugger|function|arguments|interface|protected|implements|instanceof)$)[$A-Z\_a-z\xaa\xb5\xba\xc0-\xd6\xd8-\xf6\xf8-\u02c1\u02c6-\u02d1\u02e0-\u02e4\u02ec\u02ee\u0370-\u0374\u0376\u0377\u037a-\u037d\u0386\u0388-\u038a\u038c\u038e-\u03a1\u03a3-\u03f5\u03f7-\u0481\u048a-\u0527\u0531-\u0556\u0559\u0561-\u0587\u05d0-\u05ea\u05f0-\u05f2\u0620-\u064a\u066e\u066f\u0671-\u06d3\u06d5\u06e5\u06e6\u06ee\u06ef\u06fa-\u06fc\u06ff\u0710\u0712-\u072f\u074d-\u07a5\u07b1\u07ca-\u07ea\u07f4\u07f5\u07fa\u0800-\u0815\u081a\u0824\u0828\u0840-\u0858\u08a0\u08a2-\u08ac\u0904-\u0939\u093d\u0950\u0958-\u0961\u0971-\u0977\u0979-\u097f\u0985-\u098c\u098f\u0990\u0993-\u09a8\u09aa-\u09b0\u09b2\u09b6-\u09b9\u09bd\u09ce\u09dc\u09dd\u09df-\u09e1\u09f0\u09f1\u0a05-\u0a0a\u0a0f\u0a10\u0a13-\u0a28\u0a2a-\u0a30\u0a32\u0a33\u0a35\u0a36\u0a38\u0a39\u0a59-\u0a5c\u0a5e\u0a72-\u0a74\u0a85-\u0a8d\u0a8f-\u0a91\u0a93-\u0aa8\u0aaa-\u0ab0\u0ab2\u0ab3\u0ab5-\u0ab9\u0abd\u0ad0\u0ae0\u0ae1\u0b05-\u0b0c\u0b0f\u0b10\u0b13-\u0b28\u0b2a-\u0b30\u0b32\u0b33\u0b35-\u0b39\u0b3d\u0b5c\u0b5d\u0b5f-\u0b61\u0b71\u0b83\u0b85-\u0b8a\u0b8e-\u0b90\u0b92-\u0b95\u0b99\u0b9a\u0b9c\u0b9e\u0b9f\u0ba3\u0ba4\u0ba8-\u0baa\u0bae-\u0bb9\u0bd0\u0c05-\u0c0c\u0c0e-\u0c10\u0c12-\u0c28\u0c2a-\u0c33\u0c35-\u0c39\u0c3d\u0c58\u0c59\u0c60\u0c61\u0c85-\u0c8c\u0c8e-\u0c90\u0c92-\u0ca8\u0caa-\u0cb3\u0cb5-\u0cb9\u0cbd\u0cde\u0ce0\u0ce1\u0cf1\u0cf2\u0d05-\u0d0c\u0d0e-\u0d10\u0d12-\u0d3a\u0d3d\u0d4e\u0d60\u0d61\u0d7a-\u0d7f\u0d85-\u0d96\u0d9a-\u0db1\u0db3-\u0dbb\u0dbd\u0dc0-\u0dc6\u0e01-\u0e30\u0e32\u0e33\u0e40-\u0e46\u0e81\u0e82\u0e84\u0e87\u0e88\u0e8a\u0e8d\u0e94-\u0e97\u0e99-\u0e9f\u0ea1-\u0ea3\u0ea5\u0ea7\u0eaa\u0eab\u0ead-\u0eb0\u0eb2\u0eb3\u0ebd\u0ec0-\u0ec4\u0ec6\u0edc-\u0edf\u0f00\u0f40-\u0f47\u0f49-\u0f6c\u0f88-\u0f8c\u1000-\u102a\u103f\u1050-\u1055\u105a-\u105d\u1061\u1065\u1066\u106e-\u1070\u1075-\u1081\u108e\u10a0-\u10c5\u10c7\u10cd\u10d0-\u10fa\u10fc-\u1248\u124a-\u124d\u1250-\u1256\u1258\u125a-\u125d\u1260-\u1288\u128a-\u128d\u1290-\u12b0\u12b2-\u12b5\u12b8-\u12be\u12c0\u12c2-\u12c5\u12c8-\u12d6\u12d8-\u1310\u1312-\u1315\u1318-\u135a\u1380-\u138f\u13a0-\u13f4\u1401-\u166c\u166f-\u167f\u1681-\u169a\u16a0-\u16ea\u16ee-\u16f0\u1700-\u170c\u170e-\u1711\u1720-\u1731\u1740-\u1751\u1760-\u176c\u176e-\u1770\u1780-\u17b3\u17d7\u17dc\u1820-\u1877\u1880-\u18a8\u18aa\u18b0-\u18f5\u1900-\u191c\u1950-\u196d\u1970-\u1974\u1980-\u19ab\u19c1-\u19c7\u1a00-\u1a16\u1a20-\u1a54\u1aa7\u1b05-\u1b33\u1b45-\u1b4b\u1b83-\u1ba0\u1bae\u1baf\u1bba-\u1be5\u1c00-\u1c23\u1c4d-\u1c4f\u1c5a-\u1c7d\u1ce9-\u1cec\u1cee-\u1cf1\u1cf5\u1cf6\u1d00-\u1dbf\u1e00-\u1f15\u1f18-\u1f1d\u1f20-\u1f45\u1f48-\u1f4d\u1f50-\u1f57\u1f59\u1f5b\u1f5d\u1f5f-\u1f7d\u1f80-\u1fb4\u1fb6-\u1fbc\u1fbe\u1fc2-\u1fc4\u1fc6-\u1fcc\u1fd0-\u1fd3\u1fd6-\u1fdb\u1fe0-\u1fec\u1ff2-\u1ff4\u1ff6-\u1ffc\u2071\u207f\u2090-\u209c\u2102\u2107\u210a-\u2113\u2115\u2119-\u211d\u2124\u2126\u2128\u212a-\u212d\u212f-\u2139\u213c-\u213f\u2145-\u2149\u214e\u2160-\u2188\u2c00-\u2c2e\u2c30-\u2c5e\u2c60-\u2ce4\u2ceb-\u2cee\u2cf2\u2cf3\u2d00-\u2d25\u2d27\u2d2d\u2d30-\u2d67\u2d6f\u2d80-\u2d96\u2da0-\u2da6\u2da8-\u2dae\u2db0-\u2db6\u2db8-\u2dbe\u2dc0-\u2dc6\u2dc8-\u2dce\u2dd0-\u2dd6\u2dd8-\u2dde\u2e2f\u3005-\u3007\u3021-\u3029\u3031-\u3035\u3038-\u303c\u3041-\u3096\u309d-\u309f\u30a1-\u30fa\u30fc-\u30ff\u3105-\u312d\u3131-\u318e\u31a0-\u31ba\u31f0-\u31ff\u3400-\u4db5\u4e00-\u9fcc\ua000-\ua48c\ua4d0-\ua4fd\ua500-\ua60c\ua610-\ua61f\ua62a\ua62b\ua640-\ua66e\ua67f-\ua697\ua6a0-\ua6ef\ua717-\ua71f\ua722-\ua788\ua78b-\ua78e\ua790-\ua793\ua7a0-\ua7aa\ua7f8-\ua801\ua803-\ua805\ua807-\ua80a\ua80c-\ua822\ua840-\ua873\ua882-\ua8b3\ua8f2-\ua8f7\ua8fb\ua90a-\ua925\ua930-\ua946\ua960-\ua97c\ua984-\ua9b2\ua9cf\uaa00-\uaa28\uaa40-\uaa42\uaa44-\uaa4b\uaa60-\uaa76\uaa7a\uaa80-\uaaaf\uaab1\uaab5\uaab6\uaab9-\uaabd\uaac0\uaac2\uaadb-\uaadd\uaae0-\uaaea\uaaf2-\uaaf4\uab01-\uab06\uab09-\uab0e\uab11-\uab16\uab20-\uab26\uab28-\uab2e\uabc0-\uabe2\uac00-\ud7a3\ud7b0-\ud7c6\ud7cb-\ud7fb\uf900-\ufa6d\ufa70-\ufad9\ufb00-\ufb06\ufb13-\ufb17\ufb1d\ufb1f-\ufb28\ufb2a-\ufb36\ufb38-\ufb3c\ufb3e\ufb40\ufb41\ufb43\ufb44\ufb46-\ufbb1\ufbd3-\ufd3d\ufd50-\ufd8f\ufd92-\ufdc7\ufdf0-\ufdfb\ufe70-\ufe74\ufe76-\ufefc\uff21-\uff3a\uff41-\uff5a\uff66-\uffbe\uffc2-\uffc7\uffca-\uffcf\uffd2-\uffd7\uffda-\uffdc][$A-Z\_a-z\xaa\xb5\xba\xc0-\xd6\xd8-\xf6\xf8-\u02c1\u02c6-\u02d1\u02e0-\u02e4\u02ec\u02ee\u0370-\u0374\u0376\u0377\u037a-\u037d\u0386\u0388-\u038a\u038c\u038e-\u03a1\u03a3-\u03f5\u03f7-\u0481\u048a-\u0527\u0531-\u0556\u0559\u0561-\u0587\u05d0-\u05ea\u05f0-\u05f2\u0620-\u064a\u066e\u066f\u0671-\u06d3\u06d5\u06e5\u06e6\u06ee\u06ef\u06fa-\u06fc\u06ff\u0710\u0712-\u072f\u074d-\u07a5\u07b1\u07ca-\u07ea\u07f4\u07f5\u07fa\u0800-\u0815\u081a\u0824\u0828\u0840-\u0858\u08a0\u08a2-\u08ac\u0904-\u0939\u093d\u0950\u0958-\u0961\u0971-\u0977\u0979-\u097f\u0985-\u098c\u098f\u0990\u0993-\u09a8\u09aa-\u09b0\u09b2\u09b6-\u09b9\u09bd\u09ce\u09dc\u09dd\u09df-\u09e1\u09f0\u09f1\u0a05-\u0a0a\u0a0f\u0a10\u0a13-\u0a28\u0a2a-\u0a30\u0a32\u0a33\u0a35\u0a36\u0a38\u0a39\u0a59-\u0a5c\u0a5e\u0a72-\u0a74\u0a85-\u0a8d\u0a8f-\u0a91\u0a93-\u0aa8\u0aaa-\u0ab0\u0ab2\u0ab3\u0ab5-\u0ab9\u0abd\u0ad0\u0ae0\u0ae1\u0b05-\u0b0c\u0b0f\u0b10\u0b13-\u0b28\u0b2a-\u0b30\u0b32\u0b33\u0b35-\u0b39\u0b3d\u0b5c\u0b5d\u0b5f-\u0b61\u0b71\u0b83\u0b85-\u0b8a\u0b8e-\u0b90\u0b92-\u0b95\u0b99\u0b9a\u0b9c\u0b9e\u0b9f\u0ba3\u0ba4\u0ba8-\u0baa\u0bae-\u0bb9\u0bd0\u0c05-\u0c0c\u0c0e-\u0c10\u0c12-\u0c28\u0c2a-\u0c33\u0c35-\u0c39\u0c3d\u0c58\u0c59\u0c60\u0c61\u0c85-\u0c8c\u0c8e-\u0c90\u0c92-\u0ca8\u0caa-\u0cb3\u0cb5-\u0cb9\u0cbd\u0cde\u0ce0\u0ce1\u0cf1\u0cf2\u0d05-\u0d0c\u0d0e-\u0d10\u0d12-\u0d3a\u0d3d\u0d4e\u0d60\u0d61\u0d7a-\u0d7f\u0d85-\u0d96\u0d9a-\u0db1\u0db3-\u0dbb\u0dbd\u0dc0-\u0dc6\u0e01-\u0e30\u0e32\u0e33\u0e40-\u0e46\u0e81\u0e82\u0e84\u0e87\u0e88\u0e8a\u0e8d\u0e94-\u0e97\u0e99-\u0e9f\u0ea1-\u0ea3\u0ea5\u0ea7\u0eaa\u0eab\u0ead-\u0eb0\u0eb2\u0eb3\u0ebd\u0ec0-\u0ec4\u0ec6\u0edc-\u0edf\u0f00\u0f40-\u0f47\u0f49-\u0f6c\u0f88-\u0f8c\u1000-\u102a\u103f\u1050-\u1055\u105a-\u105d\u1061\u1065\u1066\u106e-\u1070\u1075-\u1081\u108e\u10a0-\u10c5\u10c7\u10cd\u10d0-\u10fa\u10fc-\u1248\u124a-\u124d\u1250-\u1256\u1258\u125a-\u125d\u1260-\u1288\u128a-\u128d\u1290-\u12b0\u12b2-\u12b5\u12b8-\u12be\u12c0\u12c2-\u12c5\u12c8-\u12d6\u12d8-\u1310\u1312-\u1315\u1318-\u135a\u1380-\u138f\u13a0-\u13f4\u1401-\u166c\u166f-\u167f\u1681-\u169a\u16a0-\u16ea\u16ee-\u16f0\u1700-\u170c\u170e-\u1711\u1720-\u1731\u1740-\u1751\u1760-\u176c\u176e-\u1770\u1780-\u17b3\u17d7\u17dc\u1820-\u1877\u1880-\u18a8\u18aa\u18b0-\u18f5\u1900-\u191c\u1950-\u196d\u1970-\u1974\u1980-\u19ab\u19c1-\u19c7\u1a00-\u1a16\u1a20-\u1a54\u1aa7\u1b05-\u1b33\u1b45-\u1b4b\u1b83-\u1ba0\u1bae\u1baf\u1bba-\u1be5\u1c00-\u1c23\u1c4d-\u1c4f\u1c5a-\u1c7d\u1ce9-\u1cec\u1cee-\u1cf1\u1cf5\u1cf6\u1d00-\u1dbf\u1e00-\u1f15\u1f18-\u1f1d\u1f20-\u1f45\u1f48-\u1f4d\u1f50-\u1f57\u1f59\u1f5b\u1f5d\u1f5f-\u1f7d\u1f80-\u1fb4\u1fb6-\u1fbc\u1fbe\u1fc2-\u1fc4\u1fc6-\u1fcc\u1fd0-\u1fd3\u1fd6-\u1fdb\u1fe0-\u1fec\u1ff2-\u1ff4\u1ff6-\u1ffc\u2071\u207f\u2090-\u209c\u2102\u2107\u210a-\u2113\u2115\u2119-\u211d\u2124\u2126\u2128\u212a-\u212d\u212f-\u2139\u213c-\u213f\u2145-\u2149\u214e\u2160-\u2188\u2c00-\u2c2e\u2c30-\u2c5e\u2c60-\u2ce4\u2ceb-\u2cee\u2cf2\u2cf3\u2d00-\u2d25\u2d27\u2d2d\u2d30-\u2d67\u2d6f\u2d80-\u2d96\u2da0-\u2da6\u2da8-\u2dae\u2db0-\u2db6\u2db8-\u2dbe\u2dc0-\u2dc6\u2dc8-\u2dce\u2dd0-\u2dd6\u2dd8-\u2dde\u2e2f\u3005-\u3007\u3021-\u3029\u3031-\u3035\u3038-\u303c\u3041-\u3096\u309d-\u309f\u30a1-\u30fa\u30fc-\u30ff\u3105-\u312d\u3131-\u318e\u31a0-\u31ba\u31f0-\u31ff\u3400-\u4db5\u4e00-\u9fcc\ua000-\ua48c\ua4d0-\ua4fd\ua500-\ua60c\ua610-\ua61f\ua62a\ua62b\ua640-\ua66e\ua67f-\ua697\ua6a0-\ua6ef\ua717-\ua71f\ua722-\ua788\ua78b-\ua78e\ua790-\ua793\ua7a0-\ua7aa\ua7f8-\ua801\ua803-\ua805\ua807-\ua80a\ua80c-\ua822\ua840-\ua873\ua882-\ua8b3\ua8f2-\ua8f7\ua8fb\ua90a-\ua925\ua930-\ua946\ua960-\ua97c\ua984-\ua9b2\ua9cf\uaa00-\uaa28\uaa40-\uaa42\uaa44-\uaa4b\uaa60-\uaa76\uaa7a\uaa80-\uaaaf\uaab1\uaab5\uaab6\uaab9-\uaabd\uaac0\uaac2\uaadb-\uaadd\uaae0-\uaaea\uaaf2-\uaaf4\uab01-\uab06\uab09-\uab0e\uab11-\uab16\uab20-\uab26\uab28-\uab2e\uabc0-\uabe2\uac00-\ud7a3\ud7b0-\ud7c6\ud7cb-\ud7fb\uf900-\ufa6d\ufa70-\ufad9\ufb00-\ufb06\ufb13-\ufb17\ufb1d\ufb1f-\ufb28\ufb2a-\ufb36\ufb38-\ufb3c\ufb3e\ufb40\ufb41\ufb43\ufb44\ufb46-\ufbb1\ufbd3-\ufd3d\ufd50-\ufd8f\ufd92-\ufdc7\ufdf0-\ufdfb\ufe70-\ufe74\ufe76-\ufefc\uff21-\uff3a\uff41-\uff5a\uff66-\uffbe\uffc2-\uffc7\uffca-\uffcf\uffd2-\uffd7\uffda-\uffdc0-9\u0300-\u036f\u0483-\u0487\u0591-\u05bd\u05bf\u05c1\u05c2\u05c4\u05c5\u05c7\u0610-\u061a\u064b-\u0669\u0670\u06d6-\u06dc\u06df-\u06e4\u06e7\u06e8\u06ea-\u06ed\u06f0-\u06f9\u0711\u0730-\u074a\u07a6-\u07b0\u07c0-\u07c9\u07eb-\u07f3\u0816-\u0819\u081b-\u0823\u0825-\u0827\u0829-\u082d\u0859-\u085b\u08e4-\u08fe\u0900-\u0903\u093a-\u093c\u093e-\u094f\u0951-\u0957\u0962\u0963\u0966-\u096f\u0981-\u0983\u09bc\u09be-\u09c4\u09c7\u09c8\u09cb-\u09cd\u09d7\u09e2\u09e3\u09e6-\u09ef\u0a01-\u0a03\u0a3c\u0a3e-\u0a42\u0a47\u0a48\u0a4b-\u0a4d\u0a51\u0a66-\u0a71\u0a75\u0a81-\u0a83\u0abc\u0abe-\u0ac5\u0ac7-\u0ac9\u0acb-\u0acd\u0ae2\u0ae3\u0ae6-\u0aef\u0b01-\u0b03\u0b3c\u0b3e-\u0b44\u0b47\u0b48\u0b4b-\u0b4d\u0b56\u0b57\u0b62\u0b63\u0b66-\u0b6f\u0b82\u0bbe-\u0bc2\u0bc6-\u0bc8\u0bca-\u0bcd\u0bd7\u0be6-\u0bef\u0c01-\u0c03\u0c3e-\u0c44\u0c46-\u0c48\u0c4a-\u0c4d\u0c55\u0c56\u0c62\u0c63\u0c66-\u0c6f\u0c82\u0c83\u0cbc\u0cbe-\u0cc4\u0cc6-\u0cc8\u0cca-\u0ccd\u0cd5\u0cd6\u0ce2\u0ce3\u0ce6-\u0cef\u0d02\u0d03\u0d3e-\u0d44\u0d46-\u0d48\u0d4a-\u0d4d\u0d57\u0d62\u0d63\u0d66-\u0d6f\u0d82\u0d83\u0dca\u0dcf-\u0dd4\u0dd6\u0dd8-\u0ddf\u0df2\u0df3\u0e31\u0e34-\u0e3a\u0e47-\u0e4e\u0e50-\u0e59\u0eb1\u0eb4-\u0eb9\u0ebb\u0ebc\u0ec8-\u0ecd\u0ed0-\u0ed9\u0f18\u0f19\u0f20-\u0f29\u0f35\u0f37\u0f39\u0f3e\u0f3f\u0f71-\u0f84\u0f86\u0f87\u0f8d-\u0f97\u0f99-\u0fbc\u0fc6\u102b-\u103e\u1040-\u1049\u1056-\u1059\u105e-\u1060\u1062-\u1064\u1067-\u106d\u1071-\u1074\u1082-\u108d\u108f-\u109d\u135d-\u135f\u1712-\u1714\u1732-\u1734\u1752\u1753\u1772\u1773\u17b4-\u17d3\u17dd\u17e0-\u17e9\u180b-\u180d\u1810-\u1819\u18a9\u1920-\u192b\u1930-\u193b\u1946-\u194f\u19b0-\u19c0\u19c8\u19c9\u19d0-\u19d9\u1a17-\u1a1b\u1a55-\u1a5e\u1a60-\u1a7c\u1a7f-\u1a89\u1a90-\u1a99\u1b00-\u1b04\u1b34-\u1b44\u1b50-\u1b59\u1b6b-\u1b73\u1b80-\u1b82\u1ba1-\u1bad\u1bb0-\u1bb9\u1be6-\u1bf3\u1c24-\u1c37\u1c40-\u1c49\u1c50-\u1c59\u1cd0-\u1cd2\u1cd4-\u1ce8\u1ced\u1cf2-\u1cf4\u1dc0-\u1de6\u1dfc-\u1dff\u200c\u200d\u203f\u2040\u2054\u20d0-\u20dc\u20e1\u20e5-\u20f0\u2cef-\u2cf1\u2d7f\u2de0-\u2dff\u302a-\u302f\u3099\u309a\ua620-\ua629\ua66f\ua674-\ua67d\ua69f\ua6f0\ua6f1\ua802\ua806\ua80b\ua823-\ua827\ua880\ua881\ua8b4-\ua8c4\ua8d0-\ua8d9\ua8e0-\ua8f1\ua900-\ua909\ua926-\ua92d\ua947-\ua953\ua980-\ua983\ua9b3-\ua9c0\ua9d0-\ua9d9\uaa29-\uaa36\uaa43\uaa4c\uaa4d\uaa50-\uaa59\uaa7b\uaab0\uaab2-\uaab4\uaab7\uaab8\uaabe\uaabf\uaac1\uaaeb-\uaaef\uaaf5\uaaf6\uabe3-\uabea\uabec\uabed\uabf0-\uabf9\ufb1e\ufe00-\ufe0f\ufe20-\ufe26\ufe33\ufe34\ufe4d-\ufe4f\uff10-\uff19\uff3f]*$/; - -function isJsIdentifier(text: string): boolean { - return identifierPattern.test(text); -} diff --git a/packages/compiler-cli/ngcc/src/packages/build_marker.ts b/packages/compiler-cli/ngcc/src/packages/build_marker.ts deleted file mode 100644 index 6d8b1686fda..00000000000 --- a/packages/compiler-cli/ngcc/src/packages/build_marker.ts +++ /dev/null @@ -1,108 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import {AbsoluteFsPath} from '../../../src/ngtsc/file_system'; -import {NGCC_PROPERTY_EXTENSION} from '../writing/new_entry_point_file_writer'; -import {PackageJsonUpdater} from '../writing/package_json_updater'; - -import {EntryPointPackageJson, PackageJsonFormatProperties} from './entry_point'; - -export const NGCC_VERSION = '0.0.0-PLACEHOLDER'; - -/** - * Returns true if there is a format in this entry-point that was compiled with an outdated version - * of ngcc. - * - * @param packageJson The parsed contents of the package.json for the entry-point - */ -export function needsCleaning(packageJson: EntryPointPackageJson): boolean { - return Object.values(packageJson.__processed_by_ivy_ngcc__ || {}) - .some(value => value !== NGCC_VERSION); -} - -/** - * Clean any build marker artifacts from the given `packageJson` object. - * @param packageJson The parsed contents of the package.json to modify - * @returns true if the package was modified during cleaning - */ -export function cleanPackageJson(packageJson: EntryPointPackageJson): boolean { - if (packageJson.__processed_by_ivy_ngcc__ !== undefined) { - // Remove the actual marker - delete packageJson.__processed_by_ivy_ngcc__; - // Remove new format properties that have been added by ngcc - for (const prop of Object.keys(packageJson)) { - if (prop.endsWith(NGCC_PROPERTY_EXTENSION)) { - delete packageJson[prop]; - } - } - - // Also remove the prepublish script if we modified it - const scripts = packageJson.scripts; - if (scripts !== undefined && scripts.prepublishOnly) { - delete scripts.prepublishOnly; - if (scripts.prepublishOnly__ivy_ngcc_bak !== undefined) { - scripts.prepublishOnly = scripts.prepublishOnly__ivy_ngcc_bak; - delete scripts.prepublishOnly__ivy_ngcc_bak; - } - } - return true; - } - return false; -} - -/** - * Check whether ngcc has already processed a given entry-point format. - * - * @param packageJson The parsed contents of the package.json file for the entry-point. - * @param format The entry-point format property in the package.json to check. - * @returns true if the `format` in the entry-point has already been processed by this ngcc version, - * false otherwise. - */ -export function hasBeenProcessed( - packageJson: EntryPointPackageJson, format: PackageJsonFormatProperties): boolean { - return packageJson.__processed_by_ivy_ngcc__ !== undefined && - packageJson.__processed_by_ivy_ngcc__[format] === NGCC_VERSION; -} - -/** - * Write a build marker for the given entry-point and format properties, to indicate that they have - * been compiled by this version of ngcc. - * - * @param pkgJsonUpdater The writer to use for updating `package.json`. - * @param packageJson The parsed contents of the `package.json` file for the entry-point. - * @param packageJsonPath The absolute path to the `package.json` file. - * @param properties The properties in the `package.json` of the formats for which we are writing - * the marker. - */ -export function markAsProcessed( - pkgJsonUpdater: PackageJsonUpdater, packageJson: EntryPointPackageJson, - packageJsonPath: AbsoluteFsPath, formatProperties: PackageJsonFormatProperties[]): void { - const update = pkgJsonUpdater.createUpdate(); - - // Update the format properties to mark them as processed. - for (const prop of formatProperties) { - update.addChange(['__processed_by_ivy_ngcc__', prop], NGCC_VERSION, 'alphabetic'); - } - - // Update the `prepublishOnly` script (keeping a backup, if necessary) to prevent `ngcc`'d - // packages from getting accidentally published. - const oldPrepublishOnly = packageJson.scripts && packageJson.scripts.prepublishOnly; - const newPrepublishOnly = 'node --eval \"console.error(\'' + - 'ERROR: Trying to publish a package that has been compiled by NGCC. This is not allowed.\\n' + - 'Please delete and rebuild the package, without compiling with NGCC, before attempting to publish.\\n' + - 'Note that NGCC may have been run by importing this package into another project that is being built with Ivy enabled.\\n' + - '\')\" ' + - '&& exit 1'; - - if (oldPrepublishOnly && (oldPrepublishOnly !== newPrepublishOnly)) { - update.addChange(['scripts', 'prepublishOnly__ivy_ngcc_bak'], oldPrepublishOnly); - } - - update.addChange(['scripts', 'prepublishOnly'], newPrepublishOnly); - - update.writeChanges(packageJsonPath, packageJson); -} diff --git a/packages/compiler-cli/ngcc/src/packages/bundle_program.ts b/packages/compiler-cli/ngcc/src/packages/bundle_program.ts deleted file mode 100644 index e8dd30386ac..00000000000 --- a/packages/compiler-cli/ngcc/src/packages/bundle_program.ts +++ /dev/null @@ -1,87 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import ts from 'typescript'; - -import {AbsoluteFsPath, ReadonlyFileSystem} from '../../../src/ngtsc/file_system'; - -import {patchTsGetExpandoInitializer, restoreGetExpandoInitializer} from './patch_ts_expando_initializer'; - -/** - * An entry point bundle contains one or two programs, e.g. `src` and `dts`, - * that are compiled via TypeScript. - * - * To aid with processing the program, this interface exposes the program itself, - * as well as path and TS file of the entry-point to the program and the r3Symbols - * file, if appropriate. - */ -export interface BundleProgram { - program: ts.Program; - options: ts.CompilerOptions; - host: ts.CompilerHost; - path: AbsoluteFsPath; - file: ts.SourceFile; - package: AbsoluteFsPath; - r3SymbolsPath: AbsoluteFsPath|null; - r3SymbolsFile: ts.SourceFile|null; -} - -/** - * Create a bundle program. - */ -export function makeBundleProgram( - fs: ReadonlyFileSystem, isCore: boolean, pkg: AbsoluteFsPath, path: AbsoluteFsPath, - r3FileName: string, options: ts.CompilerOptions, host: ts.CompilerHost, - additionalFiles: AbsoluteFsPath[] = []): BundleProgram { - const r3SymbolsPath = isCore ? findR3SymbolsPath(fs, fs.dirname(path), r3FileName) : null; - let rootPaths = - r3SymbolsPath ? [path, r3SymbolsPath, ...additionalFiles] : [path, ...additionalFiles]; - - const originalGetExpandoInitializer = patchTsGetExpandoInitializer(); - const program = ts.createProgram(rootPaths, options, host); - // Ask for the typeChecker to trigger the binding phase of the compilation. - // This will then exercise the patched function. - program.getTypeChecker(); - restoreGetExpandoInitializer(originalGetExpandoInitializer); - - const file = program.getSourceFile(path)!; - const r3SymbolsFile = r3SymbolsPath && program.getSourceFile(r3SymbolsPath) || null; - - return {program, options, host, package: pkg, path, file, r3SymbolsPath, r3SymbolsFile}; -} - -/** - * Search the given directory hierarchy to find the path to the `r3_symbols` file. - */ -export function findR3SymbolsPath( - fs: ReadonlyFileSystem, directory: AbsoluteFsPath, filename: string): AbsoluteFsPath|null { - const r3SymbolsFilePath = fs.resolve(directory, filename); - if (fs.exists(r3SymbolsFilePath)) { - return r3SymbolsFilePath; - } - - const subDirectories = - fs.readdir(directory) - // Not interested in hidden files - .filter(p => !p.startsWith('.')) - // Ignore node_modules - .filter(p => p !== 'node_modules') - // Only interested in directories (and only those that are not symlinks) - .filter(p => { - const stat = fs.lstat(fs.resolve(directory, p)); - return stat.isDirectory() && !stat.isSymbolicLink(); - }); - - for (const subDirectory of subDirectories) { - const r3SymbolsFilePath = findR3SymbolsPath(fs, fs.resolve(directory, subDirectory), filename); - if (r3SymbolsFilePath) { - return r3SymbolsFilePath; - } - } - - return null; -} diff --git a/packages/compiler-cli/ngcc/src/packages/configuration.ts b/packages/compiler-cli/ngcc/src/packages/configuration.ts deleted file mode 100644 index 3f7f57b459f..00000000000 --- a/packages/compiler-cli/ngcc/src/packages/configuration.ts +++ /dev/null @@ -1,458 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import {createHash} from 'crypto'; -import module from 'module'; -import semver from 'semver'; -import * as vm from 'vm'; - -import {AbsoluteFsPath, PathManipulation, ReadonlyFileSystem} from '../../../src/ngtsc/file_system'; - -import {PackageJsonFormatPropertiesMap} from './entry_point'; - -/** - * The format of a project level configuration file. - */ -export interface NgccProjectConfig { - /** - * The packages that are configured by this project config. - */ - packages?: {[packagePath: string]: RawNgccPackageConfig|undefined}; - /** - * Options that control how locking the process is handled. - */ - locking?: ProcessLockingConfiguration; - /** - * Name of hash algorithm used to generate hashes of the configuration. - * - * Defaults to `sha256`. - */ - hashAlgorithm?: string; -} - -/** - * Options that control how locking the process is handled. - */ -export interface ProcessLockingConfiguration { - /** - * The number of times the AsyncLocker will attempt to lock the process before failing. - * Defaults to 500. - */ - retryAttempts?: number; - /** - * The number of milliseconds between attempts to lock the process. - * Defaults to 500ms. - * */ - retryDelay?: number; -} - -/** - * The raw format of a package level configuration (as it appears in configuration files). - */ -export interface RawNgccPackageConfig { - /** - * The entry-points to configure for this package. - * - * In the config file the keys are paths relative to the package path. - */ - entryPoints?: {[entryPointPath: string]: NgccEntryPointConfig}; - - /** - * A collection of regexes that match deep imports to ignore, for this package, rather than - * displaying a warning. - */ - ignorableDeepImportMatchers?: RegExp[]; -} - -/** - * Configuration options for an entry-point. - * - * The existence of a configuration for a path tells ngcc that this should be considered for - * processing as an entry-point. - */ -export interface NgccEntryPointConfig { - /** Do not process (or even acknowledge the existence of) this entry-point, if true. */ - ignore?: boolean; - - /** - * This property, if provided, holds values that will override equivalent properties in an - * entry-point's package.json file. - */ - override?: PackageJsonFormatPropertiesMap; - - /** - * Normally, ngcc will skip compilation of entrypoints that contain imports that can't be resolved - * or understood. If this option is specified, ngcc will proceed with compiling the entrypoint - * even in the face of such missing dependencies. - */ - ignoreMissingDependencies?: boolean; - - /** - * Enabling this option for an entrypoint tells ngcc that deep imports might be used for the files - * it contains, and that it should generate private re-exports alongside the NgModule of all the - * directives/pipes it makes available in support of those imports. - */ - generateDeepReexports?: boolean; -} - -interface VersionedPackageConfig extends RawNgccPackageConfig { - versionRange: string; -} - -/** - * The internal representation of a configuration file. Configured packages are transformed into - * `ProcessedNgccPackageConfig` when a certain version is requested. - */ -export class PartiallyProcessedConfig { - /** - * The packages that are configured by this project config, keyed by package name. - */ - packages = new Map(); - /** - * Options that control how locking the process is handled. - */ - locking: ProcessLockingConfiguration = {}; - /** - * Name of hash algorithm used to generate hashes of the configuration. - * - * Defaults to `sha256`. - */ - hashAlgorithm = 'sha256'; - - constructor(projectConfig: NgccProjectConfig) { - // locking configuration - if (projectConfig.locking !== undefined) { - this.locking = projectConfig.locking; - } - - // packages configuration - for (const packageNameAndVersion in projectConfig.packages) { - const packageConfig = projectConfig.packages[packageNameAndVersion]; - if (packageConfig) { - const [packageName, versionRange = '*'] = this.splitNameAndVersion(packageNameAndVersion); - this.addPackageConfig(packageName, {...packageConfig, versionRange}); - } - } - - // hash algorithm config - if (projectConfig.hashAlgorithm !== undefined) { - this.hashAlgorithm = projectConfig.hashAlgorithm; - } - } - - private splitNameAndVersion(packageNameAndVersion: string): [string, string|undefined] { - const versionIndex = packageNameAndVersion.lastIndexOf('@'); - // Note that > 0 is because we don't want to match @ at the start of the line - // which is what you would have with a namespaced package, e.g. `@angular/common`. - return versionIndex > 0 ? - [ - packageNameAndVersion.substring(0, versionIndex), - packageNameAndVersion.substring(versionIndex + 1), - ] : - [packageNameAndVersion, undefined]; - } - - /** - * Registers the configuration for a particular version of the provided package. - */ - private addPackageConfig(packageName: string, config: VersionedPackageConfig): void { - if (!this.packages.has(packageName)) { - this.packages.set(packageName, []); - } - this.packages.get(packageName)!.push(config); - } - - /** - * Finds the configuration for a particular version of the provided package. - */ - findPackageConfig(packageName: string, version: string|null): VersionedPackageConfig|null { - if (!this.packages.has(packageName)) { - return null; - } - - const configs = this.packages.get(packageName)!; - if (version === null) { - // The package has no version (!) - perhaps the entry-point was from a deep import, which made - // it impossible to find the package.json. - // So just return the first config that matches the package name. - return configs[0]; - } - return configs.find( - config => - semver.satisfies(version, config.versionRange, {includePrerelease: true})) ?? - null; - } - - /** - * Converts the configuration into a JSON representation that is used to compute a hash of the - * configuration. - */ - toJson(): string { - return JSON.stringify(this, (key: string, value: unknown) => { - if (value instanceof Map) { - const res: Record = {}; - for (const [k, v] of value) { - res[k] = v; - } - return res; - } else { - return value; - } - }); - } -} - -/** - * The default configuration for ngcc. - * - * This is the ultimate fallback configuration that ngcc will use if there is no configuration - * for a package at the package level or project level. - * - * This configuration is for packages that are "dead" - i.e. no longer maintained and so are - * unlikely to be fixed to work with ngcc, nor provide a package level config of their own. - * - * The fallback process for looking up configuration is: - * - * Project -> Package -> Default - * - * If a package provides its own configuration then that would override this default one. - * - * Also application developers can always provide configuration at their project level which - * will override everything else. - * - * Note that the fallback is package based not entry-point based. - * For example, if a there is configuration for a package at the project level this will replace all - * entry-point configurations that may have been provided in the package level or default level - * configurations, even if the project level configuration does not provide for a given entry-point. - */ -export const DEFAULT_NGCC_CONFIG: NgccProjectConfig = { - packages: { - // Add default package configuration here. For example: - // '@angular/fire@^5.2.0': { - // entryPoints: { - // './database-deprecated': {ignore: true}, - // }, - // }, - - // The package does not contain any `.metadata.json` files in the root directory but only inside - // `dist/`. Without this config, ngcc does not realize this is a ViewEngine-built Angular - // package that needs to be compiled to Ivy. - 'angular2-highcharts': { - entryPoints: { - '.': { - override: { - main: './index.js', - }, - }, - }, - }, - - // The `dist/` directory has a duplicate `package.json` pointing to the same files, which (under - // certain configurations) can causes ngcc to try to process the files twice and fail. - // Ignore the `dist/` entry-point. - 'ng2-dragula': { - entryPoints: { - './dist': {ignore: true}, - }, - }, - }, - locking: { - retryDelay: 500, - retryAttempts: 500, - } -}; - -const NGCC_CONFIG_FILENAME = 'ngcc.config.js'; - -/** - * The processed package level configuration as a result of processing a raw package level config. - */ -export class ProcessedNgccPackageConfig implements Omit { - /** - * The absolute path to this instance of the package. - * Note that there may be multiple instances of a package inside a project in nested - * `node_modules/`. For example, one at `/node_modules/some-package/` and one at - * `/node_modules/other-package/node_modules/some-package/`. - */ - packagePath: AbsoluteFsPath; - - /** - * The entry-points to configure for this package. - * - * In contrast to `RawNgccPackageConfig`, the paths are absolute and take the path of the specific - * instance of the package into account. - */ - entryPoints: Map; - - /** - * A collection of regexes that match deep imports to ignore, for this package, rather than - * displaying a warning. - */ - ignorableDeepImportMatchers: RegExp[]; - - constructor(fs: PathManipulation, packagePath: AbsoluteFsPath, { - entryPoints = {}, - ignorableDeepImportMatchers = [], - }: RawNgccPackageConfig) { - const absolutePathEntries: [AbsoluteFsPath, NgccEntryPointConfig][] = - Object.entries(entryPoints).map(([ - relativePath, config - ]) => [fs.resolve(packagePath, relativePath), config]); - - this.packagePath = packagePath; - this.entryPoints = new Map(absolutePathEntries); - this.ignorableDeepImportMatchers = ignorableDeepImportMatchers; - } -} - -/** - * Ngcc has a hierarchical configuration system that lets us "fix up" packages that do not - * work with ngcc out of the box. - * - * There are three levels at which configuration can be declared: - * - * * Default level - ngcc comes with built-in configuration for well known cases. - * * Package level - a library author publishes a configuration with their package to fix known - * issues. - * * Project level - the application developer provides a configuration that fixes issues specific - * to the libraries used in their application. - * - * Ngcc will match configuration based on the package name but also on its version. This allows - * configuration to provide different fixes to different version ranges of a package. - * - * * Package level configuration is specific to the package version where the configuration is - * found. - * * Default and project level configuration should provide version ranges to ensure that the - * configuration is only applied to the appropriate versions of a package. - * - * When getting a configuration for a package (via `getConfig()`) the caller should provide the - * version of the package in question, if available. If it is not provided then the first available - * configuration for a package is returned. - */ -export class NgccConfiguration { - private defaultConfig: PartiallyProcessedConfig; - private projectConfig: PartiallyProcessedConfig; - private cache = new Map(); - readonly hash: string; - readonly hashAlgorithm: string; - - constructor(private fs: ReadonlyFileSystem, baseDir: AbsoluteFsPath) { - this.defaultConfig = new PartiallyProcessedConfig(DEFAULT_NGCC_CONFIG); - this.projectConfig = new PartiallyProcessedConfig(this.loadProjectConfig(baseDir)); - this.hashAlgorithm = this.projectConfig.hashAlgorithm; - this.hash = this.computeHash(); - } - - /** - * Get the configuration options for locking the ngcc process. - */ - getLockingConfig(): Required { - let {retryAttempts, retryDelay} = this.projectConfig.locking; - if (retryAttempts === undefined) { - retryAttempts = this.defaultConfig.locking.retryAttempts!; - } - if (retryDelay === undefined) { - retryDelay = this.defaultConfig.locking.retryDelay!; - } - return {retryAttempts, retryDelay}; - } - - /** - * Get a configuration for the given `version` of a package at `packagePath`. - * - * @param packageName The name of the package whose config we want. - * @param packagePath The path to the package whose config we want. - * @param version The version of the package whose config we want, or `null` if the package's - * package.json did not exist or was invalid. - */ - getPackageConfig(packageName: string, packagePath: AbsoluteFsPath, version: string|null): - ProcessedNgccPackageConfig { - const rawPackageConfig = this.getRawPackageConfig(packageName, packagePath, version); - return new ProcessedNgccPackageConfig(this.fs, packagePath, rawPackageConfig); - } - - private getRawPackageConfig( - packageName: string, packagePath: AbsoluteFsPath, - version: string|null): VersionedPackageConfig { - const cacheKey = packageName + (version !== null ? `@${version}` : ''); - if (this.cache.has(cacheKey)) { - return this.cache.get(cacheKey)!; - } - - const projectLevelConfig = this.projectConfig.findPackageConfig(packageName, version); - if (projectLevelConfig !== null) { - this.cache.set(cacheKey, projectLevelConfig); - return projectLevelConfig; - } - - const packageLevelConfig = this.loadPackageConfig(packagePath, version); - if (packageLevelConfig !== null) { - this.cache.set(cacheKey, packageLevelConfig); - return packageLevelConfig; - } - - const defaultLevelConfig = this.defaultConfig.findPackageConfig(packageName, version); - if (defaultLevelConfig !== null) { - this.cache.set(cacheKey, defaultLevelConfig); - return defaultLevelConfig; - } - - return {versionRange: '*'}; - } - - private loadProjectConfig(baseDir: AbsoluteFsPath): NgccProjectConfig { - const configFilePath = this.fs.join(baseDir, NGCC_CONFIG_FILENAME); - if (this.fs.exists(configFilePath)) { - try { - return this.evalSrcFile(configFilePath); - } catch (e) { - throw new Error( - `Invalid project configuration file at "${configFilePath}": ` + (e as Error).message); - } - } else { - return {packages: {}}; - } - } - - private loadPackageConfig(packagePath: AbsoluteFsPath, version: string|null): - VersionedPackageConfig|null { - const configFilePath = this.fs.join(packagePath, NGCC_CONFIG_FILENAME); - if (this.fs.exists(configFilePath)) { - try { - const packageConfig = this.evalSrcFile(configFilePath); - return { - ...packageConfig, - versionRange: version || '*', - }; - } catch (e) { - throw new Error( - `Invalid package configuration file at "${configFilePath}": ` + (e as Error).message); - } - } else { - return null; - } - } - - private evalSrcFile(srcPath: AbsoluteFsPath): any { - const requireFn = module.createRequire(import.meta.url); - const src = this.fs.readFile(srcPath); - const theExports = {}; - const sandbox = { - module: {exports: theExports}, - exports: theExports, - require: requireFn, - __dirname: this.fs.dirname(srcPath), - __filename: srcPath - }; - vm.runInNewContext(src, sandbox, {filename: srcPath}); - return sandbox.module.exports; - } - - private computeHash(): string { - return createHash(this.hashAlgorithm).update(this.projectConfig.toJson()).digest('hex'); - } -} diff --git a/packages/compiler-cli/ngcc/src/packages/entry_point.ts b/packages/compiler-cli/ngcc/src/packages/entry_point.ts deleted file mode 100644 index 95ea1b4b358..00000000000 --- a/packages/compiler-cli/ngcc/src/packages/entry_point.ts +++ /dev/null @@ -1,419 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import ts from 'typescript'; - -import {AbsoluteFsPath, PathManipulation, ReadonlyFileSystem} from '../../../src/ngtsc/file_system'; -import {Logger} from '../../../src/ngtsc/logging'; -import {parseStatementForUmdModule} from '../host/umd_host'; -import {JsonObject, loadJson, loadSecondaryEntryPointInfoForApfV14, resolveFileWithPostfixes} from '../utils'; - -import {NgccConfiguration, NgccEntryPointConfig} from './configuration'; - -/** - * The possible values for the format of an entry-point. - */ -export type EntryPointFormat = 'esm5'|'esm2015'|'umd'|'commonjs'; - -/** - * An object containing information about an entry-point, including paths - * to each of the possible entry-point formats. - */ -export interface EntryPoint extends JsonObject { - /** The name of the entry-point (e.g. `@angular/core` or `@angular/common/http`). */ - name: string; - /** The path to this entry point. */ - path: AbsoluteFsPath; - /** - * The name of the package that contains this entry-point (e.g. `@angular/core` or - * `@angular/common`). - */ - packageName: string; - /** The path to the package that contains this entry-point. */ - packagePath: AbsoluteFsPath; - /** The URL of the repository. */ - repositoryUrl: string; - /** The parsed package.json file for this entry-point. */ - packageJson: EntryPointPackageJson; - /** The path to a typings (.d.ts) file for this entry-point. */ - typings: AbsoluteFsPath; - /** Is this EntryPoint compiled with the Angular View Engine compiler? */ - compiledByAngular: boolean; - /** Should ngcc ignore missing dependencies and process this entrypoint anyway? */ - ignoreMissingDependencies: boolean; - /** Should ngcc generate deep re-exports for this entrypoint? */ - generateDeepReexports: boolean; -} - -export interface PackageJsonFormatPropertiesMap { - browser?: string; - fesm2015?: string; - fesm5?: string; - es2015?: string; // if exists then it is actually FESM2015 - esm2015?: string; - esm5?: string; - main?: string; // UMD - module?: string; // if exists then it is actually FESM5 - types?: string; // Synonymous to `typings` property - see https://bit.ly/2OgWp2H - typings?: string; // TypeScript .d.ts files -} - -export type PackageJsonFormatProperties = keyof PackageJsonFormatPropertiesMap; - -/** - * The properties that may be loaded from the `package.json` file. - */ -export interface EntryPointPackageJson extends JsonObject, PackageJsonFormatPropertiesMap { - name: string; - version?: string; - scripts?: Record; - repository?: string|{url: string}; - __processed_by_ivy_ngcc__?: Record; -} - -export type EntryPointJsonProperty = Exclude; -// We need to keep the elements of this const and the `EntryPointJsonProperty` type in sync. -export const SUPPORTED_FORMAT_PROPERTIES: EntryPointJsonProperty[] = - ['fesm2015', 'fesm5', 'es2015', 'esm2015', 'esm5', 'main', 'module', 'browser']; - - -/** - * The path does not represent an entry-point, i.e. there is no package.json at the path and there - * is no config to force an entry-point. - */ -export const NO_ENTRY_POINT = 'no-entry-point'; - -/** - * The path represents an entry-point that is `ignored` by an ngcc config. - */ -export const IGNORED_ENTRY_POINT = 'ignored-entry-point'; - -/** - * The path has a package.json, but it is not a valid entry-point for ngcc processing. - */ -export const INCOMPATIBLE_ENTRY_POINT = 'incompatible-entry-point'; - -/** - * The result of calling `getEntryPointInfo()`. - * - * This will be an `EntryPoint` object if an Angular entry-point was identified; - * Otherwise it will be a flag indicating one of: - * * NO_ENTRY_POINT - the path is not an entry-point or ngcc is configured to ignore it - * * INCOMPATIBLE_ENTRY_POINT - the path was a non-processable entry-point that should be searched - * for sub-entry-points - */ -export type GetEntryPointResult = - EntryPoint|typeof IGNORED_ENTRY_POINT|typeof INCOMPATIBLE_ENTRY_POINT|typeof NO_ENTRY_POINT; - - -/** - * Try to create an entry-point from the given paths and properties. - * - * @param packagePath the absolute path to the containing npm package - * @param entryPointPath the absolute path to the potential entry-point. - * @returns - * - An entry-point if it is valid and not ignored. - * - `NO_ENTRY_POINT` when there is no package.json at the path and there is no config to force an - * entry-point, - * - `IGNORED_ENTRY_POINT` when the entry-point is ignored by an ngcc config. - * - `INCOMPATIBLE_ENTRY_POINT` when there is a package.json but it is not a valid Angular compiled - * entry-point. - */ -export function getEntryPointInfo( - fs: ReadonlyFileSystem, config: NgccConfiguration, logger: Logger, packagePath: AbsoluteFsPath, - entryPointPath: AbsoluteFsPath): GetEntryPointResult { - const packagePackageJsonPath = fs.resolve(packagePath, 'package.json'); - const entryPointPackageJsonPath = fs.resolve(entryPointPath, 'package.json'); - const loadedPackagePackageJson = loadJson(fs, packagePackageJsonPath); - const loadedEntryPointPackageJson = (packagePackageJsonPath === entryPointPackageJsonPath) ? - loadedPackagePackageJson : - loadOrSynthesizeSecondaryPackageJson( - fs, packagePath, entryPointPath, entryPointPackageJsonPath, loadedPackagePackageJson); - const {packageName, packageVersion} = getPackageNameAndVersion( - fs, packagePath, loadedPackagePackageJson, loadedEntryPointPackageJson); - const repositoryUrl = getRepositoryUrl(loadedPackagePackageJson); - - const packageConfig = config.getPackageConfig(packageName, packagePath, packageVersion); - const entryPointConfig = packageConfig.entryPoints.get(entryPointPath); - let entryPointPackageJson: EntryPointPackageJson; - - if (entryPointConfig === undefined) { - if (loadedEntryPointPackageJson !== null) { - entryPointPackageJson = loadedEntryPointPackageJson; - } else if (!fs.exists(entryPointPackageJsonPath)) { - // No entry-point `package.json` or package `package.json` with exports and no config. - return NO_ENTRY_POINT; - } else { - // `package.json` exists but could not be parsed and there is no redeeming config. - logger.warn(`Failed to read entry point info from invalid 'package.json' file: ${ - entryPointPackageJsonPath}`); - - return INCOMPATIBLE_ENTRY_POINT; - } - } else if (entryPointConfig.ignore === true) { - // Explicitly ignored entry-point. - return IGNORED_ENTRY_POINT; - } else { - entryPointPackageJson = mergeConfigAndPackageJson( - fs, loadedEntryPointPackageJson, entryPointConfig, packagePath, entryPointPath); - } - - const typings = entryPointPackageJson.typings || entryPointPackageJson.types || - guessTypingsFromPackageJson(fs, entryPointPath, entryPointPackageJson); - if (typeof typings !== 'string') { - // Missing the required `typings` property - return INCOMPATIBLE_ENTRY_POINT; - } - - // An entry-point is assumed to be compiled by Angular if there is either: - // * a `metadata.json` file next to the typings entry-point - // * a custom config for this entry-point - const metadataPath = - fs.resolve(entryPointPath, typings.replace(/\.d\.ts$/, '') + '.metadata.json'); - const compiledByAngular = entryPointConfig !== undefined || fs.exists(metadataPath); - - const entryPointInfo: EntryPoint = { - name: entryPointPackageJson.name, - path: entryPointPath, - packageName, - packagePath, - repositoryUrl, - packageJson: entryPointPackageJson, - typings: fs.resolve(entryPointPath, typings), - compiledByAngular, - ignoreMissingDependencies: - entryPointConfig !== undefined ? !!entryPointConfig.ignoreMissingDependencies : false, - generateDeepReexports: - entryPointConfig !== undefined ? !!entryPointConfig.generateDeepReexports : false, - }; - - return entryPointInfo; -} - -export function isEntryPoint(result: GetEntryPointResult): result is EntryPoint { - return result !== NO_ENTRY_POINT && result !== INCOMPATIBLE_ENTRY_POINT && - result !== IGNORED_ENTRY_POINT; -} - -/** - * Convert a package.json property into an entry-point format. - * - * @param property The property to convert to a format. - * @returns An entry-point format or `undefined` if none match the given property. - */ -export function getEntryPointFormat( - fs: ReadonlyFileSystem, entryPoint: EntryPoint, - property: EntryPointJsonProperty): EntryPointFormat|undefined { - switch (property) { - case 'fesm2015': - return 'esm2015'; - case 'fesm5': - return 'esm5'; - case 'es2015': - return 'esm2015'; - case 'esm2015': - return 'esm2015'; - case 'esm5': - return 'esm5'; - case 'browser': - const browserFile = entryPoint.packageJson['browser']; - if (typeof browserFile !== 'string') { - return undefined; - } - return sniffModuleFormat(fs, fs.join(entryPoint.path, browserFile)); - case 'main': - const mainFile = entryPoint.packageJson['main']; - if (mainFile === undefined) { - return undefined; - } - return sniffModuleFormat(fs, fs.join(entryPoint.path, mainFile)); - case 'module': - const moduleFilePath = entryPoint.packageJson['module']; - // As of version 10, the `module` property in `package.json` should point to - // the ESM2015 format output as per Angular Package format specification. This - // means that the `module` property captures multiple formats, as old libraries - // built with the old APF can still be processed. We detect the format by checking - // the paths that should be used as per APF specification. - if (typeof moduleFilePath === 'string' && moduleFilePath.includes('esm2015')) { - return `esm2015`; - } - return 'esm5'; - default: - return undefined; - } -} - -/** - * Parse the JSON from a secondary `package.json` file. If no such file exists, look for a - * corresponding entry in the primary `package.json` file's `exports` property (if any) and - * synthesize the JSON from that. - * - * @param packagePath The absolute path to the containing npm package. - * @param entryPointPath The absolute path to the secondary entry-point. - * @param secondaryPackageJsonPath The absolute path to the secondary `package.json` file. - * @param primaryPackageJson The parsed JSON of the primary `package.json` (or `null` if it failed - * to be loaded). - * @returns Parsed JSON (either loaded from a secondary `package.json` file or synthesized from a - * primary one) if it is valid, `null` otherwise. - */ -function loadOrSynthesizeSecondaryPackageJson( - fs: ReadonlyFileSystem, packagePath: AbsoluteFsPath, entryPointPath: AbsoluteFsPath, - secondaryPackageJsonPath: AbsoluteFsPath, - primaryPackageJson: EntryPointPackageJson|null): EntryPointPackageJson|null { - // If a secondary `package.json` exists and is valid, load and return that. - const loadedPackageJson = loadJson(fs, secondaryPackageJsonPath); - if (loadedPackageJson !== null) { - return loadedPackageJson; - } - - // Try to load the entry-point info from the primary `package.json` data. - const entryPointInfo = - loadSecondaryEntryPointInfoForApfV14(fs, primaryPackageJson, packagePath, entryPointPath); - if (entryPointInfo === null) { - return null; - } - - // Create a synthesized `package.json`. - // - // NOTE: - // We do not care about being able to update the synthesized `package.json` (for example, updating - // its `__processed_by_ivy_ngcc__` property), because these packages are generated with Angular - // v14+ (following the Angular Package Format v14+) and thus are already in Ivy format and do not - // require processing by `ngcc`. - const synthesizedPackageJson: EntryPointPackageJson = { - synthesized: true, - name: `${primaryPackageJson!.name}/${fs.relative(packagePath, entryPointPath)}`, - }; - - // Update the synthesized `package.json` with any of the supported format and types properties, - // changing paths to make them relative to the entry-point directory. This makes the synthesized - // `package.json` similar to how a `package.json` inside the entry-point directory would look - // like. - for (const prop of [...SUPPORTED_FORMAT_PROPERTIES, 'types', 'typings']) { - const packageRelativePath = entryPointInfo[prop]; - - if (typeof packageRelativePath === 'string') { - const absolutePath = fs.resolve(packagePath, packageRelativePath); - const entryPointRelativePath = fs.relative(entryPointPath, absolutePath); - synthesizedPackageJson[prop] = - (fs.isRooted(entryPointRelativePath) || entryPointRelativePath.startsWith('.')) ? - entryPointRelativePath : - `./${entryPointRelativePath}`; - } - } - - // Return the synthesized JSON. - return synthesizedPackageJson; -} - -function sniffModuleFormat( - fs: ReadonlyFileSystem, sourceFilePath: AbsoluteFsPath): EntryPointFormat|undefined { - const resolvedPath = resolveFileWithPostfixes(fs, sourceFilePath, ['', '.js', '/index.js']); - if (resolvedPath === null) { - return undefined; - } - - const sourceFile = - ts.createSourceFile(sourceFilePath, fs.readFile(resolvedPath), ts.ScriptTarget.ES5); - if (sourceFile.statements.length === 0) { - return undefined; - } - if (ts.isExternalModule(sourceFile)) { - return 'esm5'; - } else if (parseStatementForUmdModule(sourceFile.statements[0]) !== null) { - return 'umd'; - } else { - return 'commonjs'; - } -} - -function mergeConfigAndPackageJson( - fs: PathManipulation, entryPointPackageJson: EntryPointPackageJson|null, - entryPointConfig: NgccEntryPointConfig, packagePath: AbsoluteFsPath, - entryPointPath: AbsoluteFsPath): EntryPointPackageJson { - if (entryPointPackageJson !== null) { - return {...entryPointPackageJson, ...entryPointConfig.override}; - } else { - const name = `${fs.basename(packagePath)}/${fs.relative(packagePath, entryPointPath)}`; - return {name, ...entryPointConfig.override}; - } -} - -function guessTypingsFromPackageJson( - fs: ReadonlyFileSystem, entryPointPath: AbsoluteFsPath, - entryPointPackageJson: EntryPointPackageJson): AbsoluteFsPath|null { - for (const prop of SUPPORTED_FORMAT_PROPERTIES) { - const field = entryPointPackageJson[prop]; - if (typeof field !== 'string') { - // Some crazy packages have things like arrays in these fields! - continue; - } - const relativeTypingsPath = field.replace(/\.js$/, '.d.ts'); - const typingsPath = fs.resolve(entryPointPath, relativeTypingsPath); - if (fs.exists(typingsPath)) { - return typingsPath; - } - } - return null; -} - -/** - * Find or infer the name and version of a package. - * - * - The name is computed based on the `name` property of the package's or the entry-point's - * `package.json` file (if available) or inferred from the package's path. - * - The version is read off of the `version` property of the package's `package.json` file (if - * available). - * - * @param fs The file-system to use for processing `packagePath`. - * @param packagePath the absolute path to the package. - * @param packagePackageJson the parsed `package.json` of the package (if available). - * @param entryPointPackageJson the parsed `package.json` of an entry-point (if available). - * @returns the computed name and version of the package. - */ -function getPackageNameAndVersion( - fs: PathManipulation, packagePath: AbsoluteFsPath, - packagePackageJson: EntryPointPackageJson|null, - entryPointPackageJson: EntryPointPackageJson| - null): {packageName: string, packageVersion: string|null} { - let packageName: string; - - if (packagePackageJson !== null) { - // We have a valid `package.json` for the package: Get the package name from that. - packageName = packagePackageJson.name; - } else if (entryPointPackageJson !== null) { - // We have a valid `package.json` for the entry-point: Get the package name from that. - // This might be a secondary entry-point, so make sure we only keep the main package's name - // (e.g. only keep `@angular/common` from `@angular/common/http`). - packageName = /^(?:@[^/]+\/)?[^/]*/.exec(entryPointPackageJson.name)![0]; - } else { - // We don't have a valid `package.json`: Infer the package name from the package's path. - const lastSegment = fs.basename(packagePath); - const secondLastSegment = fs.basename(fs.dirname(packagePath)); - - packageName = - secondLastSegment.startsWith('@') ? `${secondLastSegment}/${lastSegment}` : lastSegment; - } - - return { - packageName, - packageVersion: packagePackageJson?.version ?? null, - }; -} - -/** - * Extract the URL of the repository associated with an entry-point - */ -function getRepositoryUrl(packageJson: EntryPointPackageJson|null): string { - if (packageJson?.repository === undefined) { - return ''; - } - if (typeof packageJson.repository === 'string') { - return packageJson.repository; - } - return packageJson.repository.url; -} diff --git a/packages/compiler-cli/ngcc/src/packages/entry_point_bundle.ts b/packages/compiler-cli/ngcc/src/packages/entry_point_bundle.ts deleted file mode 100644 index 2e35d40b106..00000000000 --- a/packages/compiler-cli/ngcc/src/packages/entry_point_bundle.ts +++ /dev/null @@ -1,130 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import ts from 'typescript'; - -import {AbsoluteFsPath, FileSystem, ReadonlyFileSystem} from '../../../src/ngtsc/file_system'; -import {DtsProcessing} from '../execution/tasks/api'; -import {PathMappings} from '../path_mappings'; - -import {adjustElementAccessExports} from './adjust_cjs_umd_exports'; -import {BundleProgram, makeBundleProgram} from './bundle_program'; -import {EntryPoint, EntryPointFormat} from './entry_point'; -import {NgccDtsCompilerHost, NgccSourcesCompilerHost} from './ngcc_compiler_host'; -import {EntryPointFileCache, SharedFileCache} from './source_file_cache'; - -/** - * A bundle of files and paths (and TS programs) that correspond to a particular - * format of a package entry-point. - */ -export interface EntryPointBundle { - entryPoint: EntryPoint; - format: EntryPointFormat; - isCore: boolean; - isFlatCore: boolean; - rootDirs: AbsoluteFsPath[]; - src: BundleProgram; - dts: BundleProgram|null; - dtsProcessing: DtsProcessing; - enableI18nLegacyMessageIdFormat: boolean; -} - -/** - * When processing UMD or CommonJS bundles the source text is preprocessed to transform export - * declarations using element access expressions into property access expressions, as otherwise ngcc - * would not recognize these export declarations. See `adjustElementAccessExports` for more - * information. - */ -function createSourceTextProcessor(format: EntryPointFormat): (sourceText: string) => string { - if (format === 'umd' || format === 'commonjs') { - return adjustElementAccessExports; - } else { - return sourceText => sourceText; - } -} - -/** - * Get an object that describes a formatted bundle for an entry-point. - * @param fs The current file-system being used. - * @param entryPoint The entry-point that contains the bundle. - * @param sharedFileCache The cache to use for source files that are shared across all entry-points. - * @param moduleResolutionCache The module resolution cache to use. - * @param formatPath The path to the source files for this bundle. - * @param isCore This entry point is the Angular core package. - * @param format The underlying format of the bundle. - * @param dtsProcessing Whether to transform the typings along with this bundle. - * @param pathMappings An optional set of mappings to use when compiling files. - * @param mirrorDtsFromSrc If true then the `dts` program will contain additional files that - * were guessed by mapping the `src` files to `dts` files. - * @param enableI18nLegacyMessageIdFormat Whether to render legacy message ids for i18n messages in - * component templates. - */ -export function makeEntryPointBundle( - fs: FileSystem, entryPoint: EntryPoint, sharedFileCache: SharedFileCache, - moduleResolutionCache: ts.ModuleResolutionCache, formatPath: string, isCore: boolean, - format: EntryPointFormat, dtsProcessing: DtsProcessing, pathMappings?: PathMappings, - mirrorDtsFromSrc: boolean = false, - enableI18nLegacyMessageIdFormat: boolean = true): EntryPointBundle { - // Create the TS program and necessary helpers. - const rootDir = entryPoint.packagePath; - const options: ts - .CompilerOptions = {allowJs: true, maxNodeModuleJsDepth: Infinity, rootDir, ...pathMappings}; - const processSourceText = createSourceTextProcessor(format); - const entryPointCache = new EntryPointFileCache(fs, sharedFileCache, processSourceText); - const dtsHost = new NgccDtsCompilerHost(fs, options, entryPointCache, moduleResolutionCache); - const srcHost = new NgccSourcesCompilerHost( - fs, options, entryPointCache, moduleResolutionCache, entryPoint.packagePath); - - // Create the bundle programs, as necessary. - const absFormatPath = fs.resolve(entryPoint.path, formatPath); - const typingsPath = fs.resolve(entryPoint.path, entryPoint.typings); - const src = makeBundleProgram( - fs, isCore, entryPoint.packagePath, absFormatPath, 'r3_symbols.js', options, srcHost); - const additionalDtsFiles = dtsProcessing !== DtsProcessing.No && mirrorDtsFromSrc ? - computePotentialDtsFilesFromJsFiles(fs, src.program, absFormatPath, typingsPath) : - []; - const dts = dtsProcessing !== DtsProcessing.No ? - makeBundleProgram( - fs, isCore, entryPoint.packagePath, typingsPath, 'r3_symbols.d.ts', - {...options, allowJs: false}, dtsHost, additionalDtsFiles) : - null; - const isFlatCore = isCore && src.r3SymbolsFile === null; - - return { - entryPoint, - format, - rootDirs: [rootDir], - isCore, - isFlatCore, - src, - dts, - dtsProcessing, - enableI18nLegacyMessageIdFormat - }; -} - -function computePotentialDtsFilesFromJsFiles( - fs: ReadonlyFileSystem, srcProgram: ts.Program, formatPath: AbsoluteFsPath, - typingsPath: AbsoluteFsPath) { - const formatRoot = fs.dirname(formatPath); - const typingsRoot = fs.dirname(typingsPath); - const additionalFiles: AbsoluteFsPath[] = []; - for (const sf of srcProgram.getSourceFiles()) { - if (!sf.fileName.endsWith('.js')) { - continue; - } - - // Given a source file at e.g. `esm2015/src/some/nested/index.js`, try to resolve the - // declaration file under the typings root in `src/some/nested/index.d.ts`. - const mirroredDtsPath = - fs.resolve(typingsRoot, fs.relative(formatRoot, sf.fileName.replace(/\.js$/, '.d.ts'))); - if (fs.exists(mirroredDtsPath)) { - additionalFiles.push(mirroredDtsPath); - } - } - return additionalFiles; -} diff --git a/packages/compiler-cli/ngcc/src/packages/entry_point_manifest.ts b/packages/compiler-cli/ngcc/src/packages/entry_point_manifest.ts deleted file mode 100644 index e708955e2a8..00000000000 --- a/packages/compiler-cli/ngcc/src/packages/entry_point_manifest.ts +++ /dev/null @@ -1,207 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import {createHash} from 'crypto'; - -import {AbsoluteFsPath, FileSystem, PathSegment} from '../../../src/ngtsc/file_system'; -import {Logger} from '../../../src/ngtsc/logging'; -import {EntryPointWithDependencies} from '../dependencies/dependency_host'; - -import {NGCC_VERSION} from './build_marker'; -import {NgccConfiguration} from './configuration'; -import {getEntryPointInfo, isEntryPoint, PackageJsonFormatProperties} from './entry_point'; - -/** - * Manages reading and writing a manifest file that contains a list of all the entry-points that - * were found below a given basePath. - * - * This is a super-set of the entry-points that are actually processed for a given run of ngcc, - * since some may already be processed, or excluded if they do not have the required format. - */ -export class EntryPointManifest { - constructor(private fs: FileSystem, private config: NgccConfiguration, private logger: Logger) {} - - /** - * Try to get the entry-point info from a manifest file for the given `basePath` if it exists and - * is not out of date. - * - * Reasons for the manifest to be out of date are: - * - * * the file does not exist - * * the ngcc version has changed - * * the package lock-file (i.e. yarn.lock or package-lock.json) has changed - * * the project configuration has changed - * * one or more entry-points in the manifest are not valid - * - * @param basePath The path that would contain the entry-points and the manifest file. - * @returns an array of entry-point information for all entry-points found below the given - * `basePath` or `null` if the manifest was out of date. - */ - readEntryPointsUsingManifest(basePath: AbsoluteFsPath): EntryPointWithDependencies[]|null { - try { - if (this.fs.basename(basePath) !== 'node_modules') { - return null; - } - - const manifestPath = this.getEntryPointManifestPath(basePath); - if (!this.fs.exists(manifestPath)) { - return null; - } - - const computedLockFileHash = this.computeLockFileHash(basePath); - if (computedLockFileHash === null) { - return null; - } - - const {ngccVersion, configFileHash, lockFileHash, entryPointPaths} = - JSON.parse(this.fs.readFile(manifestPath)) as EntryPointManifestFile; - if (ngccVersion !== NGCC_VERSION || configFileHash !== this.config.hash || - lockFileHash !== computedLockFileHash) { - return null; - } - - this.logger.debug(`Entry-point manifest found for ${ - basePath} so loading entry-point information directly.`); - const startTime = Date.now(); - - const entryPoints: EntryPointWithDependencies[] = []; - for (const - [packagePath, entryPointPath, dependencyPaths = [], missingPaths = [], - deepImportPaths = []] of entryPointPaths) { - const result = getEntryPointInfo( - this.fs, this.config, this.logger, this.fs.resolve(basePath, packagePath), - this.fs.resolve(basePath, entryPointPath)); - if (!isEntryPoint(result)) { - throw new Error(`The entry-point manifest at ${ - manifestPath} contained an invalid pair of package paths: [${packagePath}, ${ - entryPointPath}]`); - } else { - entryPoints.push({ - entryPoint: result, - depInfo: { - dependencies: new Set(dependencyPaths), - missing: new Set(missingPaths), - deepImports: new Set(deepImportPaths), - } - }); - } - } - const duration = Math.round((Date.now() - startTime) / 100) / 10; - this.logger.debug(`Reading entry-points using the manifest entries took ${duration}s.`); - return entryPoints; - } catch (e) { - this.logger.warn( - `Unable to read the entry-point manifest for ${basePath}:\n`, - (e as Error).stack || (e as Error).toString()); - return null; - } - } - - /** - * Write a manifest file at the given `basePath`. - * - * The manifest includes the current ngcc version and hashes of the package lock-file and current - * project config. These will be used to check whether the manifest file is out of date. See - * `readEntryPointsUsingManifest()`. - * - * @param basePath The path where the manifest file is to be written. - * @param entryPoints A collection of entry-points to record in the manifest. - */ - writeEntryPointManifest(basePath: AbsoluteFsPath, entryPoints: EntryPointWithDependencies[]): - void { - if (this.fs.basename(basePath) !== 'node_modules') { - return; - } - - const lockFileHash = this.computeLockFileHash(basePath); - if (lockFileHash === null) { - return; - } - const manifest: EntryPointManifestFile = { - ngccVersion: NGCC_VERSION, - configFileHash: this.config.hash, - lockFileHash: lockFileHash, - entryPointPaths: entryPoints.map(e => { - const entryPointPaths: EntryPointPaths = [ - this.fs.relative(basePath, e.entryPoint.packagePath), - this.fs.relative(basePath, e.entryPoint.path), - ]; - // Only add depInfo arrays if needed. - if (e.depInfo.dependencies.size > 0) { - entryPointPaths[2] = Array.from(e.depInfo.dependencies); - } else if (e.depInfo.missing.size > 0 || e.depInfo.deepImports.size > 0) { - entryPointPaths[2] = []; - } - if (e.depInfo.missing.size > 0) { - entryPointPaths[3] = Array.from(e.depInfo.missing); - } else if (e.depInfo.deepImports.size > 0) { - entryPointPaths[3] = []; - } - if (e.depInfo.deepImports.size > 0) { - entryPointPaths[4] = Array.from(e.depInfo.deepImports); - } - return entryPointPaths; - }), - }; - this.fs.writeFile(this.getEntryPointManifestPath(basePath), JSON.stringify(manifest)); - } - - private getEntryPointManifestPath(basePath: AbsoluteFsPath) { - return this.fs.resolve(basePath, '__ngcc_entry_points__.json'); - } - - private computeLockFileHash(basePath: AbsoluteFsPath): string|null { - const directory = this.fs.dirname(basePath); - for (const lockFileName of ['yarn.lock', 'package-lock.json']) { - const lockFilePath = this.fs.resolve(directory, lockFileName); - if (this.fs.exists(lockFilePath)) { - const lockFileContents = this.fs.readFile(lockFilePath); - return createHash(this.config.hashAlgorithm).update(lockFileContents).digest('hex'); - } - } - return null; - } -} - -/** - * A specialized implementation of the `EntryPointManifest` that can be used to invalidate the - * current manifest file. - * - * It always returns `null` from the `readEntryPointsUsingManifest()` method, which forces a new - * manifest to be created, which will overwrite the current file when `writeEntryPointManifest()` - * is called. - */ -export class InvalidatingEntryPointManifest extends EntryPointManifest { - override readEntryPointsUsingManifest(_basePath: AbsoluteFsPath): - EntryPointWithDependencies[]|null { - return null; - } -} - -export type EntryPointPaths = [ - string, - string, - Array?, - Array?, - Array?, -]; - -/** - * The JSON format of the manifest file that is written to disk. - */ -export interface EntryPointManifestFile { - ngccVersion: string; - configFileHash: string; - lockFileHash: string; - entryPointPaths: EntryPointPaths[]; -} - - -/** The JSON format of the entrypoint properties. */ -export type NewEntryPointPropertiesMap = { - [Property in PackageJsonFormatProperties as `${Property}_ivy_ngcc`]?: string; -}; diff --git a/packages/compiler-cli/ngcc/src/packages/ngcc_compiler_host.ts b/packages/compiler-cli/ngcc/src/packages/ngcc_compiler_host.ts deleted file mode 100644 index 272e18afe29..00000000000 --- a/packages/compiler-cli/ngcc/src/packages/ngcc_compiler_host.ts +++ /dev/null @@ -1,97 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import ts from 'typescript'; - -import {AbsoluteFsPath, FileSystem, NgtscCompilerHost} from '../../../src/ngtsc/file_system'; -import {isWithinPackage} from '../analysis/util'; -import {isRelativePath} from '../utils'; -import {EntryPointFileCache} from './source_file_cache'; - -/** - * Represents a compiler host that resolves a module import as a JavaScript source file if - * available, instead of the .d.ts typings file that would have been resolved by TypeScript. This - * is necessary for packages that have their typings in the same directory as the sources, which - * would otherwise let TypeScript prefer the .d.ts file instead of the JavaScript source file. - */ -export class NgccSourcesCompilerHost extends NgtscCompilerHost { - constructor( - fs: FileSystem, options: ts.CompilerOptions, private cache: EntryPointFileCache, - private moduleResolutionCache: ts.ModuleResolutionCache, - protected packagePath: AbsoluteFsPath) { - super(fs, options); - } - - override getSourceFile(fileName: string, languageVersion: ts.ScriptTarget): ts.SourceFile - |undefined { - return this.cache.getCachedSourceFile(fileName, languageVersion); - } - - resolveModuleNames( - moduleNames: string[], containingFile: string, reusedNames?: string[], - redirectedReference?: ts.ResolvedProjectReference): Array { - return moduleNames.map(moduleName => { - const {resolvedModule} = ts.resolveModuleName( - moduleName, containingFile, this.options, this, this.moduleResolutionCache, - redirectedReference); - - // If the module request originated from a relative import in a JavaScript source file, - // TypeScript may have resolved the module to its .d.ts declaration file if the .js source - // file was in the same directory. This is undesirable, as we need to have the actual - // JavaScript being present in the program. This logic recognizes this scenario and rewrites - // the resolved .d.ts declaration file to its .js counterpart, if it exists. - if (resolvedModule?.extension === ts.Extension.Dts && containingFile.endsWith('.js') && - isRelativePath(moduleName)) { - const jsFile = resolvedModule.resolvedFileName.replace(/\.d\.ts$/, '.js'); - if (this.fileExists(jsFile)) { - return {...resolvedModule, resolvedFileName: jsFile, extension: ts.Extension.Js}; - } - } - - // Prevent loading JavaScript source files outside of the package root, which would happen for - // packages that don't have .d.ts files. As ngcc should only operate on the .js files - // contained within the package, any files outside the package are simply discarded. This does - // result in a partial program with error diagnostics, however ngcc won't gather diagnostics - // for the program it creates so these diagnostics won't be reported. - if (resolvedModule?.extension === ts.Extension.Js && - !isWithinPackage(this.packagePath, this.fs.resolve(resolvedModule.resolvedFileName))) { - return undefined; - } - - return resolvedModule; - }); - } -} - -/** - * A compiler host implementation that is used for the typings program. It leverages the entry-point - * cache for source files and module resolution, as these results can be reused across the sources - * program. - */ -export class NgccDtsCompilerHost extends NgtscCompilerHost { - constructor( - fs: FileSystem, options: ts.CompilerOptions, private cache: EntryPointFileCache, - private moduleResolutionCache: ts.ModuleResolutionCache) { - super(fs, options); - } - - override getSourceFile(fileName: string, languageVersion: ts.ScriptTarget): ts.SourceFile - |undefined { - return this.cache.getCachedSourceFile(fileName, languageVersion); - } - - resolveModuleNames( - moduleNames: string[], containingFile: string, reusedNames?: string[], - redirectedReference?: ts.ResolvedProjectReference): Array { - return moduleNames.map(moduleName => { - const {resolvedModule} = ts.resolveModuleName( - moduleName, containingFile, this.options, this, this.moduleResolutionCache, - redirectedReference); - return resolvedModule; - }); - } -} diff --git a/packages/compiler-cli/ngcc/src/packages/patch_ts_expando_initializer.ts b/packages/compiler-cli/ngcc/src/packages/patch_ts_expando_initializer.ts deleted file mode 100644 index c3aa1d972a0..00000000000 --- a/packages/compiler-cli/ngcc/src/packages/patch_ts_expando_initializer.ts +++ /dev/null @@ -1,182 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import ts from 'typescript'; -import {hasNameIdentifier} from '../utils'; - -/** - * Consider the following ES5 code that may have been generated for a class: - * - * ``` - * var A = (function(){ - * function A() {} - * return A; - * }()); - * A.staticProp = true; - * ``` - * - * Here, TypeScript marks the symbol for "A" as a so-called "expando symbol", which causes - * "staticProp" to be added as an export of the "A" symbol. - * - * In the example above, symbol "A" has been assigned some flags to indicate that it represents a - * class. Due to this flag, the symbol is considered an expando symbol and as such, "staticProp" is - * stored in `ts.Symbol.exports`. - * - * A problem arises when "A" is not at the top-level, i.e. in UMD bundles. In that case, the symbol - * does not have the flag that marks the symbol as a class. Therefore, TypeScript inspects "A"'s - * initializer expression, which is an IIFE in the above example. Unfortunately however, only IIFEs - * of the form `(function(){})()` qualify as initializer for an "expando symbol"; the slightly - * different form seen in the example above, `(function(){}())`, does not. This prevents the "A" - * symbol from being considered an expando symbol, in turn preventing "staticProp" from being stored - * in `ts.Symbol.exports`. - * - * The logic for identifying symbols as "expando symbols" can be found here: - * https://github.com/microsoft/TypeScript/blob/v3.4.5/src/compiler/binder.ts#L2656-L2685 - * - * Notice how the `getExpandoInitializer` function is available on the "ts" namespace in the - * compiled bundle, so we are able to override this function to accommodate for the alternative - * IIFE notation. The original implementation can be found at: - * https://github.com/Microsoft/TypeScript/blob/v3.4.5/src/compiler/utilities.ts#L1864-L1887 - * - * Issue tracked in https://github.com/microsoft/TypeScript/issues/31778 - * - * @returns the function to pass to `restoreGetExpandoInitializer` to undo the patch, or null if - * the issue is known to have been fixed. - */ -export function patchTsGetExpandoInitializer(): unknown { - if (isTs31778GetExpandoInitializerFixed()) { - return null; - } - - const originalGetExpandoInitializer = (ts as any).getExpandoInitializer; - if (originalGetExpandoInitializer === undefined) { - throw makeUnsupportedTypeScriptError(); - } - - // Override the function to add support for recognizing the IIFE structure used in ES5 bundles. - (ts as any).getExpandoInitializer = (initializer: ts.Node, - isPrototypeAssignment: boolean): ts.Expression|undefined => { - // If the initializer is a call expression within parenthesis, unwrap the parenthesis - // upfront such that unsupported IIFE syntax `(function(){}())` becomes `function(){}()`, - // which is supported. - if (ts.isParenthesizedExpression(initializer) && ts.isCallExpression(initializer.expression)) { - initializer = initializer.expression; - } - return originalGetExpandoInitializer(initializer, isPrototypeAssignment); - }; - return originalGetExpandoInitializer; -} - -export function restoreGetExpandoInitializer(originalGetExpandoInitializer: unknown): void { - if (originalGetExpandoInitializer !== null) { - (ts as any).getExpandoInitializer = originalGetExpandoInitializer; - } -} - -let ts31778FixedResult: boolean|null = null; - -function isTs31778GetExpandoInitializerFixed(): boolean { - // If the result has already been computed, return early. - if (ts31778FixedResult !== null) { - return ts31778FixedResult; - } - - // Determine if the issue has been fixed by checking if an expando property is present in a - // minimum reproduction using unpatched TypeScript. - ts31778FixedResult = checkIfExpandoPropertyIsPresent(); - - // If the issue does not appear to have been fixed, verify that applying the patch has the desired - // effect. - if (!ts31778FixedResult) { - const originalGetExpandoInitializer = patchTsGetExpandoInitializer(); - try { - const patchIsSuccessful = checkIfExpandoPropertyIsPresent(); - if (!patchIsSuccessful) { - throw makeUnsupportedTypeScriptError(); - } - } finally { - restoreGetExpandoInitializer(originalGetExpandoInitializer); - } - } - - return ts31778FixedResult; -} - -/** - * Verifies whether TS issue 31778 has been fixed by inspecting a symbol from a minimum - * reproduction. If the symbol does in fact have the "expando" as export, the issue has been fixed. - * - * See https://github.com/microsoft/TypeScript/issues/31778 for details. - */ -function checkIfExpandoPropertyIsPresent(): boolean { - const sourceText = ` - (function() { - var A = (function() { - function A() {} - return A; - }()); - A.expando = true; - }());`; - const sourceFile = - ts.createSourceFile('test.js', sourceText, ts.ScriptTarget.ES5, true, ts.ScriptKind.JS); - const host: ts.CompilerHost = { - getSourceFile(): ts.SourceFile | - undefined { - return sourceFile; - }, - fileExists(): boolean { - return true; - }, - readFile(): string | - undefined { - return ''; - }, - writeFile() {}, - getDefaultLibFileName(): string { - return ''; - }, - getCurrentDirectory(): string { - return ''; - }, - getDirectories(): string[] { - return []; - }, - getCanonicalFileName(fileName: string): string { - return fileName; - }, - useCaseSensitiveFileNames(): boolean { - return true; - }, - getNewLine(): string { - return '\n'; - }, - }; - const options = {noResolve: true, noLib: true, noEmit: true, allowJs: true}; - const program = ts.createProgram(['test.js'], options, host); - - function visitor(node: ts.Node): ts.VariableDeclaration|undefined { - if (ts.isVariableDeclaration(node) && hasNameIdentifier(node) && node.name.text === 'A') { - return node; - } - return ts.forEachChild(node, visitor); - } - - const declaration = ts.forEachChild(sourceFile, visitor); - if (declaration === undefined) { - throw new Error('Unable to find declaration of outer A'); - } - - const symbol = program.getTypeChecker().getSymbolAtLocation(declaration.name); - if (symbol === undefined) { - throw new Error('Unable to resolve symbol of outer A'); - } - return symbol.exports !== undefined && symbol.exports.has('expando' as ts.__String); -} - -function makeUnsupportedTypeScriptError(): Error { - return new Error('The TypeScript version used is not supported by ngcc.'); -} diff --git a/packages/compiler-cli/ngcc/src/packages/source_file_cache.ts b/packages/compiler-cli/ngcc/src/packages/source_file_cache.ts deleted file mode 100644 index c8cdb73b136..00000000000 --- a/packages/compiler-cli/ngcc/src/packages/source_file_cache.ts +++ /dev/null @@ -1,201 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import ts from 'typescript'; - -import {AbsoluteFsPath, ReadonlyFileSystem} from '../../../src/ngtsc/file_system'; - -/** - * A cache that holds on to source files that can be shared for processing all entry-points in a - * single invocation of ngcc. In particular, the following files are shared across all entry-points - * through this cache: - * - * 1. Default library files such as `lib.dom.d.ts` and `lib.es5.d.ts`. These files don't change - * and some are very large, so parsing is expensive. Therefore, the parsed `ts.SourceFile`s for - * the default library files are cached. - * 2. The typings of @angular scoped packages. The typing files for @angular packages are typically - * used in the entry-points that ngcc processes, so benefit from a single source file cache. - * Especially `@angular/core/core.d.ts` is large and expensive to parse repeatedly. In contrast - * to default library files, we have to account for these files to be invalidated during a single - * invocation of ngcc, as ngcc will overwrite the .d.ts files during its processing. - * - * The lifecycle of this cache corresponds with a single invocation of ngcc. Separate invocations, - * e.g. the CLI's synchronous module resolution fallback will therefore all have their own cache. - * This allows for the source file cache to be garbage collected once ngcc processing has completed. - */ -export class SharedFileCache { - private sfCache = new Map(); - - constructor(private fs: ReadonlyFileSystem) {} - - /** - * Loads a `ts.SourceFile` if the provided `fileName` is deemed appropriate to be cached. To - * optimize for memory usage, only files that are generally used in all entry-points are cached. - * If `fileName` is not considered to benefit from caching or the requested file does not exist, - * then `undefined` is returned. - */ - getCachedSourceFile(fileName: string): ts.SourceFile|undefined { - const absPath = this.fs.resolve(fileName); - if (isDefaultLibrary(absPath, this.fs)) { - return this.getStableCachedFile(absPath); - } else if (isAngularDts(absPath, this.fs)) { - return this.getVolatileCachedFile(absPath); - } else { - return undefined; - } - } - - /** - * Attempts to load the source file from the cache, or parses the file into a `ts.SourceFile` if - * it's not yet cached. This method assumes that the file will not be modified for the duration - * that this cache is valid for. If that assumption does not hold, the `getVolatileCachedFile` - * method is to be used instead. - */ - private getStableCachedFile(absPath: AbsoluteFsPath): ts.SourceFile|undefined { - if (!this.sfCache.has(absPath)) { - const content = readFile(absPath, this.fs); - if (content === undefined) { - return undefined; - } - const sf = ts.createSourceFile(absPath, content, ts.ScriptTarget.ES2015); - this.sfCache.set(absPath, sf); - } - return this.sfCache.get(absPath)!; - } - - /** - * In contrast to `getStableCachedFile`, this method always verifies that the cached source file - * is the same as what's stored on disk. This is done for files that are expected to change during - * ngcc's processing, such as @angular scoped packages for which the .d.ts files are overwritten - * by ngcc. If the contents on disk have changed compared to a previously cached source file, the - * content from disk is re-parsed and the cache entry is replaced. - */ - private getVolatileCachedFile(absPath: AbsoluteFsPath): ts.SourceFile|undefined { - const content = readFile(absPath, this.fs); - if (content === undefined) { - return undefined; - } - if (!this.sfCache.has(absPath) || this.sfCache.get(absPath)!.text !== content) { - const sf = ts.createSourceFile(absPath, content, ts.ScriptTarget.ES2015); - this.sfCache.set(absPath, sf); - } - return this.sfCache.get(absPath)!; - } -} - -const DEFAULT_LIB_PATTERN = ['node_modules', 'typescript', 'lib', /^lib\..+\.d\.ts$/]; - -/** - * Determines whether the provided path corresponds with a default library file inside of the - * typescript package. - * - * @param absPath The path for which to determine if it corresponds with a default library file. - * @param fs The filesystem to use for inspecting the path. - */ -export function isDefaultLibrary(absPath: AbsoluteFsPath, fs: ReadonlyFileSystem): boolean { - return isFile(absPath, DEFAULT_LIB_PATTERN, fs); -} - -const ANGULAR_DTS_PATTERN = ['node_modules', '@angular', /./, /\.d\.ts$/]; - -/** - * Determines whether the provided path corresponds with a .d.ts file inside of an @angular - * scoped package. This logic only accounts for the .d.ts files in the root, which is sufficient - * to find the large, flattened entry-point files that benefit from caching. - * - * @param absPath The path for which to determine if it corresponds with an @angular .d.ts file. - * @param fs The filesystem to use for inspecting the path. - */ -export function isAngularDts(absPath: AbsoluteFsPath, fs: ReadonlyFileSystem): boolean { - return isFile(absPath, ANGULAR_DTS_PATTERN, fs); -} - -/** - * Helper function to determine whether a file corresponds with a given pattern of segments. - * - * @param path The path for which to determine if it corresponds with the provided segments. - * @param segments Array of segments; the `path` must have ending segments that match the - * patterns in this array. - * @param fs The filesystem to use for inspecting the path. - */ -function isFile( - path: AbsoluteFsPath, segments: ReadonlyArray, fs: ReadonlyFileSystem): boolean { - for (let i = segments.length - 1; i >= 0; i--) { - const pattern = segments[i]; - const segment = fs.basename(path); - if (typeof pattern === 'string') { - if (pattern !== segment) { - return false; - } - } else { - if (!pattern.test(segment)) { - return false; - } - } - path = fs.dirname(path); - } - return true; -} - -/** - * A cache for processing a single entry-point. This exists to share `ts.SourceFile`s between the - * source and typing programs that are created for a single program. - */ -export class EntryPointFileCache { - private readonly sfCache = new Map(); - - constructor( - private fs: ReadonlyFileSystem, private sharedFileCache: SharedFileCache, - private processSourceText: (sourceText: string) => string) {} - - /** - * Returns and caches a parsed `ts.SourceFile` for the provided `fileName`. If the `fileName` is - * cached in the shared file cache, that result is used. Otherwise, the source file is cached - * internally. This method returns `undefined` if the requested file does not exist. - * - * @param fileName The path of the file to retrieve a source file for. - * @param languageVersion The language version to use for parsing the file. - */ - getCachedSourceFile(fileName: string, languageVersion: ts.ScriptTarget): ts.SourceFile|undefined { - const staticSf = this.sharedFileCache.getCachedSourceFile(fileName); - if (staticSf !== undefined) { - return staticSf; - } - - const absPath = this.fs.resolve(fileName); - if (this.sfCache.has(absPath)) { - return this.sfCache.get(absPath); - } - - const content = readFile(absPath, this.fs); - if (content === undefined) { - return undefined; - } - const processed = this.processSourceText(content); - const sf = ts.createSourceFile(fileName, processed, languageVersion); - this.sfCache.set(absPath, sf); - return sf; - } -} - -function readFile(absPath: AbsoluteFsPath, fs: ReadonlyFileSystem): string|undefined { - if (!fs.exists(absPath) || !fs.stat(absPath).isFile()) { - return undefined; - } - return fs.readFile(absPath); -} - -/** - * Creates a `ts.ModuleResolutionCache` that uses the provided filesystem for path operations. - * - * @param fs The filesystem to use for path operations. - */ -export function createModuleResolutionCache(fs: ReadonlyFileSystem): ts.ModuleResolutionCache { - return ts.createModuleResolutionCache(fs.pwd(), fileName => { - return fs.isCaseSensitive() ? fileName : fileName.toLowerCase(); - }); -} diff --git a/packages/compiler-cli/ngcc/src/packages/transformer.ts b/packages/compiler-cli/ngcc/src/packages/transformer.ts deleted file mode 100644 index fdc7920f07e..00000000000 --- a/packages/compiler-cli/ngcc/src/packages/transformer.ts +++ /dev/null @@ -1,187 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import ts from 'typescript'; - -import {ParsedConfiguration} from '../../..'; -import {ReadonlyFileSystem} from '../../../src/ngtsc/file_system'; -import {Logger} from '../../../src/ngtsc/logging'; -import {TypeScriptReflectionHost} from '../../../src/ngtsc/reflection'; -import {DecorationAnalyzer} from '../analysis/decoration_analyzer'; -import {ModuleWithProvidersAnalyses, ModuleWithProvidersAnalyzer} from '../analysis/module_with_providers_analyzer'; -import {NgccReferencesRegistry} from '../analysis/ngcc_references_registry'; -import {ExportInfo, PrivateDeclarationsAnalyzer} from '../analysis/private_declarations_analyzer'; -import {CompiledFile} from '../analysis/types'; -import {DtsProcessing} from '../execution/tasks/api'; -import {CommonJsReflectionHost} from '../host/commonjs_host'; -import {DelegatingReflectionHost} from '../host/delegating_host'; -import {Esm2015ReflectionHost} from '../host/esm2015_host'; -import {Esm5ReflectionHost} from '../host/esm5_host'; -import {NgccReflectionHost} from '../host/ngcc_host'; -import {UmdReflectionHost} from '../host/umd_host'; -import {CommonJsRenderingFormatter} from '../rendering/commonjs_rendering_formatter'; -import {DtsRenderer} from '../rendering/dts_renderer'; -import {Esm5RenderingFormatter} from '../rendering/esm5_rendering_formatter'; -import {EsmRenderingFormatter} from '../rendering/esm_rendering_formatter'; -import {Renderer} from '../rendering/renderer'; -import {RenderingFormatter} from '../rendering/rendering_formatter'; -import {UmdRenderingFormatter} from '../rendering/umd_rendering_formatter'; -import {FileToWrite} from '../rendering/utils'; - -import {EntryPointBundle} from './entry_point_bundle'; - -export type TransformResult = { - success: true; diagnostics: ts.Diagnostic[]; transformedFiles: FileToWrite[]; -}|{ - success: false; - diagnostics: ts.Diagnostic[]; -}; - -/** - * A Package is stored in a directory on disk and that directory can contain one or more package - * formats - e.g. fesm2015, UMD, etc. Additionally, each package provides typings (`.d.ts` files). - * - * Each of these formats exposes one or more entry points, which are source files that need to be - * parsed to identify the decorated exported classes that need to be analyzed and compiled by one or - * more `DecoratorHandler` objects. - * - * Each entry point to a package is identified by a `package.json` which contains properties that - * indicate what formatted bundles are accessible via this end-point. - * - * Each bundle is identified by a root `SourceFile` that can be parsed and analyzed to - * identify classes that need to be transformed; and then finally rendered and written to disk. - * - * Along with the source files, the corresponding source maps (either inline or external) and - * `.d.ts` files are transformed accordingly. - * - * - Flat file packages have all the classes in a single file. - * - Other packages may re-export classes from other non-entry point files. - * - Some formats may contain multiple "modules" in a single file. - */ -export class Transformer { - constructor( - private fs: ReadonlyFileSystem, private logger: Logger, - private tsConfig: ParsedConfiguration|null = null) {} - - /** - * Transform the source (and typings) files of a bundle. - * @param bundle the bundle to transform. - * @returns information about the files that were transformed. - */ - transform(bundle: EntryPointBundle): TransformResult { - const ngccReflectionHost = this.getHost(bundle); - const tsReflectionHost = new TypeScriptReflectionHost(bundle.src.program.getTypeChecker()); - const reflectionHost = new DelegatingReflectionHost(tsReflectionHost, ngccReflectionHost); - - // Parse and analyze the files. - const { - decorationAnalyses, - privateDeclarationsAnalyses, - moduleWithProvidersAnalyses, - diagnostics - } = this.analyzeProgram(reflectionHost, bundle); - - // Bail if the analysis produced any errors. - if (hasErrors(diagnostics)) { - return {success: false, diagnostics}; - } - - // Transform the source files and source maps. - let renderedFiles: FileToWrite[] = []; - - if (bundle.dtsProcessing !== DtsProcessing.Only) { - // Render the transformed JavaScript files only if we are not doing "typings-only" processing. - const srcFormatter = this.getRenderingFormatter(ngccReflectionHost, bundle); - const renderer = - new Renderer(reflectionHost, srcFormatter, this.fs, this.logger, bundle, this.tsConfig); - renderedFiles = renderer.renderProgram(decorationAnalyses, privateDeclarationsAnalyses); - } - - if (bundle.dts) { - const dtsFormatter = new EsmRenderingFormatter(this.fs, reflectionHost, bundle.isCore); - const dtsRenderer = - new DtsRenderer(dtsFormatter, this.fs, this.logger, reflectionHost, bundle); - const renderedDtsFiles = dtsRenderer.renderProgram( - decorationAnalyses, privateDeclarationsAnalyses, moduleWithProvidersAnalyses); - renderedFiles = renderedFiles.concat(renderedDtsFiles); - } - - return {success: true, diagnostics, transformedFiles: renderedFiles}; - } - - getHost(bundle: EntryPointBundle): NgccReflectionHost { - switch (bundle.format) { - case 'esm2015': - return new Esm2015ReflectionHost(this.logger, bundle.isCore, bundle.src, bundle.dts); - case 'esm5': - return new Esm5ReflectionHost(this.logger, bundle.isCore, bundle.src, bundle.dts); - case 'umd': - return new UmdReflectionHost(this.logger, bundle.isCore, bundle.src, bundle.dts); - case 'commonjs': - return new CommonJsReflectionHost(this.logger, bundle.isCore, bundle.src, bundle.dts); - default: - throw new Error(`Reflection host for "${bundle.format}" not yet implemented.`); - } - } - - getRenderingFormatter(host: NgccReflectionHost, bundle: EntryPointBundle): RenderingFormatter { - switch (bundle.format) { - case 'esm2015': - return new EsmRenderingFormatter(this.fs, host, bundle.isCore); - case 'esm5': - return new Esm5RenderingFormatter(this.fs, host, bundle.isCore); - case 'umd': - if (!(host instanceof UmdReflectionHost)) { - throw new Error('UmdRenderer requires a UmdReflectionHost'); - } - return new UmdRenderingFormatter(this.fs, host, bundle.isCore); - case 'commonjs': - return new CommonJsRenderingFormatter(this.fs, host, bundle.isCore); - default: - throw new Error(`Renderer for "${bundle.format}" not yet implemented.`); - } - } - - analyzeProgram(reflectionHost: NgccReflectionHost, bundle: EntryPointBundle): ProgramAnalyses { - const referencesRegistry = new NgccReferencesRegistry(reflectionHost); - - const diagnostics: ts.Diagnostic[] = []; - const decorationAnalyzer = new DecorationAnalyzer( - this.fs, bundle, reflectionHost, referencesRegistry, - diagnostic => diagnostics.push(diagnostic), this.tsConfig); - const decorationAnalyses = decorationAnalyzer.analyzeProgram(); - - const moduleWithProvidersAnalyzer = new ModuleWithProvidersAnalyzer( - reflectionHost, bundle.src.program.getTypeChecker(), referencesRegistry, - bundle.dts !== null); - const moduleWithProvidersAnalyses = moduleWithProvidersAnalyzer && - moduleWithProvidersAnalyzer.analyzeProgram(bundle.src.program); - - const privateDeclarationsAnalyzer = - new PrivateDeclarationsAnalyzer(reflectionHost, referencesRegistry); - const privateDeclarationsAnalyses = - privateDeclarationsAnalyzer.analyzeProgram(bundle.src.program); - - return { - decorationAnalyses, - privateDeclarationsAnalyses, - moduleWithProvidersAnalyses, - diagnostics - }; - } -} - -export function hasErrors(diagnostics: ts.Diagnostic[]) { - return diagnostics.some(d => d.category === ts.DiagnosticCategory.Error); -} - -interface ProgramAnalyses { - decorationAnalyses: Map; - privateDeclarationsAnalyses: ExportInfo[]; - moduleWithProvidersAnalyses: ModuleWithProvidersAnalyses|null; - diagnostics: ts.Diagnostic[]; -} diff --git a/packages/compiler-cli/ngcc/src/path_mappings.ts b/packages/compiler-cli/ngcc/src/path_mappings.ts deleted file mode 100644 index e41311d0d6c..00000000000 --- a/packages/compiler-cli/ngcc/src/path_mappings.ts +++ /dev/null @@ -1,29 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import {AbsoluteFsPath, PathManipulation} from '../../src/ngtsc/file_system'; -import {ParsedConfiguration} from '../../src/perform_compile'; - -export type PathMappings = { - baseUrl: string, - paths: {[key: string]: string[]} -}; - -/** - * If `pathMappings` is not provided directly, then try getting it from `tsConfig`, if available. - */ -export function getPathMappingsFromTsConfig( - fs: PathManipulation, tsConfig: ParsedConfiguration|null, - projectPath: AbsoluteFsPath): PathMappings|undefined { - if (tsConfig !== null && tsConfig.options.baseUrl !== undefined && - tsConfig.options.paths !== undefined) { - return { - baseUrl: fs.resolve(projectPath, tsConfig.options.baseUrl), - paths: tsConfig.options.paths, - }; - } -} diff --git a/packages/compiler-cli/ngcc/src/rendering/commonjs_rendering_formatter.ts b/packages/compiler-cli/ngcc/src/rendering/commonjs_rendering_formatter.ts deleted file mode 100644 index c963ffff01d..00000000000 --- a/packages/compiler-cli/ngcc/src/rendering/commonjs_rendering_formatter.ts +++ /dev/null @@ -1,89 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import MagicString from 'magic-string'; -import ts from 'typescript'; - -import {PathManipulation} from '../../../src/ngtsc/file_system'; -import {Reexport} from '../../../src/ngtsc/imports'; -import {Import, ImportManager} from '../../../src/ngtsc/translator'; -import {ExportInfo} from '../analysis/private_declarations_analyzer'; -import {isRequireCall} from '../host/commonjs_umd_utils'; -import {NgccReflectionHost} from '../host/ngcc_host'; - -import {Esm5RenderingFormatter} from './esm5_rendering_formatter'; -import {stripExtension} from './utils'; - -/** - * A RenderingFormatter that works with CommonJS files, instead of `import` and `export` statements - * the module is an IIFE with a factory function call with dependencies, which are defined in a - * wrapper function for AMD, CommonJS and global module formats. - */ -export class CommonJsRenderingFormatter extends Esm5RenderingFormatter { - constructor(fs: PathManipulation, protected commonJsHost: NgccReflectionHost, isCore: boolean) { - super(fs, commonJsHost, isCore); - } - - /** - * Add the imports below any in situ imports as `require` calls. - */ - override addImports(output: MagicString, imports: Import[], file: ts.SourceFile): void { - // Avoid unnecessary work if there are no imports to add. - if (imports.length === 0) { - return; - } - - const insertionPoint = this.findEndOfImports(file); - const renderedImports = - imports.map(i => `var ${i.qualifier.text} = require('${i.specifier}');\n`).join(''); - output.appendLeft(insertionPoint, renderedImports); - } - - /** - * Add the exports to the bottom of the file. - */ - override addExports( - output: MagicString, entryPointBasePath: string, exports: ExportInfo[], - importManager: ImportManager, file: ts.SourceFile): void { - exports.forEach(e => { - const basePath = stripExtension(e.from); - const relativePath = './' + this.fs.relative(this.fs.dirname(entryPointBasePath), basePath); - const namedImport = entryPointBasePath !== basePath ? - importManager.generateNamedImport(relativePath, e.identifier) : - {symbol: e.identifier, moduleImport: null}; - const importNamespace = namedImport.moduleImport ? `${namedImport.moduleImport.text}.` : ''; - const exportStr = `\nexports.${e.identifier} = ${importNamespace}${namedImport.symbol};`; - output.append(exportStr); - }); - } - - override addDirectExports( - output: MagicString, exports: Reexport[], importManager: ImportManager, - file: ts.SourceFile): void { - for (const e of exports) { - const namedImport = importManager.generateNamedImport(e.fromModule, e.symbolName); - const importNamespace = namedImport.moduleImport ? `${namedImport.moduleImport.text}.` : ''; - const exportStr = `\nexports.${e.asAlias} = ${importNamespace}${namedImport.symbol};`; - output.append(exportStr); - } - } - - protected override findEndOfImports(sf: ts.SourceFile): number { - for (const statement of sf.statements) { - if (ts.isExpressionStatement(statement) && isRequireCall(statement.expression)) { - continue; - } - const declarations = ts.isVariableStatement(statement) ? - Array.from(statement.declarationList.declarations) : - []; - if (declarations.some(d => !d.initializer || !isRequireCall(d.initializer))) { - return statement.getStart(); - } - } - return 0; - } -} diff --git a/packages/compiler-cli/ngcc/src/rendering/dts_renderer.ts b/packages/compiler-cli/ngcc/src/rendering/dts_renderer.ts deleted file mode 100644 index ced9495f62a..00000000000 --- a/packages/compiler-cli/ngcc/src/rendering/dts_renderer.ts +++ /dev/null @@ -1,186 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import MagicString from 'magic-string'; -import ts from 'typescript'; - -import {ReadonlyFileSystem} from '../../../src/ngtsc/file_system'; -import {Reexport} from '../../../src/ngtsc/imports'; -import {Logger} from '../../../src/ngtsc/logging'; -import {CompileResult} from '../../../src/ngtsc/transform'; -import {ImportManager, translateType} from '../../../src/ngtsc/translator'; -import {ModuleWithProvidersAnalyses, ModuleWithProvidersInfo} from '../analysis/module_with_providers_analyzer'; -import {ExportInfo, PrivateDeclarationsAnalyses} from '../analysis/private_declarations_analyzer'; -import {DecorationAnalyses} from '../analysis/types'; -import {IMPORT_PREFIX} from '../constants'; -import {NgccReflectionHost} from '../host/ngcc_host'; -import {EntryPointBundle} from '../packages/entry_point_bundle'; - -import {RenderingFormatter} from './rendering_formatter'; -import {renderSourceAndMap} from './source_maps'; -import {FileToWrite, getImportRewriter} from './utils'; - -/** - * A structure that captures information about what needs to be rendered - * in a typings file. - * - * It is created as a result of processing the analysis passed to the renderer. - * - * The `renderDtsFile()` method consumes it when rendering a typings file. - */ -class DtsRenderInfo { - classInfo: DtsClassInfo[] = []; - moduleWithProviders: ModuleWithProvidersInfo[] = []; - privateExports: ExportInfo[] = []; - reexports: Reexport[] = []; -} - - -/** - * Information about a class in a typings file. - */ -export interface DtsClassInfo { - dtsDeclaration: ts.Declaration; - compilation: CompileResult[]; -} - -/** - * A base-class for rendering an `AnalyzedFile`. - * - * Package formats have output files that must be rendered differently. Concrete sub-classes must - * implement the `addImports`, `addDefinitions` and `removeDecorators` abstract methods. - */ -export class DtsRenderer { - constructor( - private dtsFormatter: RenderingFormatter, private fs: ReadonlyFileSystem, - private logger: Logger, private host: NgccReflectionHost, private bundle: EntryPointBundle) {} - - renderProgram( - decorationAnalyses: DecorationAnalyses, - privateDeclarationsAnalyses: PrivateDeclarationsAnalyses, - moduleWithProvidersAnalyses: ModuleWithProvidersAnalyses|null): FileToWrite[] { - const renderedFiles: FileToWrite[] = []; - - // Transform the .d.ts files - if (this.bundle.dts) { - const dtsFiles = this.getTypingsFilesToRender( - decorationAnalyses, privateDeclarationsAnalyses, moduleWithProvidersAnalyses); - - // If the dts entry-point is not already there (it did not have compiled classes) - // then add it now, to ensure it gets its extra exports rendered. - if (!dtsFiles.has(this.bundle.dts.file)) { - dtsFiles.set(this.bundle.dts.file, new DtsRenderInfo()); - } - dtsFiles.forEach( - (renderInfo, file) => renderedFiles.push(...this.renderDtsFile(file, renderInfo))); - } - - return renderedFiles; - } - - renderDtsFile(dtsFile: ts.SourceFile, renderInfo: DtsRenderInfo): FileToWrite[] { - const outputText = new MagicString(dtsFile.text); - const printer = ts.createPrinter(); - const importManager = new ImportManager( - getImportRewriter(this.bundle.dts!.r3SymbolsFile, this.bundle.isCore, false), - IMPORT_PREFIX); - - renderInfo.classInfo.forEach(dtsClass => { - const endOfClass = dtsClass.dtsDeclaration.getEnd(); - dtsClass.compilation.forEach(declaration => { - const type = translateType(declaration.type, importManager); - markForEmitAsSingleLine(type); - const typeStr = printer.printNode(ts.EmitHint.Unspecified, type, dtsFile); - const newStatement = ` static ${declaration.name}: ${typeStr};\n`; - outputText.appendRight(endOfClass - 1, newStatement); - }); - }); - - if (renderInfo.reexports.length > 0) { - for (const e of renderInfo.reexports) { - const newStatement = `\nexport {${e.symbolName} as ${e.asAlias}} from '${e.fromModule}';`; - outputText.append(newStatement); - } - } - - this.dtsFormatter.addModuleWithProvidersParams( - outputText, renderInfo.moduleWithProviders, importManager); - this.dtsFormatter.addExports( - outputText, dtsFile.fileName, renderInfo.privateExports, importManager, dtsFile); - this.dtsFormatter.addImports( - outputText, importManager.getAllImports(dtsFile.fileName), dtsFile); - - return renderSourceAndMap(this.logger, this.fs, dtsFile, outputText); - } - - private getTypingsFilesToRender( - decorationAnalyses: DecorationAnalyses, - privateDeclarationsAnalyses: PrivateDeclarationsAnalyses, - moduleWithProvidersAnalyses: ModuleWithProvidersAnalyses| - null): Map { - const dtsMap = new Map(); - - // Capture the rendering info from the decoration analyses - decorationAnalyses.forEach(compiledFile => { - let appliedReexports = false; - compiledFile.compiledClasses.forEach(compiledClass => { - const dtsDeclaration = this.host.getDtsDeclaration(compiledClass.declaration); - if (dtsDeclaration) { - const dtsFile = dtsDeclaration.getSourceFile(); - const renderInfo = dtsMap.has(dtsFile) ? dtsMap.get(dtsFile)! : new DtsRenderInfo(); - renderInfo.classInfo.push({dtsDeclaration, compilation: compiledClass.compilation}); - // Only add re-exports if the .d.ts tree is overlaid with the .js tree, as re-exports in - // ngcc are only used to support deep imports into e.g. commonjs code. For a deep import - // to work, the typing file and JS file must be in parallel trees. This logic will detect - // the simplest version of this case, which is sufficient to handle most commonjs - // libraries. - if (!appliedReexports && - compiledClass.declaration.getSourceFile().fileName === - dtsFile.fileName.replace(/\.d\.ts$/, '.js')) { - renderInfo.reexports.push(...compiledFile.reexports); - appliedReexports = true; - } - dtsMap.set(dtsFile, renderInfo); - } - }); - }); - - // Capture the ModuleWithProviders functions/methods that need updating - if (moduleWithProvidersAnalyses !== null) { - moduleWithProvidersAnalyses.forEach((moduleWithProvidersToFix, dtsFile) => { - const renderInfo = dtsMap.has(dtsFile) ? dtsMap.get(dtsFile)! : new DtsRenderInfo(); - renderInfo.moduleWithProviders = moduleWithProvidersToFix; - dtsMap.set(dtsFile, renderInfo); - }); - } - - // Capture the private declarations that need to be re-exported - if (privateDeclarationsAnalyses.length) { - privateDeclarationsAnalyses.forEach(e => { - if (!e.dtsFrom) { - throw new Error( - `There is no typings path for ${e.identifier} in ${e.from}.\n` + - `We need to add an export for this class to a .d.ts typings file because ` + - `Angular compiler needs to be able to reference this class in compiled code, such as templates.\n` + - `The simplest fix for this is to ensure that this class is exported from the package's entry-point.`); - } - }); - const dtsEntryPoint = this.bundle.dts!.file; - const renderInfo = - dtsMap.has(dtsEntryPoint) ? dtsMap.get(dtsEntryPoint)! : new DtsRenderInfo(); - renderInfo.privateExports = privateDeclarationsAnalyses; - dtsMap.set(dtsEntryPoint, renderInfo); - } - - return dtsMap; - } -} - -function markForEmitAsSingleLine(node: ts.Node) { - ts.setEmitFlags(node, ts.EmitFlags.SingleLine); - ts.forEachChild(node, markForEmitAsSingleLine); -} diff --git a/packages/compiler-cli/ngcc/src/rendering/esm5_rendering_formatter.ts b/packages/compiler-cli/ngcc/src/rendering/esm5_rendering_formatter.ts deleted file mode 100644 index 58e24fe8952..00000000000 --- a/packages/compiler-cli/ngcc/src/rendering/esm5_rendering_formatter.ts +++ /dev/null @@ -1,75 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import {Statement} from '@angular/compiler'; -import MagicString from 'magic-string'; -import ts from 'typescript'; - -import {ImportManager, translateStatement} from '../../../src/ngtsc/translator'; -import {CompiledClass} from '../analysis/types'; -import {getContainingStatement} from '../host/esm2015_host'; - -import {EsmRenderingFormatter} from './esm_rendering_formatter'; - -/** - * A RenderingFormatter that works with files that use ECMAScript Module `import` and `export` - * statements, but instead of `class` declarations it uses ES5 `function` wrappers for classes. - */ -export class Esm5RenderingFormatter extends EsmRenderingFormatter { - /** - * Add the definitions, directly before the return statement, inside the IIFE of each decorated - * class. - */ - override addDefinitions(output: MagicString, compiledClass: CompiledClass, definitions: string): - void { - const classSymbol = this.host.getClassSymbol(compiledClass.declaration); - if (!classSymbol) { - throw new Error( - `Compiled class "${compiledClass.name}" in "${ - compiledClass.declaration.getSourceFile() - .fileName}" does not have a valid syntax.\n` + - `Expected an ES5 IIFE wrapped function. But got:\n` + - compiledClass.declaration.getText()); - } - const declarationStatement = - getContainingStatement(classSymbol.implementation.valueDeclaration); - - const iifeBody = declarationStatement.parent; - if (!iifeBody || !ts.isBlock(iifeBody)) { - throw new Error(`Compiled class declaration is not inside an IIFE: ${compiledClass.name} in ${ - compiledClass.declaration.getSourceFile().fileName}`); - } - - const returnStatement = iifeBody.statements.find(ts.isReturnStatement); - if (!returnStatement) { - throw new Error(`Compiled class wrapper IIFE does not have a return statement: ${ - compiledClass.name} in ${compiledClass.declaration.getSourceFile().fileName}`); - } - - const insertionPoint = returnStatement.getFullStart(); - output.appendLeft(insertionPoint, '\n' + definitions); - } - - /** - * Convert a `Statement` to JavaScript code in a format suitable for rendering by this formatter. - * - * @param stmt The `Statement` to print. - * @param sourceFile A `ts.SourceFile` that provides context for the statement. See - * `ts.Printer#printNode()` for more info. - * @param importManager The `ImportManager` to use for managing imports. - * - * @return The JavaScript code corresponding to `stmt` (in the appropriate format). - */ - override printStatement(stmt: Statement, sourceFile: ts.SourceFile, importManager: ImportManager): - string { - const node = translateStatement( - stmt, importManager, {downlevelTaggedTemplates: true, downlevelVariableDeclarations: true}); - const code = this.printer.printNode(ts.EmitHint.Unspecified, node, sourceFile); - - return code; - } -} diff --git a/packages/compiler-cli/ngcc/src/rendering/esm_rendering_formatter.ts b/packages/compiler-cli/ngcc/src/rendering/esm_rendering_formatter.ts deleted file mode 100644 index 1e214c46710..00000000000 --- a/packages/compiler-cli/ngcc/src/rendering/esm_rendering_formatter.ts +++ /dev/null @@ -1,292 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import {Statement} from '@angular/compiler'; -import MagicString from 'magic-string'; -import ts from 'typescript'; - -import {absoluteFromSourceFile, AbsoluteFsPath, PathManipulation, toRelativeImport} from '../../../src/ngtsc/file_system'; -import {Reexport} from '../../../src/ngtsc/imports'; -import {Import, ImportManager, translateStatement} from '../../../src/ngtsc/translator'; -import {isDtsPath} from '../../../src/ngtsc/util/src/typescript'; -import {ModuleWithProvidersInfo} from '../analysis/module_with_providers_analyzer'; -import {ExportInfo} from '../analysis/private_declarations_analyzer'; -import {CompiledClass} from '../analysis/types'; -import {getContainingStatement, isAssignment} from '../host/esm2015_host'; -import {NgccReflectionHost} from '../host/ngcc_host'; - -import {RedundantDecoratorMap, RenderingFormatter} from './rendering_formatter'; -import {stripExtension} from './utils'; - -/** - * A RenderingFormatter that works with ECMAScript Module import and export statements. - */ -export class EsmRenderingFormatter implements RenderingFormatter { - protected printer = ts.createPrinter({newLine: ts.NewLineKind.LineFeed}); - - constructor( - protected fs: PathManipulation, protected host: NgccReflectionHost, - protected isCore: boolean) {} - - /** - * Add the imports at the top of the file, after any imports that are already there. - */ - addImports(output: MagicString, imports: Import[], sf: ts.SourceFile): void { - if (imports.length === 0) { - return; - } - - const insertionPoint = this.findEndOfImports(sf); - const renderedImports = - imports.map(i => `import * as ${i.qualifier.text} from '${i.specifier}';\n`).join(''); - output.appendLeft(insertionPoint, renderedImports); - } - - /** - * Add the exports to the end of the file. - */ - addExports( - output: MagicString, entryPointBasePath: AbsoluteFsPath, exports: ExportInfo[], - importManager: ImportManager, file: ts.SourceFile): void { - exports.forEach(e => { - let exportFrom = ''; - const isDtsFile = isDtsPath(entryPointBasePath); - const from = isDtsFile ? e.dtsFrom : e.from; - - if (from) { - const basePath = stripExtension(from); - const relativePath = this.fs.relative(this.fs.dirname(entryPointBasePath), basePath); - const relativeImport = toRelativeImport(relativePath); - exportFrom = entryPointBasePath !== basePath ? ` from '${relativeImport}'` : ''; - } - - const exportStr = `\nexport {${e.identifier}}${exportFrom};`; - output.append(exportStr); - }); - } - - - /** - * Add plain exports to the end of the file. - * - * Unlike `addExports`, direct exports go directly in a .js and .d.ts file and don't get added to - * an entrypoint. - */ - addDirectExports( - output: MagicString, exports: Reexport[], importManager: ImportManager, - file: ts.SourceFile): void { - for (const e of exports) { - const exportStatement = `\nexport {${e.symbolName} as ${e.asAlias}} from '${e.fromModule}';`; - output.append(exportStatement); - } - } - - /** - * Add the constants directly after the imports. - */ - addConstants(output: MagicString, constants: string, file: ts.SourceFile): void { - if (constants === '') { - return; - } - const insertionPoint = this.findEndOfImports(file); - - // Append the constants to the right of the insertion point, to ensure they get ordered after - // added imports (those are appended left to the insertion point). - output.appendRight(insertionPoint, '\n' + constants + '\n'); - } - - /** - * Add the definitions directly after their decorated class. - */ - addDefinitions(output: MagicString, compiledClass: CompiledClass, definitions: string): void { - const classSymbol = this.host.getClassSymbol(compiledClass.declaration); - if (!classSymbol) { - throw new Error(`Compiled class does not have a valid symbol: ${compiledClass.name}`); - } - const declarationStatement = - getContainingStatement(classSymbol.implementation.valueDeclaration); - const insertionPoint = declarationStatement.getEnd(); - output.appendLeft(insertionPoint, '\n' + definitions); - } - - /** - * Add the adjacent statements after all static properties of the class. - */ - addAdjacentStatements(output: MagicString, compiledClass: CompiledClass, statements: string): - void { - const classSymbol = this.host.getClassSymbol(compiledClass.declaration); - if (!classSymbol) { - throw new Error(`Compiled class does not have a valid symbol: ${compiledClass.name}`); - } - const endOfClass = this.host.getEndOfClass(classSymbol); - output.appendLeft(endOfClass.getEnd(), '\n' + statements); - } - - /** - * Remove static decorator properties from classes. - */ - removeDecorators(output: MagicString, decoratorsToRemove: RedundantDecoratorMap): void { - decoratorsToRemove.forEach((nodesToRemove, containerNode) => { - if (ts.isArrayLiteralExpression(containerNode)) { - const items = containerNode.elements; - if (items.length === nodesToRemove.length) { - // Remove the entire statement - const statement = findStatement(containerNode); - if (statement) { - if (ts.isExpressionStatement(statement)) { - // The statement looks like: `SomeClass = __decorate(...);` - // Remove it completely - output.remove(statement.getFullStart(), statement.getEnd()); - } else if ( - ts.isReturnStatement(statement) && statement.expression && - isAssignment(statement.expression)) { - // The statement looks like: `return SomeClass = __decorate(...);` - // We only want to end up with: `return SomeClass;` - const startOfRemoval = statement.expression.left.getEnd(); - const endOfRemoval = getEndExceptSemicolon(statement); - output.remove(startOfRemoval, endOfRemoval); - } - } - } else { - nodesToRemove.forEach(node => { - // remove any trailing comma - const nextSibling = getNextSiblingInArray(node, items); - let end: number; - - if (nextSibling !== null && - output.slice(nextSibling.getFullStart() - 1, nextSibling.getFullStart()) === ',') { - end = nextSibling.getFullStart() - 1 + nextSibling.getLeadingTriviaWidth(); - } else if (output.slice(node.getEnd(), node.getEnd() + 1) === ',') { - end = node.getEnd() + 1; - } else { - end = node.getEnd(); - } - output.remove(node.getFullStart(), end); - }); - } - } - }); - } - - /** - * Add the type parameters to the appropriate functions that return `ModuleWithProviders` - * structures. - * - * This function will only get called on typings files. - */ - addModuleWithProvidersParams( - outputText: MagicString, moduleWithProviders: ModuleWithProvidersInfo[], - importManager: ImportManager): void { - moduleWithProviders.forEach(info => { - const ngModuleName = info.ngModule.node.name.text; - const declarationFile = absoluteFromSourceFile(info.declaration.getSourceFile()); - const ngModuleFile = absoluteFromSourceFile(info.ngModule.node.getSourceFile()); - const relativePath = this.fs.relative(this.fs.dirname(declarationFile), ngModuleFile); - const relativeImport = toRelativeImport(relativePath); - const importPath = info.ngModule.ownedByModuleGuess || - (declarationFile !== ngModuleFile ? stripExtension(relativeImport) : null); - const ngModule = generateImportString(importManager, importPath, ngModuleName); - - if (info.declaration.type) { - const typeName = info.declaration.type && ts.isTypeReferenceNode(info.declaration.type) ? - info.declaration.type.typeName : - null; - if (this.isCoreModuleWithProvidersType(typeName)) { - // The declaration already returns `ModuleWithProvider` but it needs the `NgModule` type - // parameter adding. - outputText.overwrite( - info.declaration.type.getStart(), info.declaration.type.getEnd(), - `ModuleWithProviders<${ngModule}>`); - } else { - // The declaration returns an unknown type so we need to convert it to a union that - // includes the ngModule property. - const originalTypeString = info.declaration.type.getText(); - outputText.overwrite( - info.declaration.type.getStart(), info.declaration.type.getEnd(), - `(${originalTypeString})&{ngModule:${ngModule}}`); - } - } else { - // The declaration has no return type so provide one. - const lastToken = info.declaration.getLastToken(); - const insertPoint = lastToken && lastToken.kind === ts.SyntaxKind.SemicolonToken ? - lastToken.getStart() : - info.declaration.getEnd(); - outputText.appendLeft( - insertPoint, - `: ${generateImportString(importManager, '@angular/core', 'ModuleWithProviders')}<${ - ngModule}>`); - } - }); - } - - /** - * Convert a `Statement` to JavaScript code in a format suitable for rendering by this formatter. - * - * @param stmt The `Statement` to print. - * @param sourceFile A `ts.SourceFile` that provides context for the statement. See - * `ts.Printer#printNode()` for more info. - * @param importManager The `ImportManager` to use for managing imports. - * - * @return The JavaScript code corresponding to `stmt` (in the appropriate format). - */ - printStatement(stmt: Statement, sourceFile: ts.SourceFile, importManager: ImportManager): string { - const node = translateStatement(stmt, importManager); - const code = this.printer.printNode(ts.EmitHint.Unspecified, node, sourceFile); - - return code; - } - - protected findEndOfImports(sf: ts.SourceFile): number { - for (const stmt of sf.statements) { - if (!ts.isImportDeclaration(stmt) && !ts.isImportEqualsDeclaration(stmt) && - !ts.isNamespaceImport(stmt)) { - return stmt.getStart(); - } - } - return 0; - } - - /** - * Check whether the given type is the core Angular `ModuleWithProviders` interface. - * @param typeName The type to check. - * @returns true if the type is the core Angular `ModuleWithProviders` interface. - */ - private isCoreModuleWithProvidersType(typeName: ts.EntityName|null) { - const id = - typeName && ts.isIdentifier(typeName) ? this.host.getImportOfIdentifier(typeName) : null; - return ( - id && id.name === 'ModuleWithProviders' && (this.isCore || id.from === '@angular/core')); - } -} - -function findStatement(node: ts.Node): ts.Statement|undefined { - while (node) { - if (ts.isExpressionStatement(node) || ts.isReturnStatement(node)) { - return node; - } - node = node.parent; - } - return undefined; -} - -function generateImportString( - importManager: ImportManager, importPath: string|null, importName: string) { - const importAs = importPath ? importManager.generateNamedImport(importPath, importName) : null; - return importAs && importAs.moduleImport ? `${importAs.moduleImport.text}.${importAs.symbol}` : - `${importName}`; -} - -function getNextSiblingInArray(node: T, array: ts.NodeArray): T|null { - const index = array.indexOf(node); - return index !== -1 && array.length > index + 1 ? array[index + 1] : null; -} - -function getEndExceptSemicolon(statement: ts.Statement): number { - const lastToken = statement.getLastToken(); - return (lastToken && lastToken.kind === ts.SyntaxKind.SemicolonToken) ? statement.getEnd() - 1 : - statement.getEnd(); -} diff --git a/packages/compiler-cli/ngcc/src/rendering/ngcc_import_rewriter.ts b/packages/compiler-cli/ngcc/src/rendering/ngcc_import_rewriter.ts deleted file mode 100644 index ece42449b67..00000000000 --- a/packages/compiler-cli/ngcc/src/rendering/ngcc_import_rewriter.ts +++ /dev/null @@ -1,34 +0,0 @@ - -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ - -import {ImportRewriter, validateAndRewriteCoreSymbol} from '../../../src/ngtsc/imports'; - -export class NgccFlatImportRewriter implements ImportRewriter { - shouldImportSymbol(symbol: string, specifier: string): boolean { - if (specifier === '@angular/core') { - // Don't use imports for @angular/core symbols in a flat bundle, as they'll be visible - // directly. - return false; - } else { - return true; - } - } - - rewriteSymbol(symbol: string, specifier: string): string { - if (specifier === '@angular/core') { - return validateAndRewriteCoreSymbol(symbol); - } else { - return symbol; - } - } - - rewriteSpecifier(originalModulePath: string, inContextOfFile: string): string { - return originalModulePath; - } -} diff --git a/packages/compiler-cli/ngcc/src/rendering/renderer.ts b/packages/compiler-cli/ngcc/src/rendering/renderer.ts deleted file mode 100644 index 9454d644b08..00000000000 --- a/packages/compiler-cli/ngcc/src/rendering/renderer.ts +++ /dev/null @@ -1,219 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import {ConstantPool, Expression, jsDocComment, LeadingComment, Statement, WrappedNodeExpr, WritePropExpr} from '@angular/compiler'; -import MagicString from 'magic-string'; -import ts from 'typescript'; - -import {ReadonlyFileSystem} from '../../../src/ngtsc/file_system'; -import {Logger} from '../../../src/ngtsc/logging'; -import {ImportManager} from '../../../src/ngtsc/translator'; -import {ParsedConfiguration} from '../../../src/perform_compile'; -import {PrivateDeclarationsAnalyses} from '../analysis/private_declarations_analyzer'; -import {CompiledClass, CompiledFile, DecorationAnalyses} from '../analysis/types'; -import {IMPORT_PREFIX} from '../constants'; -import {NgccReflectionHost} from '../host/ngcc_host'; -import {EntryPointBundle} from '../packages/entry_point_bundle'; - -import {RedundantDecoratorMap, RenderingFormatter} from './rendering_formatter'; -import {renderSourceAndMap} from './source_maps'; -import {FileToWrite, getImportRewriter, stripExtension} from './utils'; - -/** - * A base-class for rendering an `AnalyzedFile`. - * - * Package formats have output files that must be rendered differently. Concrete sub-classes must - * implement the `addImports`, `addDefinitions` and `removeDecorators` abstract methods. - */ -export class Renderer { - constructor( - private host: NgccReflectionHost, private srcFormatter: RenderingFormatter, - private fs: ReadonlyFileSystem, private logger: Logger, private bundle: EntryPointBundle, - private tsConfig: ParsedConfiguration|null = null) {} - - renderProgram( - decorationAnalyses: DecorationAnalyses, - privateDeclarationsAnalyses: PrivateDeclarationsAnalyses): FileToWrite[] { - const renderedFiles: FileToWrite[] = []; - - // Transform the source files. - this.bundle.src.program.getSourceFiles().forEach(sourceFile => { - if (decorationAnalyses.has(sourceFile) || sourceFile === this.bundle.src.file) { - const compiledFile = decorationAnalyses.get(sourceFile); - renderedFiles.push( - ...this.renderFile(sourceFile, compiledFile, privateDeclarationsAnalyses)); - } - }); - - return renderedFiles; - } - - /** - * Render the source code and source-map for an Analyzed file. - * @param compiledFile The analyzed file to render. - * @param targetPath The absolute path where the rendered file will be written. - */ - renderFile( - sourceFile: ts.SourceFile, compiledFile: CompiledFile|undefined, - privateDeclarationsAnalyses: PrivateDeclarationsAnalyses): FileToWrite[] { - const isEntryPoint = sourceFile === this.bundle.src.file; - const outputText = new MagicString(sourceFile.text); - - const importManager = new ImportManager( - getImportRewriter( - this.bundle.src.r3SymbolsFile, this.bundle.isCore, this.bundle.isFlatCore), - IMPORT_PREFIX); - - if (compiledFile) { - // TODO: remove constructor param metadata and property decorators (we need info from the - // handlers to do this) - const decoratorsToRemove = this.computeDecoratorsToRemove(compiledFile.compiledClasses); - this.srcFormatter.removeDecorators(outputText, decoratorsToRemove); - - compiledFile.compiledClasses.forEach(clazz => { - const renderedDefinition = this.renderDefinitions( - compiledFile.sourceFile, clazz, importManager, - !!this.tsConfig?.options.annotateForClosureCompiler); - this.srcFormatter.addDefinitions(outputText, clazz, renderedDefinition); - - const renderedStatements = - this.renderAdjacentStatements(compiledFile.sourceFile, clazz, importManager); - this.srcFormatter.addAdjacentStatements(outputText, clazz, renderedStatements); - }); - - if (!isEntryPoint && compiledFile.reexports.length > 0) { - this.srcFormatter.addDirectExports( - outputText, compiledFile.reexports, importManager, compiledFile.sourceFile); - } - - this.srcFormatter.addConstants( - outputText, - renderConstantPool( - this.srcFormatter, compiledFile.sourceFile, compiledFile.constantPool, importManager), - compiledFile.sourceFile); - } - - // Add exports to the entry-point file - if (isEntryPoint) { - const entryPointBasePath = stripExtension(this.bundle.src.path); - this.srcFormatter.addExports( - outputText, entryPointBasePath, privateDeclarationsAnalyses, importManager, sourceFile); - } - - if (isEntryPoint || compiledFile) { - this.srcFormatter.addImports( - outputText, importManager.getAllImports(sourceFile.fileName), sourceFile); - } - - if (compiledFile || isEntryPoint) { - return renderSourceAndMap(this.logger, this.fs, sourceFile, outputText); - } else { - return []; - } - } - - /** - * From the given list of classes, computes a map of decorators that should be removed. - * The decorators to remove are keyed by their container node, such that we can tell if - * we should remove the entire decorator property. - * @param classes The list of classes that may have decorators to remove. - * @returns A map of decorators to remove, keyed by their container node. - */ - private computeDecoratorsToRemove(classes: CompiledClass[]): RedundantDecoratorMap { - const decoratorsToRemove = new RedundantDecoratorMap(); - classes.forEach(clazz => { - if (clazz.decorators === null) { - return; - } - - clazz.decorators.forEach(dec => { - if (dec.node === null) { - return; - } - const decoratorArray = dec.node.parent!; - if (!decoratorsToRemove.has(decoratorArray)) { - decoratorsToRemove.set(decoratorArray, [dec.node]); - } else { - decoratorsToRemove.get(decoratorArray)!.push(dec.node); - } - }); - }); - return decoratorsToRemove; - } - - /** - * Render the definitions as source code for the given class. - * @param sourceFile The file containing the class to process. - * @param clazz The class whose definitions are to be rendered. - * @param compilation The results of analyzing the class - this is used to generate the rendered - * definitions. - * @param imports An object that tracks the imports that are needed by the rendered definitions. - */ - private renderDefinitions( - sourceFile: ts.SourceFile, compiledClass: CompiledClass, imports: ImportManager, - annotateForClosureCompiler: boolean): string { - const name = this.host.getInternalNameOfClass(compiledClass.declaration); - const leadingComment = - annotateForClosureCompiler ? jsDocComment([{tagName: 'nocollapse'}]) : undefined; - const statements: Statement[] = compiledClass.compilation.map( - c => createAssignmentStatement(name, c.name, c.initializer, leadingComment)); - return this.renderStatements(sourceFile, statements, imports); - } - - /** - * Render the adjacent statements as source code for the given class. - * @param sourceFile The file containing the class to process. - * @param clazz The class whose statements are to be rendered. - * @param compilation The results of analyzing the class - this is used to generate the rendered - * definitions. - * @param imports An object that tracks the imports that are needed by the rendered definitions. - */ - private renderAdjacentStatements( - sourceFile: ts.SourceFile, compiledClass: CompiledClass, imports: ImportManager): string { - const statements: Statement[] = []; - for (const c of compiledClass.compilation) { - statements.push(...c.statements); - } - return this.renderStatements(sourceFile, statements, imports); - } - - private renderStatements( - sourceFile: ts.SourceFile, statements: Statement[], imports: ImportManager): string { - const printStatement = (stmt: Statement) => - this.srcFormatter.printStatement(stmt, sourceFile, imports); - return statements.map(printStatement).join('\n'); - } -} - -/** - * Render the constant pool as source code for the given class. - */ -export function renderConstantPool( - formatter: RenderingFormatter, sourceFile: ts.SourceFile, constantPool: ConstantPool, - imports: ImportManager): string { - const printStatement = (stmt: Statement) => formatter.printStatement(stmt, sourceFile, imports); - return constantPool.statements.map(printStatement).join('\n'); -} - -/** - * Create an Angular AST statement node that contains the assignment of the - * compiled decorator to be applied to the class. - * @param analyzedClass The info about the class whose statement we want to create. - */ -function createAssignmentStatement( - receiverName: ts.DeclarationName, propName: string, initializer: Expression, - leadingComment?: LeadingComment): Statement { - const receiver = new WrappedNodeExpr(receiverName); - const statement = - new WritePropExpr( - receiver, propName, initializer, /* type */ undefined, /* sourceSpan */ undefined) - .toStmt(); - if (leadingComment !== undefined) { - statement.addLeadingComment(leadingComment); - } - return statement; -} diff --git a/packages/compiler-cli/ngcc/src/rendering/rendering_formatter.ts b/packages/compiler-cli/ngcc/src/rendering/rendering_formatter.ts deleted file mode 100644 index 861d92c7ba7..00000000000 --- a/packages/compiler-cli/ngcc/src/rendering/rendering_formatter.ts +++ /dev/null @@ -1,47 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import {Statement} from '@angular/compiler'; -import MagicString from 'magic-string'; -import ts from 'typescript'; - -import {Reexport} from '../../../src/ngtsc/imports'; -import {Import, ImportManager} from '../../../src/ngtsc/translator'; -import {ModuleWithProvidersInfo} from '../analysis/module_with_providers_analyzer'; -import {ExportInfo} from '../analysis/private_declarations_analyzer'; -import {CompiledClass} from '../analysis/types'; - -/** - * The collected decorators that have become redundant after the compilation - * of Ivy static fields. The map is keyed by the container node, such that we - * can tell if we should remove the entire decorator property - */ -export type RedundantDecoratorMap = Map; -export const RedundantDecoratorMap = Map; - -/** - * Implement this interface with methods that know how to render a specific format, - * such as ESM5 or UMD. - */ -export interface RenderingFormatter { - addConstants(output: MagicString, constants: string, file: ts.SourceFile): void; - addImports(output: MagicString, imports: Import[], sf: ts.SourceFile): void; - addExports( - output: MagicString, entryPointBasePath: string, exports: ExportInfo[], - importManager: ImportManager, file: ts.SourceFile): void; - addDirectExports( - output: MagicString, exports: Reexport[], importManager: ImportManager, - file: ts.SourceFile): void; - addDefinitions(output: MagicString, compiledClass: CompiledClass, definitions: string): void; - addAdjacentStatements(output: MagicString, compiledClass: CompiledClass, statements: string): - void; - removeDecorators(output: MagicString, decoratorsToRemove: RedundantDecoratorMap): void; - addModuleWithProvidersParams( - outputText: MagicString, moduleWithProviders: ModuleWithProvidersInfo[], - importManager: ImportManager): void; - printStatement(stmt: Statement, sourceFile: ts.SourceFile, importManager: ImportManager): string; -} diff --git a/packages/compiler-cli/ngcc/src/rendering/source_maps.ts b/packages/compiler-cli/ngcc/src/rendering/source_maps.ts deleted file mode 100644 index 4c6c744414e..00000000000 --- a/packages/compiler-cli/ngcc/src/rendering/source_maps.ts +++ /dev/null @@ -1,73 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import mapHelpers from 'convert-source-map'; -import MagicString from 'magic-string'; -import ts from 'typescript'; - -import {absoluteFrom, absoluteFromSourceFile, ReadonlyFileSystem} from '../../../src/ngtsc/file_system'; -import {Logger} from '../../../src/ngtsc/logging'; -import {ContentOrigin, RawSourceMap, SourceFileLoader} from '../../../src/ngtsc/sourcemaps'; - -import {FileToWrite} from './utils'; - -export interface SourceMapInfo { - source: string; - map: mapHelpers.SourceMapConverter|null; - isInline: boolean; -} - -/** - * Merge the input and output source-maps, replacing the source-map comment in the output file - * with an appropriate source-map comment pointing to the merged source-map. - */ -export function renderSourceAndMap( - logger: Logger, fs: ReadonlyFileSystem, sourceFile: ts.SourceFile, - generatedMagicString: MagicString): FileToWrite[] { - const sourceFilePath = absoluteFromSourceFile(sourceFile); - const sourceMapPath = absoluteFrom(`${sourceFilePath}.map`); - const generatedContent = generatedMagicString.toString(); - const generatedMap: RawSourceMap = generatedMagicString.generateMap( - {file: sourceFilePath, source: sourceFilePath, includeContent: true}); - - try { - const loader = new SourceFileLoader(fs, logger, {}); - const generatedFile = loader.loadSourceFile( - sourceFilePath, generatedContent, {map: generatedMap, mapPath: sourceMapPath}); - - const rawMergedMap: RawSourceMap = generatedFile.renderFlattenedSourceMap(); - const mergedMap = mapHelpers.fromObject(rawMergedMap); - const originalFile = loader.loadSourceFile(sourceFilePath, generatedMagicString.original); - if (originalFile.rawMap === null && !sourceFile.isDeclarationFile || - originalFile.rawMap?.origin === ContentOrigin.Inline) { - // We render an inline source map if one of: - // * there was no input source map and this is not a typings file; - // * the input source map exists and was inline. - // - // We do not generate inline source maps for typings files unless there explicitly was one in - // the input file because these inline source maps can be very large and it impacts on the - // performance of IDEs that need to read them to provide intellisense etc. - return [ - {path: sourceFilePath, contents: `${generatedFile.contents}\n${mergedMap.toComment()}`} - ]; - } - - const sourceMapComment = - mapHelpers.generateMapFileComment(`${fs.basename(sourceFilePath)}.map`); - return [ - {path: sourceFilePath, contents: `${generatedFile.contents}\n${sourceMapComment}`}, - {path: sourceMapPath, contents: mergedMap.toJSON()} - ]; - } catch (e) { - logger.error(`Error when flattening the source-map "${sourceMapPath}" for "${ - sourceFilePath}": ${(e as Error).toString()}`); - return [ - {path: sourceFilePath, contents: generatedContent}, - {path: sourceMapPath, contents: mapHelpers.fromObject(generatedMap).toJSON()}, - ]; - } -} diff --git a/packages/compiler-cli/ngcc/src/rendering/umd_rendering_formatter.ts b/packages/compiler-cli/ngcc/src/rendering/umd_rendering_formatter.ts deleted file mode 100644 index 4030d7b1a98..00000000000 --- a/packages/compiler-cli/ngcc/src/rendering/umd_rendering_formatter.ts +++ /dev/null @@ -1,252 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import MagicString from 'magic-string'; -import ts from 'typescript'; - -import {PathManipulation} from '../../../src/ngtsc/file_system'; -import {Reexport} from '../../../src/ngtsc/imports'; -import {Import, ImportManager} from '../../../src/ngtsc/translator'; -import {ExportInfo} from '../analysis/private_declarations_analyzer'; -import {UmdReflectionHost} from '../host/umd_host'; - -import {Esm5RenderingFormatter} from './esm5_rendering_formatter'; -import {stripExtension} from './utils'; - -/** - * A RenderingFormatter that works with UMD files, instead of `import` and `export` statements - * the module is an IIFE with a factory function call with dependencies, which are defined in a - * wrapper function for AMD, CommonJS and global module formats. - */ -export class UmdRenderingFormatter extends Esm5RenderingFormatter { - constructor(fs: PathManipulation, protected umdHost: UmdReflectionHost, isCore: boolean) { - super(fs, umdHost, isCore); - } - - /** - * Add the imports to the UMD module IIFE. - * - * Note that imports at "prepended" to the start of the parameter list of the factory function, - * and so also to the arguments passed to it when it is called. - * This is because there are scenarios where the factory function does not accept as many - * parameters as are passed as argument in the call. For example: - * - * ``` - * (function (global, factory) { - * typeof exports === 'object' && typeof module !== 'undefined' ? - * factory(exports,require('x'),require('z')) : - * typeof define === 'function' && define.amd ? - * define(['exports', 'x', 'z'], factory) : - * (global = global || self, factory(global.myBundle = {}, global.x)); - * }(this, (function (exports, x) { ... } - * ``` - * - * (See that the `z` import is not being used by the factory function.) - */ - override addImports(output: MagicString, imports: Import[], file: ts.SourceFile): void { - if (imports.length === 0) { - return; - } - - // Assume there is only one UMD module in the file - const umdModule = this.umdHost.getUmdModule(file); - if (!umdModule) { - return; - } - - const {factoryFn, factoryCalls} = umdModule; - - // We need to add new `require()` calls for each import in the CommonJS initializer - renderCommonJsDependencies(output, factoryCalls.commonJs, imports); - renderCommonJsDependencies(output, factoryCalls.commonJs2, imports); - renderAmdDependencies(output, factoryCalls.amdDefine, imports); - renderGlobalDependencies(output, factoryCalls.global, imports); - renderFactoryParameters(output, factoryFn, imports); - } - - /** - * Add the exports to the bottom of the UMD module factory function. - */ - override addExports( - output: MagicString, entryPointBasePath: string, exports: ExportInfo[], - importManager: ImportManager, file: ts.SourceFile): void { - const umdModule = this.umdHost.getUmdModule(file); - if (!umdModule) { - return; - } - const factoryFunction = umdModule.factoryFn; - const lastStatement = - factoryFunction.body.statements[factoryFunction.body.statements.length - 1]; - const insertionPoint = - lastStatement ? lastStatement.getEnd() : factoryFunction.body.getEnd() - 1; - exports.forEach(e => { - const basePath = stripExtension(e.from); - const relativePath = './' + this.fs.relative(this.fs.dirname(entryPointBasePath), basePath); - const namedImport = entryPointBasePath !== basePath ? - importManager.generateNamedImport(relativePath, e.identifier) : - {symbol: e.identifier, moduleImport: null}; - const importNamespace = namedImport.moduleImport ? `${namedImport.moduleImport.text}.` : ''; - const exportStr = `\nexports.${e.identifier} = ${importNamespace}${namedImport.symbol};`; - output.appendRight(insertionPoint, exportStr); - }); - } - - override addDirectExports( - output: MagicString, exports: Reexport[], importManager: ImportManager, - file: ts.SourceFile): void { - const umdModule = this.umdHost.getUmdModule(file); - if (!umdModule) { - return; - } - const factoryFunction = umdModule.factoryFn; - const lastStatement = - factoryFunction.body.statements[factoryFunction.body.statements.length - 1]; - const insertionPoint = - lastStatement ? lastStatement.getEnd() : factoryFunction.body.getEnd() - 1; - for (const e of exports) { - const namedImport = importManager.generateNamedImport(e.fromModule, e.symbolName); - const importNamespace = namedImport.moduleImport ? `${namedImport.moduleImport.text}.` : ''; - const exportStr = `\nexports.${e.asAlias} = ${importNamespace}${namedImport.symbol};`; - output.appendRight(insertionPoint, exportStr); - } - } - - /** - * Add the constants to the top of the UMD factory function. - */ - override addConstants(output: MagicString, constants: string, file: ts.SourceFile): void { - if (constants === '') { - return; - } - const umdModule = this.umdHost.getUmdModule(file); - if (!umdModule) { - return; - } - const factoryFunction = umdModule.factoryFn; - const firstStatement = factoryFunction.body.statements[0]; - const insertionPoint = - firstStatement ? firstStatement.getStart() : factoryFunction.body.getStart() + 1; - output.appendLeft(insertionPoint, '\n' + constants + '\n'); - } -} - -/** - * Add dependencies to the CommonJS/CommonJS2 part of the UMD wrapper function. - */ -function renderCommonJsDependencies( - output: MagicString, factoryCall: ts.CallExpression|null, imports: Import[]) { - if (factoryCall === null) { - return; - } - - const injectionPoint = factoryCall.arguments.length > 0 ? - // Add extra dependencies before the first argument - factoryCall.arguments[0].getFullStart() : - // Backup one char to account for the closing parenthesis on the call - factoryCall.getEnd() - 1; - const importString = imports.map(i => `require('${i.specifier}')`).join(','); - output.appendLeft(injectionPoint, importString + (factoryCall.arguments.length > 0 ? ',' : '')); -} - -/** - * Add dependencies to the AMD part of the UMD wrapper function. - */ -function renderAmdDependencies( - output: MagicString, amdDefineCall: ts.CallExpression|null, imports: Import[]) { - if (amdDefineCall === null) { - return; - } - - const importString = imports.map(i => `'${i.specifier}'`).join(','); - // The dependency array (if it exists) is the second to last argument - // `define(id?, dependencies?, factory);` - const factoryIndex = amdDefineCall.arguments.length - 1; - const dependencyArray = amdDefineCall.arguments[factoryIndex - 1]; - if (dependencyArray === undefined || !ts.isArrayLiteralExpression(dependencyArray)) { - // No array provided: `define(factory)` or `define(id, factory)`. - // Insert a new array in front the `factory` call. - const injectionPoint = amdDefineCall.arguments[factoryIndex].getFullStart(); - output.appendLeft(injectionPoint, `[${importString}],`); - } else { - // Already an array - const injectionPoint = dependencyArray.elements.length > 0 ? - // Add imports before the first item. - dependencyArray.elements[0].getFullStart() : - // Backup one char to account for the closing square bracket on the array - dependencyArray.getEnd() - 1; - output.appendLeft( - injectionPoint, importString + (dependencyArray.elements.length > 0 ? ',' : '')); - } -} - -/** - * Add dependencies to the global part of the UMD wrapper function. - */ -function renderGlobalDependencies( - output: MagicString, factoryCall: ts.CallExpression|null, imports: Import[]) { - if (factoryCall === null) { - return; - } - - const injectionPoint = factoryCall.arguments.length > 0 ? - // Add extra dependencies before the first argument - factoryCall.arguments[0].getFullStart() : - // Backup one char to account for the closing parenthesis on the call - factoryCall.getEnd() - 1; - const importString = imports.map(i => `global.${getGlobalIdentifier(i)}`).join(','); - output.appendLeft(injectionPoint, importString + (factoryCall.arguments.length > 0 ? ',' : '')); -} - -/** - * Add dependency parameters to the UMD factory function. - */ -function renderFactoryParameters( - output: MagicString, factoryFunction: ts.FunctionExpression, imports: Import[]) { - const parameters = factoryFunction.parameters; - const parameterString = imports.map(i => i.qualifier.text).join(','); - if (parameters.length > 0) { - const injectionPoint = parameters[0].getFullStart(); - output.appendLeft(injectionPoint, parameterString + ','); - } else { - // If there are no parameters then the factory function will look like: - // function () { ... } - // The AST does not give us a way to find the insertion point - between the two parentheses. - // So we must use a regular expression on the text of the function. - const injectionPoint = factoryFunction.getStart() + factoryFunction.getText().indexOf('()') + 1; - output.appendLeft(injectionPoint, parameterString); - } -} - -/** - * Compute a global identifier for the given import (`i`). - * - * The identifier used to access a package when using the "global" form of a UMD bundle usually - * follows a special format where snake-case is converted to camelCase and path separators are - * converted to dots. In addition there are special cases such as `@angular` is mapped to `ng`. - * - * For example - * - * * `@ns/package/entry-point` => `ns.package.entryPoint` - * * `@angular/common/testing` => `ng.common.testing` - * * `@angular/platform-browser-dynamic` => `ng.platformBrowserDynamic` - * - * It is possible for packages to specify completely different identifiers for attaching the package - * to the global, and so there is no guaranteed way to compute this. - * Currently, this approach appears to work for the known scenarios; also it is not known how common - * it is to use globals for importing packages. - * - * If it turns out that there are packages that are being used via globals, where this approach - * fails, we should consider implementing a configuration based solution, similar to what would go - * in a rollup configuration for mapping import paths to global identifiers. - */ -function getGlobalIdentifier(i: Import): string { - return i.specifier.replace(/^@angular\//, 'ng.') - .replace(/^@/, '') - .replace(/\//g, '.') - .replace(/[-_]+(.?)/g, (_, c) => c.toUpperCase()) - .replace(/^./, c => c.toLowerCase()); -} diff --git a/packages/compiler-cli/ngcc/src/rendering/utils.ts b/packages/compiler-cli/ngcc/src/rendering/utils.ts deleted file mode 100644 index a6eed19f155..00000000000 --- a/packages/compiler-cli/ngcc/src/rendering/utils.ts +++ /dev/null @@ -1,39 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import ts from 'typescript'; -import {AbsoluteFsPath} from '../../../src/ngtsc/file_system'; -import {ImportRewriter, NoopImportRewriter, R3SymbolsImportRewriter} from '../../../src/ngtsc/imports'; -import {NgccFlatImportRewriter} from './ngcc_import_rewriter'; - -/** - * Information about a file that has been rendered. - */ -export interface FileToWrite { - /** Path to where the file should be written. */ - path: AbsoluteFsPath; - /** The contents of the file to be be written. */ - contents: string; -} - -/** - * Create an appropriate ImportRewriter given the parameters. - */ -export function getImportRewriter( - r3SymbolsFile: ts.SourceFile|null, isCore: boolean, isFlat: boolean): ImportRewriter { - if (isCore && isFlat) { - return new NgccFlatImportRewriter(); - } else if (isCore) { - return new R3SymbolsImportRewriter(r3SymbolsFile!.fileName); - } else { - return new NoopImportRewriter(); - } -} - -export function stripExtension(filePath: T): T { - return filePath.replace(/\.(js|d\.ts)$/, '') as T; -} diff --git a/packages/compiler-cli/ngcc/src/utils.ts b/packages/compiler-cli/ngcc/src/utils.ts deleted file mode 100644 index 78794c0e180..00000000000 --- a/packages/compiler-cli/ngcc/src/utils.ts +++ /dev/null @@ -1,242 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import ts from 'typescript'; - -import {absoluteFrom, AbsoluteFsPath, isRooted, ReadonlyFileSystem} from '../../src/ngtsc/file_system'; -import {DeclarationNode, KnownDeclaration} from '../../src/ngtsc/reflection'; - -export type JsonPrimitive = string|number|boolean|null; -export type JsonValue = JsonPrimitive|JsonArray|JsonObject|undefined; -export interface JsonArray extends Array {} -export interface JsonObject { - [key: string]: JsonValue; -} - -/** - * A list (`Array`) of partially ordered `T` items. - * - * The items in the list are partially ordered in the sense that any element has either the same or - * higher precedence than any element which appears later in the list. What "higher precedence" - * means and how it is determined is implementation-dependent. - * - * See [PartiallyOrderedSet](https://en.wikipedia.org/wiki/Partially_ordered_set) for more details. - * (Refraining from using the term "set" here, to avoid confusion with JavaScript's - * [Set](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Set).) - * - * NOTE: A plain `Array` is not assignable to a `PartiallyOrderedList`, but a - * `PartiallyOrderedList` is assignable to an `Array`. - */ -export interface PartiallyOrderedList extends Array { - _partiallyOrdered: true; - - map(callbackfn: (value: T, index: number, array: PartiallyOrderedList) => U, thisArg?: any): - PartiallyOrderedList; - slice(...args: Parameters['slice']>): PartiallyOrderedList; -} - -export function getOriginalSymbol(checker: ts.TypeChecker): (symbol: ts.Symbol) => ts.Symbol { - return function(symbol: ts.Symbol) { - return ts.SymbolFlags.Alias & symbol.flags ? checker.getAliasedSymbol(symbol) : symbol; - }; -} - -export function isDefined(value: T|undefined|null): value is T { - return (value !== undefined) && (value !== null); -} - -export function getNameText(name: ts.PropertyName|ts.BindingName): string { - return ts.isIdentifier(name) || ts.isLiteralExpression(name) ? name.text : name.getText(); -} - -/** - * Does the given declaration have a name which is an identifier? - * @param declaration The declaration to test. - * @returns true if the declaration has an identifier for a name. - */ -export function hasNameIdentifier(declaration: ts.Node): declaration is DeclarationNode& - {name: ts.Identifier} { - const namedDeclaration: ts.Node&{name?: ts.Node} = declaration; - return namedDeclaration.name !== undefined && ts.isIdentifier(namedDeclaration.name); -} - -/** - * Test whether a path is "relative". - * - * Relative paths start with `/`, `./` or `../` (or the Windows equivalents); or are simply `.` or - * `..`. - */ -export function isRelativePath(path: string): boolean { - return isRooted(path) || /^\.\.?(\/|\\|$)/.test(path); -} - -/** - * A `Map`-like object that can compute and memoize a missing value for any key. - * - * The computed values are memoized, so the factory function is not called more than once per key. - * This is useful for storing values that are expensive to compute and may be used multiple times. - */ -// NOTE: -// Ideally, this class should extend `Map`, but that causes errors in ES5 transpiled code: -// `TypeError: Constructor Map requires 'new'` -export class FactoryMap { - private internalMap: Map; - - constructor(private factory: (key: K) => V, entries?: readonly(readonly[K, V])[]|null) { - this.internalMap = new Map(entries); - } - - get(key: K): V { - if (!this.internalMap.has(key)) { - this.internalMap.set(key, this.factory(key)); - } - - return this.internalMap.get(key)!; - } - - set(key: K, value: V): void { - this.internalMap.set(key, value); - } -} - -/** - * Attempt to resolve a `path` to a file by appending the provided `postFixes` - * to the `path` and checking if the file exists on disk. - * @returns An absolute path to the first matching existing file, or `null` if none exist. - */ -export function resolveFileWithPostfixes( - fs: ReadonlyFileSystem, path: AbsoluteFsPath, postFixes: string[]): AbsoluteFsPath|null { - for (const postFix of postFixes) { - const testPath = absoluteFrom(path + postFix); - if (fs.exists(testPath) && fs.stat(testPath).isFile()) { - return testPath; - } - } - return null; -} - -/** - * Determine whether a function declaration corresponds with a TypeScript helper function, returning - * its kind if so or null if the declaration does not seem to correspond with such a helper. - */ -export function getTsHelperFnFromDeclaration(decl: DeclarationNode): KnownDeclaration|null { - if (!ts.isFunctionDeclaration(decl) && !ts.isVariableDeclaration(decl)) { - return null; - } - - if (decl.name === undefined || !ts.isIdentifier(decl.name)) { - return null; - } - - return getTsHelperFnFromIdentifier(decl.name); -} - -/** - * Determine whether an identifier corresponds with a TypeScript helper function (based on its - * name), returning its kind if so or null if the identifier does not seem to correspond with such a - * helper. - */ -export function getTsHelperFnFromIdentifier(id: ts.Identifier): KnownDeclaration|null { - switch (stripDollarSuffix(id.text)) { - case '__assign': - return KnownDeclaration.TsHelperAssign; - case '__spread': - return KnownDeclaration.TsHelperSpread; - case '__spreadArrays': - return KnownDeclaration.TsHelperSpreadArrays; - case '__spreadArray': - return KnownDeclaration.TsHelperSpreadArray; - case '__read': - return KnownDeclaration.TsHelperRead; - default: - return null; - } -} - -/** - * An identifier may become repeated when bundling multiple source files into a single bundle, so - * bundlers have a strategy of suffixing non-unique identifiers with a suffix like $2. This function - * strips off such suffixes, so that ngcc deals with the canonical name of an identifier. - * @param value The value to strip any suffix of, if applicable. - * @returns The canonical representation of the value, without any suffix. - */ -export function stripDollarSuffix(value: string): string { - return value.replace(/\$\d+$/, ''); -} - -export function stripExtension(fileName: string): string { - return fileName.replace(/\..+$/, ''); -} - -/** - * Parse the JSON from a `package.json` file. - * - * @param packageJsonPath The absolute path to the `package.json` file. - * @returns JSON from the `package.json` file if it exists and is valid, `null` otherwise. - */ -export function loadJson( - fs: ReadonlyFileSystem, packageJsonPath: AbsoluteFsPath): T|null { - try { - return JSON.parse(fs.readFile(packageJsonPath)) as T; - } catch { - return null; - } -} - -/** - * Given the parsed JSON of a `package.json` file, try to extract info for a secondary entry-point - * from the `exports` property. Such info will only be present for packages following Angular - * Package Format v14+. - * - * @param primaryPackageJson The parsed JSON of the primary `package.json` (or `null` if it failed - * to be loaded). - * @param packagePath The absolute path to the containing npm package. - * @param entryPointPath The absolute path to the secondary entry-point. - * @returns The `exports` info for the specified entry-point if it exists, `null` otherwise. - */ -export function loadSecondaryEntryPointInfoForApfV14( - fs: ReadonlyFileSystem, primaryPackageJson: JsonObject|null, packagePath: AbsoluteFsPath, - entryPointPath: AbsoluteFsPath): JsonObject|null { - // Check if primary `package.json` has been loaded and has an `exports` property that is an - // object. - const exportMap = primaryPackageJson?.exports; - if (!isExportObject(exportMap)) { - return null; - } - - // Find the `exports` key for the secondary entry-point. - const relativeEntryPointPath = fs.relative(packagePath, entryPointPath); - const entryPointExportKey = `./${relativeEntryPointPath}`; - - // Read the info for the entry-point. - const entryPointInfo = exportMap[entryPointExportKey]; - - // Check whether the entry-point info exists and is an export map. - return isExportObject(entryPointInfo) ? entryPointInfo : null; -} - -/** - * Check whether a value read from a JSON file is a Node.js export map (either the top-level one or - * one for a subpath). - * - * In `package.json` files, the `exports` field can be of type `Object | string | string[]`, but APF - * v14+ uses an object with subpath exports for each entry-point, which in turn are conditional - * exports (see references below). This function verifies that a value read from the top-level - * `exports` field or a subpath is of type `Object` (and not `string` or `string[]`). - * - * References: - * - https://nodejs.org/api/packages.html#exports - * - https://nodejs.org/api/packages.html#subpath-exports - * - https://nodejs.org/api/packages.html#conditional-exports - * - https://v14.angular.io/guide/angular-package-format#exports - * - * @param thing The value read from the JSON file - * @returns True if the value is an `Object` (and not an `Array`). - */ -function isExportObject(thing: JsonValue): thing is JsonObject { - return (typeof thing === 'object') && (thing !== null) && !Array.isArray(thing); -} diff --git a/packages/compiler-cli/ngcc/src/writing/cleaning/cleaning_strategies.ts b/packages/compiler-cli/ngcc/src/writing/cleaning/cleaning_strategies.ts deleted file mode 100644 index b7a778ee482..00000000000 --- a/packages/compiler-cli/ngcc/src/writing/cleaning/cleaning_strategies.ts +++ /dev/null @@ -1,67 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import {absoluteFrom, AbsoluteFsPath, FileSystem, PathSegment} from '../../../../src/ngtsc/file_system'; -import {cleanPackageJson} from '../../packages/build_marker'; -import {EntryPointPackageJson} from '../../packages/entry_point'; -import {NGCC_BACKUP_EXTENSION} from '../in_place_file_writer'; -import {NGCC_DIRECTORY} from '../new_entry_point_file_writer'; - -import {isLocalDirectory} from './utils'; - -/** - * Implement this interface to extend the cleaning strategies of the `PackageCleaner`. - */ -export interface CleaningStrategy { - canClean(path: AbsoluteFsPath, basename: PathSegment): boolean; - clean(path: AbsoluteFsPath, basename: PathSegment): void; -} - -/** - * A CleaningStrategy that reverts changes to package.json files by removing the build marker and - * other properties. - */ -export class PackageJsonCleaner implements CleaningStrategy { - constructor(private fs: FileSystem) {} - canClean(_path: AbsoluteFsPath, basename: PathSegment): boolean { - return basename === 'package.json'; - } - clean(path: AbsoluteFsPath, _basename: PathSegment): void { - const packageJson = JSON.parse(this.fs.readFile(path)) as EntryPointPackageJson; - if (cleanPackageJson(packageJson)) { - this.fs.writeFile(path, `${JSON.stringify(packageJson, null, 2)}\n`); - } - } -} - -/** - * A CleaningStrategy that removes the extra directory containing generated entry-point formats. - */ -export class NgccDirectoryCleaner implements CleaningStrategy { - constructor(private fs: FileSystem) {} - canClean(path: AbsoluteFsPath, basename: PathSegment): boolean { - return basename === NGCC_DIRECTORY && isLocalDirectory(this.fs, path); - } - clean(path: AbsoluteFsPath, _basename: PathSegment): void { - this.fs.removeDeep(path); - } -} - -/** - * A CleaningStrategy that reverts files that were overwritten and removes the backup files that - * ngcc created. - */ -export class BackupFileCleaner implements CleaningStrategy { - constructor(private fs: FileSystem) {} - canClean(path: AbsoluteFsPath, basename: PathSegment): boolean { - return this.fs.extname(basename) === NGCC_BACKUP_EXTENSION && - this.fs.exists(absoluteFrom(path.replace(NGCC_BACKUP_EXTENSION, ''))); - } - clean(path: AbsoluteFsPath, _basename: PathSegment): void { - this.fs.moveFile(path, absoluteFrom(path.replace(NGCC_BACKUP_EXTENSION, ''))); - } -} diff --git a/packages/compiler-cli/ngcc/src/writing/cleaning/package_cleaner.ts b/packages/compiler-cli/ngcc/src/writing/cleaning/package_cleaner.ts deleted file mode 100644 index 5b3321af2a4..00000000000 --- a/packages/compiler-cli/ngcc/src/writing/cleaning/package_cleaner.ts +++ /dev/null @@ -1,80 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import {AbsoluteFsPath, FileSystem, ReadonlyFileSystem} from '../../../../src/ngtsc/file_system'; -import {needsCleaning} from '../../packages/build_marker'; -import {EntryPoint} from '../../packages/entry_point'; - -import {BackupFileCleaner, CleaningStrategy, NgccDirectoryCleaner, PackageJsonCleaner} from './cleaning_strategies'; -import {isLocalDirectory} from './utils'; - -/** - * A class that can clean ngcc artifacts from a directory. - */ -export class PackageCleaner { - constructor(private fs: ReadonlyFileSystem, private cleaners: CleaningStrategy[]) {} - - /** - * Recurse through the file-system cleaning files and directories as determined by the configured - * cleaning-strategies. - * - * @param directory the current directory to clean - */ - clean(directory: AbsoluteFsPath) { - const basenames = this.fs.readdir(directory); - for (const basename of basenames) { - if (basename === 'node_modules') { - continue; - } - - const path = this.fs.resolve(directory, basename); - for (const cleaner of this.cleaners) { - if (cleaner.canClean(path, basename)) { - cleaner.clean(path, basename); - break; - } - } - // Recurse into subdirectories (note that a cleaner may have removed this path) - if (isLocalDirectory(this.fs, path)) { - this.clean(path); - } - } - } -} - - -/** - * Iterate through the given `entryPoints` identifying the package for each that has at least one - * outdated processed format, then cleaning those packages. - * - * Note that we have to clean entire packages because there is no clear file-system boundary - * between entry-points within a package. So if one entry-point is outdated we have to clean - * everything within that package. - * - * @param fileSystem the current file-system - * @param entryPoints the entry-points that have been collected for this run of ngcc - * @returns true if packages needed to be cleaned. - */ -export function cleanOutdatedPackages(fileSystem: FileSystem, entryPoints: EntryPoint[]): boolean { - const packagesToClean = new Set(); - for (const entryPoint of entryPoints) { - if (needsCleaning(entryPoint.packageJson)) { - packagesToClean.add(entryPoint.packagePath); - } - } - - const cleaner = new PackageCleaner(fileSystem, [ - new PackageJsonCleaner(fileSystem), - new NgccDirectoryCleaner(fileSystem), - new BackupFileCleaner(fileSystem), - ]); - for (const packagePath of packagesToClean) { - cleaner.clean(packagePath); - } - - return packagesToClean.size > 0; -} diff --git a/packages/compiler-cli/ngcc/src/writing/cleaning/utils.ts b/packages/compiler-cli/ngcc/src/writing/cleaning/utils.ts deleted file mode 100644 index 8d36f84637b..00000000000 --- a/packages/compiler-cli/ngcc/src/writing/cleaning/utils.ts +++ /dev/null @@ -1,23 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import {AbsoluteFsPath, ReadonlyFileSystem} from '../../../../src/ngtsc/file_system'; - -/** - * Returns true if the given `path` is a directory (not a symlink) and actually exists. - * - * @param fs the current filesystem - * @param path the path to check - */ -export function isLocalDirectory(fs: ReadonlyFileSystem, path: AbsoluteFsPath): boolean { - if (fs.exists(path)) { - const stat = fs.lstat(path); - return stat.isDirectory(); - } else { - return false; - } -} diff --git a/packages/compiler-cli/ngcc/src/writing/file_writer.ts b/packages/compiler-cli/ngcc/src/writing/file_writer.ts deleted file mode 100644 index 859776a690f..00000000000 --- a/packages/compiler-cli/ngcc/src/writing/file_writer.ts +++ /dev/null @@ -1,35 +0,0 @@ - -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import {AbsoluteFsPath} from '../../../src/ngtsc/file_system'; -import {EntryPoint, EntryPointJsonProperty} from '../packages/entry_point'; -import {EntryPointBundle} from '../packages/entry_point_bundle'; -import {FileToWrite} from '../rendering/utils'; - -/** - * Responsible for writing out the transformed files to disk. - */ -export interface FileWriter { - writeBundle( - bundle: EntryPointBundle, transformedFiles: FileToWrite[], - formatProperties: EntryPointJsonProperty[]): void; - - /** - * Revert the changes to an entry-point processed for the specified format-properties by the same - * `FileWriter` implementation. - * - * @param entryPoint The entry-point to revert. - * @param transformedFilePaths The original paths of the transformed files. (The transformed files - * may be written at the same or a different location, depending on the `FileWriter` - * implementation.) - * @param formatProperties The format-properties pointing to the entry-point. - */ - revertBundle( - entryPoint: EntryPoint, transformedFilePaths: AbsoluteFsPath[], - formatProperties: EntryPointJsonProperty[]): void; -} diff --git a/packages/compiler-cli/ngcc/src/writing/in_place_file_writer.ts b/packages/compiler-cli/ngcc/src/writing/in_place_file_writer.ts deleted file mode 100644 index 98564638c9a..00000000000 --- a/packages/compiler-cli/ngcc/src/writing/in_place_file_writer.ts +++ /dev/null @@ -1,77 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import {absoluteFrom, AbsoluteFsPath, dirname, FileSystem} from '../../../src/ngtsc/file_system'; -import {Logger} from '../../../src/ngtsc/logging'; -import {EntryPoint, EntryPointJsonProperty} from '../packages/entry_point'; -import {EntryPointBundle} from '../packages/entry_point_bundle'; -import {FileToWrite} from '../rendering/utils'; - -import {FileWriter} from './file_writer'; - -export const NGCC_BACKUP_EXTENSION = '.__ivy_ngcc_bak'; -/** - * This FileWriter overwrites the transformed file, in-place, while creating - * a back-up of the original file with an extra `.__ivy_ngcc_bak` extension. - */ -export class InPlaceFileWriter implements FileWriter { - constructor( - protected fs: FileSystem, protected logger: Logger, - protected errorOnFailedEntryPoint: boolean) {} - - writeBundle( - _bundle: EntryPointBundle, transformedFiles: FileToWrite[], - _formatProperties?: EntryPointJsonProperty[]) { - transformedFiles.forEach(file => this.writeFileAndBackup(file)); - } - - revertBundle( - _entryPoint: EntryPoint, transformedFilePaths: AbsoluteFsPath[], - _formatProperties: EntryPointJsonProperty[]): void { - for (const filePath of transformedFilePaths) { - this.revertFileAndBackup(filePath); - } - } - - protected writeFileAndBackup(file: FileToWrite): void { - this.fs.ensureDir(dirname(file.path)); - const backPath = absoluteFrom(`${file.path}${NGCC_BACKUP_EXTENSION}`); - if (this.fs.exists(backPath)) { - if (this.errorOnFailedEntryPoint) { - throw new Error( - `Tried to overwrite ${backPath} with an ngcc back up file, which is disallowed.`); - } else { - this.logger.error( - `Tried to write ${ - backPath} with an ngcc back up file but it already exists so not writing, nor backing up, ${ - file.path}.\n` + - `This error may be caused by one of the following:\n` + - `* two or more entry-points overlap and ngcc has been asked to process some files more than once.\n` + - ` In this case, you should check other entry-points in this package\n` + - ` and set up a config to ignore any that you are not using.\n` + - `* a previous run of ngcc was killed in the middle of processing, in a way that cannot be recovered.\n` + - ` In this case, you should try cleaning the node_modules directory and any dist directories that contain local libraries. Then try again.`); - } - } else { - if (this.fs.exists(file.path)) { - this.fs.moveFile(file.path, backPath); - } - this.fs.writeFile(file.path, file.contents); - } - } - - protected revertFileAndBackup(filePath: AbsoluteFsPath): void { - if (this.fs.exists(filePath)) { - this.fs.removeFile(filePath); - - const backPath = absoluteFrom(`${filePath}${NGCC_BACKUP_EXTENSION}`); - if (this.fs.exists(backPath)) { - this.fs.moveFile(backPath, filePath); - } - } - } -} diff --git a/packages/compiler-cli/ngcc/src/writing/new_entry_point_file_writer.ts b/packages/compiler-cli/ngcc/src/writing/new_entry_point_file_writer.ts deleted file mode 100644 index cf5cc3e0581..00000000000 --- a/packages/compiler-cli/ngcc/src/writing/new_entry_point_file_writer.ts +++ /dev/null @@ -1,201 +0,0 @@ - -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import {absoluteFromSourceFile, AbsoluteFsPath, FileSystem, isLocalRelativePath} from '../../../src/ngtsc/file_system'; -import {Logger} from '../../../src/ngtsc/logging'; -import {isDtsPath} from '../../../src/ngtsc/util/src/typescript'; -import {EntryPoint, EntryPointJsonProperty} from '../packages/entry_point'; -import {EntryPointBundle} from '../packages/entry_point_bundle'; -import {FileToWrite} from '../rendering/utils'; - -import {InPlaceFileWriter} from './in_place_file_writer'; -import {PackageJsonUpdater} from './package_json_updater'; - -export const NGCC_DIRECTORY = '__ivy_ngcc__'; -export const NGCC_PROPERTY_EXTENSION = '_ivy_ngcc'; - -/** - * This FileWriter creates a copy of the original entry-point, then writes the transformed - * files onto the files in this copy, and finally updates the package.json with a new - * entry-point format property that points to this new entry-point. - * - * If there are transformed typings files in this bundle, they are updated in-place (see the - * `InPlaceFileWriter`). - */ -export class NewEntryPointFileWriter extends InPlaceFileWriter { - constructor( - fs: FileSystem, logger: Logger, errorOnFailedEntryPoint: boolean, - private pkgJsonUpdater: PackageJsonUpdater) { - super(fs, logger, errorOnFailedEntryPoint); - } - - override writeBundle( - bundle: EntryPointBundle, transformedFiles: FileToWrite[], - formatProperties: EntryPointJsonProperty[]) { - // The new folder is at the root of the overall package - const entryPoint = bundle.entryPoint; - const ngccFolder = this.fs.join(entryPoint.packagePath, NGCC_DIRECTORY); - this.copyBundle(bundle, entryPoint.packagePath, ngccFolder, transformedFiles); - transformedFiles.forEach(file => this.writeFile(file, entryPoint.packagePath, ngccFolder)); - this.updatePackageJson(entryPoint, formatProperties, ngccFolder); - } - - override revertBundle( - entryPoint: EntryPoint, transformedFilePaths: AbsoluteFsPath[], - formatProperties: EntryPointJsonProperty[]): void { - // IMPLEMENTATION NOTE: - // - // The changes made by `copyBundle()` are not reverted here. The non-transformed copied files - // are identical to the original ones and they will be overwritten when re-processing the - // entry-point anyway. - // - // This way, we avoid the overhead of having to inform the master process about all source files - // being copied in `copyBundle()`. - - // Revert the transformed files. - for (const filePath of transformedFilePaths) { - this.revertFile(filePath, entryPoint.packagePath); - } - - // Revert any changes to `package.json`. - this.revertPackageJson(entryPoint, formatProperties); - } - - protected copyBundle( - bundle: EntryPointBundle, packagePath: AbsoluteFsPath, ngccFolder: AbsoluteFsPath, - transformedFiles: FileToWrite[]) { - const doNotCopy = new Set(transformedFiles.map(f => f.path)); - bundle.src.program.getSourceFiles().forEach(sourceFile => { - const originalPath = absoluteFromSourceFile(sourceFile); - if (doNotCopy.has(originalPath)) { - return; - } - const relativePath = this.fs.relative(packagePath, originalPath); - const isInsidePackage = isLocalRelativePath(relativePath); - if (!sourceFile.isDeclarationFile && isInsidePackage) { - const newPath = this.fs.resolve(ngccFolder, relativePath); - this.fs.ensureDir(this.fs.dirname(newPath)); - this.fs.copyFile(originalPath, newPath); - this.copyAndUpdateSourceMap(originalPath, newPath); - } - }); - } - - /** - * If a source file has an associated source-map, then copy this, while updating its sourceRoot - * accordingly. - * - * For now don't try to parse the source for inline source-maps or external source-map links, - * since that is more complex and will slow ngcc down. - * Instead just check for a source-map file residing next to the source file, which is by far - * the most common case. - * - * @param originalSrcPath absolute path to the original source file being copied. - * @param newSrcPath absolute path to where the source will be written. - */ - protected copyAndUpdateSourceMap(originalSrcPath: AbsoluteFsPath, newSrcPath: AbsoluteFsPath): - void { - const sourceMapPath = (originalSrcPath + '.map') as AbsoluteFsPath; - if (this.fs.exists(sourceMapPath)) { - try { - const sourceMap = - JSON.parse(this.fs.readFile(sourceMapPath)) as {sourceRoot: string, [key: string]: any}; - const newSourceMapPath = (newSrcPath + '.map') as AbsoluteFsPath; - const relativePath = - this.fs.relative(this.fs.dirname(newSourceMapPath), this.fs.dirname(sourceMapPath)); - sourceMap.sourceRoot = this.fs.join(relativePath, sourceMap.sourceRoot || '.'); - this.fs.ensureDir(this.fs.dirname(newSourceMapPath)); - this.fs.writeFile(newSourceMapPath, JSON.stringify(sourceMap)); - } catch (e) { - this.logger.warn(`Failed to process source-map at ${sourceMapPath}`); - this.logger.warn((e as Error).message ?? e); - } - } - } - - protected writeFile(file: FileToWrite, packagePath: AbsoluteFsPath, ngccFolder: AbsoluteFsPath): - void { - if (isDtsPath(file.path.replace(/\.map$/, ''))) { - // This is either `.d.ts` or `.d.ts.map` file - super.writeFileAndBackup(file); - } else { - const relativePath = this.fs.relative(packagePath, file.path); - const newFilePath = this.fs.resolve(ngccFolder, relativePath); - this.fs.ensureDir(this.fs.dirname(newFilePath)); - this.fs.writeFile(newFilePath, file.contents); - } - } - - protected revertFile(filePath: AbsoluteFsPath, packagePath: AbsoluteFsPath): void { - if (isDtsPath(filePath.replace(/\.map$/, ''))) { - // This is either `.d.ts` or `.d.ts.map` file - super.revertFileAndBackup(filePath); - } else if (this.fs.exists(filePath)) { - const relativePath = this.fs.relative(packagePath, filePath); - const newFilePath = this.fs.resolve(packagePath, NGCC_DIRECTORY, relativePath); - this.fs.removeFile(newFilePath); - } - } - - protected updatePackageJson( - entryPoint: EntryPoint, formatProperties: EntryPointJsonProperty[], - ngccFolder: AbsoluteFsPath) { - if (formatProperties.length === 0) { - // No format properties need updating. - return; - } - - const packageJson = entryPoint.packageJson; - const packageJsonPath = this.fs.join(entryPoint.path, 'package.json'); - - // All format properties point to the same format-path. - const oldFormatProp = formatProperties[0]!; - const oldFormatPath = packageJson[oldFormatProp]!; - const oldAbsFormatPath = this.fs.resolve(entryPoint.path, oldFormatPath); - const newAbsFormatPath = - this.fs.resolve(ngccFolder, this.fs.relative(entryPoint.packagePath, oldAbsFormatPath)); - const newFormatPath = this.fs.relative(entryPoint.path, newAbsFormatPath); - - // Update all properties in `package.json` (both in memory and on disk). - const update = this.pkgJsonUpdater.createUpdate(); - - for (const formatProperty of formatProperties) { - if (packageJson[formatProperty] !== oldFormatPath) { - throw new Error( - `Unable to update '${packageJsonPath}': Format properties ` + - `(${formatProperties.join(', ')}) map to more than one format-path.`); - } - - update.addChange( - [`${formatProperty}${NGCC_PROPERTY_EXTENSION}`], newFormatPath, {before: formatProperty}); - } - - update.writeChanges(packageJsonPath, packageJson); - } - - protected revertPackageJson(entryPoint: EntryPoint, formatProperties: EntryPointJsonProperty[]) { - if (formatProperties.length === 0) { - // No format properties need reverting. - return; - } - - const packageJson = entryPoint.packageJson; - const packageJsonPath = this.fs.join(entryPoint.path, 'package.json'); - - // Revert all properties in `package.json` (both in memory and on disk). - // Since `updatePackageJson()` only adds properties, it is safe to just remove them (if they - // exist). - const update = this.pkgJsonUpdater.createUpdate(); - - for (const formatProperty of formatProperties) { - update.addChange([`${formatProperty}${NGCC_PROPERTY_EXTENSION}`], undefined); - } - - update.writeChanges(packageJsonPath, packageJson); - } -} diff --git a/packages/compiler-cli/ngcc/src/writing/package_json_updater.ts b/packages/compiler-cli/ngcc/src/writing/package_json_updater.ts deleted file mode 100644 index 4ced59f580d..00000000000 --- a/packages/compiler-cli/ngcc/src/writing/package_json_updater.ts +++ /dev/null @@ -1,226 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ - -import {AbsoluteFsPath, dirname, FileSystem} from '../../../src/ngtsc/file_system'; -import {JsonObject, JsonValue} from '../utils'; - - -export type PackageJsonChange = [string[], JsonValue, PackageJsonPropertyPositioning]; -export type PackageJsonPropertyPositioning = 'unimportant'|'alphabetic'|{before: string}; -export type WritePackageJsonChangesFn = - (changes: PackageJsonChange[], packageJsonPath: AbsoluteFsPath, parsedJson?: JsonObject) => - void; - -/** - * A utility object that can be used to safely update values in a `package.json` file. - * - * Example usage: - * ```ts - * const updatePackageJson = packageJsonUpdater - * .createUpdate() - * .addChange(['name'], 'package-foo') - * .addChange(['scripts', 'foo'], 'echo FOOOO...', 'unimportant') - * .addChange(['dependencies', 'baz'], '1.0.0', 'alphabetic') - * .addChange(['dependencies', 'bar'], '2.0.0', {before: 'baz'}) - * .writeChanges('/foo/package.json'); - * // or - * // .writeChanges('/foo/package.json', inMemoryParsedJson); - * ``` - */ -export interface PackageJsonUpdater { - /** - * Create a `PackageJsonUpdate` object, which provides a fluent API for batching updates to a - * `package.json` file. (Batching the updates is useful, because it avoids unnecessary I/O - * operations.) - */ - createUpdate(): PackageJsonUpdate; - - /** - * Write a set of changes to the specified `package.json` file (and optionally a pre-existing, - * in-memory representation of it). - * - * @param changes The set of changes to apply. - * @param packageJsonPath The path to the `package.json` file that needs to be updated. - * @param parsedJson A pre-existing, in-memory representation of the `package.json` file that - * needs to be updated as well. - */ - writeChanges( - changes: PackageJsonChange[], packageJsonPath: AbsoluteFsPath, parsedJson?: JsonObject): void; -} - -/** - * A utility class providing a fluent API for recording multiple changes to a `package.json` file - * (and optionally its in-memory parsed representation). - * - * NOTE: This class should generally not be instantiated directly; instances are implicitly created - * via `PackageJsonUpdater#createUpdate()`. - */ -export class PackageJsonUpdate { - private changes: PackageJsonChange[] = []; - private applied = false; - - constructor(private writeChangesImpl: WritePackageJsonChangesFn) {} - - /** - * Record a change to a `package.json` property. - * - * If the ancestor objects do not yet exist in the `package.json` file, they will be created. The - * positioning of the property can also be specified. (If the property already exists, it will be - * moved accordingly.) - * - * NOTE: Property positioning is only guaranteed to be respected in the serialized `package.json` - * file. Positioning will not be taken into account when updating in-memory representations. - * - * NOTE 2: Property positioning only affects the last property in `propertyPath`. Ancestor - * objects' positioning will not be affected. - * - * @param propertyPath The path of a (possibly nested) property to add/update. - * @param value The new value to set the property to. - * @param position The desired position for the added/updated property. - */ - addChange( - propertyPath: string[], value: JsonValue, - positioning: PackageJsonPropertyPositioning = 'unimportant'): this { - this.ensureNotApplied(); - this.changes.push([propertyPath, value, positioning]); - return this; - } - - /** - * Write the recorded changes to the associated `package.json` file (and optionally a - * pre-existing, in-memory representation of it). - * - * @param packageJsonPath The path to the `package.json` file that needs to be updated. - * @param parsedJson A pre-existing, in-memory representation of the `package.json` file that - * needs to be updated as well. - */ - writeChanges(packageJsonPath: AbsoluteFsPath, parsedJson?: JsonObject): void { - this.ensureNotApplied(); - this.ensureNotSynthesized(parsedJson); - this.writeChangesImpl(this.changes, packageJsonPath, parsedJson); - this.applied = true; - } - - private ensureNotApplied() { - if (this.applied) { - throw new Error('Trying to apply a `PackageJsonUpdate` that has already been applied.'); - } - } - - private ensureNotSynthesized(parsedJson?: JsonObject) { - if (parsedJson?.synthesized) { - // Theoretically, this should never happen, because synthesized `package.json` files should - // only be created for libraries following the Angular Package Format v14+, which means they - // should already be in Ivy format and not require processing by `ngcc`. - throw new Error('Trying to update a non-existent (synthesized) `package.json` file.'); - } - } -} - -/** A `PackageJsonUpdater` that writes directly to the file-system. */ -export class DirectPackageJsonUpdater implements PackageJsonUpdater { - constructor(private fs: FileSystem) {} - - createUpdate(): PackageJsonUpdate { - return new PackageJsonUpdate((...args) => this.writeChanges(...args)); - } - - writeChanges( - changes: PackageJsonChange[], packageJsonPath: AbsoluteFsPath, - preExistingParsedJson?: JsonObject): void { - if (changes.length === 0) { - throw new Error(`No changes to write to '${packageJsonPath}'.`); - } - - // Read and parse the `package.json` content. - // NOTE: We are not using `preExistingParsedJson` (even if specified) to avoid corrupting the - // content on disk in case `preExistingParsedJson` is outdated. - const parsedJson = this.fs.exists(packageJsonPath) ? - JSON.parse(this.fs.readFile(packageJsonPath)) as JsonObject : - {}; - - // Apply all changes to both the canonical representation (read from disk) and any pre-existing, - // in-memory representation. - for (const [propPath, value, positioning] of changes) { - if (propPath.length === 0) { - throw new Error(`Missing property path for writing value to '${packageJsonPath}'.`); - } - - applyChange(parsedJson, propPath, value, positioning); - - if (preExistingParsedJson) { - // No need to take property positioning into account for in-memory representations. - applyChange(preExistingParsedJson, propPath, value, 'unimportant'); - } - } - - // Ensure the containing directory exists (in case this is a synthesized `package.json` due to a - // custom configuration) and write the updated content to disk. - this.fs.ensureDir(dirname(packageJsonPath)); - this.fs.writeFile(packageJsonPath, `${JSON.stringify(parsedJson, null, 2)}\n`); - } -} - -// Helpers -export function applyChange( - ctx: JsonObject, propPath: string[], value: JsonValue, - positioning: PackageJsonPropertyPositioning): void { - const lastPropIdx = propPath.length - 1; - const lastProp = propPath[lastPropIdx]; - - for (let i = 0; i < lastPropIdx; i++) { - const key = propPath[i]; - const newCtx = ctx.hasOwnProperty(key) ? ctx[key] : (ctx[key] = {}); - - if ((typeof newCtx !== 'object') || (newCtx === null) || Array.isArray(newCtx)) { - throw new Error(`Property path '${propPath.join('.')}' does not point to an object.`); - } - - ctx = newCtx; - } - - ctx[lastProp] = value; - positionProperty(ctx, lastProp, positioning); -} - -function movePropBefore(ctx: JsonObject, prop: string, isNextProp: (p: string) => boolean): void { - const allProps = Object.keys(ctx); - const otherProps = allProps.filter(p => p !== prop); - const nextPropIdx = otherProps.findIndex(isNextProp); - const propsToShift = (nextPropIdx === -1) ? [] : otherProps.slice(nextPropIdx); - - movePropToEnd(ctx, prop); - propsToShift.forEach(p => movePropToEnd(ctx, p)); -} - -function movePropToEnd(ctx: JsonObject, prop: string): void { - const value = ctx[prop]; - delete ctx[prop]; - ctx[prop] = value; -} - -function positionProperty( - ctx: JsonObject, prop: string, positioning: PackageJsonPropertyPositioning): void { - switch (positioning) { - case 'alphabetic': - movePropBefore(ctx, prop, p => p > prop); - break; - case 'unimportant': - // Leave the property order unchanged; i.e. newly added properties will be last and existing - // ones will remain in their old position. - break; - default: - if ((typeof positioning !== 'object') || (positioning.before === undefined)) { - throw new Error( - `Unknown positioning (${JSON.stringify(positioning)}) for property '${prop}'.`); - } - - movePropBefore(ctx, prop, p => p === positioning.before); - break; - } -} diff --git a/packages/compiler-cli/ngcc/test/BUILD.bazel b/packages/compiler-cli/ngcc/test/BUILD.bazel deleted file mode 100644 index 092708deeb4..00000000000 --- a/packages/compiler-cli/ngcc/test/BUILD.bazel +++ /dev/null @@ -1,107 +0,0 @@ -load("@npm//@babel/cli:index.bzl", "babel") -load("//tools:defaults.bzl", "jasmine_node_test", "ts_library") - -package(default_visibility = ["//visibility:public"]) - -ts_library( - name = "test_lib", - testonly = True, - srcs = glob( - ["**/*.ts"], - exclude = ["integration/**/*.ts"], - ), - deps = [ - "//packages/compiler", - "//packages/compiler-cli", - "//packages/compiler-cli/ngcc", - "//packages/compiler-cli/ngcc/test/helpers", - "//packages/compiler-cli/src/ngtsc/diagnostics", - "//packages/compiler-cli/src/ngtsc/file_system", - "//packages/compiler-cli/src/ngtsc/file_system/testing", - "//packages/compiler-cli/src/ngtsc/imports", - "//packages/compiler-cli/src/ngtsc/incremental/semantic_graph", - "//packages/compiler-cli/src/ngtsc/logging/testing", - "//packages/compiler-cli/src/ngtsc/partial_evaluator", - "//packages/compiler-cli/src/ngtsc/reflection", - "//packages/compiler-cli/src/ngtsc/sourcemaps", - "//packages/compiler-cli/src/ngtsc/testing", - "//packages/compiler-cli/src/ngtsc/transform", - "//packages/compiler-cli/src/ngtsc/translator", - "@npm//@jridgewell/sourcemap-codec", - "@npm//@types/convert-source-map", - "@npm//@types/yargs", - "@npm//dependency-graph", - "@npm//magic-string", - "@npm//typescript", - ], -) - -jasmine_node_test( - name = "test", - bootstrap = ["//tools/testing:node_no_angular"], - data = [ - "//packages/compiler-cli/src/ngtsc/testing/fake_core:npm_package", - ], - deps = [ - ":test_lib", - ], -) - -ts_library( - name = "integration_lib", - testonly = True, - srcs = glob( - ["integration/**/*.ts"], - ), - deps = [ - "//packages/compiler-cli/ngcc", - "//packages/compiler-cli/ngcc/test/helpers", - "//packages/compiler-cli/src/ngtsc/file_system", - "//packages/compiler-cli/src/ngtsc/file_system/testing", - "//packages/compiler-cli/src/ngtsc/logging/testing", - "//packages/compiler-cli/src/ngtsc/testing", - "@npm//@bazel/runfiles", - "@npm//rxjs", - "@npm//typescript", - ], -) - -# As of version 10, the release packages do not contain esm5 output anymore. The ngcc integration -# tests intend to test ES5 features though, so we downlevel the flat esm2015 file to ES5 using -# Babel. We can then link that into the mock file system as if the Angular core package is still -# built with previous APF versions where esm5 output was shipped. This allows us to ensure that ngcc -# properly processes libraries with esm5 output. -# **Note**: We are using Babel instead of `tsc` as TypeScript does not allow us to downlevel the -# file without setting the module resolution to either `amd` or `system`. We want to preserve ES -# modules. -babel( - name = "fesm5_angular_core", - outs = ["fesm5_angular_core.js"], - args = [ - "$(execpath @npm//:node_modules/@angular/core-12/fesm2015/core.js)", - "--presets @babel/preset-env", - "--out-file $(execpath fesm5_angular_core.js)", - ], - data = [ - "@npm//:node_modules/@angular/core-12/fesm2015/core.js", - "@npm//@babel/preset-env", - ], -) - -jasmine_node_test( - name = "integration", - timeout = "long", - bootstrap = ["//tools/testing:node_no_angular"], - data = [ - ":fesm5_angular_core", - "@npm//@angular/common-12", - "@npm//@angular/core-12", - "@npm//rxjs", - ], - shard_count = 4, - tags = [], - deps = [ - ":integration_lib", - "@npm//convert-source-map", - ], -) diff --git a/packages/compiler-cli/ngcc/test/analysis/decoration_analyzer_spec.ts b/packages/compiler-cli/ngcc/test/analysis/decoration_analyzer_spec.ts deleted file mode 100644 index 0d2a77305f8..00000000000 --- a/packages/compiler-cli/ngcc/test/analysis/decoration_analyzer_spec.ts +++ /dev/null @@ -1,487 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import ts from 'typescript'; - -import {FatalDiagnosticError, makeDiagnostic} from '../../../src/ngtsc/diagnostics'; -import {absoluteFrom, getFileSystem, getSourceFileOrError} from '../../../src/ngtsc/file_system'; -import {runInEachFileSystem, TestFile} from '../../../src/ngtsc/file_system/testing'; -import {SemanticSymbol} from '../../../src/ngtsc/incremental/semantic_graph'; -import {MockLogger} from '../../../src/ngtsc/logging/testing'; -import {ClassDeclaration, DeclarationNode, Decorator} from '../../../src/ngtsc/reflection'; -import {loadFakeCore, loadTestFiles} from '../../../src/ngtsc/testing'; -import {AnalysisOutput, CompileResult, DecoratorHandler, DetectResult, HandlerPrecedence} from '../../../src/ngtsc/transform'; -import {DecorationAnalyzer} from '../../src/analysis/decoration_analyzer'; -import {NgccReferencesRegistry} from '../../src/analysis/ngcc_references_registry'; -import {CompiledClass, DecorationAnalyses} from '../../src/analysis/types'; -import {Esm2015ReflectionHost} from '../../src/host/esm2015_host'; -import {Migration, MigrationHost} from '../../src/migrations/migration'; -import {getRootFiles, makeTestEntryPointBundle} from '../helpers/utils'; - -type DecoratorHandlerWithResolve = - DecoratorHandler&{ - resolve: NonNullable['resolve']>; -}; - -runInEachFileSystem(() => { - describe('DecorationAnalyzer', () => { - let _: typeof absoluteFrom; - - beforeEach(() => { - _ = absoluteFrom; - }); - - describe('analyzeProgram()', () => { - let logs: string[]; - let migrationLogs: string[]; - let diagnosticLogs: ts.Diagnostic[]; - let program: ts.Program; - let testHandler: jasmine.SpyObj; - let result: DecorationAnalyses; - - // Helpers - const createTestHandler = (options: {analyzeError: boolean, resolveError: boolean}) => { - const handler = jasmine.createSpyObj('TestDecoratorHandler', [ - 'detect', - 'analyze', - 'symbol', - 'register', - 'resolve', - 'compileFull', - ]); - // Only detect the Component and Directive decorators - handler.detect.and.callFake( - (node: DeclarationNode, decorators: Decorator[]|null): DetectResult| - undefined => { - const className = (node as any).name.text; - if (decorators === null) { - logs.push(`detect: ${className} (no decorators)`); - } else { - logs.push(`detect: ${className}@${decorators.map(d => d.name)}`); - } - if (!decorators) { - return undefined; - } - const metadata = - decorators.find(d => d.name === 'Component' || d.name === 'Directive'); - if (metadata === undefined) { - return undefined; - } else { - return { - metadata, - decorator: metadata, - trigger: metadata.node, - }; - } - }); - // The "test" analysis is an object with the name of the decorator being analyzed - handler.analyze.and.callFake((decl: DeclarationNode, dec: Decorator) => { - logs.push(`analyze: ${(decl as any).name.text}@${dec.name}`); - return { - analysis: !options.analyzeError ? {decoratorName: dec.name} : undefined, - diagnostics: options.analyzeError ? [makeDiagnostic(9999, decl, 'analyze diagnostic')] : - undefined - }; - }); - // The "test" resolution is just setting `resolved: true` on the analysis - handler.resolve.and.callFake((decl: DeclarationNode, analysis: any) => { - logs.push(`resolve: ${(decl as any).name.text}@${analysis.decoratorName}`); - analysis.resolved = true; - return { - diagnostics: options.resolveError ? [makeDiagnostic(9998, decl, 'resolve diagnostic')] : - undefined - }; - }); - // The "test" compilation result is just the name of the decorator being compiled - // (suffixed with `(compiled)`) - handler.compileFull.and.callFake((decl: DeclarationNode, analysis: any) => { - logs.push(`compile: ${(decl as any).name.text}@${analysis.decoratorName} (resolved: ${ - analysis.resolved})`); - return `@${analysis.decoratorName} (compiled)` as any; - }); - return handler; - }; - - function setUpAnalyzer(testFiles: TestFile[], options: { - analyzeError: boolean, - resolveError: boolean - } = {analyzeError: false, resolveError: false}) { - logs = []; - loadTestFiles(testFiles); - loadFakeCore(getFileSystem()); - const rootFiles = getRootFiles(testFiles); - const bundle = makeTestEntryPointBundle('test-package', 'esm2015', false, rootFiles); - program = bundle.src.program; - - const reflectionHost = new Esm2015ReflectionHost(new MockLogger(), false, bundle.src); - const referencesRegistry = new NgccReferencesRegistry(reflectionHost); - diagnosticLogs = []; - const analyzer = new DecorationAnalyzer( - getFileSystem(), bundle, reflectionHost, referencesRegistry, - (error) => diagnosticLogs.push(error)); - testHandler = createTestHandler(options); - - // Replace the default handlers with the test handler in the original array of handlers - analyzer.handlers.splice(0, analyzer.handlers.length, testHandler); - migrationLogs = []; - const migration1 = new MockMigration('migration1', migrationLogs); - const migration2 = new MockMigration('migration2', migrationLogs); - analyzer.migrations = [migration1, migration2]; - return analyzer; - } - - describe('basic usage', () => { - beforeEach(() => { - const TEST_PROGRAM = [ - { - name: _('/node_modules/test-package/index.js'), - contents: ` - import * as test from './test'; - import * as other from './other'; - `, - }, - { - name: _('/node_modules/test-package/test.js'), - contents: ` - import {Component, Directive, Injectable} from '@angular/core'; - - export class NoDecorators {} - - export class MyComponent {} - MyComponent.decorators = [{type: Component}]; - - export class MyDirective {} - MyDirective.decorators = [{type: Directive}]; - - export class MyService {} - MyService.decorators = [{type: Injectable}]; - - `, - }, - { - name: _('/node_modules/test-package/other.js'), - contents: ` - import {Component} from '@angular/core'; - - export class MyOtherComponent {} - MyOtherComponent.decorators = [{type: Component}]; - `, - }, - ]; - const analyzer = setUpAnalyzer(TEST_PROGRAM); - result = analyzer.analyzeProgram(); - }); - - it('should return an object containing a reference to the original source file', () => { - const testFile = getSourceFileOrError(program, _('/node_modules/test-package/test.js')); - expect(result.get(testFile)!.sourceFile).toBe(testFile); - const otherFile = getSourceFileOrError(program, _('/node_modules/test-package/other.js')); - expect(result.get(otherFile)!.sourceFile).toBe(otherFile); - }); - - it('should call detect on the decorator handlers with each class from the parsed file', - () => { - expect(testHandler.detect).toHaveBeenCalledTimes(5); - expect(testHandler.detect.calls.allArgs().map((args: any[]) => args[1])).toEqual([ - null, - jasmine.arrayContaining([jasmine.objectContaining({name: 'Component'})]), - jasmine.arrayContaining([jasmine.objectContaining({name: 'Directive'})]), - jasmine.arrayContaining([jasmine.objectContaining({name: 'Injectable'})]), - jasmine.arrayContaining([jasmine.objectContaining({name: 'Component'})]), - ]); - }); - - it('should return an object containing the classes that were analyzed', () => { - const file1 = getSourceFileOrError(program, _('/node_modules/test-package/test.js')); - const compiledFile1 = result.get(file1)!; - expect(compiledFile1.compiledClasses.length).toEqual(2); - expect(compiledFile1.compiledClasses[0]).toEqual(jasmine.objectContaining({ - name: 'MyComponent', - compilation: ['@Component (compiled)'], - } as unknown as CompiledClass)); - expect(compiledFile1.compiledClasses[1]).toEqual(jasmine.objectContaining({ - name: 'MyDirective', - compilation: ['@Directive (compiled)'], - } as unknown as CompiledClass)); - - const file2 = getSourceFileOrError(program, _('/node_modules/test-package/other.js')); - const compiledFile2 = result.get(file2)!; - expect(compiledFile2.compiledClasses.length).toEqual(1); - expect(compiledFile2.compiledClasses[0]).toEqual(jasmine.objectContaining({ - name: 'MyOtherComponent', - compilation: ['@Component (compiled)'], - } as unknown as CompiledClass)); - }); - - it('should call `apply()` on each migration for each class', () => { - expect(migrationLogs).toEqual([ - 'migration1:MyComponent', - 'migration1:MyDirective', - 'migration1:MyOtherComponent', - 'migration2:MyComponent', - 'migration2:MyDirective', - 'migration2:MyOtherComponent', - ]); - }); - - - it('should analyze, resolve and compile the classes that are detected', () => { - expect(logs).toEqual([ - // Classes without decorators should also be detected. - 'detect: NoDecorators (no decorators)', - // First detect and (potentially) analyze. - 'detect: MyComponent@Component', - 'analyze: MyComponent@Component', - 'detect: MyDirective@Directive', - 'analyze: MyDirective@Directive', - 'detect: MyService@Injectable', - 'detect: MyOtherComponent@Component', - 'analyze: MyOtherComponent@Component', - // The resolve. - 'resolve: MyComponent@Component', - 'resolve: MyDirective@Directive', - 'resolve: MyOtherComponent@Component', - // Finally compile. - 'compile: MyComponent@Component (resolved: true)', - 'compile: MyDirective@Directive (resolved: true)', - 'compile: MyOtherComponent@Component (resolved: true)', - ]); - }); - }); - - describe('internal components', () => { - beforeEach(() => { - const INTERNAL_COMPONENT_PROGRAM = [ - { - name: _('/node_modules/test-package/entrypoint.js'), - contents: ` - import {Component, NgModule} from '@angular/core'; - import {ImportedComponent} from './component'; - - export class LocalComponent {} - LocalComponent.decorators = [{type: Component}]; - - export class MyModule {} - MyModule.decorators = [{type: NgModule, args: [{ - declarations: [ImportedComponent, LocalComponent], - exports: [ImportedComponent, LocalComponent], - },] }]; - ` - }, - { - name: _('/node_modules/test-package/component.js'), - contents: ` - import {Component} from '@angular/core'; - export class ImportedComponent {} - ImportedComponent.decorators = [{type: Component}]; - `, - isRoot: false, - } - ]; - const analyzer = setUpAnalyzer(INTERNAL_COMPONENT_PROGRAM); - result = analyzer.analyzeProgram(); - }); - - // The problem of exposing the type of these internal components in the .d.ts typing - // files is not yet solved. - it('should analyze an internally imported component, which is not publicly exported from the entry-point', - () => { - const file = - getSourceFileOrError(program, _('/node_modules/test-package/component.js')); - const analysis = result.get(file)!; - expect(analysis).toBeDefined(); - const ImportedComponent = - analysis.compiledClasses.find(f => f.name === 'ImportedComponent')!; - expect(ImportedComponent).toBeDefined(); - }); - - it('should analyze an internally defined component, which is not exported at all', () => { - const file = getSourceFileOrError(program, _('/node_modules/test-package/entrypoint.js')); - const analysis = result.get(file)!; - expect(analysis).toBeDefined(); - const LocalComponent = analysis.compiledClasses.find(f => f.name === 'LocalComponent')!; - expect(LocalComponent).toBeDefined(); - }); - }); - - describe('external components', () => { - beforeEach(() => { - const EXTERNAL_COMPONENT_PROGRAM: TestFile[] = [ - { - name: _('/node_modules/test-package/entrypoint.js'), - contents: ` - import {Component, NgModule} from '@angular/core'; - import {ImportedComponent} from 'other/component'; - import {NestedDependencyComponent} from 'nested/component'; - - export class LocalComponent {} - LocalComponent.decorators = [{type: Component}]; - - export class MyModule {} - MyModule.decorators = [{type: NgModule, args: [{ - declarations: [ImportedComponent, NestedDependencyComponent, LocalComponent], - exports: [ImportedComponent, NestedDependencyComponent, LocalComponent], - },] }]; - ` - }, - // Do not define a `.d.ts` file to ensure that the `.js` file will be part of the TS - // program. - { - name: _('/node_modules/test-package/node_modules/nested/component.js'), - contents: ` - import {Component} from '@angular/core'; - export class NestedDependencyComponent {} - NestedDependencyComponent.decorators = [{type: Component}]; - `, - isRoot: false, - }, - // Do not define a `.d.ts` file to ensure that the `.js` file will be part of the TS - // program. - { - name: _('/node_modules/other/component.js'), - contents: ` - import {Component} from '@angular/core'; - export class ImportedComponent {} - ImportedComponent.decorators = [{type: Component}]; - `, - isRoot: false, - }, - ]; - - const analyzer = setUpAnalyzer(EXTERNAL_COMPONENT_PROGRAM); - result = analyzer.analyzeProgram(); - }); - - it('should ignore classes from an externally imported file', () => { - const file = program.getSourceFile(_('/node_modules/other/component.js'))!; - expect(result.has(file)).toBe(false); - }); - - it('should ignore classes from a file imported from a nested `node_modules/`', () => { - const file = program.getSourceFile( - _('/node_modules/test-package/node_modules/nested/component.js'))!; - expect(result.has(file)).toBe(false); - }); - }); - - describe('diagnostic handling', () => { - it('should report migration diagnostics to the `diagnosticHandler` callback', () => { - const analyzer = setUpAnalyzer([ - { - name: _('/node_modules/test-package/index.js'), - contents: ` - import {Component, Directive, Injectable} from '@angular/core'; - export class MyComponent {} - MyComponent.decorators = [{type: Component}]; - `, - }, - ]); - analyzer.migrations = [ - { - apply(clazz: ClassDeclaration) { - return makeDiagnostic(9999, clazz, 'normal diagnostic'); - } - }, - { - apply(clazz: ClassDeclaration) { - throw new FatalDiagnosticError(6666, clazz, 'fatal diagnostic'); - } - } - ]; - analyzer.analyzeProgram(); - expect(diagnosticLogs.length).toEqual(2); - expect(diagnosticLogs[0]).toEqual(jasmine.objectContaining({code: -999999})); - expect(diagnosticLogs[1]).toEqual(jasmine.objectContaining({code: -996666})); - }); - - it('should report analyze diagnostics to the `diagnosticHandler` callback', () => { - const analyzer = setUpAnalyzer( - [ - { - name: _('/node_modules/test-package/index.js'), - contents: ` - import {Component, Directive, Injectable} from '@angular/core'; - export class MyComponent {} - MyComponent.decorators = [{type: Component}]; - `, - }, - ], - {analyzeError: true, resolveError: false}); - analyzer.analyzeProgram(); - expect(diagnosticLogs.length).toEqual(1); - expect(diagnosticLogs[0]).toEqual(jasmine.objectContaining({code: -999999})); - expect(testHandler.analyze).toHaveBeenCalled(); - expect(testHandler.register).not.toHaveBeenCalled(); - expect(testHandler.resolve).not.toHaveBeenCalled(); - expect(testHandler.compileFull).not.toHaveBeenCalled(); - }); - - it('should report resolve diagnostics to the `diagnosticHandler` callback', () => { - const analyzer = setUpAnalyzer( - [ - { - name: _('/node_modules/test-package/index.js'), - contents: ` - import {Component, Directive, Injectable} from '@angular/core'; - export class MyComponent {} - MyComponent.decorators = [{type: Component}]; - `, - }, - ], - {analyzeError: false, resolveError: true}); - analyzer.analyzeProgram(); - expect(diagnosticLogs.length).toEqual(1); - expect(diagnosticLogs[0]).toEqual(jasmine.objectContaining({code: -999998})); - expect(testHandler.analyze).toHaveBeenCalled(); - expect(testHandler.register).toHaveBeenCalled(); - expect(testHandler.resolve).toHaveBeenCalled(); - expect(testHandler.compileFull).not.toHaveBeenCalled(); - }); - }); - - describe('declaration files', () => { - it('should not run decorator handlers against declaration files', () => { - class FakeDecoratorHandler implements DecoratorHandler<{}|null, unknown, null, unknown> { - name = 'FakeDecoratorHandler'; - precedence = HandlerPrecedence.PRIMARY; - - detect(): undefined { - throw new Error('detect should not have been called'); - } - analyze(): AnalysisOutput { - throw new Error('analyze should not have been called'); - } - symbol(): null { - throw new Error('symbol should not have been called'); - } - compileFull(): CompileResult { - throw new Error('compile should not have been called'); - } - } - - const analyzer = setUpAnalyzer([{ - name: _('/node_modules/test-package/index.d.ts'), - contents: 'export declare class SomeDirective {}', - }]); - - // Replace the default handlers with the test handler in the original array of handlers - analyzer.handlers.splice(0, analyzer.handlers.length, new FakeDecoratorHandler()); - result = analyzer.analyzeProgram(); - expect(result.size).toBe(0); - }); - }); - }); - }); -}); - -class MockMigration implements Migration { - constructor(private name: string, private log: string[]) {} - apply(clazz: ClassDeclaration, host: MigrationHost): ts.Diagnostic|null { - this.log.push(`${this.name}:${clazz.name.text}`); - return null; - } -} diff --git a/packages/compiler-cli/ngcc/test/analysis/migration_host_spec.ts b/packages/compiler-cli/ngcc/test/analysis/migration_host_spec.ts deleted file mode 100644 index 7613fff000a..00000000000 --- a/packages/compiler-cli/ngcc/test/analysis/migration_host_spec.ts +++ /dev/null @@ -1,244 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ - -import ts from 'typescript'; - -import {makeDiagnostic} from '../../../src/ngtsc/diagnostics'; -import {absoluteFrom} from '../../../src/ngtsc/file_system'; -import {runInEachFileSystem} from '../../../src/ngtsc/file_system/testing'; -import {SemanticSymbol} from '../../../src/ngtsc/incremental/semantic_graph'; -import {MockLogger} from '../../../src/ngtsc/logging/testing'; -import {ClassDeclaration, Decorator, isNamedClassDeclaration} from '../../../src/ngtsc/reflection'; -import {getDeclaration, loadTestFiles} from '../../../src/ngtsc/testing'; -import {AnalysisOutput, CompileResult, DecoratorHandler, DetectResult, HandlerPrecedence} from '../../../src/ngtsc/transform'; -import {DefaultMigrationHost} from '../../src/analysis/migration_host'; -import {NgccTraitCompiler} from '../../src/analysis/ngcc_trait_compiler'; -import {Esm2015ReflectionHost} from '../../src/host/esm2015_host'; -import {createComponentDecorator} from '../../src/migrations/utils'; -import {EntryPointBundle} from '../../src/packages/entry_point_bundle'; -import {makeTestEntryPointBundle} from '../helpers/utils'; -import {getTraitDiagnostics} from '../host/util'; - -runInEachFileSystem(() => { - describe('DefaultMigrationHost', () => { - let _: typeof absoluteFrom; - let mockMetadata: any = {}; - let mockEvaluator: any = {}; - let mockClazz: any; - let injectedDecorator: any = {name: 'InjectedDecorator'}; - beforeEach(() => { - _ = absoluteFrom; - const mockSourceFile: any = { - fileName: _('/node_modules/some-package/entry-point/test-file.js'), - }; - mockClazz = { - name: {text: 'MockClazz'}, - getSourceFile: () => mockSourceFile, - getStart: () => 0, - getWidth: () => 0, - }; - }); - - function createMigrationHost({entryPoint, handlers}: { - entryPoint: EntryPointBundle; - handlers: DecoratorHandler[] - }) { - const reflectionHost = new Esm2015ReflectionHost(new MockLogger(), false, entryPoint.src); - const compiler = new NgccTraitCompiler(handlers, reflectionHost); - const host = new DefaultMigrationHost( - reflectionHost, mockMetadata, mockEvaluator, compiler, entryPoint.entryPoint.path); - return {compiler, host}; - } - - describe('injectSyntheticDecorator()', () => { - it('should add the injected decorator into the compilation', () => { - const handler = new DetectDecoratorHandler('InjectedDecorator', HandlerPrecedence.WEAK); - loadTestFiles([{name: _('/node_modules/test/index.js'), contents: ``}]); - const entryPoint = - makeTestEntryPointBundle('test', 'esm2015', false, [_('/node_modules/test/index.js')]); - const {host, compiler} = createMigrationHost({entryPoint, handlers: [handler]}); - host.injectSyntheticDecorator(mockClazz, injectedDecorator); - - const record = compiler.recordFor(mockClazz)!; - expect(record).toBeDefined(); - expect(record.traits.length).toBe(1); - expect(record.traits[0].detected.decorator).toBe(injectedDecorator); - }); - - it('should mention the migration that failed in the diagnostics message', () => { - const handler = new DiagnosticProducingHandler(); - loadTestFiles([{name: _('/node_modules/test/index.js'), contents: ``}]); - const entryPoint = - makeTestEntryPointBundle('test', 'esm2015', false, [_('/node_modules/test/index.js')]); - const {host, compiler} = createMigrationHost({entryPoint, handlers: [handler]}); - const decorator = createComponentDecorator(mockClazz, {selector: 'comp', exportAs: null}); - host.injectSyntheticDecorator(mockClazz, decorator); - - const record = compiler.recordFor(mockClazz)!; - const migratedTrait = record.traits[0]; - const diagnostics = getTraitDiagnostics(migratedTrait); - if (diagnostics === null) { - return fail('Expected migrated class trait to be in an error state'); - } - - expect(diagnostics.length).toBe(1); - expect(ts.flattenDiagnosticMessageText(diagnostics[0].messageText, '\n')) - .toEqual( - `test diagnostic\n` + - ` Occurs for @Component decorator inserted by an automatic migration\n` + - ` @Component({ template: "", selector: "comp" })`); - }); - }); - - describe('getAllDecorators', () => { - it('should include injected decorators', () => { - const directiveHandler = new DetectDecoratorHandler('Directive', HandlerPrecedence.WEAK); - const injectedHandler = - new DetectDecoratorHandler('InjectedDecorator', HandlerPrecedence.WEAK); - loadTestFiles([{ - name: _('/node_modules/test/index.js'), - contents: ` - import {Directive} from '@angular/core'; - - export class MyClass {}; - MyClass.decorators = [{ type: Directive }]; - ` - }]); - const entryPoint = - makeTestEntryPointBundle('test', 'esm2015', false, [_('/node_modules/test/index.js')]); - const {host, compiler} = - createMigrationHost({entryPoint, handlers: [directiveHandler, injectedHandler]}); - const myClass = getDeclaration( - entryPoint.src.program, _('/node_modules/test/index.js'), 'MyClass', - isNamedClassDeclaration); - - compiler.analyzeFile(entryPoint.src.file); - - host.injectSyntheticDecorator(myClass, injectedDecorator); - - const decorators = host.getAllDecorators(myClass)!; - expect(decorators.length).toBe(2); - expect(decorators[0].name).toBe('Directive'); - expect(decorators[1].name).toBe('InjectedDecorator'); - }); - }); - - describe('isInScope', () => { - it('should be true for nodes within the entry-point', () => { - loadTestFiles([ - {name: _('/node_modules/test/index.js'), contents: `export * from './internal';`}, - {name: _('/node_modules/test/internal.js'), contents: `export class InternalClass {}`}, - ]); - const entryPoint = - makeTestEntryPointBundle('test', 'esm2015', false, [_('/node_modules/test/index.js')]); - const {host} = createMigrationHost({entryPoint, handlers: []}); - const internalClass = getDeclaration( - entryPoint.src.program, _('/node_modules/test/internal.js'), 'InternalClass', - isNamedClassDeclaration); - - expect(host.isInScope(internalClass)).toBe(true); - }); - - it('should be false for nodes outside the entry-point (in sibling package)', () => { - loadTestFiles([ - {name: _('/node_modules/external/index.js'), contents: `export class ExternalClass {}`}, - { - name: _('/node_modules/test/index.js'), - contents: ` - export {ExternalClass} from 'external'; - export class InternalClass {} - ` - }, - ]); - const entryPoint = - makeTestEntryPointBundle('test', 'esm2015', false, [_('/node_modules/test/index.js')]); - const {host} = createMigrationHost({entryPoint, handlers: []}); - const externalClass = getDeclaration( - entryPoint.src.program, _('/node_modules/external/index.js'), 'ExternalClass', - isNamedClassDeclaration); - - expect(host.isInScope(externalClass)).toBe(false); - }); - - it('should be false for nodes outside the entry-point (in nested `node_modules/`)', () => { - loadTestFiles([ - { - name: _('/node_modules/test/index.js'), - contents: ` - export {NestedDependencyClass} from 'nested'; - export class InternalClass {} - `, - }, - { - name: _('/node_modules/test/node_modules/nested/index.js'), - contents: `export class NestedDependencyClass {}`, - }, - ]); - const entryPoint = - makeTestEntryPointBundle('test', 'esm2015', false, [_('/node_modules/test/index.js')]); - const {host} = createMigrationHost({entryPoint, handlers: []}); - const nestedDepClass = getDeclaration( - entryPoint.src.program, _('/node_modules/test/node_modules/nested/index.js'), - 'NestedDependencyClass', isNamedClassDeclaration); - - expect(host.isInScope(nestedDepClass)).toBe(false); - }); - }); - }); -}); - -class DetectDecoratorHandler implements DecoratorHandler { - readonly name = DetectDecoratorHandler.name; - - constructor(private decorator: string, readonly precedence: HandlerPrecedence) {} - - detect(node: ClassDeclaration, decorators: Decorator[]|null): DetectResult|undefined { - if (decorators === null) { - return undefined; - } - const decorator = decorators.find(decorator => decorator.name === this.decorator); - if (decorator === undefined) { - return undefined; - } - return {trigger: node, decorator, metadata: {}}; - } - - analyze(node: ClassDeclaration): AnalysisOutput { - return {}; - } - - symbol(node: ClassDeclaration, analysis: Readonly): null { - return null; - } - - compileFull(node: ClassDeclaration): CompileResult|CompileResult[] { - return []; - } -} - -class DiagnosticProducingHandler implements DecoratorHandler { - readonly name = DiagnosticProducingHandler.name; - readonly precedence = HandlerPrecedence.PRIMARY; - - detect(node: ClassDeclaration, decorators: Decorator[]|null): DetectResult|undefined { - const decorator = decorators !== null ? decorators[0] : null; - return {trigger: node, decorator, metadata: {}}; - } - - analyze(node: ClassDeclaration): AnalysisOutput { - return {diagnostics: [makeDiagnostic(9999, node, 'test diagnostic')]}; - } - - symbol(node: ClassDeclaration, analysis: Readonly): null { - return null; - } - - compileFull(node: ClassDeclaration): CompileResult|CompileResult[] { - return []; - } -} diff --git a/packages/compiler-cli/ngcc/test/analysis/module_with_providers_analyzer_spec.ts b/packages/compiler-cli/ngcc/test/analysis/module_with_providers_analyzer_spec.ts deleted file mode 100644 index 65da400f585..00000000000 --- a/packages/compiler-cli/ngcc/test/analysis/module_with_providers_analyzer_spec.ts +++ /dev/null @@ -1,836 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import ts from 'typescript'; - -import {absoluteFrom, AbsoluteFsPath, getSourceFileOrError} from '../../../src/ngtsc/file_system'; -import {runInEachFileSystem, TestFile} from '../../../src/ngtsc/file_system/testing'; -import {MockLogger} from '../../../src/ngtsc/logging/testing'; -import {DeclarationNode} from '../../../src/ngtsc/reflection'; -import {getDeclaration, isNamedDeclaration, loadTestFiles} from '../../../src/ngtsc/testing'; -import {ModuleWithProvidersAnalyses, ModuleWithProvidersAnalyzer} from '../../src/analysis/module_with_providers_analyzer'; -import {NgccReferencesRegistry} from '../../src/analysis/ngcc_references_registry'; -import {Esm2015ReflectionHost} from '../../src/host/esm2015_host'; -import {BundleProgram} from '../../src/packages/bundle_program'; -import {getRootFiles, makeTestEntryPointBundle} from '../helpers/utils'; - -runInEachFileSystem(() => { - describe('ModuleWithProvidersAnalyzer', () => { - describe('analyzeProgram()', () => { - let _: typeof absoluteFrom; - let analyses: ModuleWithProvidersAnalyses; - let program: ts.Program; - let dtsProgram: BundleProgram; - let referencesRegistry: NgccReferencesRegistry; - - beforeEach(() => { - _ = absoluteFrom; - - const TEST_PROGRAM: TestFile[] = [ - { - name: _('/node_modules/test-package/src/entry-point.js'), - contents: ` - export * from './explicit'; - export * from './any'; - export * from './implicit'; - export * from './no-providers'; - export * from './module'; - export * from './delegated'; - export * from './iife-wrapped'; - ` - }, - { - name: _('/node_modules/test-package/src/explicit.js'), - contents: ` - import {ExternalModule} from './module'; - import {LibraryModule} from 'some-library'; - export class ExplicitInternalModule {} - export function explicitInternalFunction() { - return { - ngModule: ExplicitInternalModule, - providers: [] - }; - } - export function explicitExternalFunction() { - return { - ngModule: ExternalModule, - providers: [] - }; - } - export function explicitLibraryFunction() { - return { - ngModule: LibraryModule, - providers: [] - }; - } - export class ExplicitClass { - static explicitInternalMethod() { - return { - ngModule: ExplicitInternalModule, - providers: [] - }; - } - static explicitExternalMethod() { - return { - ngModule: ExternalModule, - providers: [] - }; - } - static explicitLibraryMethod() { - return { - ngModule: LibraryModule, - providers: [] - }; - } - } - ` - }, - { - name: _('/node_modules/test-package/src/any.js'), - contents: ` - import {ExternalModule} from './module'; - import {LibraryModule} from 'some-library'; - export class AnyInternalModule {} - export function anyInternalFunction() { - return { - ngModule: AnyInternalModule, - providers: [] - }; - } - export function anyExternalFunction() { - return { - ngModule: ExternalModule, - providers: [] - }; - } - export function anyLibraryFunction() { - return { - ngModule: LibraryModule, - providers: [] - }; - } - export class AnyClass { - static anyInternalMethod() { - return { - ngModule: AnyInternalModule, - providers: [] - }; - } - static anyExternalMethod() { - return { - ngModule: ExternalModule, - providers: [] - }; - } - static anyLibraryMethod() { - return { - ngModule: LibraryModule, - providers: [] - }; - } - } - ` - }, - { - name: _('/node_modules/test-package/src/implicit.js'), - contents: ` - import {ExternalModule} from './module'; - import {LibraryModule} from 'some-library'; - export class ImplicitInternalModule {} - export function implicitInternalFunction() { - return { - ngModule: ImplicitInternalModule, - providers: [], - }; - } - export function implicitExternalFunction() { - return { - ngModule: ExternalModule, - providers: [], - }; - } - export function implicitLibraryFunction() { - return { - ngModule: LibraryModule, - providers: [], - }; - } - export class ImplicitClass { - static implicitInternalMethod() { - return { - ngModule: ImplicitInternalModule, - providers: [], - }; - } - static implicitExternalMethod() { - return { - ngModule: ExternalModule, - providers: [], - }; - } - static implicitLibraryMethod() { - return { - ngModule: LibraryModule, - providers: [], - }; - } - } - ` - }, - { - name: _('/node_modules/test-package/src/no-providers.js'), - contents: ` - import {ExternalModule} from './module'; - import {LibraryModule} from 'some-library'; - export class NoProvidersInternalModule {} - export function noProvExplicitInternalFunction() { - return {ngModule: NoProvidersInternalModule}; - } - export function noProvExplicitExternalFunction() { - return {ngModule: ExternalModule}; - } - export function noProvExplicitLibraryFunction() { - return {ngModule: LibraryModule}; - } - export function noProvAnyInternalFunction() { - return {ngModule: NoProvidersInternalModule}; - } - export function noProvAnyExternalFunction() { - return {ngModule: ExternalModule}; - } - export function noProvAnyLibraryFunction() { - return {ngModule: LibraryModule}; - } - export function noProvImplicitInternalFunction() { - return {ngModule: NoProvidersInternalModule}; - } - export function noProvImplicitExternalFunction() { - return {ngModule: ExternalModule}; - } - export function noProvImplicitLibraryFunction() { - return {ngModule: LibraryModule}; - } - ` - }, - { - name: _('/node_modules/test-package/src/delegated.js'), - contents: ` - import * as implicit from './implicit'; - import * as explicit from './explicit'; - import * as anyModule from './any'; - - export function delegatedImplicitInternalFunction() { - return implicit.implicitInternalFunction(); - } - export function delegatedImplicitExternalFunction() { - return implicit.implicitExternalFunction(); - } - export function delegatedImplicitLibraryFunction() { - return implicit.implicitLibraryFunction(); - } - export class DelegatedImplicitClass { - static implicitInternalMethod() { - return implicit.ImplicitClass.implicitInternalMethod(); - } - static implicitExternalMethod() { - return implicit.ImplicitClass.implicitExternalMethod(); - } - static implicitLibraryMethod() { - return implicit.ImplicitClass.implicitLibraryMethod(); - } - } - - export function delegatedExplicitInternalFunction() { - return explicit.explicitInternalFunction(); - } - export function delegatedExplicitExternalFunction() { - return explicit.explicitExternalFunction(); - } - export function delegatedExplicitLibraryFunction() { - return explicit.explicitLibraryFunction(); - } - export class DelegatedExplicitClass { - static explicitInternalMethod() { - return explicit.ExplicitClass.explicitInternalMethod(); - } - static explicitExternalMethod() { - return explicit.ExplicitClass.explicitExternalMethod(); - } - static explicitLibraryMethod() { - return explicit.ExplicitClass.explicitLibraryMethod(); - } - } - - export function delegatedAnyInternalFunction() { - return anyModule.anyInternalFunction(); - } - export function delegatedAnyExternalFunction() { - return anyModule.anyExternalFunction(); - } - export function delegatedAnyLibraryFunction() { - return anyModule.anyLibraryFunction(); - } - export class DelegatedAnyClass { - static anyInternalMethod() { - return anyModule.AnyClass.anyInternalMethod(); - } - static anyExternalMethod() { - return anyModule.AnyClass.anyExternalMethod(); - } - static anyLibraryMethod() { - return anyModule.AnyClass.anyLibraryMethod(); - } - } - - export function withParams(a: string) { - return explicit.explicitInternalFunction(); - } - - export function withOptionalParams(a: string = 'default') { - return explicit.explicitInternalFunction(); - } - - export function doubleDelegation(a: string = 'default') { - return withParams(a); - } - ` - }, - { - name: _('/node_modules/test-package/src/module.js'), - contents: ` - export class ExternalModule {} - ` - }, - { - name: _('/node_modules/some-library/index.d.ts'), - contents: 'export declare class LibraryModule {}' - }, - { - name: _('/node_modules/test-package/src/iife-wrapped.js'), - contents: ` - import {NgModule} from './core'; - let WrappedClass = (() => { - var WrappedClass_Alias; - let AdjacentWrappedClass = WrappedClass_Alias = class InnerWrappedClass { - static forRoot() { - return { - ngModule: WrappedClass_Alias, - providers: [] - }; - } - }; - AdjacentWrappedClass = WrappedClass_Alias = __decorate([], AdjacentWrappedClass); - return AdjacentWrappedClass; - })(); - export {WrappedClass};` - }, - ]; - const TEST_DTS_PROGRAM: TestFile[] = [ - { - name: _('/node_modules/test-package/typings/entry-point.d.ts'), - contents: ` - export * from './explicit'; - export * from './any'; - export * from './implicit'; - export * from './no-providers'; - export * from './module'; - export * from './delegated'; - export * from './iife-wrapped'; - ` - }, - { - name: _('/node_modules/test-package/typings/explicit.d.ts'), - contents: ` - import {ModuleWithProviders} from './core'; - import {ExternalModule} from './module'; - import {LibraryModule} from 'some-library'; - export declare class ExplicitInternalModule {} - export declare function explicitInternalFunction(): ModuleWithProviders; - export declare function explicitExternalFunction(): ModuleWithProviders; - export declare function explicitLibraryFunction(): ModuleWithProviders; - export declare class ExplicitClass { - static explicitInternalMethod(): ModuleWithProviders; - static explicitExternalMethod(): ModuleWithProviders; - static explicitLibraryMethod(): ModuleWithProviders; - } - ` - }, - { - name: _('/node_modules/test-package/typings/any.d.ts'), - contents: ` - import {ModuleWithProviders} from './core'; - export declare class AnyInternalModule {} - export declare function anyInternalFunction(): ModuleWithProviders; - export declare function anyExternalFunction(): ModuleWithProviders; - export declare function anyLibraryFunction(): ModuleWithProviders; - export declare class AnyClass { - static anyInternalMethod(): ModuleWithProviders; - static anyExternalMethod(): ModuleWithProviders; - static anyLibraryMethod(): ModuleWithProviders; - } - ` - }, - { - name: _('/node_modules/test-package/typings/implicit.d.ts'), - contents: ` - import {ExternalModule} from './module'; - import {LibraryModule} from 'some-library'; - export declare class ImplicitInternalModule {} - export declare function implicitInternalFunction(): { ngModule: typeof ImplicitInternalModule; providers: never[]; }; - export declare function implicitExternalFunction(): { ngModule: typeof ExternalModule; providers: never[]; }; - export declare function implicitLibraryFunction(): { ngModule: typeof LibraryModule; providers: never[]; }; - export declare class ImplicitClass { - static implicitInternalMethod(): { ngModule: typeof ImplicitInternalModule; providers: never[]; }; - static implicitExternalMethod(): { ngModule: typeof ExternalModule; providers: never[]; }; - static implicitLibraryMethod(): { ngModule: typeof LibraryModule; providers: never[]; }; - } - ` - }, - { - name: _('/node_modules/test-package/typings/delegated.d.ts'), - contents: ` - // None of the ModuleWithProviders functions/methods in this file provide the - // necessary type parameters and so need to be processed by the analyzer. - // Each group of functions/methods here delegate their return values to other - // functions/methods that either explicitly provide a type parameter or need - // processing by the analyzer themselves. - - export declare function delegatedImplicitInternalFunction(): ModuleWithProviders; - export declare function delegatedImplicitExternalFunction(): ModuleWithProviders; - export declare function delegatedImplicitLibraryFunction(): ModuleWithProviders; - export declare class DelegatedImplicitClass { - static implicitInternalMethod(): ModuleWithProviders; - static implicitExternalMethod(): ModuleWithProviders; - static implicitLibraryMethod(): ModuleWithProviders; - } - - export declare function delegatedExplicitInternalFunction(): ModuleWithProviders; - export declare function delegatedExplicitExternalFunction(): ModuleWithProviders; - export declare function delegatedExplicitLibraryFunction(): ModuleWithProviders; - export declare class DelegatedExplicitClass { - static explicitInternalMethod(): ModuleWithProviders; - static explicitExternalMethod(): ModuleWithProviders; - static explicitLibraryMethod(): ModuleWithProviders; - } - - export declare function delegatedAnyInternalFunction(): ModuleWithProviders; - export declare function delegatedAnyExternalFunction(): ModuleWithProviders; - export declare function delegatedAnyLibraryFunction(): ModuleWithProviders; - export declare class DelegatedAnyClass { - static anyInternalMethod(): ModuleWithProviders; - static anyExternalMethod(): ModuleWithProviders; - static anyLibraryMethod(): ModuleWithProviders; - } - - export declare function withParams(a: string): ModuleWithProviders; - export declare function withOptionalParams(a: string = 'default'): ModuleWithProviders; - export declare function doubleDelegation(a: string = 'default'): ModuleWithProviders; - ` - }, - { - name: _('/node_modules/test-package/typings/no-providers.d.ts'), - contents: ` - import {ModuleWithProviders} from './core'; - import {ExternalModule} from './module'; - import {LibraryModule} from 'some-library'; - export declare class NoProvidersInternalModule {} - export declare function noProvExplicitInternalFunction(): ModuleWithProviders; - export declare function noProvExplicitExternalFunction(): ModuleWithProviders; - export declare function noProvExplicitLibraryFunction(): ModuleWithProviders; - export declare function noProvAnyInternalFunction(): ModuleWithProviders; - export declare function noProvAnyExternalFunction(): ModuleWithProviders; - export declare function noProvAnyLibraryFunction(): ModuleWithProviders; - export declare function noProvImplicitInternalFunction(): { ngModule: typeof NoProvidersInternalModule; }; - export declare function noProvImplicitExternalFunction(): { ngModule: typeof ExternalModule; }; - export declare function noProvImplicitLibraryFunction(): { ngModule: typeof LibraryModule; }; - ` - }, - { - name: _('/node_modules/test-package/typings/module.d.ts'), - contents: ` - export declare class ExternalModule {} - ` - }, - { - name: _('/node_modules/test-package/typings/core.d.ts'), - contents: ` - - export declare interface Type { - new (...args: any[]): T - } - export declare type Provider = any; - export declare interface ModuleWithProviders { - ngModule: Type - providers?: Provider[] - } - ` - }, - { - name: _('/node_modules/some-library/index.d.ts'), - contents: 'export declare class LibraryModule {}' - }, - { - name: _('/node_modules/test-package/typings/iife-wrapped.d.ts'), - contents: ` - import {ModuleWithProviders} from './core'; - export declare class WrappedClass { - static forRoot(): ModuleWithProviders; - }` - }, - ]; - loadTestFiles(TEST_PROGRAM); - loadTestFiles(TEST_DTS_PROGRAM); - const bundle = makeTestEntryPointBundle( - 'test-package', 'esm2015', false, getRootFiles(TEST_PROGRAM), - getRootFiles(TEST_DTS_PROGRAM)); - program = bundle.src.program; - dtsProgram = bundle.dts!; - const host = new Esm2015ReflectionHost(new MockLogger(), false, bundle.src, dtsProgram); - referencesRegistry = new NgccReferencesRegistry(host); - - const processDts = true; - const analyzer = new ModuleWithProvidersAnalyzer( - host, bundle.src.program.getTypeChecker(), referencesRegistry, processDts); - analyses = analyzer.analyzeProgram(program); - }); - - it('should ignore declarations that already have explicit NgModule type params', () => { - expect( - getAnalysisDescription(analyses, _('/node_modules/test-package/typings/explicit.d.ts'))) - .toEqual([]); - }); - - it('should find declarations that use `any` for the NgModule type param', () => { - const anyAnalysis = - getAnalysisDescription(analyses, _('/node_modules/test-package/typings/any.d.ts')); - expect(anyAnalysis).toContain(['anyInternalFunction', 'AnyInternalModule', null]); - expect(anyAnalysis).toContain(['anyExternalFunction', 'ExternalModule', null]); - expect(anyAnalysis).toContain(['anyLibraryFunction', 'LibraryModule', 'some-library']); - expect(anyAnalysis).toContain(['AnyClass.anyInternalMethod', 'AnyInternalModule', null]); - expect(anyAnalysis).toContain(['AnyClass.anyExternalMethod', 'ExternalModule', null]); - expect(anyAnalysis).toContain([ - 'AnyClass.anyLibraryMethod', 'LibraryModule', 'some-library' - ]); - }); - - it('should track internal module references in the references registry', () => { - const declarations = referencesRegistry.getDeclarationMap(); - const externalModuleDeclaration = getDeclaration( - program, absoluteFrom('/node_modules/test-package/src/module.js'), 'ExternalModule', - ts.isClassDeclaration); - const libraryModuleDeclaration = getDeclaration( - program, absoluteFrom('/node_modules/some-library/index.d.ts'), 'LibraryModule', - ts.isClassDeclaration); - expect(declarations.has(externalModuleDeclaration.name!)).toBe(true); - expect(declarations.has(libraryModuleDeclaration.name!)).toBe(false); - }); - - it('should find declarations that have implicit return types', () => { - const anyAnalysis = - getAnalysisDescription(analyses, _('/node_modules/test-package/typings/implicit.d.ts')); - expect(anyAnalysis).toContain(['implicitInternalFunction', 'ImplicitInternalModule', null]); - expect(anyAnalysis).toContain(['implicitExternalFunction', 'ExternalModule', null]); - expect(anyAnalysis).toContain(['implicitLibraryFunction', 'LibraryModule', 'some-library']); - expect(anyAnalysis).toContain([ - 'ImplicitClass.implicitInternalMethod', 'ImplicitInternalModule', null - ]); - expect(anyAnalysis).toContain([ - 'ImplicitClass.implicitExternalMethod', 'ExternalModule', null - ]); - expect(anyAnalysis).toContain([ - 'ImplicitClass.implicitLibraryMethod', 'LibraryModule', 'some-library' - ]); - }); - - - it('should find declarations that delegate by calling another function', () => { - const delegatedAnalysis = getAnalysisDescription( - analyses, _('/node_modules/test-package/typings/delegated.d.ts')); - - expect(delegatedAnalysis).toContain([ - 'delegatedExplicitInternalFunction', 'ExplicitInternalModule', null - ]); - expect(delegatedAnalysis).toContain([ - 'delegatedExplicitExternalFunction', 'ExternalModule', null - ]); - expect(delegatedAnalysis).toContain([ - 'delegatedExplicitLibraryFunction', 'LibraryModule', 'some-library' - ]); - expect(delegatedAnalysis).toContain([ - 'DelegatedExplicitClass.explicitInternalMethod', 'ExplicitInternalModule', null - ]); - expect(delegatedAnalysis).toContain([ - 'DelegatedExplicitClass.explicitExternalMethod', 'ExternalModule', null - ]); - expect(delegatedAnalysis).toContain([ - 'DelegatedExplicitClass.explicitLibraryMethod', 'LibraryModule', 'some-library' - ]); - - expect(delegatedAnalysis).toContain([ - 'delegatedImplicitInternalFunction', 'ImplicitInternalModule', null - ]); - expect(delegatedAnalysis).toContain([ - 'delegatedImplicitExternalFunction', 'ExternalModule', null - ]); - expect(delegatedAnalysis).toContain([ - 'delegatedImplicitLibraryFunction', 'LibraryModule', 'some-library' - ]); - expect(delegatedAnalysis).toContain([ - 'DelegatedImplicitClass.implicitInternalMethod', 'ImplicitInternalModule', null - ]); - expect(delegatedAnalysis).toContain([ - 'DelegatedImplicitClass.implicitExternalMethod', 'ExternalModule', null - ]); - expect(delegatedAnalysis).toContain([ - 'DelegatedImplicitClass.implicitLibraryMethod', 'LibraryModule', 'some-library' - ]); - - expect(delegatedAnalysis).toContain([ - 'delegatedAnyInternalFunction', 'AnyInternalModule', null - ]); - expect(delegatedAnalysis).toContain([ - 'delegatedAnyExternalFunction', 'ExternalModule', null - ]); - expect(delegatedAnalysis).toContain([ - 'delegatedAnyLibraryFunction', 'LibraryModule', 'some-library' - ]); - expect(delegatedAnalysis).toContain([ - 'DelegatedAnyClass.anyInternalMethod', 'AnyInternalModule', null - ]); - expect(delegatedAnalysis).toContain([ - 'DelegatedAnyClass.anyExternalMethod', 'ExternalModule', null - ]); - expect(delegatedAnalysis).toContain([ - 'DelegatedAnyClass.anyLibraryMethod', 'LibraryModule', 'some-library' - ]); - - expect(delegatedAnalysis).toContain(['withParams', 'ExplicitInternalModule', null]); - expect(delegatedAnalysis).toContain(['withOptionalParams', 'ExplicitInternalModule', null]); - expect(delegatedAnalysis).toContain(['doubleDelegation', 'ExplicitInternalModule', null]); - }); - - it('should find declarations that do not specify a `providers` property in the return type', - () => { - const anyAnalysis = getAnalysisDescription( - analyses, _('/node_modules/test-package/typings/no-providers.d.ts')); - expect(anyAnalysis).not.toContain([ - 'noProvExplicitInternalFunction', 'NoProvidersInternalModule' - ]); - expect(anyAnalysis).not.toContain([ - 'noProvExplicitExternalFunction', 'ExternalModule', null - ]); - expect(anyAnalysis).toContain([ - 'noProvAnyInternalFunction', 'NoProvidersInternalModule', null - ]); - expect(anyAnalysis).toContain(['noProvAnyExternalFunction', 'ExternalModule', null]); - expect(anyAnalysis).toContain([ - 'noProvAnyLibraryFunction', 'LibraryModule', 'some-library' - ]); - expect(anyAnalysis).toContain([ - 'noProvImplicitInternalFunction', 'NoProvidersInternalModule', null - ]); - expect(anyAnalysis).toContain([ - 'noProvImplicitExternalFunction', 'ExternalModule', null - ]); - expect(anyAnalysis).toContain([ - 'noProvImplicitLibraryFunction', 'LibraryModule', 'some-library' - ]); - }); - - it('should find declarations that reference an aliased IIFE wrapped class', () => { - const analysis = getAnalysisDescription( - analyses, _('/node_modules/test-package/typings/iife-wrapped.d.ts')); - expect(analysis).toContain(['WrappedClass.forRoot', 'WrappedClass', null]); - }); - - function getAnalysisDescription( - analyses: ModuleWithProvidersAnalyses, fileName: AbsoluteFsPath) { - const file = getSourceFileOrError(dtsProgram.program, fileName); - const analysis = analyses.get(file); - return analysis ? analysis.map( - info => - [getName(info.container) + info.declaration.name!.getText(), - (info.ngModule.node as ts.ClassDeclaration).name!.getText(), - info.ngModule.ownedByModuleGuess]) : - []; - } - - function getName(node: DeclarationNode|null): string { - return node && isNamedDeclaration(node) ? `${node.name.text}.` : ''; - } - }); - }); - - describe('tracking references when generic types already present', () => { - let _: typeof absoluteFrom; - let TEST_DTS_PROGRAM: TestFile[]; - let TEST_PROGRAM: TestFile[]; - - beforeEach(() => { - _ = absoluteFrom; - - TEST_PROGRAM = [ - { - name: _('/node_modules/test-package/src/entry-point.js'), - contents: ` - export * from './explicit'; - export * from './module'; - `, - }, - { - name: _('/node_modules/test-package/src/explicit.js'), - contents: ` - import {ExternalModule} from './module'; - import {LibraryModule} from 'some-library'; - export class ExplicitInternalModule {} - export class ExplicitClass { - static explicitInternalMethod() { - return { - ngModule: ExplicitInternalModule, - providers: [] - }; - } - static explicitExternalMethod() { - return { - ngModule: ExternalModule, - providers: [] - }; - } - static explicitLibraryMethod() { - return { - ngModule: LibraryModule, - providers: [] - }; - } - } - `, - }, - { - name: _('/node_modules/test-package/src/module.js'), - contents: ` - export class ExternalModule {} - `, - }, - { - name: _('/node_modules/some-library/index.d.ts'), - contents: 'export declare class LibraryModule {}', - }, - ]; - TEST_DTS_PROGRAM = [ - { - name: _('/node_modules/test-package/typings/entry-point.d.ts'), - contents: ` - export * from './explicit'; - export * from './module'; - `, - }, - { - name: _('/node_modules/test-package/typings/explicit.d.ts'), - contents: ` - import {ModuleWithProviders} from './core'; - import {ExternalModule} from './module'; - import {LibraryModule} from 'some-library'; - export declare class ExplicitInternalModule {} - export declare class ExplicitClass { - static explicitInternalMethod(): ModuleWithProviders; - static explicitExternalMethod(): ModuleWithProviders; - static explicitLibraryMethod(): ModuleWithProviders; - } - `, - }, - { - name: _('/node_modules/test-package/typings/module.d.ts'), - contents: ` - export declare class ExternalModule {} - `, - }, - { - name: _('/node_modules/test-package/typings/core.d.ts'), - contents: ` - - export declare interface Type { - new (...args: any[]): T - } - export declare type Provider = any; - export declare interface ModuleWithProviders { - ngModule: Type - providers?: Provider[] - } - `, - }, - { - name: _('/node_modules/some-library/index.d.ts'), - contents: 'export declare class LibraryModule {}', - }, - ]; - loadTestFiles(TEST_PROGRAM); - loadTestFiles(TEST_DTS_PROGRAM); - }); - - it('should track references even when nothing needs to be updated', () => { - const bundle = makeTestEntryPointBundle( - 'test-package', 'esm2015', false, getRootFiles(TEST_PROGRAM), - getRootFiles(TEST_DTS_PROGRAM)); - const program = bundle.src.program; - const dtsProgram = bundle.dts!; - const host = new Esm2015ReflectionHost(new MockLogger(), false, bundle.src, dtsProgram); - const referencesRegistry = new NgccReferencesRegistry(host); - - const processDts = true; - const analyzer = new ModuleWithProvidersAnalyzer( - host, bundle.src.program.getTypeChecker(), referencesRegistry, processDts); - const analyses = analyzer.analyzeProgram(program); - - const file = getSourceFileOrError( - dtsProgram.program, _('/node_modules/test-package/typings/explicit.d.ts')); - expect(analyses.has(file)).toBe(false); - - const declarations = referencesRegistry.getDeclarationMap(); - const explicitInternalModuleDeclaration = getDeclaration( - program, absoluteFrom('/node_modules/test-package/src/explicit.js'), - 'ExplicitInternalModule', ts.isClassDeclaration); - const externalModuleDeclaration = getDeclaration( - program, absoluteFrom('/node_modules/test-package/src/module.js'), 'ExternalModule', - ts.isClassDeclaration); - const libraryModuleDeclaration = getDeclaration( - program, absoluteFrom('/node_modules/some-library/index.d.ts'), 'LibraryModule', - ts.isClassDeclaration); - expect(declarations.has(explicitInternalModuleDeclaration.name!)).toBe(true); - expect(declarations.has(externalModuleDeclaration.name!)).toBe(true); - expect(declarations.has(libraryModuleDeclaration.name!)).toBe(false); - }); - - it('should track references even when typings have already been processed', () => { - const bundle = - makeTestEntryPointBundle('test-package', 'esm2015', false, getRootFiles(TEST_PROGRAM)); - const program = bundle.src.program; - const host = new Esm2015ReflectionHost(new MockLogger(), false, bundle.src, null); - const referencesRegistry = new NgccReferencesRegistry(host); - - const processDts = false; // Emulate the scenario where typings have already been processed - const analyzer = new ModuleWithProvidersAnalyzer( - host, bundle.src.program.getTypeChecker(), referencesRegistry, processDts); - const analyses = analyzer.analyzeProgram(program); - - expect(analyses.size).toBe(0); - - const declarations = referencesRegistry.getDeclarationMap(); - const explicitInternalModuleDeclaration = getDeclaration( - program, absoluteFrom('/node_modules/test-package/src/explicit.js'), - 'ExplicitInternalModule', ts.isClassDeclaration); - const externalModuleDeclaration = getDeclaration( - program, absoluteFrom('/node_modules/test-package/src/module.js'), 'ExternalModule', - ts.isClassDeclaration); - const libraryModuleDeclaration = getDeclaration( - program, absoluteFrom('/node_modules/some-library/index.d.ts'), 'LibraryModule', - ts.isClassDeclaration); - expect(declarations.has(explicitInternalModuleDeclaration.name!)).toBe(true); - expect(declarations.has(externalModuleDeclaration.name!)).toBe(true); - expect(declarations.has(libraryModuleDeclaration.name!)).toBe(false); - }); - }); -}); diff --git a/packages/compiler-cli/ngcc/test/analysis/ngcc_trait_compiler_spec.ts b/packages/compiler-cli/ngcc/test/analysis/ngcc_trait_compiler_spec.ts deleted file mode 100644 index bfcbdc9440e..00000000000 --- a/packages/compiler-cli/ngcc/test/analysis/ngcc_trait_compiler_spec.ts +++ /dev/null @@ -1,358 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ - -import {ErrorCode, makeDiagnostic, ngErrorCode} from '../../../src/ngtsc/diagnostics'; -import {absoluteFrom} from '../../../src/ngtsc/file_system'; -import {runInEachFileSystem} from '../../../src/ngtsc/file_system/testing'; -import {SemanticSymbol} from '../../../src/ngtsc/incremental/semantic_graph'; -import {MockLogger} from '../../../src/ngtsc/logging/testing'; -import {ClassDeclaration, Decorator, isNamedClassDeclaration} from '../../../src/ngtsc/reflection'; -import {getDeclaration, loadTestFiles} from '../../../src/ngtsc/testing'; -import {AnalysisOutput, CompileResult, DecoratorHandler, DetectResult, HandlerPrecedence, TraitState} from '../../../src/ngtsc/transform'; -import {NgccTraitCompiler} from '../../src/analysis/ngcc_trait_compiler'; -import {Esm2015ReflectionHost} from '../../src/host/esm2015_host'; -import {createComponentDecorator} from '../../src/migrations/utils'; -import {EntryPointBundle} from '../../src/packages/entry_point_bundle'; -import {makeTestEntryPointBundle} from '../helpers/utils'; -import {getTraitDiagnostics} from '../host/util'; - -runInEachFileSystem(() => { - describe('NgccTraitCompiler', () => { - let _: typeof absoluteFrom; - let mockClazz: any; - let injectedDecorator: any = {name: 'InjectedDecorator'}; - beforeEach(() => { - _ = absoluteFrom; - const mockSourceFile: any = { - fileName: _('/node_modules/some-package/entry-point/test-file.js'), - }; - mockClazz = { - name: {text: 'MockClazz'}, - getSourceFile: () => mockSourceFile, - getStart: () => 0, - getWidth: () => 0, - }; - }); - - function createCompiler({entryPoint, handlers}: { - entryPoint: EntryPointBundle; - handlers: DecoratorHandler[] - }) { - const reflectionHost = new Esm2015ReflectionHost(new MockLogger(), false, entryPoint.src); - return new NgccTraitCompiler(handlers, reflectionHost); - } - - describe('injectSyntheticDecorator()', () => { - it('should call `detect()` on each of the provided handlers', () => { - const log: string[] = []; - const handler1 = new TestHandler('handler1', log); - const handler2 = new TestHandler('handler2', log); - loadTestFiles([{name: _('/node_modules/test/index.js'), contents: ``}]); - const entryPoint = - makeTestEntryPointBundle('test', 'esm2015', false, [_('/node_modules/test/index.js')]); - const compiler = createCompiler({entryPoint, handlers: [handler1, handler2]}); - compiler.injectSyntheticDecorator(mockClazz, injectedDecorator); - expect(log).toEqual([ - `handler1:detect:MockClazz:InjectedDecorator`, - `handler2:detect:MockClazz:InjectedDecorator`, - ]); - }); - - it('should call `analyze()` on each of the provided handlers whose `detect()` call returns a result', - () => { - const log: string[] = []; - const handler1 = new TestHandler('handler1', log); - const handler2 = new AlwaysDetectHandler('handler2', log); - const handler3 = new TestHandler('handler3', log); - loadTestFiles([{name: _('/node_modules/test/index.js'), contents: ``}]); - const entryPoint = makeTestEntryPointBundle( - 'test', 'esm2015', false, [_('/node_modules/test/index.js')]); - const compiler = createCompiler({entryPoint, handlers: [handler1, handler2, handler3]}); - compiler.injectSyntheticDecorator(mockClazz, injectedDecorator); - expect(log).toEqual([ - `handler1:detect:MockClazz:InjectedDecorator`, - `handler2:detect:MockClazz:InjectedDecorator`, - `handler3:detect:MockClazz:InjectedDecorator`, - 'handler2:analyze:MockClazz', - ]); - }); - - it('should inject a new class record into the compilation', () => { - const injectedHandler = - new DetectDecoratorHandler('InjectedDecorator', HandlerPrecedence.WEAK); - loadTestFiles([{name: _('/node_modules/test/index.js'), contents: ``}]); - const entryPoint = - makeTestEntryPointBundle('test', 'esm2015', false, [_('/node_modules/test/index.js')]); - const compiler = createCompiler({entryPoint, handlers: [injectedHandler]}); - compiler.injectSyntheticDecorator(mockClazz, injectedDecorator); - - const record = compiler.recordFor(mockClazz); - expect(record).toBeDefined(); - expect(record!.traits.length).toBe(1); - }); - - it('should add a new trait to an existing class record', () => { - const directiveHandler = new DetectDecoratorHandler('Directive', HandlerPrecedence.WEAK); - const injectedHandler = - new DetectDecoratorHandler('InjectedDecorator', HandlerPrecedence.WEAK); - loadTestFiles([{ - name: _('/node_modules/test/index.js'), - contents: ` - import {Directive} from '@angular/core'; - - export class MyClass {}; - MyClass.decorators = [{ type: Directive }]; - ` - }]); - const entryPoint = - makeTestEntryPointBundle('test', 'esm2015', false, [_('/node_modules/test/index.js')]); - const compiler = - createCompiler({entryPoint, handlers: [directiveHandler, injectedHandler]}); - const myClass = getDeclaration( - entryPoint.src.program, _('/node_modules/test/index.js'), 'MyClass', - isNamedClassDeclaration); - - compiler.analyzeFile(entryPoint.src.file); - compiler.injectSyntheticDecorator(myClass, injectedDecorator); - - const record = compiler.recordFor(myClass)!; - expect(record).toBeDefined(); - expect(record.traits.length).toBe(2); - expect(record.traits[0].detected.decorator!.name).toBe('Directive'); - expect(record.traits[1].detected.decorator!.name).toBe('InjectedDecorator'); - }); - - it('should not add a weak handler when a primary handler already exists', () => { - const directiveHandler = new DetectDecoratorHandler('Directive', HandlerPrecedence.PRIMARY); - const injectedHandler = - new DetectDecoratorHandler('InjectedDecorator', HandlerPrecedence.WEAK); - loadTestFiles([{ - name: _('/node_modules/test/index.js'), - contents: ` - import {Directive} from '@angular/core'; - - export class MyClass {}; - MyClass.decorators = [{ type: Directive }]; - ` - }]); - const entryPoint = - makeTestEntryPointBundle('test', 'esm2015', false, [_('/node_modules/test/index.js')]); - const compiler = - createCompiler({entryPoint, handlers: [directiveHandler, injectedHandler]}); - const myClass = getDeclaration( - entryPoint.src.program, _('/node_modules/test/index.js'), 'MyClass', - isNamedClassDeclaration); - - compiler.analyzeFile(entryPoint.src.file); - - compiler.injectSyntheticDecorator(myClass, injectedDecorator); - - const record = compiler.recordFor(myClass)!; - expect(record).toBeDefined(); - expect(record.traits.length).toBe(1); - expect(record.traits[0].detected.decorator!.name).toBe('Directive'); - }); - - it('should replace an existing weak handler when injecting a primary handler', () => { - const directiveHandler = new DetectDecoratorHandler('Directive', HandlerPrecedence.WEAK); - const injectedHandler = - new DetectDecoratorHandler('InjectedDecorator', HandlerPrecedence.PRIMARY); - loadTestFiles([{ - name: _('/node_modules/test/index.js'), - contents: ` - import {Directive} from '@angular/core'; - - export class MyClass {}; - MyClass.decorators = [{ type: Directive }]; - ` - }]); - const entryPoint = - makeTestEntryPointBundle('test', 'esm2015', false, [_('/node_modules/test/index.js')]); - const compiler = - createCompiler({entryPoint, handlers: [directiveHandler, injectedHandler]}); - const myClass = getDeclaration( - entryPoint.src.program, _('/node_modules/test/index.js'), 'MyClass', - isNamedClassDeclaration); - - compiler.analyzeFile(entryPoint.src.file); - - compiler.injectSyntheticDecorator(myClass, injectedDecorator); - - const record = compiler.recordFor(myClass)!; - expect(record).toBeDefined(); - expect(record.traits.length).toBe(1); - expect(record.traits[0].detected.decorator!.name).toBe('InjectedDecorator'); - }); - - it('should produce an error when a primary handler is added when a primary handler is already present', - () => { - const directiveHandler = - new DetectDecoratorHandler('Directive', HandlerPrecedence.PRIMARY); - const injectedHandler = - new DetectDecoratorHandler('InjectedDecorator', HandlerPrecedence.PRIMARY); - loadTestFiles([{ - name: _('/node_modules/test/index.js'), - contents: ` - import {Directive} from '@angular/core'; - - export class MyClass {}; - MyClass.decorators = [{ type: Directive }]; - ` - }]); - const entryPoint = makeTestEntryPointBundle( - 'test', 'esm2015', false, [_('/node_modules/test/index.js')]); - const compiler = - createCompiler({entryPoint, handlers: [directiveHandler, injectedHandler]}); - const myClass = getDeclaration( - entryPoint.src.program, _('/node_modules/test/index.js'), 'MyClass', - isNamedClassDeclaration); - - compiler.analyzeFile(entryPoint.src.file); - - compiler.injectSyntheticDecorator(myClass, injectedDecorator); - - const record = compiler.recordFor(myClass)!; - expect(record).toBeDefined(); - expect(record.metaDiagnostics).toBeDefined(); - expect(record.metaDiagnostics!.length).toBe(1); - expect(record.metaDiagnostics![0].code).toBe(ngErrorCode(ErrorCode.DECORATOR_COLLISION)); - expect(record.traits.length).toBe(0); - }); - - it('should report diagnostics from handlers', () => { - const log: string[] = []; - const handler = new DiagnosticProducingHandler('handler', log); - loadTestFiles([{name: _('/node_modules/test/index.js'), contents: ``}]); - const entryPoint = - makeTestEntryPointBundle('test', 'esm2015', false, [_('/node_modules/test/index.js')]); - const compiler = createCompiler({entryPoint, handlers: [handler]}); - const decorator = createComponentDecorator(mockClazz, {selector: 'comp', exportAs: null}); - compiler.injectSyntheticDecorator(mockClazz, decorator); - - const record = compiler.recordFor(mockClazz)!; - const migratedTrait = record.traits[0]; - const diagnostics = getTraitDiagnostics(migratedTrait); - if (diagnostics === null) { - return fail('Expected migrated class trait to be in an error state'); - } - - expect(diagnostics.length).toBe(1); - expect(diagnostics[0].messageText).toEqual(`test diagnostic`); - }); - }); - - - - describe('getAllDecorators', () => { - it('should be null for classes without decorators', () => { - loadTestFiles( - [{name: _('/node_modules/test/index.js'), contents: `export class MyClass {};`}]); - const entryPoint = - makeTestEntryPointBundle('test', 'esm2015', false, [_('/node_modules/test/index.js')]); - const compiler = createCompiler({entryPoint, handlers: []}); - const myClass = getDeclaration( - entryPoint.src.program, _('/node_modules/test/index.js'), 'MyClass', - isNamedClassDeclaration); - - const decorators = compiler.getAllDecorators(myClass); - expect(decorators).toBeNull(); - }); - - it('should include injected decorators', () => { - const directiveHandler = new DetectDecoratorHandler('Directive', HandlerPrecedence.WEAK); - const injectedHandler = - new DetectDecoratorHandler('InjectedDecorator', HandlerPrecedence.WEAK); - loadTestFiles([{ - name: _('/node_modules/test/index.js'), - contents: ` - import {Directive} from '@angular/core'; - - export class MyClass {}; - MyClass.decorators = [{ type: Directive }]; - ` - }]); - const entryPoint = - makeTestEntryPointBundle('test', 'esm2015', false, [_('/node_modules/test/index.js')]); - const compiler = - createCompiler({entryPoint, handlers: [directiveHandler, injectedHandler]}); - const myClass = getDeclaration( - entryPoint.src.program, _('/node_modules/test/index.js'), 'MyClass', - isNamedClassDeclaration); - - compiler.analyzeFile(entryPoint.src.file); - - compiler.injectSyntheticDecorator(myClass, injectedDecorator); - - const decorators = compiler.getAllDecorators(myClass)!; - expect(decorators.length).toBe(2); - expect(decorators[0].name).toBe('Directive'); - expect(decorators[1].name).toBe('InjectedDecorator'); - }); - }); - }); -}); - -class TestHandler implements DecoratorHandler { - constructor(readonly name: string, protected log: string[]) {} - - precedence = HandlerPrecedence.PRIMARY; - - detect(node: ClassDeclaration, decorators: Decorator[]|null): DetectResult|undefined { - this.log.push(`${this.name}:detect:${node.name.text}:${decorators!.map(d => d.name)}`); - return undefined; - } - - analyze(node: ClassDeclaration): AnalysisOutput { - this.log.push(this.name + ':analyze:' + node.name.text); - return {}; - } - - symbol(node: ClassDeclaration, analysis: Readonly): null { - return null; - } - - compileFull(node: ClassDeclaration): CompileResult|CompileResult[] { - this.log.push(this.name + ':compile:' + node.name.text); - return []; - } -} - -class AlwaysDetectHandler extends TestHandler { - override detect(node: ClassDeclaration, decorators: Decorator[]|null): - DetectResult|undefined { - super.detect(node, decorators); - const decorator = decorators !== null ? decorators[0] : null; - return {trigger: node, decorator, metadata: {}}; - } -} - -class DetectDecoratorHandler extends TestHandler { - constructor(private decorator: string, override readonly precedence: HandlerPrecedence) { - super(decorator, []); - } - - override detect(node: ClassDeclaration, decorators: Decorator[]|null): - DetectResult|undefined { - super.detect(node, decorators); - if (decorators === null) { - return undefined; - } - const decorator = decorators.find(decorator => decorator.name === this.decorator); - if (decorator === undefined) { - return undefined; - } - return {trigger: node, decorator, metadata: {}}; - } -} - -class DiagnosticProducingHandler extends AlwaysDetectHandler { - override analyze(node: ClassDeclaration): AnalysisOutput { - super.analyze(node); - return {diagnostics: [makeDiagnostic(9999, node, 'test diagnostic')]}; - } -} diff --git a/packages/compiler-cli/ngcc/test/analysis/private_declarations_analyzer_spec.ts b/packages/compiler-cli/ngcc/test/analysis/private_declarations_analyzer_spec.ts deleted file mode 100644 index 999628db2c6..00000000000 --- a/packages/compiler-cli/ngcc/test/analysis/private_declarations_analyzer_spec.ts +++ /dev/null @@ -1,196 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import ts from 'typescript'; - -import {absoluteFrom, AbsoluteFsPath} from '../../../src/ngtsc/file_system'; -import {runInEachFileSystem, TestFile} from '../../../src/ngtsc/file_system/testing'; -import {Reference} from '../../../src/ngtsc/imports'; -import {MockLogger} from '../../../src/ngtsc/logging/testing'; -import {getDeclaration, loadTestFiles} from '../../../src/ngtsc/testing'; -import {NgccReferencesRegistry} from '../../src/analysis/ngcc_references_registry'; -import {PrivateDeclarationsAnalyzer} from '../../src/analysis/private_declarations_analyzer'; -import {Esm2015ReflectionHost} from '../../src/host/esm2015_host'; -import {getRootFiles, makeTestEntryPointBundle} from '../helpers/utils'; - -runInEachFileSystem(() => { - describe('PrivateDeclarationsAnalyzer', () => { - describe('analyzeProgram()', () => { - it('should find all NgModule declarations that were not publicly exported from the entry-point', - () => { - const _ = absoluteFrom; - - const TEST_PROGRAM: TestFile[] = [ - { - name: _('/node_modules/test-package/src/entry_point.js'), - isRoot: true, - contents: ` - export {PublicComponent} from './a'; - export {ModuleA} from './mod'; - export {ModuleB} from './b'; - ` - }, - { - name: _('/node_modules/test-package/src/a.js'), - isRoot: false, - contents: ` - import {Component} from '@angular/core'; - export class PublicComponent {} - PublicComponent.decorators = [ - {type: Component, args: [{selectors: 'a', template: ''}]} - ]; - ` - }, - { - name: _('/node_modules/test-package/src/b.js'), - isRoot: false, - contents: ` - import {Component, NgModule} from '@angular/core'; - class PrivateComponent1 {} - PrivateComponent1.decorators = [ - {type: Component, args: [{selectors: 'b', template: ''}]} - ]; - class PrivateComponent2 {} - PrivateComponent2.decorators = [ - {type: Component, args: [{selectors: 'c', template: ''}]} - ]; - export class ModuleB {} - ModuleB.decorators = [ - {type: NgModule, args: [{declarations: [PrivateComponent1]}]} - ]; - ` - }, - { - name: _('/node_modules/test-package/src/c.js'), - isRoot: false, - contents: ` - import {Component} from '@angular/core'; - export class InternalComponent1 {} - InternalComponent1.decorators = [ - {type: Component, args: [{selectors: 'd', template: ''}]} - ]; - export class InternalComponent2 {} - InternalComponent2.decorators = [ - {type: Component, args: [{selectors: 'e', template: ''}]} - ]; - ` - }, - { - name: _('/node_modules/test-package/src/mod.js'), - isRoot: false, - contents: ` - import {Component, NgModule} from '@angular/core'; - import {PublicComponent} from './a'; - import {ModuleB} from './b'; - import {InternalComponent1} from './c'; - export class ModuleA {} - ModuleA.decorators = [ - {type: NgModule, args: [{ - declarations: [PublicComponent, InternalComponent1], - imports: [ModuleB] - }]} - ]; - ` - } - ]; - const TEST_DTS_PROGRAM = [ - { - name: _('/node_modules/test-package/typings/entry_point.d.ts'), - isRoot: true, - contents: ` - export {PublicComponent} from './a'; - export {ModuleA} from './mod'; - export {ModuleB} from './b'; - ` - }, - { - name: _('/node_modules/test-package/typings/a.d.ts'), - isRoot: false, - contents: ` - export declare class PublicComponent {} - ` - }, - { - name: _('/node_modules/test-package/typings/b.d.ts'), - isRoot: false, - contents: ` - export declare class ModuleB {} - ` - }, - { - name: _('/node_modules/test-package/typings/c.d.ts'), - isRoot: false, - contents: ` - export declare class InternalComponent1 {} - ` - }, - { - name: _('/node_modules/test-package/typings/mod.d.ts'), - isRoot: false, - contents: ` - import {PublicComponent} from './a'; - import {ModuleB} from './b'; - import {InternalComponent1} from './c'; - export declare class ModuleA {} - ` - }, - ]; - const {program, referencesRegistry, analyzer} = setup(TEST_PROGRAM, TEST_DTS_PROGRAM); - - addToReferencesRegistry( - program, referencesRegistry, _('/node_modules/test-package/src/a.js'), - 'PublicComponent'); - addToReferencesRegistry( - program, referencesRegistry, _('/node_modules/test-package/src/b.js'), - 'PrivateComponent1'); - addToReferencesRegistry( - program, referencesRegistry, _('/node_modules/test-package/src/c.js'), - 'InternalComponent1'); - - const analyses = analyzer.analyzeProgram(program); - // Note that `PrivateComponent2` and `InternalComponent2` are not found because they are - // not added to the ReferencesRegistry (i.e. they were not declared in an NgModule). - expect(analyses.length).toEqual(2); - expect(analyses).toEqual([ - { - identifier: 'PrivateComponent1', - from: _('/node_modules/test-package/src/b.js'), - dtsFrom: null, - }, - { - identifier: 'InternalComponent1', - from: _('/node_modules/test-package/src/c.js'), - dtsFrom: _('/node_modules/test-package/typings/c.d.ts'), - }, - ]); - }); - }); - }); - - function setup(jsProgram: TestFile[], dtsProgram: TestFile[]) { - loadTestFiles(jsProgram); - loadTestFiles(dtsProgram); - const {src, dts} = makeTestEntryPointBundle( - 'test-package', 'esm2015', false, getRootFiles(jsProgram), getRootFiles(dtsProgram)); - const host = new Esm2015ReflectionHost(new MockLogger(), false, src, dts); - const referencesRegistry = new NgccReferencesRegistry(host); - const analyzer = new PrivateDeclarationsAnalyzer(host, referencesRegistry); - return {program: src.program, referencesRegistry, analyzer}; - } - - /** - * Add up the named component to the references registry. - * - * This would normally be done by the decoration handlers in the `DecorationAnalyzer`. - */ - function addToReferencesRegistry( - program: ts.Program, registry: NgccReferencesRegistry, fileName: AbsoluteFsPath, - componentName: string) { - const declaration = getDeclaration(program, fileName, componentName, ts.isClassDeclaration); - registry.add(null!, new Reference(declaration)); - } -}); diff --git a/packages/compiler-cli/ngcc/test/analysis/references_registry_spec.ts b/packages/compiler-cli/ngcc/test/analysis/references_registry_spec.ts deleted file mode 100644 index 4bdcf5e9a18..00000000000 --- a/packages/compiler-cli/ngcc/test/analysis/references_registry_spec.ts +++ /dev/null @@ -1,66 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import ts from 'typescript'; - -import {absoluteFrom} from '../../../src/ngtsc/file_system'; -import {runInEachFileSystem, TestFile} from '../../../src/ngtsc/file_system/testing'; -import {Reference} from '../../../src/ngtsc/imports'; -import {PartialEvaluator} from '../../../src/ngtsc/partial_evaluator'; -import {DeclarationNode, TypeScriptReflectionHost} from '../../../src/ngtsc/reflection'; -import {getDeclaration, loadTestFiles} from '../../../src/ngtsc/testing'; -import {NgccReferencesRegistry} from '../../src/analysis/ngcc_references_registry'; -import {makeTestBundleProgram} from '../helpers/utils'; - -runInEachFileSystem(() => { - describe('NgccReferencesRegistry', () => { - it('should return a mapping from resolved reference identifiers to their declarations', () => { - const _ = absoluteFrom; - const TEST_FILES: TestFile[] = [{ - name: _('/index.ts'), - contents: ` - export class SomeClass {} - export function someFunction() {} - export const someVariable = 42; - - export const testArray = [SomeClass, someFunction, someVariable]; - ` - }]; - loadTestFiles(TEST_FILES); - const {program} = makeTestBundleProgram(TEST_FILES[0].name); - - const checker = program.getTypeChecker(); - - const indexPath = _('/index.ts'); - const testArrayDeclaration = - getDeclaration(program, indexPath, 'testArray', ts.isVariableDeclaration); - const someClassDecl = getDeclaration(program, indexPath, 'SomeClass', ts.isClassDeclaration); - const someFunctionDecl = - getDeclaration(program, indexPath, 'someFunction', ts.isFunctionDeclaration); - const someVariableDecl = - getDeclaration(program, indexPath, 'someVariable', ts.isVariableDeclaration); - const testArrayExpression = testArrayDeclaration.initializer!; - - const reflectionHost = new TypeScriptReflectionHost(checker); - const evaluator = new PartialEvaluator(reflectionHost, checker, /* dependencyTracker */ null); - const registry = new NgccReferencesRegistry(reflectionHost); - - const references = (evaluator.evaluate(testArrayExpression) as any[]).filter(isReference); - registry.add(null!, ...references); - - const map = registry.getDeclarationMap(); - expect(map.size).toEqual(2); - expect(map.get(someClassDecl.name!)!.node).toBe(someClassDecl); - expect(map.get(someFunctionDecl.name!)!.node).toBe(someFunctionDecl); - expect(map.has(someVariableDecl.name as ts.Identifier)).toBe(false); - }); - }); - - function isReference(ref: any): ref is Reference { - return ref instanceof Reference; - } -}); diff --git a/packages/compiler-cli/ngcc/test/analysis/util_spec.ts b/packages/compiler-cli/ngcc/test/analysis/util_spec.ts deleted file mode 100644 index 91287c5b9c1..00000000000 --- a/packages/compiler-cli/ngcc/test/analysis/util_spec.ts +++ /dev/null @@ -1,42 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import {absoluteFrom} from '../../../src/ngtsc/file_system'; -import {runInEachFileSystem} from '../../../src/ngtsc/file_system/testing'; -import {isWithinPackage} from '../../src/analysis/util'; - -runInEachFileSystem(() => { - describe('isWithinPackage', () => { - let _: typeof absoluteFrom; - - beforeEach(() => _ = absoluteFrom); - - it('should return true if the source-file is contained in the package', () => { - const packagePath = _('/node_modules/test'); - const file = _('/node_modules/test/src/index.js'); - expect(isWithinPackage(packagePath, file)).toBe(true); - }); - - it('should return false if the source-file is not contained in the package', () => { - const packagePath = _('/node_modules/test'); - const file = _('/node_modules/other/src/index.js'); - expect(isWithinPackage(packagePath, file)).toBe(false); - }); - - it('should return false if the source-file is inside the package\'s `node_modules/`', () => { - const packagePath = _('/node_modules/test'); - - // An external file inside the package's `node_modules/`. - const file1 = _('/node_modules/test/node_modules/other/src/index.js'); - expect(isWithinPackage(packagePath, file1)).toBe(false); - - // An internal file starting with `node_modules`. - const file2 = _('/node_modules/test/node_modules_optimizer.js'); - expect(isWithinPackage(packagePath, file2)).toBe(true); - }); - }); -}); diff --git a/packages/compiler-cli/ngcc/test/dependencies/commonjs_dependency_host_spec.ts b/packages/compiler-cli/ngcc/test/dependencies/commonjs_dependency_host_spec.ts deleted file mode 100644 index 8094dda73ff..00000000000 --- a/packages/compiler-cli/ngcc/test/dependencies/commonjs_dependency_host_spec.ts +++ /dev/null @@ -1,467 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import ts from 'typescript'; -import {absoluteFrom, getFileSystem, relativeFrom} from '../../../src/ngtsc/file_system'; -import {runInEachFileSystem} from '../../../src/ngtsc/file_system/testing'; -import {loadTestFiles} from '../../../src/ngtsc/testing'; -import {CommonJsDependencyHost} from '../../src/dependencies/commonjs_dependency_host'; -import {createDependencyInfo} from '../../src/dependencies/dependency_host'; -import {ModuleResolver} from '../../src/dependencies/module_resolver'; - -runInEachFileSystem(() => { - describe('CommonJsDependencyHost', () => { - let _: typeof absoluteFrom; - let host: CommonJsDependencyHost; - - beforeEach(() => { - _ = absoluteFrom; - loadTestFiles([ - { - name: _('/no/imports/or/re-exports/index.js'), - contents: '// some text but no import-like statements' - }, - {name: _('/no/imports/or/re-exports/package.json'), contents: '{"main": "./index.js"}'}, - {name: _('/no/imports/or/re-exports/index.metadata.json'), contents: 'MOCK METADATA'}, - {name: _('/external/imports/index.js'), contents: commonJs(['lib_1', 'lib_1/sub_1'])}, - {name: _('/external/imports/package.json'), contents: '{"main": "./index.js"}'}, - {name: _('/external/imports/index.metadata.json'), contents: 'MOCK METADATA'}, - { - name: _('/external/re-exports/index.js'), - contents: commonJs(['lib_1', 'lib_1/sub_1'], ['lib_1.X', 'lib_1sub_1.Y']) - }, - {name: _('/external/re-exports/package.json'), contents: '{"main": "./index.js"}'}, - {name: _('/external/re-exports/index.metadata.json'), contents: 'MOCK METADATA'}, - {name: _('/external/imports-missing/index.js'), contents: commonJs(['lib_1', 'missing'])}, - {name: _('/external/imports-missing/package.json'), contents: '{"main": "./index.js"}'}, - {name: _('/external/imports-missing/index.metadata.json'), contents: 'MOCK METADATA'}, - {name: _('/external/deep-import/index.js'), contents: commonJs(['lib_1/deep/import'])}, - {name: _('/external/deep-import/package.json'), contents: '{"main": "./index.js"}'}, - {name: _('/external/deep-import/index.metadata.json'), contents: 'MOCK METADATA'}, - {name: _('/internal/outer/index.js'), contents: commonJs(['../inner'])}, - {name: _('/internal/outer/package.json'), contents: '{"main": "./index.js"}'}, - {name: _('/internal/outer/index.metadata.json'), contents: 'MOCK METADATA'}, - {name: _('/internal/inner/index.js'), contents: commonJs(['lib_1/sub_1'], ['X'])}, - { - name: _('/internal/circular_a/index.js'), - contents: commonJs(['../circular_b', 'lib_1/sub_1'], ['Y']) - }, - { - name: _('/internal/circular_b/index.js'), - contents: commonJs(['../circular_a', 'lib_1'], ['X']) - }, - {name: _('/internal/circular_a/package.json'), contents: '{"main": "./index.js"}'}, - {name: _('/internal/circular_a/index.metadata.json'), contents: 'MOCK METADATA'}, - {name: _('/re-directed/index.js'), contents: commonJs(['lib_1/sub_2'])}, - {name: _('/re-directed/package.json'), contents: '{"main": "./index.js"}'}, - {name: _('/re-directed/index.metadata.json'), contents: 'MOCK METADATA'}, - { - name: _('/path-alias/index.js'), - contents: commonJs(['@app/components', '@app/shared', '@lib/shared/test', 'lib_1']) - }, - {name: _('/path-alias/package.json'), contents: '{"main": "./index.js"}'}, - {name: _('/path-alias/index.metadata.json'), contents: 'MOCK METADATA'}, - {name: _('/node_modules/lib_1/index.d.ts'), contents: 'export declare class X {}'}, - { - name: _('/node_modules/lib_1/package.json'), - contents: '{"esm2015": "./index.js", "typings": "./index.d.ts"}' - }, - {name: _('/node_modules/lib_1/index.metadata.json'), contents: 'MOCK METADATA'}, - { - name: _('/node_modules/lib_1/deep/import/index.js'), - contents: 'export class DeepImport {}' - }, - {name: _('/node_modules/lib_1/sub_1/index.d.ts'), contents: 'export declare class Y {}'}, - { - name: _('/node_modules/lib_1/sub_1/package.json'), - contents: '{"esm2015": "./index.js", "typings": "./index.d.ts"}' - }, - {name: _('/node_modules/lib_1/sub_1/index.metadata.json'), contents: 'MOCK METADATA'}, - {name: _('/node_modules/lib_1/sub_2.d.ts'), contents: `export * from './sub_2/sub_2';`}, - {name: _('/node_modules/lib_1/sub_2/sub_2.d.ts'), contents: `export declare class Z {}';`}, - { - name: _('/node_modules/lib_1/sub_2/package.json'), - contents: '{"esm2015": "./sub_2.js", "typings": "./sub_2.d.ts"}' - }, - {name: _('/node_modules/lib_1/sub_2/sub_2.metadata.json'), contents: 'MOCK METADATA'}, - {name: _('/dist/components/index.d.ts'), contents: `export declare class MyComponent {};`}, - { - name: _('/dist/components/package.json'), - contents: '{"esm2015": "./index.js", "typings": "./index.d.ts"}' - }, - {name: _('/dist/components/index.metadata.json'), contents: 'MOCK METADATA'}, - { - name: _('/dist/shared/index.d.ts'), - contents: `import {X} from 'lib_1';\nexport declare class Service {}` - }, - { - name: _('/dist/shared/package.json'), - contents: '{"esm2015": "./index.js", "typings": "./index.d.ts"}' - }, - {name: _('/dist/shared/index.metadata.json'), contents: 'MOCK METADATA'}, - {name: _('/dist/lib/shared/test/index.d.ts'), contents: `export class TestHelper {}`}, - { - name: _('/dist/lib/shared/test/package.json'), - contents: '{"esm2015": "./index.js", "typings": "./index.d.ts"}' - }, - {name: _('/dist/lib/shared/test/index.metadata.json'), contents: 'MOCK METADATA'}, - ]); - const fs = getFileSystem(); - host = new CommonJsDependencyHost(fs, new ModuleResolver(fs)); - }); - - describe('collectDependencies()', () => { - it('should not generate a TS AST if the source does not contain any require calls', () => { - spyOn(ts, 'createSourceFile'); - host.collectDependencies(_('/no/imports/or/re-exports/index.js'), createDependencyInfo()); - expect(ts.createSourceFile).not.toHaveBeenCalled(); - }); - - it('should resolve all the external imports of the source file', () => { - const {dependencies, missing, deepImports} = createDependencyInfo(); - host.collectDependencies( - _('/external/imports/index.js'), {dependencies, missing, deepImports}); - expect(dependencies.size).toBe(2); - expect(missing.size).toBe(0); - expect(deepImports.size).toBe(0); - expect(dependencies.has(_('/node_modules/lib_1'))).toBe(true); - expect(dependencies.has(_('/node_modules/lib_1/sub_1'))).toBe(true); - }); - - it('should resolve all the external re-exports of the source file', () => { - const {dependencies, missing, deepImports} = createDependencyInfo(); - host.collectDependencies( - _('/external/re-exports/index.js'), {dependencies, missing, deepImports}); - expect(dependencies.size).toBe(2); - expect(missing.size).toBe(0); - expect(deepImports.size).toBe(0); - expect(dependencies.has(_('/node_modules/lib_1'))).toBe(true); - expect(dependencies.has(_('/node_modules/lib_1/sub_1'))).toBe(true); - }); - - it('should recognize imports in a variable declaration list', () => { - loadTestFiles([ - { - name: _('/test/index.js'), - contents: commonJs({ - varDeclarations: [ - ['lib_1/sub_1', 'lib_1/sub_2'], - ], - }), - }, - {name: _('/test/package.json'), contents: '{"main": "./index.js"}'}, - {name: _('/test/index.metadata.json'), contents: 'MOCK METADATA'}, - ]); - - const {dependencies, missing, deepImports} = createDependencyInfo(); - host.collectDependencies(_('/test/index.js'), {dependencies, missing, deepImports}); - - expect(dependencies.size).toBe(2); - expect(missing.size).toBe(0); - expect(deepImports.size).toBe(0); - expect(dependencies.has(_('/node_modules/lib_1/sub_1'))).toBe(true); - expect(dependencies.has(_('/node_modules/lib_1/sub_2'))).toBe(true); - }); - - it('should recognize imports as property assignments (on existing object)', () => { - loadTestFiles([ - { - name: _('/test/index.js'), - contents: commonJs({ - propAssignment: ['lib_1/sub_1', 'lib_1/sub_2'], - }), - }, - {name: _('/test/package.json'), contents: '{"main": "./index.js"}'}, - {name: _('/test/index.metadata.json'), contents: 'MOCK METADATA'}, - ]); - - const {dependencies, missing, deepImports} = createDependencyInfo(); - host.collectDependencies(_('/test/index.js'), {dependencies, missing, deepImports}); - - expect(dependencies.size).toBe(2); - expect(missing.size).toBe(0); - expect(deepImports.size).toBe(0); - expect(dependencies.has(_('/node_modules/lib_1/sub_1'))).toBe(true); - expect(dependencies.has(_('/node_modules/lib_1/sub_2'))).toBe(true); - }); - - it('should recognize imports as property assignments (in object literal)', () => { - loadTestFiles([ - { - name: _('/test/index.js'), - contents: commonJs({ - inObjectLiteral: ['lib_1/sub_1', 'lib_1/sub_2'], - }), - }, - {name: _('/test/package.json'), contents: '{"main": "./index.js"}'}, - {name: _('/test/index.metadata.json'), contents: 'MOCK METADATA'}, - ]); - - const {dependencies, missing, deepImports} = createDependencyInfo(); - host.collectDependencies(_('/test/index.js'), {dependencies, missing, deepImports}); - - expect(dependencies.size).toBe(2); - expect(missing.size).toBe(0); - expect(deepImports.size).toBe(0); - expect(dependencies.has(_('/node_modules/lib_1/sub_1'))).toBe(true); - expect(dependencies.has(_('/node_modules/lib_1/sub_2'))).toBe(true); - }); - - it('should recognize imports used for their side-effects only', () => { - loadTestFiles([ - { - name: _('/test/index.js'), - contents: commonJs({ - forSideEffects: ['lib_1/sub_1', 'lib_1/sub_2'], - }), - }, - {name: _('/test/package.json'), contents: '{"main": "./index.js"}'}, - {name: _('/test/index.metadata.json'), contents: 'MOCK METADATA'}, - ]); - - const {dependencies, missing, deepImports} = createDependencyInfo(); - host.collectDependencies(_('/test/index.js'), {dependencies, missing, deepImports}); - - expect(dependencies.size).toBe(2); - expect(missing.size).toBe(0); - expect(deepImports.size).toBe(0); - expect(dependencies.has(_('/node_modules/lib_1/sub_1'))).toBe(true); - expect(dependencies.has(_('/node_modules/lib_1/sub_2'))).toBe(true); - }); - - it('should recognize star re-exports (with both emitted and imported helpers)', () => { - loadTestFiles([ - { - name: _('/test/index.js'), - contents: commonJs({ - reExportsWithEmittedHelper: ['lib_1', 'lib_1/sub_1'], - reExportsWithImportedHelper: ['lib_1', 'lib_1/sub_2'], - }), - }, - {name: _('/test/package.json'), contents: '{"main": "./index.js"}'}, - {name: _('/test/index.metadata.json'), contents: 'MOCK METADATA'}, - ]); - - const {dependencies, missing, deepImports} = createDependencyInfo(); - host.collectDependencies(_('/test/index.js'), {dependencies, missing, deepImports}); - - expect(dependencies.size).toBe(3); - expect(missing.size).toBe(0); - expect(deepImports.size).toBe(0); - expect(dependencies.has(_('/node_modules/lib_1'))).toBe(true); - expect(dependencies.has(_('/node_modules/lib_1/sub_1'))).toBe(true); - expect(dependencies.has(_('/node_modules/lib_1/sub_2'))).toBe(true); - }); - - it('should not get confused by re-exports with a separate `require()` call', () => { - loadTestFiles([ - { - name: _('/test/index.js'), - contents: commonJs({ - reExportsWithoutRequire: ['lib_1', 'lib_1/sub_2'], - }), - }, - {name: _('/test/package.json'), contents: '{"main": "./index.js"}'}, - {name: _('/test/index.metadata.json'), contents: 'MOCK METADATA'}, - ]); - - const {dependencies, missing, deepImports} = createDependencyInfo(); - host.collectDependencies(_('/test/index.js'), {dependencies, missing, deepImports}); - - expect(dependencies.size).toBe(2); - expect(missing.size).toBe(0); - expect(deepImports.size).toBe(0); - expect(dependencies.has(_('/node_modules/lib_1'))).toBe(true); - expect(dependencies.has(_('/node_modules/lib_1/sub_2'))).toBe(true); - }); - - it('should capture missing external imports', () => { - const {dependencies, missing, deepImports} = createDependencyInfo(); - host.collectDependencies( - _('/external/imports-missing/index.js'), {dependencies, missing, deepImports}); - - expect(dependencies.size).toBe(1); - expect(dependencies.has(_('/node_modules/lib_1'))).toBe(true); - expect(missing.size).toBe(1); - expect(missing.has(relativeFrom('missing'))).toBe(true); - expect(deepImports.size).toBe(0); - }); - - it('should not register deep imports as missing', () => { - // This scenario verifies the behavior of the dependency analysis when an external import - // is found that does not map to an entry-point but still exists on disk, i.e. a deep - // import. Such deep imports are captured for diagnostics purposes. - const {dependencies, missing, deepImports} = createDependencyInfo(); - host.collectDependencies( - _('/external/deep-import/index.js'), {dependencies, missing, deepImports}); - - expect(dependencies.size).toBe(0); - expect(missing.size).toBe(0); - expect(deepImports.size).toBe(1); - expect(deepImports.has(_('/node_modules/lib_1/deep/import'))).toBe(true); - }); - - it('should recurse into internal dependencies', () => { - const {dependencies, missing, deepImports} = createDependencyInfo(); - host.collectDependencies( - _('/internal/outer/index.js'), {dependencies, missing, deepImports}); - - expect(dependencies.size).toBe(1); - expect(dependencies.has(_('/node_modules/lib_1/sub_1'))).toBe(true); - expect(missing.size).toBe(0); - expect(deepImports.size).toBe(0); - }); - - it('should handle circular internal dependencies', () => { - const {dependencies, missing, deepImports} = createDependencyInfo(); - host.collectDependencies( - _('/internal/circular_a/index.js'), {dependencies, missing, deepImports}); - expect(dependencies.size).toBe(2); - expect(dependencies.has(_('/node_modules/lib_1'))).toBe(true); - expect(dependencies.has(_('/node_modules/lib_1/sub_1'))).toBe(true); - expect(missing.size).toBe(0); - expect(deepImports.size).toBe(0); - }); - - it('should support `paths` alias mappings when resolving modules', () => { - const fs = getFileSystem(); - host = new CommonJsDependencyHost(fs, new ModuleResolver(fs, { - baseUrl: '/dist', - paths: { - '@app/*': ['*'], - '@lib/*/test': ['lib/*/test'], - } - })); - const {dependencies, missing, deepImports} = createDependencyInfo(); - host.collectDependencies(_('/path-alias/index.js'), {dependencies, missing, deepImports}); - expect(dependencies.size).toBe(4); - expect(dependencies.has(_('/dist/components'))).toBe(true); - expect(dependencies.has(_('/dist/shared'))).toBe(true); - expect(dependencies.has(_('/dist/lib/shared/test'))).toBe(true); - expect(dependencies.has(_('/node_modules/lib_1'))).toBe(true); - expect(missing.size).toBe(0); - expect(deepImports.size).toBe(0); - }); - - it('should handle entry-point paths with no extension', () => { - const {dependencies, missing, deepImports} = createDependencyInfo(); - host.collectDependencies( - _('/external/imports/index'), {dependencies, missing, deepImports}); - expect(dependencies.size).toBe(2); - expect(missing.size).toBe(0); - expect(deepImports.size).toBe(0); - expect(dependencies.has(_('/node_modules/lib_1'))).toBe(true); - expect(dependencies.has(_('/node_modules/lib_1/sub_1'))).toBe(true); - }); - }); - }); - - interface ImportsPerType { - // var foo = require('...'); - varDeclaration?: string[]; - - // var foo = require('...'), bar = require('...'); - varDeclarations?: string[][]; - - // exports.foo = require('...'); - propAssignment?: string[]; - - // module.exports = {foo: require('...')}; - inObjectLiteral?: string[]; - - // require('...'); - forSideEffects?: string[]; - - // __export(require('...')); - reExportsWithEmittedHelper?: string[]; - - // tslib_1.__exportStar(require('...'), exports); - reExportsWithImportedHelper?: string[]; - - // var foo = require('...'); - // __export(foo); - reExportsWithoutRequire?: string[]; - } - - function commonJs(importsPerType: ImportsPerType|string[], exportNames: string[] = []): string { - if (Array.isArray(importsPerType)) { - importsPerType = {varDeclaration: importsPerType}; - } - - const importStatements = generateImportStatements(importsPerType); - const exportStatements = - exportNames.map(e => `exports.${e.replace(/.+\./, '')} = ${e};`).join('\n'); - - return `${importStatements}\n\n${exportStatements}`; - } - - function generateImportStatements(importsPerType: ImportsPerType): string { - const importStatements: string[] = []; - - const { - varDeclaration: importsOfTypeVarDeclaration = [], - varDeclarations: importsOfTypeVarDeclarations = [], - propAssignment: importsOfTypePropAssignment = [], - inObjectLiteral: importsOfTypeInObjectLiteral = [], - forSideEffects: importsOfTypeForSideEffects = [], - reExportsWithEmittedHelper: importsOfTypeReExportsWithEmittedHelper = [], - reExportsWithImportedHelper: importsOfTypeReExportsWithImportedHelper = [], - reExportsWithoutRequire: importsOfTypeReExportsWithoutRequire = [], - } = importsPerType; - - // var foo = require('...'); - importsOfTypeVarDeclaration.forEach(p => { - importStatements.push(`var ${pathToVarName(p)} = require('${p}');`); - }); - - // var foo = require('...'), bar = require('...'); - importsOfTypeVarDeclarations.forEach(pp => { - const declarations = pp.map(p => `${pathToVarName(p)} = require('${p}')`); - importStatements.push(`var ${declarations.join(', ')};`); - }); - - // exports.foo = require('...'); - importsOfTypePropAssignment.forEach(p => { - importStatements.push(`exports.${pathToVarName(p)} = require('${p}');`); - }); - - // module.exports = {foo: require('...')}; - const propAssignments = - importsOfTypeInObjectLiteral.map(p => `\n ${pathToVarName(p)}: require('${p}')`) - .join(', '); - importStatements.push(`module.exports = {${propAssignments}\n};`); - - // require('...'); - importsOfTypeForSideEffects.forEach(p => { - importStatements.push(`require('${p}');`); - }); - - // __export(require('...')); - importsOfTypeReExportsWithEmittedHelper.forEach(p => { - importStatements.push(`__export(require('${p}'));`); - }); - - // tslib_1.__exportStar(require('...'), exports); - importsOfTypeReExportsWithImportedHelper.forEach(p => { - importStatements.push(`tslib_1.__exportStar(require('${p}'), exports);`); - }); - - // var foo = require('...'); - // __export(foo); - importsOfTypeReExportsWithoutRequire.forEach(p => { - const varName = pathToVarName(p); - importStatements.push(`var ${varName} = require('${p}');`); - importStatements.push(`__export(varName);`); - }); - - return importStatements.join('\n'); - } - - function pathToVarName(path: string): string { - return path.replace(/^@(angular\/)?/, '').replace(/\.{0,2}\//g, ''); - } -}); diff --git a/packages/compiler-cli/ngcc/test/dependencies/dependency_resolver_spec.ts b/packages/compiler-cli/ngcc/test/dependencies/dependency_resolver_spec.ts deleted file mode 100644 index 154c3a7942c..00000000000 --- a/packages/compiler-cli/ngcc/test/dependencies/dependency_resolver_spec.ts +++ /dev/null @@ -1,507 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ - -import {DepGraph} from 'dependency-graph'; - -import {absoluteFrom, AbsoluteFsPath, FileSystem, getFileSystem, relativeFrom} from '../../../src/ngtsc/file_system'; -import {runInEachFileSystem} from '../../../src/ngtsc/file_system/testing'; -import {MockLogger} from '../../../src/ngtsc/logging/testing'; -import {DependencyInfo, EntryPointWithDependencies} from '../../src/dependencies/dependency_host'; -import {DependencyResolver, SortedEntryPointsInfo} from '../../src/dependencies/dependency_resolver'; -import {DtsDependencyHost} from '../../src/dependencies/dts_dependency_host'; -import {EsmDependencyHost} from '../../src/dependencies/esm_dependency_host'; -import {ModuleResolver} from '../../src/dependencies/module_resolver'; -import {NgccConfiguration} from '../../src/packages/configuration'; -import {EntryPoint} from '../../src/packages/entry_point'; - - -interface DepMap { - [path: string]: {resolved: string[], missing: string[], deepImports?: AbsoluteFsPath[]}; -} - -runInEachFileSystem(() => { - describe('DependencyResolver', () => { - let _: typeof absoluteFrom; - let host: EsmDependencyHost; - let dtsHost: EsmDependencyHost; - let resolver: DependencyResolver; - let fs: FileSystem; - let config: NgccConfiguration; - let logger: MockLogger; - let moduleResolver: ModuleResolver; - - beforeEach(() => { - _ = absoluteFrom; - fs = getFileSystem(); - config = new NgccConfiguration(fs, _('/')); - logger = new MockLogger(); - moduleResolver = new ModuleResolver(fs); - host = new EsmDependencyHost(fs, moduleResolver); - dtsHost = new DtsDependencyHost(fs); - resolver = new DependencyResolver(fs, logger, config, {esm5: host, esm2015: host}, dtsHost); - }); - - describe('sortEntryPointsByDependency()', () => { - let first: EntryPoint; - let second: EntryPoint; - let third: EntryPoint; - let fourth: EntryPoint; - let fifth: EntryPoint; - let sixthIgnoreMissing: EntryPoint; - let dependencies: DepMap; - let dtsDependencies: DepMap; - - beforeEach(() => { - first = { - name: 'first', - path: _('/first'), - packageName: 'first', - packagePath: _('/first'), - packageJson: {esm5: './index.js'}, - typings: _('/first/index.d.ts'), - compiledByAngular: true, - ignoreMissingDependencies: false, - } as EntryPoint; - second = { - name: 'second', - path: _('/second'), - packageName: 'second', - packagePath: _('/second'), - packageJson: {esm2015: './sub/index.js'}, - typings: _('/second/sub/index.d.ts'), - compiledByAngular: true, - ignoreMissingDependencies: false, - } as EntryPoint; - third = { - name: 'third', - path: _('/third'), - packageName: 'third', - packagePath: _('/third'), - packageJson: {fesm5: './index.js'}, - typings: _('/third/index.d.ts'), - compiledByAngular: true, - ignoreMissingDependencies: false, - } as EntryPoint; - fourth = { - name: 'fourth', - path: _('/fourth'), - packageName: 'fourth', - packagePath: _('/fourth'), - packageJson: {fesm2015: './sub2/index.js'}, - typings: _('/fourth/sub2/index.d.ts'), - compiledByAngular: true, - ignoreMissingDependencies: false, - } as EntryPoint; - fifth = { - name: 'fifth', - path: _('/fifth'), - packageName: 'fifth', - packagePath: _('/fifth'), - packageJson: {module: './index.js'}, - typings: _('/fifth/index.d.ts'), - compiledByAngular: true, - ignoreMissingDependencies: false, - } as EntryPoint; - - sixthIgnoreMissing = { - name: 'sixth', - path: _('/sixth'), - packageName: 'sixth', - packagePath: _('/sixth'), - packageJson: {module: './index.js'}, - typings: _('/sixth/index.d.ts'), - compiledByAngular: true, - ignoreMissingDependencies: true, - } as EntryPoint; - - dependencies = { - [_('/first/index.js')]: - {resolved: [second.path, third.path, _('/ignored-1')], missing: []}, - [_('/second/sub/index.js')]: {resolved: [third.path, fifth.path], missing: []}, - [_('/third/index.js')]: {resolved: [fourth.path, _('/ignored-2')], missing: []}, - [_('/fourth/sub2/index.js')]: {resolved: [fifth.path], missing: []}, - [_('/fifth/index.js')]: {resolved: [], missing: []}, - }; - dtsDependencies = { - [_('/first/index.d.ts')]: - {resolved: [second.path, third.path, _('/ignored-1')], missing: []}, - [_('/second/sub/index.d.ts')]: {resolved: [third.path, fifth.path], missing: []}, - [_('/third/index.d.ts')]: {resolved: [fourth.path, _('/ignored-2')], missing: []}, - [_('/fourth/sub2/index.d.ts')]: {resolved: [fifth.path], missing: []}, - [_('/fifth/index.d.ts')]: {resolved: [], missing: []}, - }; - }); - - it('should order the entry points by their dependency on each other', () => { - spyOn(host, 'collectDependencies') - .and.callFake(createFakeComputeDependencies(dependencies)); - spyOn(dtsHost, 'collectDependencies') - .and.callFake(createFakeComputeDependencies(dtsDependencies)); - const result = resolver.sortEntryPointsByDependency( - getEntryPointsWithDeps(resolver, [fifth, first, fourth, second, third])); - expect(result.entryPoints).toEqual([fifth, fourth, third, second, first]); - }); - - it('should remove entry-points that have missing direct dependencies', () => { - spyOn(host, 'collectDependencies').and.callFake(createFakeComputeDependencies({ - [_('/first/index.js')]: {resolved: [], missing: [_('/missing')]}, - [_('/second/sub/index.js')]: {resolved: [], missing: []}, - })); - spyOn(dtsHost, 'collectDependencies').and.callFake(createFakeComputeDependencies({ - [_('/first/index.d.ts')]: {resolved: [], missing: [_('/missing')]}, - [_('/second/sub/index.d.ts')]: {resolved: [], missing: []}, - })); - const result = - resolver.sortEntryPointsByDependency(getEntryPointsWithDeps(resolver, [first, second])); - expect(result.entryPoints).toEqual([second]); - expect(result.invalidEntryPoints).toEqual([ - {entryPoint: first, missingDependencies: [_('/missing')]}, - ]); - }); - - it('should remove entry points that depended upon an invalid entry-point', () => { - spyOn(host, 'collectDependencies').and.callFake(createFakeComputeDependencies({ - [_('/first/index.js')]: {resolved: [second.path, third.path], missing: []}, - [_('/second/sub/index.js')]: {resolved: [], missing: [_('/missing')]}, - [_('/third/index.js')]: {resolved: [], missing: []}, - })); - spyOn(dtsHost, 'collectDependencies').and.callFake(createFakeComputeDependencies({ - [_('/first/index.d.ts')]: {resolved: [second.path, third.path], missing: []}, - [_('/second/sub/index.d.ts')]: {resolved: [], missing: [_('/missing')]}, - [_('/third/index.d.ts')]: {resolved: [], missing: []}, - })); - // Note that we will process `first` before `second`, which has the missing dependency. - const result = resolver.sortEntryPointsByDependency( - getEntryPointsWithDeps(resolver, [first, second, third])); - expect(result.entryPoints).toEqual([third]); - expect(result.invalidEntryPoints).toEqual([ - {entryPoint: second, missingDependencies: [_('/missing')]}, - {entryPoint: first, missingDependencies: [_('/missing')]}, - ]); - }); - - it('should remove entry points that will depend upon an invalid entry-point', () => { - spyOn(host, 'collectDependencies').and.callFake(createFakeComputeDependencies({ - [_('/first/index.js')]: {resolved: [second.path, third.path], missing: []}, - [_('/second/sub/index.js')]: {resolved: [], missing: [_('/missing')]}, - [_('/third/index.js')]: {resolved: [], missing: []}, - })); - spyOn(dtsHost, 'collectDependencies').and.callFake(createFakeComputeDependencies({ - [_('/first/index.d.ts')]: {resolved: [second.path, third.path], missing: []}, - [_('/second/sub/index.d.ts')]: {resolved: [], missing: [_('/missing')]}, - [_('/third/index.d.ts')]: {resolved: [], missing: []}, - })); - // Note that we will process `first` after `second`, which has the missing dependency. - const result = resolver.sortEntryPointsByDependency( - getEntryPointsWithDeps(resolver, [second, first, third])); - expect(result.entryPoints).toEqual([third]); - expect(result.invalidEntryPoints).toEqual([ - {entryPoint: second, missingDependencies: [_('/missing')]}, - {entryPoint: first, missingDependencies: [second.path]}, - ]); - }); - - it('should cope with entry points that will depend upon an invalid entry-point, when told to ignore missing dependencies', - () => { - spyOn(host, 'collectDependencies').and.callFake(createFakeComputeDependencies({ - [_('/first/index.js')]: {resolved: [sixthIgnoreMissing.path], missing: []}, - [_('/sixth/index.js')]: {resolved: [], missing: [_('/missing')]}, - })); - spyOn(dtsHost, 'collectDependencies').and.callFake(createFakeComputeDependencies({ - [_('/first/index.d.ts')]: {resolved: [sixthIgnoreMissing.path], missing: []}, - [_('/sixth/index.d.ts')]: {resolved: [], missing: [_('/missing')]}, - })); - // Note that we will process `first` after `second`, which has the missing dependency. - const result = resolver.sortEntryPointsByDependency( - getEntryPointsWithDeps(resolver, [sixthIgnoreMissing, first])); - expect(result.entryPoints).toEqual([sixthIgnoreMissing, first]); - expect(result.invalidEntryPoints).toEqual([]); - }); - - it('should not transitively ignore missing dependencies', () => { - spyOn(host, 'collectDependencies').and.callFake(createFakeComputeDependencies({ - [_('/first/index.js')]: {resolved: [], missing: [_('/missing')]}, - [_('/second/sub/index.js')]: {resolved: [first.path], missing: []}, - [_('/sixth/index.js')]: {resolved: [second.path], missing: []}, - })); - spyOn(dtsHost, 'collectDependencies').and.callFake(createFakeComputeDependencies({ - [_('/first/index.d.ts')]: {resolved: [], missing: [_('/missing')]}, - [_('/second/sub/index.d.ts')]: {resolved: [first.path], missing: []}, - [_('/sixth/index.d.ts')]: {resolved: [second.path], missing: []}, - })); - const result = resolver.sortEntryPointsByDependency( - getEntryPointsWithDeps(resolver, [first, second, sixthIgnoreMissing])); - // sixth has no missing dependencies, but it has _invalid_ dependencies, so it's not - // compiled. - expect(result.entryPoints).toEqual([]); - }); - - it('should cope with entry points having multiple indirect missing dependencies', () => { - spyOn(host, 'collectDependencies').and.callFake(createFakeComputeDependencies({ - [_('/first/index.js')]: {resolved: [], missing: [_('/missing1')]}, - [_('/second/sub/index.js')]: {resolved: [], missing: [_('/missing2')]}, - [_('/third/index.js')]: {resolved: [first.path, second.path], missing: []}, - })); - spyOn(dtsHost, 'collectDependencies').and.callFake(createFakeComputeDependencies({ - [_('/first/index.d.ts')]: {resolved: [], missing: [_('/missing1')]}, - [_('/second/sub/index.d.ts')]: {resolved: [], missing: [_('/missing2')]}, - [_('/third/index.d.ts')]: {resolved: [first.path, second.path], missing: []}, - })); - const result = resolver.sortEntryPointsByDependency( - getEntryPointsWithDeps(resolver, [first, second, third])); - expect(result.entryPoints).toEqual([]); - expect(result.invalidEntryPoints).toEqual([ - {entryPoint: first, missingDependencies: [_('/missing1')]}, - {entryPoint: second, missingDependencies: [_('/missing2')]}, - {entryPoint: third, missingDependencies: [first.path]}, - ]); - }); - - it('should log a warning for deep imports', () => { - spyOn(host, 'collectDependencies').and.callFake(createFakeComputeDependencies({ - [_('/first/index.js')]: {resolved: [], missing: [], deepImports: [_('/deep/one')]}, - })); - spyOn(dtsHost, 'collectDependencies').and.callFake(createFakeComputeDependencies({ - [_('/first/index.d.ts')]: {resolved: [], missing: []}, - })); - const result = - resolver.sortEntryPointsByDependency(getEntryPointsWithDeps(resolver, [first])); - expect(result.entryPoints).toEqual([first]); - expect(logger.logs.warn).toEqual([[ - `Entry point 'first' contains deep imports into '${ - _('/deep/one')}'. This is probably not a problem, but may cause the compilation of entry points to be out of order.` - ]]); - }); - - it('should not log a warning for ignored deep imports', () => { - spyOn(host, 'collectDependencies').and.callFake(createFakeComputeDependencies({ - [_('/project/node_modules/test-package/test-entry-point/index.js')]: { - resolved: [], - missing: [], - deepImports: [ - _('/project/node_modules/deep/one'), _('/project/node_modules/deep/two'), - _('/project/node_modules/deeper/one'), _('/project/node_modules/deeper/two') - ] - }, - })); - spyOn(dtsHost, 'collectDependencies').and.callFake(createFakeComputeDependencies({ - [_('/project/node_modules/test-package/test-entry-point/index.d.ts')]: { - resolved: [], - missing: [], - }, - })); - // Setup the configuration to ignore deep imports that contain either "deep/" or "two". - fs.ensureDir(_('/project')); - fs.writeFile( - _('/project/ngcc.config.js'), - `module.exports = { packages: { 'test-package': { ignorableDeepImportMatchers: [/deep\\//, /two/] } } };`); - config = new NgccConfiguration(fs, _('/project')); - resolver = new DependencyResolver(fs, logger, config, {esm5: host, esm2015: host}, dtsHost); - const testEntryPoint = { - name: 'test-package/test-entry-point', - path: _('/project/node_modules/test-package/test-entry-point'), - packageName: 'test-package', - packagePath: _('/project/node_modules/test-package'), - packageJson: {esm5: './index.js'}, - typings: _('/project/node_modules/test-package/test-entry-point/index.d.ts'), - compiledByAngular: true, - ignoreMissingDependencies: false, - } as EntryPoint; - - const result = resolver.sortEntryPointsByDependency( - getEntryPointsWithDeps(resolver, [testEntryPoint])); - expect(result.entryPoints).toEqual([testEntryPoint]); - expect(logger.logs.warn).toEqual([[ - `Entry point 'test-package/test-entry-point' contains deep imports into '${ - _('/project/node_modules/deeper/one')}'. This is probably not a problem, but may cause the compilation of entry points to be out of order.` - ]]); - }); - - it('should error if the entry point does not have a suitable format', () => { - expect(() => resolver.sortEntryPointsByDependency(getEntryPointsWithDeps(resolver, [ - {path: '/first', packageJson: {}, compiledByAngular: true} as EntryPoint - ]))).toThrowError(`There is no appropriate source code format in '/first' entry-point.`); - }); - - it('should error if there is no appropriate DependencyHost for the given formats', () => { - resolver = new DependencyResolver(fs, new MockLogger(), config, {esm2015: host}, host); - expect( - () => resolver.sortEntryPointsByDependency(getEntryPointsWithDeps(resolver, [first]))) - .toThrowError( - `Could not find a suitable format for computing dependencies of entry-point: '${ - first.path}'.`); - }); - - it('should capture any dependencies that were ignored', () => { - spyOn(host, 'collectDependencies') - .and.callFake(createFakeComputeDependencies(dependencies)); - spyOn(dtsHost, 'collectDependencies') - .and.callFake(createFakeComputeDependencies(dtsDependencies)); - const result = resolver.sortEntryPointsByDependency( - getEntryPointsWithDeps(resolver, [fifth, first, fourth, second, third])); - expect(result.ignoredDependencies).toEqual([ - {entryPoint: first, dependencyPath: _('/ignored-1')}, - {entryPoint: third, dependencyPath: _('/ignored-2')}, - ]); - }); - - it('should return the computed dependency graph', () => { - spyOn(host, 'collectDependencies') - .and.callFake(createFakeComputeDependencies(dependencies)); - spyOn(dtsHost, 'collectDependencies') - .and.callFake(createFakeComputeDependencies(dtsDependencies)); - const result = resolver.sortEntryPointsByDependency( - getEntryPointsWithDeps(resolver, [fifth, first, fourth, second, third])); - - expect(result.graph).toEqual(jasmine.any(DepGraph)); - expect(result.graph.size()).toBe(5); - expect(result.graph.dependenciesOf(third.path)).toEqual([fifth.path, fourth.path]); - }); - - it('should only return dependencies of the target, if provided', () => { - spyOn(host, 'collectDependencies') - .and.callFake(createFakeComputeDependencies(dependencies)); - spyOn(dtsHost, 'collectDependencies') - .and.callFake(createFakeComputeDependencies(dtsDependencies)); - const entryPoints = getEntryPointsWithDeps(resolver, [fifth, first, fourth, second, third]); - let sorted: SortedEntryPointsInfo; - - sorted = resolver.sortEntryPointsByDependency(entryPoints, first); - expect(sorted.entryPoints).toEqual([fifth, fourth, third, second, first]); - sorted = resolver.sortEntryPointsByDependency(entryPoints, second); - expect(sorted.entryPoints).toEqual([fifth, fourth, third, second]); - sorted = resolver.sortEntryPointsByDependency(entryPoints, third); - expect(sorted.entryPoints).toEqual([fifth, fourth, third]); - sorted = resolver.sortEntryPointsByDependency(entryPoints, fourth); - expect(sorted.entryPoints).toEqual([fifth, fourth]); - sorted = resolver.sortEntryPointsByDependency(entryPoints, fifth); - expect(sorted.entryPoints).toEqual([fifth]); - }); - - it('should not process the provided target if it has missing dependencies', () => { - spyOn(host, 'collectDependencies').and.callFake(createFakeComputeDependencies({ - [_('/first/index.js')]: {resolved: [], missing: [_('/missing')]}, - })); - spyOn(dtsHost, 'collectDependencies').and.callFake(createFakeComputeDependencies({ - [_('/first/index.d.ts')]: {resolved: [], missing: [_('/missing')]}, - })); - const entryPoints = getEntryPointsWithDeps(resolver, [first]); - let sorted: SortedEntryPointsInfo; - - sorted = resolver.sortEntryPointsByDependency(entryPoints, first); - expect(sorted.entryPoints).toEqual([]); - expect(sorted.invalidEntryPoints[0].entryPoint).toEqual(first); - expect(sorted.invalidEntryPoints[0].missingDependencies).toEqual([_('/missing')]); - }); - - it('should not consider builtin NodeJS modules as missing dependency', () => { - spyOn(host, 'collectDependencies').and.callFake(createFakeComputeDependencies({ - [_('/first/index.js')]: {resolved: [], missing: ['fs']}, - })); - spyOn(dtsHost, 'collectDependencies').and.callFake(createFakeComputeDependencies({ - [_('/first/index.d.ts')]: {resolved: [], missing: ['fs']}, - })); - const entryPoints = getEntryPointsWithDeps(resolver, [first]); - let sorted: SortedEntryPointsInfo; - - sorted = resolver.sortEntryPointsByDependency(entryPoints, first); - expect(sorted.entryPoints).toEqual([first]); - expect(sorted.invalidEntryPoints).toEqual([]); - expect(sorted.ignoredDependencies).toEqual([]); - }); - - it('should use the appropriate DependencyHost for each entry-point', () => { - const esm5Host = new EsmDependencyHost(fs, moduleResolver); - const esm2015Host = new EsmDependencyHost(fs, moduleResolver); - const dtsHost = new DtsDependencyHost(fs); - resolver = new DependencyResolver( - fs, new MockLogger(), config, {esm5: esm5Host, esm2015: esm2015Host}, dtsHost); - spyOn(esm5Host, 'collectDependencies') - .and.callFake(createFakeComputeDependencies(dependencies)); - spyOn(esm2015Host, 'collectDependencies') - .and.callFake(createFakeComputeDependencies(dependencies)); - spyOn(dtsHost, 'collectDependencies') - .and.callFake(createFakeComputeDependencies(dtsDependencies)); - const result = resolver.sortEntryPointsByDependency( - getEntryPointsWithDeps(resolver, [fifth, first, fourth, second, third])); - expect(result.entryPoints).toEqual([fifth, fourth, third, second, first]); - - expect(esm5Host.collectDependencies) - .toHaveBeenCalledWith(fs.resolve(first.path, 'index.js'), jasmine.any(Object)); - expect(esm5Host.collectDependencies) - .not.toHaveBeenCalledWith(fs.resolve(second.path, 'sub/index.js'), jasmine.any(Object)); - expect(esm5Host.collectDependencies) - .toHaveBeenCalledWith(fs.resolve(third.path, 'index.js'), jasmine.any(Object)); - expect(esm5Host.collectDependencies) - .not.toHaveBeenCalledWith( - fs.resolve(fourth.path, 'sub2/index.js'), jasmine.any(Object)); - expect(esm5Host.collectDependencies) - .toHaveBeenCalledWith(fs.resolve(fifth.path, 'index.js'), jasmine.any(Object)); - - expect(esm2015Host.collectDependencies) - .not.toHaveBeenCalledWith(fs.resolve(first.path, 'index.js'), jasmine.any(Object)); - expect(esm2015Host.collectDependencies) - .toHaveBeenCalledWith(fs.resolve(second.path, 'sub/index.js'), jasmine.any(Object)); - expect(esm2015Host.collectDependencies) - .not.toHaveBeenCalledWith(fs.resolve(third.path, 'index.js'), jasmine.any(Object)); - expect(esm2015Host.collectDependencies) - .toHaveBeenCalledWith(fs.resolve(fourth.path, 'sub2/index.js'), jasmine.any(Object)); - expect(esm2015Host.collectDependencies) - .not.toHaveBeenCalledWith(fs.resolve(fifth.path, 'index.js'), jasmine.any(Object)); - - expect(dtsHost.collectDependencies) - .toHaveBeenCalledWith(fs.resolve(first.path, 'index.d.ts'), jasmine.any(Object)); - expect(dtsHost.collectDependencies) - .toHaveBeenCalledWith(fs.resolve(second.path, 'sub/index.d.ts'), jasmine.any(Object)); - expect(dtsHost.collectDependencies) - .toHaveBeenCalledWith(fs.resolve(third.path, 'index.d.ts'), jasmine.any(Object)); - expect(dtsHost.collectDependencies) - .toHaveBeenCalledWith(fs.resolve(fourth.path, 'sub2/index.d.ts'), jasmine.any(Object)); - expect(dtsHost.collectDependencies) - .toHaveBeenCalledWith(fs.resolve(fifth.path, 'index.d.ts'), jasmine.any(Object)); - }); - - it('should merge "typings-only" dependencies with source dependencies', () => { - spyOn(host, 'collectDependencies').and.callFake(createFakeComputeDependencies({ - [_('/first/index.js')]: {resolved: [], missing: []}, - [_('/second/sub/index.js')]: {resolved: [], missing: [_('/missing1')]}, - [_('/third/index.js')]: {resolved: [first.path], missing: []}, - })); - spyOn(dtsHost, 'collectDependencies').and.callFake(createFakeComputeDependencies({ - [_('/first/index.d.ts')]: {resolved: [], missing: []}, - [_('/second/sub/index.d.ts')]: {resolved: [], missing: [_('/missing2')]}, - [_('/third/index.d.ts')]: {resolved: [second.path], missing: []}, - })); - const entryPoints = getEntryPointsWithDeps(resolver, [first, second, third]); - const sorted = resolver.sortEntryPointsByDependency(entryPoints); - expect(sorted.entryPoints).toEqual([first]); - expect(sorted.invalidEntryPoints).toEqual([ - {entryPoint: second, missingDependencies: [_('/missing1'), _('/missing2')]}, - {entryPoint: third, missingDependencies: [_('/second')]}, - ]); - }); - - function createFakeComputeDependencies(deps: DepMap) { - return (entryPointPath: string, {dependencies, missing, deepImports}: DependencyInfo) => { - deps[entryPointPath].resolved.forEach(dep => dependencies.add(absoluteFrom(dep))); - deps[entryPointPath].missing.forEach( - dep => missing.add(fs.isRooted(dep) ? absoluteFrom(dep) : relativeFrom(dep))); - if (deps[entryPointPath].deepImports) { - deps[entryPointPath].deepImports!.forEach(dep => deepImports.add(dep)); - } - return {dependencies, missing, deepImports}; - }; - } - - function getEntryPointsWithDeps( - resolver: DependencyResolver, entryPoints: EntryPoint[]): EntryPointWithDependencies[] { - return entryPoints.map(entryPoint => resolver.getEntryPointWithDependencies(entryPoint)); - } - }); - }); -}); diff --git a/packages/compiler-cli/ngcc/test/dependencies/dts_dependency_host_spec.ts b/packages/compiler-cli/ngcc/test/dependencies/dts_dependency_host_spec.ts deleted file mode 100644 index 5304946d30d..00000000000 --- a/packages/compiler-cli/ngcc/test/dependencies/dts_dependency_host_spec.ts +++ /dev/null @@ -1,273 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import ts from 'typescript'; - -import {absoluteFrom, getFileSystem, relativeFrom} from '../../../src/ngtsc/file_system'; -import {runInEachFileSystem} from '../../../src/ngtsc/file_system/testing'; -import {loadTestFiles} from '../../../src/ngtsc/testing'; -import {createDependencyInfo} from '../../src/dependencies/dependency_host'; -import {DtsDependencyHost} from '../../src/dependencies/dts_dependency_host'; - -runInEachFileSystem(() => { - describe('DtsDependencyHost', () => { - let _: typeof absoluteFrom; - let host: DtsDependencyHost; - beforeEach(() => { - _ = absoluteFrom; - setupMockFileSystem(); - const fs = getFileSystem(); - host = new DtsDependencyHost(fs); - }); - - describe('collectDependencies()', () => { - it('should not generate a TS AST if the source does not contain any imports or re-exports', - () => { - spyOn(ts, 'createSourceFile'); - host.collectDependencies( - _('/no/imports/or/re-exports/index.d.ts'), createDependencyInfo()); - expect(ts.createSourceFile).not.toHaveBeenCalled(); - }); - - it('should resolve all the external imports of the source file', () => { - const {dependencies, missing, deepImports} = createDependencyInfo(); - host.collectDependencies( - _('/external/imports/index.d.ts'), {dependencies, missing, deepImports}); - expect(dependencies.size).toBe(2); - expect(missing.size).toBe(0); - expect(deepImports.size).toBe(0); - expect(dependencies.has(_('/node_modules/lib-1'))).toBe(true); - expect(dependencies.has(_('/node_modules/lib-1/sub-1'))).toBe(true); - }); - - it('should ignore synthetic type imports', () => { - const {dependencies, missing, deepImports} = createDependencyInfo(); - host.collectDependencies( - _('/external/synthetic-type-imports/index.d.ts'), {dependencies, missing, deepImports}); - expect(dependencies.size).toBe(0); - expect(missing.size).toBe(0); - expect(deepImports.size).toBe(0); - }); - - it('should resolve all the external re-exports of the source file', () => { - const {dependencies, missing, deepImports} = createDependencyInfo(); - host.collectDependencies( - _('/external/re-exports/index.d.ts'), {dependencies, missing, deepImports}); - expect(dependencies.size).toBe(2); - expect(missing.size).toBe(0); - expect(deepImports.size).toBe(0); - expect(dependencies.has(_('/node_modules/lib-1'))).toBe(true); - expect(dependencies.has(_('/node_modules/lib-1/sub-1'))).toBe(true); - }); - - it('should capture missing external imports', () => { - const {dependencies, missing, deepImports} = createDependencyInfo(); - host.collectDependencies( - _('/external/imports-missing/index.d.ts'), {dependencies, missing, deepImports}); - - expect(dependencies.size).toBe(1); - expect(dependencies.has(_('/node_modules/lib-1'))).toBe(true); - expect(missing.size).toBe(1); - expect(missing.has(relativeFrom('missing'))).toBe(true); - expect(deepImports.size).toBe(0); - }); - - it('should not register deep imports as missing', () => { - // This scenario verifies the behavior of the dependency analysis when an external import - // is found that does not map to an entry-point but still exists on disk, i.e. a deep - // import. Such deep imports are captured for diagnostics purposes. - // Note that in the DTS version, the deep import may not map to a .d.ts file, but instead - // a .js file. This test exercises this particular scenario. - const {dependencies, missing, deepImports} = createDependencyInfo(); - host.collectDependencies( - _('/external/deep-import/index.d.ts'), {dependencies, missing, deepImports}); - - expect(dependencies.size).toBe(0); - expect(missing.size).toBe(0); - expect(deepImports.size).toBe(1); - expect(deepImports.has(_('/node_modules/lib-1/deep/import'))).toBe(true); - }); - - it('should not register deep imports as missing, if available in `@types/...`', () => { - // This scenario verifies the behavior of the dependency analysis when an external import - // is found that does not map to an entry-point but still exists in an `@types/...` package, - // i.e. a type-only deep import. Such deep imports are captured for diagnostics purposes. - const {dependencies, missing, deepImports} = createDependencyInfo(); - host.collectDependencies( - _('/external/deep-import-2/index.d.ts'), {dependencies, missing, deepImports}); - - expect(dependencies.size).toBe(0); - expect(missing.size).toBe(0); - expect(deepImports.size).toBe(1); - expect(deepImports.has(_('/node_modules/@types/type-only/deep/import'))).toBe(true); - }); - - it('should recurse into internal dependencies', () => { - const {dependencies, missing, deepImports} = createDependencyInfo(); - host.collectDependencies( - _('/internal/outer/index.d.ts'), {dependencies, missing, deepImports}); - - expect(dependencies.size).toBe(1); - expect(dependencies.has(_('/node_modules/lib-1/sub-1'))).toBe(true); - expect(missing.size).toBe(0); - expect(deepImports.size).toBe(0); - }); - - it('should handle circular internal dependencies', () => { - const {dependencies, missing, deepImports} = createDependencyInfo(); - host.collectDependencies( - _('/internal/circular-a/index.d.ts'), {dependencies, missing, deepImports}); - expect(dependencies.size).toBe(2); - expect(dependencies.has(_('/node_modules/lib-1'))).toBe(true); - expect(dependencies.has(_('/node_modules/lib-1/sub-1'))).toBe(true); - expect(missing.size).toBe(0); - expect(deepImports.size).toBe(0); - }); - - it('should support `paths` alias mappings when resolving modules', () => { - const fs = getFileSystem(); - host = new DtsDependencyHost(fs, { - baseUrl: '/dist', - paths: { - '@app/*': ['*'], - '@lib/*/test': ['lib/*/test'], - } - }); - const {dependencies, missing, deepImports} = createDependencyInfo(); - host.collectDependencies(_('/path-alias/index.d.ts'), {dependencies, missing, deepImports}); - expect(dependencies.size).toBe(4); - expect(dependencies.has(_('/dist/components'))).toBe(true); - expect(dependencies.has(_('/dist/shared'))).toBe(true); - expect(dependencies.has(_('/dist/lib/shared/test'))).toBe(true); - expect(dependencies.has(_('/node_modules/lib-1'))).toBe(true); - expect(missing.size).toBe(0); - expect(deepImports.size).toBe(0); - }); - - it('should handle entry-point paths with no extension', () => { - const {dependencies, missing, deepImports} = createDependencyInfo(); - host.collectDependencies( - _('/external/imports/index'), {dependencies, missing, deepImports}); - expect(dependencies.size).toBe(2); - expect(missing.size).toBe(0); - expect(deepImports.size).toBe(0); - expect(dependencies.has(_('/node_modules/lib-1'))).toBe(true); - expect(dependencies.has(_('/node_modules/lib-1/sub-1'))).toBe(true); - }); - }); - - function setupMockFileSystem(): void { - loadTestFiles([ - { - name: _('/no/imports/or/re-exports/index.d.ts'), - contents: '// some text but no import-like statements' - }, - {name: _('/no/imports/or/re-exports/package.json'), contents: '{"esm2015": "./index.js"}'}, - {name: _('/no/imports/or/re-exports/index.metadata.json'), contents: 'MOCK METADATA'}, - { - name: _('/external/imports/index.d.ts'), - contents: `import {X} from 'lib-1';\nimport {Y} from 'lib-1/sub-1';` - }, - {name: _('/external/imports/package.json'), contents: '{"esm2015": "./index.js"}'}, - {name: _('/external/imports/index.metadata.json'), contents: 'MOCK METADATA'}, - { - name: _('/external/synthetic-type-imports/index.d.ts'), - contents: `const function foo(): Array;` - }, - { - name: _('/external/synthetic-type-imports/package.json'), - contents: '{"esm2015": "./index.js"}' - }, - { - name: _('/external/synthetic-type-imports/index.metadata.json'), - contents: 'MOCK METADATA' - }, - { - name: _('/external/re-exports/index.d.ts'), - contents: `export {X} from 'lib-1';\nexport {Y} from 'lib-1/sub-1';` - }, - {name: _('/external/re-exports/package.json'), contents: '{"esm2015": "./index.js"}'}, - {name: _('/external/re-exports/index.metadata.json'), contents: 'MOCK METADATA'}, - { - name: _('/external/imports-missing/index.d.ts'), - contents: `import {X} from 'lib-1';\nimport {Y} from 'missing';` - }, - {name: _('/external/imports-missing/package.json'), contents: '{"esm2015": "./index.js"}'}, - {name: _('/external/imports-missing/index.metadata.json'), contents: 'MOCK METADATA'}, - { - name: _('/external/deep-import/index.d.ts'), - contents: `import {Y} from 'lib-1/deep/import';` - }, - {name: _('/external/deep-import/package.json'), contents: '{"esm2015": "./index.js"}'}, - {name: _('/external/deep-import/index.metadata.json'), contents: 'MOCK METADATA'}, - { - name: _('/external/deep-import-2/index.d.ts'), - contents: `import {Y} from 'type-only/deep/import';` - }, - { - name: _('/node_modules/@types/type-only/deep/import/index.d.ts'), - contents: `export declare class Y {}` - }, - {name: _('/internal/outer/index.d.ts'), contents: `import {X} from '../inner';`}, - {name: _('/internal/outer/package.json'), contents: '{"esm2015": "./index.js"}'}, - {name: _('/internal/outer/index.metadata.json'), contents: 'MOCK METADATA'}, - { - name: _('/internal/inner/index.d.ts'), - contents: `import {Y} from 'lib-1/sub-1'; export declare class X {}` - }, - { - name: _('/internal/circular-a/index.d.ts'), - contents: - `import {B} from '../circular-b'; import {X} from '../circular-b'; export {Y} from 'lib-1/sub-1';` - }, - { - name: _('/internal/circular-b/index.d.ts'), - contents: - `import {A} from '../circular-a'; import {Y} from '../circular-a'; export {X} from 'lib-1';` - }, - {name: _('/internal/circular-a/package.json'), contents: '{"esm2015": "./index.js"}'}, - {name: _('/internal/circular-a/index.metadata.json'), contents: 'MOCK METADATA'}, - { - name: _('/path-alias/index.d.ts'), - contents: - `import {TestHelper} from '@app/components';\nimport {Service} from '@app/shared';\nimport {TestHelper} from '@lib/shared/test';\nimport {X} from 'lib-1';` - }, - {name: _('/path-alias/package.json'), contents: '{"esm2015": "./index.js"}'}, - {name: _('/path-alias/index.metadata.json'), contents: 'MOCK METADATA'}, - {name: _('/node_modules/lib-1/index.d.ts'), contents: 'export declare class X {}'}, - {name: _('/node_modules/lib-1/package.json'), contents: '{"esm2015": "./index.js"}'}, - {name: _('/node_modules/lib-1/index.metadata.json'), contents: 'MOCK METADATA'}, - { - name: _('/node_modules/lib-1/deep/import/index.js'), - contents: 'export class DeepImport {}' - }, - {name: _('/node_modules/lib-1/sub-1/index.d.ts'), contents: 'export declare class Y {}'}, - {name: _('/node_modules/lib-1/sub-1/package.json'), contents: '{"esm2015": "./index.js"}'}, - {name: _('/node_modules/lib-1/sub-1/index.metadata.json'), contents: 'MOCK METADATA'}, - {name: _('/node_modules/lib-1/sub-2.d.ts'), contents: `export * from './sub-2/sub-2';`}, - {name: _('/node_modules/lib-1/sub-2/sub-2.d.ts'), contents: `export declare class Z {}';`}, - {name: _('/node_modules/lib-1/sub-2/package.json'), contents: '{"esm2015": "./sub-2.js"}'}, - {name: _('/node_modules/lib-1/sub-2/sub-2.metadata.json'), contents: 'MOCK METADATA'}, - {name: _('/dist/components/index.d.ts'), contents: `class MyComponent {};`}, - {name: _('/dist/components/package.json'), contents: '{"esm2015": "./index.js"}'}, - {name: _('/dist/components/index.metadata.json'), contents: 'MOCK METADATA'}, - { - name: _('/dist/shared/index.d.ts'), - contents: `import {X} from 'lib-1';\nexport declare class Service {}` - }, - {name: _('/dist/shared/package.json'), contents: '{"esm2015": "./index.js"}'}, - {name: _('/dist/shared/index.metadata.json'), contents: 'MOCK METADATA'}, - { - name: _('/dist/lib/shared/test/index.d.ts'), - contents: `export declare class TestHelper {}` - }, - {name: _('/dist/lib/shared/test/package.json'), contents: '{"esm2015": "./index.js"}'}, - {name: _('/dist/lib/shared/test/index.metadata.json'), contents: 'MOCK METADATA'}, - ]); - } - }); -}); diff --git a/packages/compiler-cli/ngcc/test/dependencies/esm_dependency_host_spec.ts b/packages/compiler-cli/ngcc/test/dependencies/esm_dependency_host_spec.ts deleted file mode 100644 index f9564f37e41..00000000000 --- a/packages/compiler-cli/ngcc/test/dependencies/esm_dependency_host_spec.ts +++ /dev/null @@ -1,355 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import ts from 'typescript'; - -import {absoluteFrom, getFileSystem, relativeFrom} from '../../../src/ngtsc/file_system'; -import {runInEachFileSystem} from '../../../src/ngtsc/file_system/testing'; -import {loadTestFiles} from '../../../src/ngtsc/testing'; -import {createDependencyInfo} from '../../src/dependencies/dependency_host'; -import {DtsDependencyHost} from '../../src/dependencies/dts_dependency_host'; -import {EsmDependencyHost, hasImportOrReexportStatements, isStringImportOrReexport} from '../../src/dependencies/esm_dependency_host'; -import {ModuleResolver} from '../../src/dependencies/module_resolver'; - -runInEachFileSystem(() => { - describe('EsmDependencyHost', () => { - let _: typeof absoluteFrom; - let host: EsmDependencyHost; - beforeEach(() => { - _ = absoluteFrom; - setupMockFileSystem(); - const fs = getFileSystem(); - host = new EsmDependencyHost(fs, new ModuleResolver(fs)); - }); - - describe('collectDependencies()', () => { - it('should not try to extract import paths if the source does not contain any imports or re-exports', - () => { - const extractImportsSpy = spyOn(host as any, 'extractImports'); - host.collectDependencies( - _('/no/imports/or/re-exports/index.js'), createDependencyInfo()); - expect(extractImportsSpy).not.toHaveBeenCalled(); - }); - - it('should resolve all the external imports of the source file', () => { - const {dependencies, missing, deepImports} = createDependencyInfo(); - host.collectDependencies( - _('/external/imports/index.js'), {dependencies, missing, deepImports}); - expect(dependencies.size).toBe(2); - expect(missing.size).toBe(0); - expect(deepImports.size).toBe(0); - expect(dependencies.has(_('/node_modules/lib-1'))).toBe(true); - expect(dependencies.has(_('/node_modules/lib-1/sub-1'))).toBe(true); - }); - - it('should resolve all the external re-exports of the source file', () => { - const {dependencies, missing, deepImports} = createDependencyInfo(); - host.collectDependencies( - _('/external/re-exports/index.js'), {dependencies, missing, deepImports}); - expect(dependencies.size).toBe(2); - expect(missing.size).toBe(0); - expect(deepImports.size).toBe(0); - expect(dependencies.has(_('/node_modules/lib-1'))).toBe(true); - expect(dependencies.has(_('/node_modules/lib-1/sub-1'))).toBe(true); - }); - - it('should resolve all the external dynamic imports of the source file', () => { - const {dependencies, missing, deepImports} = createDependencyInfo(); - host.collectDependencies( - _('/external/dynamic/index.js'), {dependencies, missing, deepImports}); - expect(dependencies.size).toBe(2); - expect(missing.size).toBe(0); - expect(deepImports.size).toBe(0); - expect(dependencies.has(_('/node_modules/lib-1'))).toBe(true); - expect(dependencies.has(_('/node_modules/lib-1/sub-1'))).toBe(true); - }); - - it('should capture missing external imports', () => { - const {dependencies, missing, deepImports} = createDependencyInfo(); - host.collectDependencies( - _('/external/imports-missing/index.js'), {dependencies, missing, deepImports}); - - expect(dependencies.size).toBe(1); - expect(dependencies.has(_('/node_modules/lib-1'))).toBe(true); - expect(missing.size).toBe(1); - expect(missing.has(relativeFrom('missing'))).toBe(true); - expect(deepImports.size).toBe(0); - }); - - it('should not register deep imports as missing', () => { - // This scenario verifies the behavior of the dependency analysis when an external import - // is found that does not map to an entry-point but still exists on disk, i.e. a deep - // import. Such deep imports are captured for diagnostics purposes. - const {dependencies, missing, deepImports} = createDependencyInfo(); - host.collectDependencies( - _('/external/deep-import/index.js'), {dependencies, missing, deepImports}); - - expect(dependencies.size).toBe(0); - expect(missing.size).toBe(0); - expect(deepImports.size).toBe(1); - expect(deepImports.has(_('/node_modules/lib-1/deep/import'))).toBe(true); - }); - - it('should recurse into internal dependencies', () => { - const {dependencies, missing, deepImports} = createDependencyInfo(); - host.collectDependencies( - _('/internal/outer/index.js'), {dependencies, missing, deepImports}); - - expect(dependencies.size).toBe(1); - expect(dependencies.has(_('/node_modules/lib-1/sub-1'))).toBe(true); - expect(missing.size).toBe(0); - expect(deepImports.size).toBe(0); - }); - - it('should handle circular internal dependencies', () => { - const {dependencies, missing, deepImports} = createDependencyInfo(); - host.collectDependencies( - _('/internal/circular-a/index.js'), {dependencies, missing, deepImports}); - expect(dependencies.size).toBe(2); - expect(dependencies.has(_('/node_modules/lib-1'))).toBe(true); - expect(dependencies.has(_('/node_modules/lib-1/sub-1'))).toBe(true); - expect(missing.size).toBe(0); - expect(deepImports.size).toBe(0); - }); - - it('should support `paths` alias mappings when resolving modules', () => { - const fs = getFileSystem(); - host = new EsmDependencyHost(fs, new ModuleResolver(fs, { - baseUrl: '/dist', - paths: { - '@app/*': ['*'], - '@lib/*/test': ['lib/*/test'], - } - })); - const {dependencies, missing, deepImports} = createDependencyInfo(); - host.collectDependencies(_('/path-alias/index.js'), {dependencies, missing, deepImports}); - expect(dependencies.size).toBe(4); - expect(dependencies.has(_('/dist/components'))).toBe(true); - expect(dependencies.has(_('/dist/shared'))).toBe(true); - expect(dependencies.has(_('/dist/lib/shared/test'))).toBe(true); - expect(dependencies.has(_('/node_modules/lib-1'))).toBe(true); - expect(missing.size).toBe(0); - expect(deepImports.size).toBe(0); - }); - - it('should handle entry-point paths with no extension', () => { - const {dependencies, missing, deepImports} = createDependencyInfo(); - host.collectDependencies( - _('/external/imports/index'), {dependencies, missing, deepImports}); - expect(dependencies.size).toBe(2); - expect(missing.size).toBe(0); - expect(deepImports.size).toBe(0); - expect(dependencies.has(_('/node_modules/lib-1'))).toBe(true); - expect(dependencies.has(_('/node_modules/lib-1/sub-1'))).toBe(true); - }); - - it('should resolve modules based on the provided `moduleResolver`', () => { - const fs = getFileSystem(); - loadTestFiles([ - { - name: _('/external/index.d.ts'), - contents: `import * from './internal-typings';`, - }, - { - name: _('/external/internal-typings.d.ts'), - contents: `export {X} from 'lib-1';\nexport {Y} from 'lib-1/sub-1';`, - } - ]); - - // Default JS mode will not pick up `internal-typings.d.ts` dependency - const jsHost = new EsmDependencyHost(fs, new ModuleResolver(fs)); - const jsDeps = createDependencyInfo(); - jsHost.collectDependencies(_('/external/index.d.ts'), jsDeps); - expect(jsDeps.dependencies.size).toEqual(0); - expect(jsDeps.deepImports.size).toEqual(0); - expect(jsDeps.missing.size).toEqual(1); - expect(jsDeps.missing.has(relativeFrom('./internal-typings'))).toBeTruthy(); - - // Typings mode will pick up `internal-typings.d.ts` dependency - const dtsHost = new DtsDependencyHost(fs); - const dtsDeps = createDependencyInfo(); - dtsHost.collectDependencies(_('/external/index.d.ts'), dtsDeps); - expect(dtsDeps.dependencies.size).toEqual(2); - expect(dtsDeps.dependencies.has(_('/node_modules/lib-1'))).toBeTruthy(); - expect(dtsDeps.dependencies.has(_('/node_modules/lib-1/sub-1'))).toBeTruthy(); - expect(dtsDeps.deepImports.size).toEqual(0); - expect(dtsDeps.missing.size).toEqual(0); - }); - }); - - function setupMockFileSystem(): void { - loadTestFiles([ - { - name: _('/no/imports/or/re-exports/index.js'), - contents: '// some text but no import-like statements' - }, - {name: _('/no/imports/or/re-exports/package.json'), contents: '{"esm2015": "./index.js"}'}, - {name: _('/no/imports/or/re-exports/index.metadata.json'), contents: 'MOCK METADATA'}, - { - name: _('/external/imports/index.js'), - contents: `import {\n X\n} from 'lib-1';\nimport {Y, Z} from 'lib-1/sub-1';` - }, - {name: _('/external/imports/package.json'), contents: '{"esm2015": "./index.js"}'}, - {name: _('/external/imports/index.metadata.json'), contents: 'MOCK METADATA'}, - { - name: _('/external/dynamic/index.js'), - contents: - `async function foo() { await const x = import('lib-1');\n const promise = import('lib-1/sub-1'); }` - }, - {name: _('/external/dynamic/package.json'), contents: '{"esm2015": "./index.js"}'}, - {name: _('/external/dynamic/index.metadata.json'), contents: 'MOCK METADATA'}, - { - name: _('/external/re-exports/index.js'), - contents: `export {X} from 'lib-1';\nexport {\n Y,\n Z\n} from 'lib-1/sub-1';` - }, - {name: _('/external/re-exports/package.json'), contents: '{"esm2015": "./index.js"}'}, - {name: _('/external/re-exports/index.metadata.json'), contents: 'MOCK METADATA'}, - { - name: _('/external/imports-missing/index.js'), - contents: `import {X} from 'lib-1';\nimport {Y} from 'missing';` - }, - {name: _('/external/imports-missing/package.json'), contents: '{"esm2015": "./index.js"}'}, - {name: _('/external/imports-missing/index.metadata.json'), contents: 'MOCK METADATA'}, - { - name: _('/external/deep-import/index.js'), - contents: `import {Y} from 'lib-1/deep/import';` - }, - {name: _('/external/deep-import/package.json'), contents: '{"esm2015": "./index.js"}'}, - {name: _('/external/deep-import/index.metadata.json'), contents: 'MOCK METADATA'}, - {name: _('/internal/outer/index.js'), contents: `import {X} from '../inner';`}, - {name: _('/internal/outer/package.json'), contents: '{"esm2015": "./index.js"}'}, - {name: _('/internal/outer/index.metadata.json'), contents: 'MOCK METADATA'}, - { - name: _('/internal/inner/index.js'), - contents: `import {Y} from 'lib-1/sub-1'; export class X {}` - }, - { - name: _('/internal/circular-a/index.js'), - contents: - `import {B} from '../circular-b'; import {X} from '../circular-b'; export {Y} from 'lib-1/sub-1';` - }, - { - name: _('/internal/circular-b/index.js'), - contents: - `import {A} from '../circular-a'; import {Y} from '../circular-a'; export {X} from 'lib-1';` - }, - {name: _('/internal/circular-a/package.json'), contents: '{"esm2015": "./index.js"}'}, - {name: _('/internal/circular-a/index.metadata.json'), contents: 'MOCK METADATA'}, - {name: _('/re-directed/index.js'), contents: `import {Z} from 'lib-1/sub-2';`}, - {name: _('/re-directed/package.json'), contents: '{"esm2015": "./index.js"}'}, - {name: _('/re-directed/index.metadata.json'), contents: 'MOCK METADATA'}, - { - name: _('/path-alias/index.js'), - contents: - `import {TestHelper} from '@app/components';\nimport {Service} from '@app/shared';\nimport {TestHelper} from '@lib/shared/test';\nimport {X} from 'lib-1';` - }, - {name: _('/path-alias/package.json'), contents: '{"esm2015": "./index.js"}'}, - {name: _('/path-alias/index.metadata.json'), contents: 'MOCK METADATA'}, - {name: _('/node_modules/lib-1/index.js'), contents: 'export class X {}'}, - {name: _('/node_modules/lib-1/package.json'), contents: '{"esm2015": "./index.js"}'}, - {name: _('/node_modules/lib-1/index.metadata.json'), contents: 'MOCK METADATA'}, - { - name: _('/node_modules/lib-1/deep/import/index.js'), - contents: 'export class DeepImport {}' - }, - {name: _('/node_modules/lib-1/sub-1/index.js'), contents: 'export class Y {}'}, - {name: _('/node_modules/lib-1/sub-1/package.json'), contents: '{"esm2015": "./index.js"}'}, - {name: _('/node_modules/lib-1/sub-1/index.metadata.json'), contents: 'MOCK METADATA'}, - {name: _('/node_modules/lib-1/sub-2.js'), contents: `export * from './sub-2/sub-2';`}, - {name: _('/node_modules/lib-1/sub-2/sub-2.js'), contents: `export class Z {}';`}, - {name: _('/node_modules/lib-1/sub-2/package.json'), contents: '{"esm2015": "./sub-2.js"}'}, - {name: _('/node_modules/lib-1/sub-2/sub-2.metadata.json'), contents: 'MOCK METADATA'}, - {name: _('/dist/components/index.js'), contents: `class MyComponent {};`}, - {name: _('/dist/components/package.json'), contents: '{"esm2015": "./index.js"}'}, - {name: _('/dist/components/index.metadata.json'), contents: 'MOCK METADATA'}, - { - name: _('/dist/shared/index.js'), - contents: `import {X} from 'lib-1';\nexport class Service {}` - }, - {name: _('/dist/shared/package.json'), contents: '{"esm2015": "./index.js"}'}, - {name: _('/dist/shared/index.metadata.json'), contents: 'MOCK METADATA'}, - {name: _('/dist/lib/shared/test/index.js'), contents: `export class TestHelper {}`}, - {name: _('/dist/lib/shared/test/package.json'), contents: '{"esm2015": "./index.js"}'}, - {name: _('/dist/lib/shared/test/index.metadata.json'), contents: 'MOCK METADATA'}, - ]); - } - - describe('isStringImportOrReexport', () => { - it('should return true if the statement is an import', () => { - expect(isStringImportOrReexport(createStatement('import {X} from "some/x";'))).toBe(true); - expect(isStringImportOrReexport(createStatement('import * as X from "some/x";'))) - .toBe(true); - }); - - it('should return true if the statement is a re-export', () => { - expect(isStringImportOrReexport(createStatement('export {X} from "some/x";'))).toBe(true); - expect(isStringImportOrReexport(createStatement('export * from "some/x";'))).toBe(true); - }); - - it('should return false if the statement is not an import or a re-export', () => { - expect(isStringImportOrReexport(createStatement('class X {}'))).toBe(false); - expect(isStringImportOrReexport(createStatement('export function foo() {}'))).toBe(false); - expect(isStringImportOrReexport(createStatement('export const X = 10;'))).toBe(false); - }); - - function createStatement(source: string) { - return ts - .createSourceFile('source.js', source, ts.ScriptTarget.ES2015, false, ts.ScriptKind.JS) - .statements[0]; - } - }); - - describe('hasImportOrReexportStatements', () => { - it('should return true if there is an import statement', () => { - expect(hasImportOrReexportStatements('import {X} from "some/x";')).toBe(true); - expect(hasImportOrReexportStatements('import {X} from \'some/x\';')).toBe(true); - expect(hasImportOrReexportStatements('import * as X from "some/x";')).toBe(true); - expect(hasImportOrReexportStatements('import * as X from \'some/x\';')).toBe(true); - expect(hasImportOrReexportStatements('blah blah\n\n import {X} from "some/x";\nblah blah')) - .toBe(true); - expect( - hasImportOrReexportStatements('blah blah\n\n import {X} from \'some/x\';\nblah blah')) - .toBe(true); - expect(hasImportOrReexportStatements('\t\timport {X} from "some/x";')).toBe(true); - expect(hasImportOrReexportStatements('\t\timport {X} from \'some/x\';')).toBe(true); - expect(hasImportOrReexportStatements('\t\timport {\n X,\n Y\n} from "some/x";')) - .toBe(true); - expect(hasImportOrReexportStatements('\t\timport {\n X,\n Y\n} from \'some/x\';')) - .toBe(true); - expect(hasImportOrReexportStatements('\t\timport "some/x";')).toBe(true); - expect(hasImportOrReexportStatements('\t\timport \'some/x\';')).toBe(true); - }); - - it('should return true if there is a re-export statement', () => { - expect(hasImportOrReexportStatements('export {X} from "some/x";')).toBe(true); - expect(hasImportOrReexportStatements('export {X} from \'some/x\';')).toBe(true); - expect(hasImportOrReexportStatements('blah blah\n\n export {X} from "some/x";\nblah blah')) - .toBe(true); - expect( - hasImportOrReexportStatements('blah blah\n\n export {X} from \'some/x\';\nblah blah')) - .toBe(true); - expect(hasImportOrReexportStatements('\t\texport {X} from "some/x";')).toBe(true); - expect(hasImportOrReexportStatements('\t\texport {X} from \'some/x\';')).toBe(true); - expect(hasImportOrReexportStatements('export {\n X,\n Y\n} from "some/x";')).toBe(true); - expect(hasImportOrReexportStatements('export {\n X,\n Y\n} from \'some/x\';')).toBe(true); - expect(hasImportOrReexportStatements( - 'blah blah\n\n export * from "@angular/core";\nblah blah')) - .toBe(true); - expect(hasImportOrReexportStatements( - 'blah blah\n\n export * from \'@angular/core\';\nblah blah')) - .toBe(true); - }); - - it('should return false if there is no import nor re-export statement', () => { - expect(hasImportOrReexportStatements('blah blah')).toBe(false); - expect(hasImportOrReexportStatements('export function moo() {}')).toBe(false); - expect(hasImportOrReexportStatements('Some text that happens to include the word import')) - .toBe(false); - }); - }); - }); -}); diff --git a/packages/compiler-cli/ngcc/test/dependencies/module_resolver_spec.ts b/packages/compiler-cli/ngcc/test/dependencies/module_resolver_spec.ts deleted file mode 100644 index 70673179716..00000000000 --- a/packages/compiler-cli/ngcc/test/dependencies/module_resolver_spec.ts +++ /dev/null @@ -1,316 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import {absoluteFrom, getFileSystem} from '../../../src/ngtsc/file_system'; -import {runInEachFileSystem} from '../../../src/ngtsc/file_system/testing'; -import {loadTestFiles} from '../../../src/ngtsc/testing'; -import {ModuleResolver, ResolvedDeepImport, ResolvedExternalModule, ResolvedRelativeModule} from '../../src/dependencies/module_resolver'; - -runInEachFileSystem(() => { - describe('ModuleResolver', () => { - let _: typeof absoluteFrom; - - beforeEach(() => { - _ = absoluteFrom; - loadTestFiles([ - {name: _('/libs/local-package/package.json'), contents: 'PACKAGE.JSON for local-package'}, - {name: _('/libs/local-package/index.js'), contents: `import {X} from './x';`}, - {name: _('/libs/local-package/x.js'), contents: `export class X {}`}, - {name: _('/libs/local-package/sub-folder/index.js'), contents: `import {X} from '../x';`}, - { - name: _('/libs/local-package/node_modules/package-1/sub-folder/index.js'), - contents: `export class Z {}` - }, - { - name: _('/libs/local-package/node_modules/package-1/package.json'), - contents: 'PACKAGE.JSON for package-1' - }, - { - name: _('/libs/node_modules/package-2/package.json'), - contents: 'PACKAGE.JSON for package-2' - }, - { - name: _('/libs/node_modules/package-2/node_modules/package-3/package.json'), - contents: 'PACKAGE.JSON for package-3' - }, - {name: _('/dist/package-4/x.js'), contents: `export class X {}`}, - {name: _('/dist/package-4/package.json'), contents: 'PACKAGE.JSON for package-4'}, - { - name: _('/dist/package-4/sub-folder/index.js'), - contents: `import {X} from '@shared/package-4/x';` - }, - {name: _('/dist/package-4/secondary-entry-point/x.js'), contents: `export class X {}`}, - { - name: _('/dist/package-4/secondary-entry-point/package.json'), - contents: 'PACKAGE.JSON for secondary-entry-point' - }, - { - name: _('/dist/sub-folder/package-4/package.json'), - contents: 'PACKAGE.JSON for package-4' - }, - { - name: _('/dist/sub-folder/package-5/package.json'), - contents: 'PACKAGE.JSON for package-5' - }, - { - name: _('/dist/sub-folder/package-5/post-fix/package.json'), - contents: 'PACKAGE.JSON for package-5/post-fix' - }, - { - name: _('/node_modules/top-package/package.json'), - contents: 'PACKAGE.JSON for top-package' - }, - { - name: _('/node_modules/apf-v14-package/package.json'), - contents: `{ - "name": "apf-v14-package", - "exports": { - "./second/ary": { - "main": "./second/ary/index.js" - } - } - }`, - }, - { - name: _('/node_modules/apf-v14-package/index.js'), - contents: `export const type = 'primary';`, - }, - { - name: _('/node_modules/apf-v14-package/second/ary/index.js'), - contents: `export const type = 'secondary';`, - }, - ]); - }); - - describe('resolveModuleImport()', () => { - describe('with relative paths', () => { - it('should resolve sibling, child and aunt modules', () => { - const resolver = new ModuleResolver(getFileSystem()); - - // With relative file paths. - expect(resolver.resolveModuleImport('./x', _('/libs/local-package/index.js'))) - .toEqual(new ResolvedRelativeModule(_('/libs/local-package/x.js'))); - expect(resolver.resolveModuleImport('./sub-folder', _('/libs/local-package/index.js'))) - .toEqual(new ResolvedRelativeModule(_('/libs/local-package/sub-folder/index.js'))); - expect(resolver.resolveModuleImport('../x', _('/libs/local-package/sub-folder/index.js'))) - .toEqual(new ResolvedRelativeModule(_('/libs/local-package/x.js'))); - - // With absolute file paths. - expect(resolver.resolveModuleImport( - _('/libs/local-package/x'), _('/libs/local-package/index.js'))) - .toEqual(new ResolvedRelativeModule(_('/libs/local-package/x.js'))); - expect(resolver.resolveModuleImport( - _('/libs/local-package/sub-folder'), _('/libs/local-package/index.js'))) - .toEqual(new ResolvedRelativeModule(_('/libs/local-package/sub-folder/index.js'))); - expect(resolver.resolveModuleImport( - _('/libs/local-package/x'), _('/libs/local-package/sub-folder/index.js'))) - .toEqual(new ResolvedRelativeModule(_('/libs/local-package/x.js'))); - }); - - it('should return `null` if the resolved module relative module does not exist', () => { - const resolver = new ModuleResolver(getFileSystem()); - expect(resolver.resolveModuleImport('./y', _('/libs/local-package/index.js'))).toBe(null); - }); - - it('should resolve modules that already include an extension', () => { - const resolver = new ModuleResolver(getFileSystem()); - expect(resolver.resolveModuleImport('./x.js', _('/libs/local-package/index.js'))) - .toEqual(new ResolvedRelativeModule(_('/libs/local-package/x.js'))); - }); - }); - - describe('with non-mapped external paths', () => { - it('should resolve to the package.json of a local node_modules package', () => { - const resolver = new ModuleResolver(getFileSystem()); - expect(resolver.resolveModuleImport('package-1', _('/libs/local-package/index.js'))) - .toEqual(new ResolvedExternalModule(_('/libs/local-package/node_modules/package-1'))); - expect(resolver.resolveModuleImport( - 'package-1', _('/libs/local-package/sub-folder/index.js'))) - .toEqual(new ResolvedExternalModule(_('/libs/local-package/node_modules/package-1'))); - expect(resolver.resolveModuleImport('package-1', _('/libs/local-package/x.js'))) - .toEqual(new ResolvedExternalModule(_('/libs/local-package/node_modules/package-1'))); - }); - - it('should resolve to the package.json of a higher node_modules package', () => { - const resolver = new ModuleResolver(getFileSystem()); - expect(resolver.resolveModuleImport('package-2', _('/libs/local-package/index.js'))) - .toEqual(new ResolvedExternalModule(_('/libs/node_modules/package-2'))); - expect(resolver.resolveModuleImport('top-package', _('/libs/local-package/index.js'))) - .toEqual(new ResolvedExternalModule(_('/node_modules/top-package'))); - }); - - it('should return `null` if the package cannot be found', () => { - const resolver = new ModuleResolver(getFileSystem()); - expect(resolver.resolveModuleImport('missing-2', _('/libs/local-package/index.js'))) - .toBe(null); - }); - - it('should return `null` if the package is not accessible because it is in a inner node_modules package', - () => { - const resolver = new ModuleResolver(getFileSystem()); - expect(resolver.resolveModuleImport('package-3', _('/libs/local-package/index.js'))) - .toBe(null); - }); - - it('should identify deep imports into an external module', () => { - const resolver = new ModuleResolver(getFileSystem()); - expect(resolver.resolveModuleImport( - 'package-1/sub-folder', _('/libs/local-package/index.js'))) - .toEqual(new ResolvedDeepImport( - _('/libs/local-package/node_modules/package-1/sub-folder'))); - }); - - it('should resolve to APF v14+ secondary entry-points', () => { - const resolver = new ModuleResolver(getFileSystem()); - - expect(resolver.resolveModuleImport( - 'apf-v14-package/second/ary', _('/libs/local-package/index.js'))) - .toEqual(new ResolvedExternalModule(_('/node_modules/apf-v14-package/second/ary'))); - expect(resolver.resolveModuleImport( - 'apf-v14-package/second/ary', _('/libs/local-package/sub-folder/index.js'))) - .toEqual(new ResolvedExternalModule(_('/node_modules/apf-v14-package/second/ary'))); - }); - }); - - describe('with mapped path external modules', () => { - it('should resolve to the package.json of simple mapped packages', () => { - const resolver = new ModuleResolver( - getFileSystem(), {baseUrl: '/dist', paths: {'*': ['*', 'sub-folder/*']}}); - - expect(resolver.resolveModuleImport('package-4', _('/libs/local-package/index.js'))) - .toEqual(new ResolvedExternalModule(_('/dist/package-4'))); - - expect(resolver.resolveModuleImport('package-5', _('/libs/local-package/index.js'))) - .toEqual(new ResolvedExternalModule(_('/dist/sub-folder/package-5'))); - }); - - it('should select the best match by the length of prefix before the *', () => { - const resolver = new ModuleResolver(getFileSystem(), { - baseUrl: '/dist', - paths: { - '@lib/*': ['*'], - '@lib/sub-folder/*': ['*'], - } - }); - - // We should match the second path (e.g. `'@lib/sub-folder/*'`), which will actually map - // to `*` and so the final resolved path will not include the `sub-folder` segment. - expect(resolver.resolveModuleImport( - '@lib/sub-folder/package-4', _('/libs/local-package/index.js'))) - .toEqual(new ResolvedExternalModule(_('/dist/package-4'))); - }); - - it('should follow the ordering of `paths` when matching mapped packages', () => { - let resolver: ModuleResolver; - - const fs = getFileSystem(); - resolver = - new ModuleResolver(fs, {baseUrl: '/dist', paths: {'*': ['*', 'sub-folder/*']}}); - expect(resolver.resolveModuleImport('package-4', _('/libs/local-package/index.js'))) - .toEqual(new ResolvedExternalModule(_('/dist/package-4'))); - - resolver = - new ModuleResolver(fs, {baseUrl: '/dist', paths: {'*': ['sub-folder/*', '*']}}); - expect(resolver.resolveModuleImport('package-4', _('/libs/local-package/index.js'))) - .toEqual(new ResolvedExternalModule(_('/dist/sub-folder/package-4'))); - }); - - it('should resolve packages when the path mappings have post-fixes', () => { - const resolver = new ModuleResolver( - getFileSystem(), {baseUrl: '/dist', paths: {'*': ['sub-folder/*/post-fix']}}); - expect(resolver.resolveModuleImport('package-5', _('/libs/local-package/index.js'))) - .toEqual(new ResolvedExternalModule(_('/dist/sub-folder/package-5/post-fix'))); - }); - - it('should match paths against complex path matchers', () => { - const resolver = new ModuleResolver( - getFileSystem(), {baseUrl: '/dist', paths: {'@shared/*': ['sub-folder/*']}}); - expect( - resolver.resolveModuleImport('@shared/package-4', _('/libs/local-package/index.js'))) - .toEqual(new ResolvedExternalModule(_('/dist/sub-folder/package-4'))); - expect(resolver.resolveModuleImport('package-5', _('/libs/local-package/index.js'))) - .toBe(null); - }); - - it('should resolve path as "relative" if the mapped path is inside the current package', - () => { - const resolver = new ModuleResolver( - getFileSystem(), {baseUrl: '/dist', paths: {'@shared/*': ['*']}}); - expect(resolver.resolveModuleImport( - '@shared/package-4/x', _('/dist/package-4/sub-folder/index.js'))) - .toEqual(new ResolvedRelativeModule(_('/dist/package-4/x.js'))); - }); - - it('should resolve paths where the wildcard matches more than one path segment', () => { - const resolver = new ModuleResolver( - getFileSystem(), {baseUrl: '/dist', paths: {'@shared/*/post-fix': ['*/post-fix']}}); - expect(resolver.resolveModuleImport( - '@shared/sub-folder/package-5/post-fix', - _('/dist/package-4/sub-folder/index.js'))) - .toEqual(new ResolvedExternalModule(_('/dist/sub-folder/package-5/post-fix'))); - }); - - it('should resolve primary entry-points if they match non-wildcards exactly', () => { - const resolver = new ModuleResolver( - getFileSystem(), {baseUrl: '/dist', paths: {'package-4': ['package-4']}}); - expect(resolver.resolveModuleImport('package-4', _('/libs/local-package/index.js'))) - .toEqual(new ResolvedExternalModule(_('/dist/package-4'))); - expect(resolver.resolveModuleImport( - 'package-4/secondary-entry-point', _('/libs/local-package/index.js'))) - .toEqual(null); - }); - - it('should resolve secondary entry-points if wildcards match', () => { - const resolver = new ModuleResolver(getFileSystem(), { - baseUrl: '/dist', - paths: {'package-4': ['package-4'], 'package-4/*': ['package-4/*']} - }); - expect(resolver.resolveModuleImport('package-4', _('/libs/local-package/index.js'))) - .toEqual(new ResolvedExternalModule(_('/dist/package-4'))); - expect(resolver.resolveModuleImport( - 'package-4/secondary-entry-point', _('/libs/local-package/index.js'))) - .toEqual(new ResolvedExternalModule(_('/dist/package-4/secondary-entry-point'))); - }); - - it('should resolve secondary-entry-points referenced from their primary entry-point', - () => { - const resolver = new ModuleResolver(getFileSystem(), { - baseUrl: '/dist', - paths: {'package-4': ['package-4'], 'package-4/*': ['package-4/*']} - }); - expect(resolver.resolveModuleImport( - 'package-4/secondary-entry-point', _('/dist/package-4/index.js'))) - .toEqual(new ResolvedExternalModule(_('/dist/package-4/secondary-entry-point'))); - }); - - it('should resolve APF v14+ secondary entry-points', () => { - const resolver = new ModuleResolver(getFileSystem(), { - baseUrl: '/node_modules', - paths: {'package-42': ['apf-v14-package'], 'package-42/*': ['apf-v14-package/*']}, - }); - - expect(resolver.resolveModuleImport( - 'package-42/second/ary', _('/libs/local-package/index.js'))) - .toEqual(new ResolvedExternalModule(_('/node_modules/apf-v14-package/second/ary'))); - expect(resolver.resolveModuleImport( - 'package-42/second/ary', _('/libs/local-package/sub-folder/index.js'))) - .toEqual(new ResolvedExternalModule(_('/node_modules/apf-v14-package/second/ary'))); - }); - }); - - describe('with mapped path relative paths', () => { - it('should resolve to a relative file if found via a paths mapping', () => { - const resolver = new ModuleResolver( - getFileSystem(), {baseUrl: '/', paths: {'mapped/*': ['libs/local-package/*']}}); - - expect(resolver.resolveModuleImport('mapped/x', _('/libs/local-package/index.js'))) - .toEqual(new ResolvedRelativeModule(_('/libs/local-package/x.js'))); - }); - }); - }); - }); -}); diff --git a/packages/compiler-cli/ngcc/test/dependencies/umd_dependency_host_spec.ts b/packages/compiler-cli/ngcc/test/dependencies/umd_dependency_host_spec.ts deleted file mode 100644 index c30b5e04a71..00000000000 --- a/packages/compiler-cli/ngcc/test/dependencies/umd_dependency_host_spec.ts +++ /dev/null @@ -1,322 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import ts from 'typescript'; - -import {absoluteFrom, getFileSystem, relativeFrom} from '../../../src/ngtsc/file_system'; -import {runInEachFileSystem} from '../../../src/ngtsc/file_system/testing'; -import {loadTestFiles} from '../../../src/ngtsc/testing'; -import {createDependencyInfo} from '../../src/dependencies/dependency_host'; -import {ModuleResolver} from '../../src/dependencies/module_resolver'; -import {UmdDependencyHost} from '../../src/dependencies/umd_dependency_host'; -import {AdditionalFormatOptions, testForEachUmdFormat} from '../helpers/umd_utils'; - -runInEachFileSystem(() => { - describe( - 'UmdDependencyHost', testForEachUmdFormat(({createUmdModule}) => { - let _: typeof absoluteFrom; - let host: UmdDependencyHost; - - beforeEach(() => { - _ = absoluteFrom; - setupMockFileSystem(); - const fs = getFileSystem(); - host = new UmdDependencyHost(fs, new ModuleResolver(fs)); - }); - - describe('collectDependencies()', () => { - it('should not generate a TS AST if the source does not contain any require calls', - () => { - spyOn(ts, 'createSourceFile'); - host.collectDependencies( - _('/no/imports/or/re-exports/index.js'), createDependencyInfo()); - expect(ts.createSourceFile).not.toHaveBeenCalled(); - }); - - it('should correctly process files with no imports', () => { - const {dependencies, missing, deepImports} = createDependencyInfo(); - host.collectDependencies( - _('/no/imports/but/cannot/skip/index.js'), {dependencies, missing, deepImports}); - expect(dependencies.size).toBe(0); - expect(missing.size).toBe(0); - expect(deepImports.size).toBe(0); - }); - - it('should resolve all the external imports of the source file', () => { - const {dependencies, missing, deepImports} = createDependencyInfo(); - host.collectDependencies( - _('/external/imports/index.js'), {dependencies, missing, deepImports}); - expect(dependencies.size).toBe(2); - expect(missing.size).toBe(0); - expect(deepImports.size).toBe(0); - expect(dependencies.has(_('/node_modules/lib_1'))).toBe(true); - expect(dependencies.has(_('/node_modules/lib_1/sub_1'))).toBe(true); - }); - - it('should resolve imports preceeding the `exports` parameter', () => { - const {dependencies, missing, deepImports} = createDependencyInfo(); - host.collectDependencies( - _('/external/preceeding/index.js'), {dependencies, missing, deepImports}); - expect(dependencies.size).toBe(2); - expect(missing.size).toBe(0); - expect(deepImports.size).toBe(0); - expect(dependencies.has(_('/node_modules/lib_1'))).toBe(true); - expect(dependencies.has(_('/node_modules/lib_1/sub_1'))).toBe(true); - }); - - it('should resolve all the external re-exports of the source file', () => { - const {dependencies, missing, deepImports} = createDependencyInfo(); - host.collectDependencies( - _('/external/re-exports/index.js'), {dependencies, missing, deepImports}); - expect(dependencies.size).toBe(2); - expect(missing.size).toBe(0); - expect(deepImports.size).toBe(0); - expect(dependencies.has(_('/node_modules/lib_1'))).toBe(true); - expect(dependencies.has(_('/node_modules/lib_1/sub_1'))).toBe(true); - }); - - it('should capture missing external imports', () => { - const {dependencies, missing, deepImports} = createDependencyInfo(); - host.collectDependencies( - _('/external/imports-missing/index.js'), {dependencies, missing, deepImports}); - - expect(dependencies.size).toBe(1); - expect(dependencies.has(_('/node_modules/lib_1'))).toBe(true); - expect(missing.size).toBe(1); - expect(missing.has(relativeFrom('missing'))).toBe(true); - expect(deepImports.size).toBe(0); - }); - - it('should not register deep imports as missing', () => { - // This scenario verifies the behavior of the dependency analysis when an external - // import is found that does not map to an entry-point but still exists on disk, i.e. a - // deep import. Such deep imports are captured for diagnostics purposes. - const {dependencies, missing, deepImports} = createDependencyInfo(); - host.collectDependencies( - _('/external/deep-import/index.js'), {dependencies, missing, deepImports}); - - expect(dependencies.size).toBe(0); - expect(missing.size).toBe(0); - expect(deepImports.size).toBe(1); - expect(deepImports.has(_('/node_modules/lib_1/deep/import'))).toBe(true); - }); - - it('should recurse into internal dependencies', () => { - const {dependencies, missing, deepImports} = createDependencyInfo(); - host.collectDependencies( - _('/internal/outer/index.js'), {dependencies, missing, deepImports}); - - expect(dependencies.size).toBe(1); - expect(dependencies.has(_('/node_modules/lib_1/sub_1'))).toBe(true); - expect(missing.size).toBe(0); - expect(deepImports.size).toBe(0); - }); - - it('should handle circular internal dependencies', () => { - const {dependencies, missing, deepImports} = createDependencyInfo(); - host.collectDependencies( - _('/internal/circular_a/index.js'), {dependencies, missing, deepImports}); - expect(dependencies.size).toBe(2); - expect(dependencies.has(_('/node_modules/lib_1'))).toBe(true); - expect(dependencies.has(_('/node_modules/lib_1/sub_1'))).toBe(true); - expect(missing.size).toBe(0); - expect(deepImports.size).toBe(0); - }); - - it('should support `paths` alias mappings when resolving modules', () => { - const fs = getFileSystem(); - host = new UmdDependencyHost(fs, new ModuleResolver(fs, { - baseUrl: '/dist', - paths: { - '@app/*': ['*'], - '@lib/*/test': ['lib/*/test'], - } - })); - const {dependencies, missing, deepImports} = createDependencyInfo(); - host.collectDependencies( - _('/path-alias/index.js'), {dependencies, missing, deepImports}); - expect(dependencies.size).toBe(4); - expect(dependencies.has(_('/dist/components'))).toBe(true); - expect(dependencies.has(_('/dist/shared'))).toBe(true); - expect(dependencies.has(_('/dist/lib/shared/test'))).toBe(true); - expect(dependencies.has(_('/node_modules/lib_1'))).toBe(true); - expect(missing.size).toBe(0); - expect(deepImports.size).toBe(0); - }); - - it('should handle entry-point paths with no extension', () => { - const {dependencies, missing, deepImports} = createDependencyInfo(); - host.collectDependencies( - _('/external/imports/index'), {dependencies, missing, deepImports}); - expect(dependencies.size).toBe(2); - expect(missing.size).toBe(0); - expect(deepImports.size).toBe(0); - expect(dependencies.has(_('/node_modules/lib_1'))).toBe(true); - expect(dependencies.has(_('/node_modules/lib_1/sub_1'))).toBe(true); - }); - - it('should correctly report errors for invalid source files', () => { - const {dependencies, missing, deepImports} = createDependencyInfo(); - - expect( - () => host.collectDependencies( - _('/invalid/format/index.js'), {dependencies, missing, deepImports})) - .toThrowError( - /^UMD wrapper body is not in a supported format \(expected a conditional expression or if statement\)/); - }); - }); - - function setupMockFileSystem(): void { - loadTestFiles([ - { - name: _('/no/imports/or/re-exports/index.js'), - contents: '// some text but no import-like statements' - }, - {name: _('/no/imports/or/re-exports/package.json'), contents: '{"main": "./index.js"}'}, - {name: _('/no/imports/or/re-exports/index.metadata.json'), contents: 'MOCK METADATA'}, - { - name: _('/no/imports/but/cannot/skip/index.js'), - contents: - '// A file containing text that looks like a `require("call")` to trick the host\n' + - '// into processing it even if it has no actual dependencies.\n' + - umd('no_imports_but_cannot_skip', []), - }, - { - name: _('/no/imports/but/cannot/skip/package.json'), - contents: '{"main": "./index.js"}' - }, - {name: _('/no/imports/but/cannot/skip/index.metadata.json'), contents: 'MOCK METADATA'}, - { - name: _('/invalid/format/index.js'), - contents: ` - (function (global, factory) { - const invalidWrapperFunctionFormat = require('true'); - }(this, function () {})); - `, - }, - {name: _('/invalid/format/package.json'), contents: '{"main": "./index.js"}'}, - {name: _('/invalid/format/index.metadata.json'), contents: 'MOCK METADATA'}, - { - name: _('/external/imports/index.js'), - contents: umd('imports_index', ['lib_1', 'lib_1/sub_1']) - }, - {name: _('/external/imports/package.json'), contents: '{"main": "./index.js"}'}, - {name: _('/external/imports/index.metadata.json'), contents: 'MOCK METADATA'}, - { - name: _('/external/preceeding/index.js'), - contents: - umd('imports_index', ['lib_1', 'lib_1/sub_1'], undefined, {exportsParamIndex: 1}) - }, - {name: _('/external/preceeding/package.json'), contents: '{"main": "./index.js"}'}, - {name: _('/external/preceeding/index.metadata.json'), contents: 'MOCK METADATA'}, - { - name: _('/external/re-exports/index.js'), - contents: umd('imports_index', ['lib_1', 'lib_1/sub_1'], ['lib_1.X', 'lib_1sub_1.Y']) - }, - {name: _('/external/re-exports/package.json'), contents: '{"main": "./index.js"}'}, - {name: _('/external/re-exports/index.metadata.json'), contents: 'MOCK METADATA'}, - { - name: _('/external/imports-missing/index.js'), - contents: umd('imports_missing', ['lib_1', 'missing']) - }, - {name: _('/external/imports-missing/package.json'), contents: '{"main": "./index.js"}'}, - {name: _('/external/imports-missing/index.metadata.json'), contents: 'MOCK METADATA'}, - { - name: _('/external/deep-import/index.js'), - contents: umd('deep_import', ['lib_1/deep/import']) - }, - {name: _('/external/deep-import/package.json'), contents: '{"main": "./index.js"}'}, - {name: _('/external/deep-import/index.metadata.json'), contents: 'MOCK METADATA'}, - {name: _('/internal/outer/index.js'), contents: umd('outer', ['../inner'])}, - {name: _('/internal/outer/package.json'), contents: '{"main": "./index.js"}'}, - {name: _('/internal/outer/index.metadata.json'), contents: 'MOCK METADATA'}, - {name: _('/internal/inner/index.js'), contents: umd('inner', ['lib_1/sub_1'], ['X'])}, - { - name: _('/internal/circular_a/index.js'), - contents: umd('circular_a', ['../circular_b', 'lib_1/sub_1'], ['Y']) - }, - { - name: _('/internal/circular_b/index.js'), - contents: umd('circular_b', ['../circular_a', 'lib_1'], ['X']) - }, - {name: _('/internal/circular_a/package.json'), contents: '{"main": "./index.js"}'}, - {name: _('/internal/circular_a/index.metadata.json'), contents: 'MOCK METADATA'}, - {name: _('/re-directed/index.js'), contents: umd('re_directed', ['lib_1/sub_2'])}, - {name: _('/re-directed/package.json'), contents: '{"main": "./index.js"}'}, - {name: _('/re-directed/index.metadata.json'), contents: 'MOCK METADATA'}, - { - name: _('/path-alias/index.js'), - contents: - umd('path_alias', ['@app/components', '@app/shared', '@lib/shared/test', 'lib_1']) - }, - {name: _('/path-alias/package.json'), contents: '{"main": "./index.js"}'}, - {name: _('/path-alias/index.metadata.json'), contents: 'MOCK METADATA'}, - {name: _('/node_modules/lib_1/index.d.ts'), contents: 'export declare class X {}'}, - { - name: _('/node_modules/lib_1/package.json'), - contents: '{"esm2015": "./index.js", "typings": "./index.d.ts"}' - }, - {name: _('/node_modules/lib_1/index.metadata.json'), contents: 'MOCK METADATA'}, - { - name: _('/node_modules/lib_1/deep/import/index.js'), - contents: 'export class DeepImport {}' - }, - { - name: _('/node_modules/lib_1/sub_1/index.d.ts'), - contents: 'export declare class Y {}' - }, - { - name: _('/node_modules/lib_1/sub_1/package.json'), - contents: '{"esm2015": "./index.js", "typings": "./index.d.ts"}' - }, - {name: _('/node_modules/lib_1/sub_1/index.metadata.json'), contents: 'MOCK METADATA'}, - {name: _('/node_modules/lib_1/sub_2.d.ts'), contents: `export * from './sub_2/sub_2';`}, - { - name: _('/node_modules/lib_1/sub_2/sub_2.d.ts'), - contents: `export declare class Z {}';` - }, - { - name: _('/node_modules/lib_1/sub_2/package.json'), - contents: '{"esm2015": "./sub_2.js", "typings": "./sub_2.d.ts"}' - }, - {name: _('/node_modules/lib_1/sub_2/sub_2.metadata.json'), contents: 'MOCK METADATA'}, - { - name: _('/dist/components/index.d.ts'), - contents: `export declare class MyComponent {};` - }, - { - name: _('/dist/components/package.json'), - contents: '{"esm2015": "./index.js", "typings": "./index.d.ts"}' - }, - {name: _('/dist/components/index.metadata.json'), contents: 'MOCK METADATA'}, - { - name: _('/dist/shared/index.d.ts'), - contents: `import {X} from 'lib_1';\nexport declare class Service {}` - }, - { - name: _('/dist/shared/package.json'), - contents: '{"esm2015": "./index.js", "typings": "./index.d.ts"}' - }, - {name: _('/dist/shared/index.metadata.json'), contents: 'MOCK METADATA'}, - {name: _('/dist/lib/shared/test/index.d.ts'), contents: `export class TestHelper {}`}, - { - name: _('/dist/lib/shared/test/package.json'), - contents: '{"esm2015": "./index.js", "typings": "./index.d.ts"}' - }, - {name: _('/dist/lib/shared/test/index.metadata.json'), contents: 'MOCK METADATA'}, - ]); - } - - function umd( - moduleName: string, importPaths: string[], exportNames: string[] = [], - additionalOptions?: AdditionalFormatOptions) { - const exportStatements = - exportNames.map(e => ` exports.${e.replace(/.+\./, '')} = ${e};`).join('\n'); - return createUmdModule(moduleName, importPaths, exportStatements, additionalOptions); - } - })); -}); diff --git a/packages/compiler-cli/ngcc/test/entry_point_finder/directory_walker_entry_point_finder_spec.ts b/packages/compiler-cli/ngcc/test/entry_point_finder/directory_walker_entry_point_finder_spec.ts deleted file mode 100644 index 2084962b951..00000000000 --- a/packages/compiler-cli/ngcc/test/entry_point_finder/directory_walker_entry_point_finder_spec.ts +++ /dev/null @@ -1,464 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import {absoluteFrom, AbsoluteFsPath, FileSystem, getFileSystem} from '../../../src/ngtsc/file_system'; -import {runInEachFileSystem, TestFile} from '../../../src/ngtsc/file_system/testing'; -import {MockLogger} from '../../../src/ngtsc/logging/testing'; -import {loadTestFiles} from '../../../src/ngtsc/testing'; -import {DependencyResolver} from '../../src/dependencies/dependency_resolver'; -import {DtsDependencyHost} from '../../src/dependencies/dts_dependency_host'; -import {EsmDependencyHost} from '../../src/dependencies/esm_dependency_host'; -import {ModuleResolver} from '../../src/dependencies/module_resolver'; -import {DirectoryWalkerEntryPointFinder} from '../../src/entry_point_finder/directory_walker_entry_point_finder'; -import {EntryPointCollector} from '../../src/entry_point_finder/entry_point_collector'; -import {NgccConfiguration, ProcessedNgccPackageConfig} from '../../src/packages/configuration'; -import {EntryPoint} from '../../src/packages/entry_point'; -import {EntryPointManifest, EntryPointManifestFile} from '../../src/packages/entry_point_manifest'; -import {PathMappings} from '../../src/path_mappings'; - -runInEachFileSystem(() => { - describe('DirectoryWalkerEntryPointFinder', () => { - let fs: FileSystem; - let resolver: DependencyResolver; - let logger: MockLogger; - let config: NgccConfiguration; - let collector: EntryPointCollector; - let manifest: EntryPointManifest; - let _Abs: typeof absoluteFrom; - - beforeEach(() => { - fs = getFileSystem(); - _Abs = absoluteFrom; - logger = new MockLogger(); - const srcHost = new EsmDependencyHost(fs, new ModuleResolver(fs)); - const dtsHost = new DtsDependencyHost(fs); - config = new NgccConfiguration(fs, _Abs('/')); - resolver = new DependencyResolver(fs, logger, config, {esm2015: srcHost}, dtsHost); - manifest = new EntryPointManifest(fs, config, logger); - collector = new EntryPointCollector(fs, config, logger, resolver); - }); - - describe('findEntryPoints()', () => { - it('should find sub-entry-points within a package', () => { - const basePath = _Abs('/sub_entry_points/node_modules'); - loadTestFiles([ - ...createPackage(basePath, 'common'), - ...createPackage(fs.resolve(basePath, 'common'), 'http', ['common']), - ...createPackage( - fs.resolve(basePath, 'common/http'), 'testing', ['common/http', 'common/testing']), - ...createPackage(fs.resolve(basePath, 'common'), 'testing', ['common']), - ]); - const finder = new DirectoryWalkerEntryPointFinder( - logger, resolver, collector, manifest, basePath, undefined); - const {entryPoints} = finder.findEntryPoints(); - expect(dumpEntryPointPaths(basePath, entryPoints)).toEqual([ - ['common', 'common'], - ['common', 'common/http'], - ['common', 'common/testing'], - ['common', 'common/http/testing'], - ]); - }); - - it('should find packages inside a namespace', () => { - const basePath = _Abs('/namespaced/node_modules'); - loadTestFiles([ - ...createPackage(fs.resolve(basePath, '@angular'), 'common'), - ...createPackage(fs.resolve(basePath, '@angular/common'), 'http', ['@angular/common']), - ...createPackage( - fs.resolve(basePath, '@angular/common/http'), 'testing', - ['@angular/common/http', '@angular/common/testing']), - ...createPackage(fs.resolve(basePath, '@angular/common'), 'testing', ['@angular/common']), - ]); - const finder = new DirectoryWalkerEntryPointFinder( - logger, resolver, collector, manifest, basePath, undefined); - const {entryPoints} = finder.findEntryPoints(); - expect(dumpEntryPointPaths(basePath, entryPoints)).toEqual([ - ['@angular/common', '@angular/common'], - ['@angular/common', '@angular/common/http'], - ['@angular/common', '@angular/common/testing'], - ['@angular/common', '@angular/common/http/testing'], - ]); - }); - - it('should return an empty array if there are no packages', () => { - fs.ensureDir(_Abs('/no_packages/node_modules/should_not_be_found')); - const finder = new DirectoryWalkerEntryPointFinder( - logger, resolver, collector, manifest, _Abs('/no_packages/node_modules'), undefined); - const {entryPoints} = finder.findEntryPoints(); - expect(entryPoints).toEqual([]); - }); - - it('should return an empty array if there are no valid entry-points', () => { - loadTestFiles([ - { - name: _Abs('/no_valid_entry_points/node_modules/some_package/package.json'), - contents: '{}' - }, - ]); - const finder = new DirectoryWalkerEntryPointFinder( - logger, resolver, collector, manifest, _Abs('/no_valid_entry_points/node_modules'), - undefined); - const {entryPoints} = finder.findEntryPoints(); - expect(entryPoints).toEqual([]); - }); - - it('should not include ignored entry-points', () => { - const basePath = _Abs('/project/node_modules'); - const finder = new DirectoryWalkerEntryPointFinder( - logger, resolver, collector, manifest, basePath, undefined); - - loadTestFiles(createPackage(basePath, 'some-package')); - spyOn(config, 'getPackageConfig') - .and.returnValue( - new ProcessedNgccPackageConfig(fs, _Abs('/project/node_modules/some-package'), { - entryPoints: { - '.': {ignore: true}, - }, - })); - - const {entryPoints} = finder.findEntryPoints(); - expect(entryPoints).toEqual([]); - }); - - it('should look for sub-entry-points even if a containing entry-point is ignored', () => { - const basePath = _Abs('/project/node_modules'); - const finder = new DirectoryWalkerEntryPointFinder( - logger, resolver, collector, manifest, basePath, undefined); - - loadTestFiles([ - ...createPackage(basePath, 'some-package'), - ...createPackage(fs.resolve(basePath, 'some-package'), 'sub-entry-point-1'), - ...createPackage( - fs.resolve(basePath, 'some-package/sub-entry-point-1'), 'sub-entry-point-2'), - ]); - spyOn(config, 'getPackageConfig') - .and.returnValue( - new ProcessedNgccPackageConfig(fs, _Abs('/project/node_modules/some-package'), { - entryPoints: { - './sub-entry-point-1': {ignore: true}, - }, - })); - - const {entryPoints} = finder.findEntryPoints(); - expect(dumpEntryPointPaths(basePath, entryPoints)).toEqual([ - ['some-package', 'some-package'], - ['some-package', 'some-package/sub-entry-point-1/sub-entry-point-2'], - ]); - }); - - it('should write an entry-point manifest file if none was found and basePath is `node_modules`', - () => { - const basePath = _Abs('/sub_entry_points/node_modules'); - loadTestFiles([ - ...createPackage(basePath, 'common'), - ...createPackage(fs.resolve(basePath, 'common'), 'http', ['common']), - ...createPackage( - fs.resolve(basePath, 'common/http'), 'testing', ['common/http', 'common/testing']), - ...createPackage(fs.resolve(basePath, 'common'), 'testing', ['common']), - {name: _Abs('/sub_entry_points/yarn.lock'), contents: 'MOCK LOCK FILE'}, - ]); - spyOn(manifest, 'readEntryPointsUsingManifest').and.callThrough(); - spyOn(manifest, 'writeEntryPointManifest').and.callThrough(); - const finder = new DirectoryWalkerEntryPointFinder( - logger, resolver, collector, manifest, basePath, undefined); - finder.findEntryPoints(); - expect(manifest.readEntryPointsUsingManifest).toHaveBeenCalled(); - expect(manifest.writeEntryPointManifest).toHaveBeenCalled(); - expect(fs.exists(_Abs('/sub_entry_points/node_modules/__ngcc_entry_points__.json'))) - .toBe(true); - }); - - it('should not write an entry-point manifest file if basePath is not `node_modules`', () => { - const basePath = _Abs('/sub_entry_points/dist'); - loadTestFiles([ - ...createPackage(basePath, 'common'), - ...createPackage(fs.resolve(basePath, 'common'), 'http', ['common']), - ...createPackage( - fs.resolve(basePath, 'common/http'), 'testing', ['common/http', 'common/testing']), - ...createPackage(fs.resolve(basePath, 'common'), 'testing', ['common']), - {name: _Abs('/sub_entry_points/yarn.lock'), contents: 'MOCK LOCK FILE'}, - ]); - spyOn(manifest, 'readEntryPointsUsingManifest').and.callThrough(); - spyOn(manifest, 'writeEntryPointManifest').and.callThrough(); - const finder = new DirectoryWalkerEntryPointFinder( - logger, resolver, collector, manifest, basePath, undefined); - finder.findEntryPoints(); - expect(manifest.readEntryPointsUsingManifest).toHaveBeenCalled(); - expect(manifest.writeEntryPointManifest).toHaveBeenCalled(); - expect(fs.exists(_Abs('/sub_entry_points/dist/__ngcc_entry_points__.json'))).toBe(false); - }); - - it('should read from the entry-point manifest file if found', () => { - const basePath = _Abs('/sub_entry_points/node_modules'); - loadTestFiles([ - ...createPackage(basePath, 'common'), - ...createPackage(fs.resolve(basePath, 'common'), 'http', ['common']), - ...createPackage( - fs.resolve(basePath, 'common/http'), 'testing', ['common/http', 'common/testing']), - ...createPackage(fs.resolve(basePath, 'common'), 'testing', ['common']), - {name: _Abs('/sub_entry_points/yarn.lock'), contents: 'MOCK LOCK FILE'}, - ]); - const finder = new DirectoryWalkerEntryPointFinder( - logger, resolver, collector, manifest, basePath, undefined); - - // Prime the manifest by calling findEntryPoints() once. - finder.findEntryPoints(); - - // Modify the manifest to prove that we use it to find the entry-points - const manifestPath = _Abs('/sub_entry_points/node_modules/__ngcc_entry_points__.json'); - const manifestFile = JSON.parse(fs.readFile(manifestPath)) as EntryPointManifestFile; - manifestFile.entryPointPaths.pop(); - fs.writeFile(manifestPath, JSON.stringify(manifestFile)); - - // Now see if the manifest is read on a second call. - spyOn(manifest, 'readEntryPointsUsingManifest').and.callThrough(); - spyOn(manifest, 'writeEntryPointManifest').and.callThrough(); - const {entryPoints} = finder.findEntryPoints(); - expect(manifest.readEntryPointsUsingManifest).toHaveBeenCalled(); - expect(manifest.writeEntryPointManifest).not.toHaveBeenCalled(); - expect(dumpEntryPointPaths(basePath, entryPoints)).toEqual([ - ['common', 'common'], - ['common', 'common/http'], - ['common', 'common/http/testing'], - ]); - }); - - it('should ignore folders starting with .', () => { - loadTestFiles([ - ...createPackage(_Abs('/dotted_folders/node_modules/'), '.common'), - ]); - const finder = new DirectoryWalkerEntryPointFinder( - logger, resolver, collector, manifest, _Abs('/dotted_folders/node_modules'), undefined); - const {entryPoints} = finder.findEntryPoints(); - expect(entryPoints).toEqual([]); - }); - - it('should ignore folders that are symlinked', () => { - fs.ensureDir(_Abs('/symlinked_folders/node_modules')); - fs.symlink( - _Abs('/external/node_modules/common'), _Abs('/symlinked_folders/node_modules/common')); - loadTestFiles(createPackage(_Abs('/external/node_modules'), 'common')); - const finder = new DirectoryWalkerEntryPointFinder( - logger, resolver, collector, manifest, _Abs('/symlinked_folders/node_modules'), - undefined); - const {entryPoints} = finder.findEntryPoints(); - expect(entryPoints).toEqual([]); - }); - - it('should handle nested node_modules folders', () => { - loadTestFiles([ - ...createPackage(_Abs('/nested_node_modules/node_modules'), 'outer', ['inner']), - ...createPackage(_Abs('/nested_node_modules/node_modules/outer/node_modules'), 'inner'), - ]); - const finder = new DirectoryWalkerEntryPointFinder( - logger, resolver, collector, manifest, _Abs('/nested_node_modules/node_modules'), - undefined); - const {entryPoints} = finder.findEntryPoints(); - // Note that the `inner` entry-point is not part of the `outer` package - expect(dumpEntryPointPaths(_Abs('/nested_node_modules/node_modules'), entryPoints)) - .toEqual([ - ['outer/node_modules/inner', 'outer/node_modules/inner'], - ['outer', 'outer'], - ]); - }); - - it('should not try to process nested node_modules of non Angular packages', () => { - const basePath = _Abs('/nested_node_modules/node_modules'); - loadTestFiles([ - ...createPackage(basePath, 'outer', ['inner'], false), - ...createPackage(_Abs(`${basePath}/outer/node_modules`), 'inner', undefined, false), - ]); - - const finder = new DirectoryWalkerEntryPointFinder( - logger, resolver, collector, manifest, _Abs('/nested_node_modules/node_modules'), - undefined); - const spy = spyOn(collector, 'walkDirectoryForPackages').and.callThrough(); - const {entryPoints} = finder.findEntryPoints(); - expect(spy.calls.allArgs()).toEqual([ - [_Abs(basePath)], - [_Abs(`${basePath}/outer`)], - ]); - - expect(entryPoints).toEqual([]); - }); - - it('should not try to process deeply nested folders of non TypeScript packages', () => { - const basePath = _Abs('/namespaced/node_modules'); - loadTestFiles([ - ...createNonTsPackage(_Abs(`${basePath}/@schematics`), 'angular'), - { - name: _Abs(`${basePath}/@schematics/angular/src/nested/index.js`), - contents: 'index', - }, - ]); - - const finder = new DirectoryWalkerEntryPointFinder( - logger, resolver, collector, manifest, basePath, undefined); - const spy = spyOn(collector, 'walkDirectoryForPackages').and.callThrough(); - const {entryPoints} = finder.findEntryPoints(); - expect(spy.calls.allArgs()).toEqual([ - [_Abs(basePath)], - [_Abs(`${basePath}/@schematics`)], - [_Abs(`${basePath}/@schematics/angular`)], - ]); - - expect(entryPoints).toEqual([]); - }); - - it('should not try to process nested node_modules of non TypeScript packages', () => { - const basePath = _Abs('/namespaced/node_modules'); - loadTestFiles([ - ...createNonTsPackage(_Abs(`${basePath}/@schematics`), 'angular'), - ...createNonTsPackage(_Abs(`${basePath}/@schematics/angular/node_modules`), 'test'), - { - name: _Abs(`${basePath}/@schematics/angular/src/nested/index.js`), - contents: 'index', - }, - ]); - - const finder = new DirectoryWalkerEntryPointFinder( - logger, resolver, collector, manifest, basePath, undefined); - const spy = spyOn(collector, 'walkDirectoryForPackages').and.callThrough(); - const {entryPoints} = finder.findEntryPoints(); - expect(spy.calls.allArgs()).toEqual([ - [_Abs(basePath)], - [_Abs(`${basePath}/@schematics`)], - [_Abs(`${basePath}/@schematics/angular`)], - ]); - - expect(entryPoints).toEqual([]); - }); - - it('should process sub-entry-points within a non-entry-point containing folder in a package', - () => { - const basePath = _Abs('/containing_folders/node_modules'); - loadTestFiles([ - ...createPackage(basePath, 'package'), - ...createPackage(fs.resolve(basePath, 'package/container'), 'entry-point-1'), - ]); - const finder = new DirectoryWalkerEntryPointFinder( - logger, resolver, collector, manifest, basePath, undefined); - const {entryPoints} = finder.findEntryPoints(); - expect(dumpEntryPointPaths(basePath, entryPoints)).toEqual([ - ['package', 'package'], - ['package', 'package/container/entry-point-1'], - ]); - }); - - it('should handle dependencies via pathMappings', () => { - const basePath = _Abs('/path_mapped/node_modules'); - const pathMappings: PathMappings = { - baseUrl: '/path_mapped/dist', - paths: { - '@x/*': ['*'], - '@y/*/test': ['lib/*/test'], - } - }; - loadTestFiles([ - ...createPackage( - _Abs('/path_mapped/node_modules'), 'test', ['pkg1', '@x/pkg2', '@y/pkg3/test']), - ...createPackage(_Abs('/path_mapped/node_modules'), 'pkg1'), - ...createPackage(_Abs('/path_mapped/dist'), 'pkg2', ['pkg4']), - ...createPackage(_Abs('/path_mapped/dist/pkg2/node_modules'), 'pkg4'), - ...createPackage(_Abs('/path_mapped/dist/lib/pkg3'), 'test'), - ]); - const srcHost = new EsmDependencyHost(fs, new ModuleResolver(fs, pathMappings)); - const dtsHost = new DtsDependencyHost(fs, pathMappings); - resolver = new DependencyResolver(fs, logger, config, {esm2015: srcHost}, dtsHost); - collector = new EntryPointCollector(fs, config, logger, resolver); - const finder = new DirectoryWalkerEntryPointFinder( - logger, resolver, collector, manifest, basePath, pathMappings); - const {entryPoints} = finder.findEntryPoints(); - expect(dumpEntryPointPaths(basePath, entryPoints)).toEqual([ - ['pkg1', 'pkg1'], - ['../dist/pkg2/node_modules/pkg4', '../dist/pkg2/node_modules/pkg4'], - ['../dist/pkg2', '../dist/pkg2'], - ['../dist/lib/pkg3/test', '../dist/lib/pkg3/test'], - ['test', 'test'], - ]); - }); - - it('should handle pathMappings that map to files or non-existent directories', () => { - const basePath = _Abs('/path_mapped/node_modules'); - const pathMappings: PathMappings = { - baseUrl: '/path_mapped/dist', - paths: { - '@test': ['pkg2/pkg2.d.ts'], - '@missing': ['pkg3'], - } - }; - loadTestFiles([ - ...createPackage(_Abs('/path_mapped/node_modules'), 'test', []), - ...createPackage(_Abs('/path_mapped/dist'), 'pkg2'), - ]); - const srcHost = new EsmDependencyHost(fs, new ModuleResolver(fs, pathMappings)); - const dtsHost = new DtsDependencyHost(fs, pathMappings); - resolver = new DependencyResolver(fs, logger, config, {esm2015: srcHost}, dtsHost); - const finder = new DirectoryWalkerEntryPointFinder( - logger, resolver, collector, manifest, basePath, pathMappings); - const {entryPoints} = finder.findEntryPoints(); - expect(dumpEntryPointPaths(basePath, entryPoints)).toEqual([ - ['test', 'test'], - ['../dist/pkg2', '../dist/pkg2'], - ]); - }); - - function createPackage( - basePath: AbsoluteFsPath, packageName: string, deps: string[] = [], - isCompiledByAngular = true): TestFile[] { - const files: TestFile[] = [ - { - name: _Abs(`${basePath}/${packageName}/package.json`), - contents: JSON.stringify({ - typings: `./${packageName}.d.ts`, - fesm2015: `./fesm2015/${packageName}.js`, - }) - }, - { - name: _Abs(`${basePath}/${packageName}/${packageName}.d.ts`), - contents: deps.map((dep, i) => `import * as i${i} from '${dep}';`).join('\n'), - }, - { - name: _Abs(`${basePath}/${packageName}/fesm2015/${packageName}.js`), - contents: deps.map((dep, i) => `import * as i${i} from '${dep}';`).join('\n'), - }, - ]; - - if (isCompiledByAngular) { - files.push({ - name: _Abs(`${basePath}/${packageName}/${packageName}.metadata.json`), - contents: 'metadata info' - }); - } - - return files; - } - - function createNonTsPackage( - basePath: AbsoluteFsPath, packageName: string, deps: string[] = []): TestFile[] { - return [ - { - name: _Abs(`${basePath}/${packageName}/package.json`), - contents: JSON.stringify({ - fesm2015: `./fesm2015/${packageName}.js`, - }) - }, - { - name: _Abs(`${basePath}/${packageName}/fesm2015/${packageName}.js`), - contents: deps.map((dep, i) => `import * as i${i} from '${dep}';`).join('\n'), - }, - ]; - } - - function dumpEntryPointPaths( - basePath: AbsoluteFsPath, entryPoints: EntryPoint[]): [string, string][] { - return entryPoints.map( - x => [fs.relative(basePath, x.packagePath), fs.relative(basePath, x.path)]); - } - }); - }); -}); diff --git a/packages/compiler-cli/ngcc/test/entry_point_finder/program_based_entry_point_finder_spec.ts b/packages/compiler-cli/ngcc/test/entry_point_finder/program_based_entry_point_finder_spec.ts deleted file mode 100644 index b38af49eb8a..00000000000 --- a/packages/compiler-cli/ngcc/test/entry_point_finder/program_based_entry_point_finder_spec.ts +++ /dev/null @@ -1,238 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import {absoluteFrom, AbsoluteFsPath, FileSystem, getFileSystem} from '../../../src/ngtsc/file_system'; -import {runInEachFileSystem, TestFile} from '../../../src/ngtsc/file_system/testing'; -import {MockLogger} from '../../../src/ngtsc/logging/testing'; -import {loadTestFiles} from '../../../src/ngtsc/testing'; -import {readConfiguration} from '../../../src/perform_compile'; -import {DependencyResolver} from '../../src/dependencies/dependency_resolver'; -import {DtsDependencyHost} from '../../src/dependencies/dts_dependency_host'; -import {EsmDependencyHost} from '../../src/dependencies/esm_dependency_host'; -import {ModuleResolver} from '../../src/dependencies/module_resolver'; -import {EntryPointCollector} from '../../src/entry_point_finder/entry_point_collector'; -import {ProgramBasedEntryPointFinder} from '../../src/entry_point_finder/program_based_entry_point_finder'; -import {NgccConfiguration} from '../../src/packages/configuration'; -import {EntryPoint} from '../../src/packages/entry_point'; -import {EntryPointManifest} from '../../src/packages/entry_point_manifest'; - -runInEachFileSystem(() => { - describe('ProgramBasedEntryPointFinder', () => { - let fs: FileSystem; - let _Abs: typeof absoluteFrom; - let projectPath: AbsoluteFsPath; - let basePath: AbsoluteFsPath; - let angularNamespacePath: AbsoluteFsPath; - - beforeEach(() => { - fs = getFileSystem(); - _Abs = absoluteFrom; - projectPath = _Abs('/sub_entry_points'); - basePath = _Abs('/sub_entry_points/node_modules'); - angularNamespacePath = fs.resolve(basePath, '@angular'); - }); - - describe('findEntryPoints()', () => { - it('should find entry-points imported into the program', () => { - loadTestFiles([ - ...createProgram(projectPath), - ...createPackage(angularNamespacePath, 'core'), - ...createPackage(angularNamespacePath, 'common'), - ...createPackage(fs.resolve(angularNamespacePath, 'common'), 'http', ['@angular/common']), - ...createPackage( - fs.resolve(angularNamespacePath, 'common/http'), 'testing', - ['common/http', 'common/testing']), - ...createPackage( - fs.resolve(angularNamespacePath, 'common'), 'testing', ['@angular/common']), - ]); - const finder = createFinder(); - const {entryPoints} = finder.findEntryPoints(); - expect(dumpEntryPointPaths(basePath, entryPoints)).toEqual([ - ['@angular/core', '@angular/core'], - ['@angular/common', '@angular/common'], - ['@angular/common', '@angular/common/http'], - ]); - }); - - it('should scan source files only once, even if they are referenced from multiple root files', - () => { - // https://github.com/angular/angular/issues/39240 - // When scanning the program for imports to determine which entry-points to process, the - // root files as configured in the tsconfig file are used to start scanning from. This - // test asserts that a file is not scanned multiple times even if it is referenced from - // multiple root files. - loadTestFiles([ - { - name: _Abs(`${projectPath}/package.json`), - contents: '', - }, - { - name: _Abs(`${projectPath}/tsconfig.json`), - contents: `{ - "files": [ - "root-one.ts", - "root-two.ts", - ], - "compilerOptions": { - "baseUrl": ".", - "paths": { - "lib/*": ["lib/*"] - } - } - }`, - }, - { - name: _Abs(`${projectPath}/root-one.ts`), - contents: ` - import './root-two'; - import './not-root'; - `, - }, - { - name: _Abs(`${projectPath}/root-two.ts`), - contents: ` - import './not-root'; - `, - }, - { - name: _Abs(`${projectPath}/not-root.ts`), - contents: ` - import {Component} from '@angular/core'; - `, - }, - ...createPackage(angularNamespacePath, 'core'), - ]); - - const extractImportsSpy = - spyOn(EsmDependencyHost.prototype, 'extractImports' as any).and.callThrough(); - - const finder = createFinder(); - const {entryPoints} = finder.findEntryPoints(); - expect(dumpEntryPointPaths(basePath, entryPoints)).toEqual([ - ['@angular/core', '@angular/core'], - ]); - - // Three `extractImports` calls should have been made corresponding with the three - // distinct source files in the project. - const extractImportFiles = - extractImportsSpy.calls.all().map((call: jasmine.CallInfo) => call.args[0]); - expect(extractImportFiles).toEqual([ - _Abs(`${projectPath}/root-one.ts`), - _Abs(`${projectPath}/root-two.ts`), - _Abs(`${projectPath}/not-root.ts`), - ]); - }); - - function createFinder(): ProgramBasedEntryPointFinder { - const tsConfig = readConfiguration(`${projectPath}/tsconfig.json`); - const baseUrl = fs.resolve(projectPath, tsConfig.options.basePath!); - const paths = tsConfig.options.paths!; - - const logger = new MockLogger(); - const srcHost = new EsmDependencyHost(fs, new ModuleResolver(fs, {baseUrl, paths})); - const dtsHost = new DtsDependencyHost(fs); - const config = new NgccConfiguration(fs, projectPath); - const resolver = new DependencyResolver(fs, logger, config, {esm2015: srcHost}, dtsHost); - const collector = new EntryPointCollector(fs, config, logger, resolver); - const manifest = new EntryPointManifest(fs, config, logger); - return new ProgramBasedEntryPointFinder( - fs, config, logger, resolver, collector, manifest, basePath, tsConfig, projectPath); - } - - function createProgram(projectPath: AbsoluteFsPath): TestFile[] { - return [ - { - name: _Abs(`${projectPath}/package.json`), - contents: '', - }, - { - name: _Abs(`${projectPath}/tsconfig.json`), - contents: `{ - "files": [ - "src/main.ts" - ], - "compilerOptions": { - "baseUrl": ".", - "paths": { - "lib/*": ["lib/*"] - } - } - }`, - }, - { - name: _Abs(`${projectPath}/src/main.ts`), - contents: ` - import {AppComponent} from './app.component'; - import * from './app.module'; - import * from 'lib/service'; - `, - }, - { - name: _Abs(`${projectPath}/src/app.component.ts`), - contents: ` - import * as core from '@angular/core'; - export class AppComponent {} - `, - }, - { - name: _Abs(`${projectPath}/src/app.module.ts`), - contents: ` - import {NgModule} from '@angular/core'; - import * as common from '@angular/common'; - import {AppComponent} from './app.component'; - export class AppModule {} - `, - }, - { - name: _Abs(`${projectPath}/lib/service/index.ts`), - contents: ` - import * as http from '@angular/common/http'; - export class Service {} - `, - }, - ]; - } - - function createPackage( - basePath: AbsoluteFsPath, packageName: string, deps: string[] = [], - isCompiledByAngular = true): TestFile[] { - const files: TestFile[] = [ - { - name: _Abs(`${basePath}/${packageName}/package.json`), - contents: JSON.stringify({ - typings: `./${packageName}.d.ts`, - fesm2015: `./fesm2015/${packageName}.js`, - }) - }, - { - name: _Abs(`${basePath}/${packageName}/${packageName}.d.ts`), - contents: deps.map((dep, i) => `import * as i${i} from '${dep}';`).join('\n'), - }, - { - name: _Abs(`${basePath}/${packageName}/fesm2015/${packageName}.js`), - contents: deps.map((dep, i) => `import * as i${i} from '${dep}';`).join('\n'), - }, - ]; - - if (isCompiledByAngular) { - files.push({ - name: _Abs(`${basePath}/${packageName}/${packageName}.metadata.json`), - contents: 'metadata info' - }); - } - - return files; - } - - function dumpEntryPointPaths( - basePath: AbsoluteFsPath, entryPoints: EntryPoint[]): [string, string][] { - return entryPoints.map( - x => [fs.relative(basePath, x.packagePath), fs.relative(basePath, x.path)]); - } - }); - }); -}); diff --git a/packages/compiler-cli/ngcc/test/entry_point_finder/targeted_entry_point_finder_spec.ts b/packages/compiler-cli/ngcc/test/entry_point_finder/targeted_entry_point_finder_spec.ts deleted file mode 100644 index f4c231e44bf..00000000000 --- a/packages/compiler-cli/ngcc/test/entry_point_finder/targeted_entry_point_finder_spec.ts +++ /dev/null @@ -1,700 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import {absoluteFrom, AbsoluteFsPath, FileSystem, getFileSystem} from '../../../src/ngtsc/file_system'; -import {runInEachFileSystem, TestFile} from '../../../src/ngtsc/file_system/testing'; -import {MockLogger} from '../../../src/ngtsc/logging/testing'; -import {loadTestFiles} from '../../../src/ngtsc/testing'; -import {DependencyResolver} from '../../src/dependencies/dependency_resolver'; -import {DtsDependencyHost} from '../../src/dependencies/dts_dependency_host'; -import {EsmDependencyHost} from '../../src/dependencies/esm_dependency_host'; -import {ModuleResolver} from '../../src/dependencies/module_resolver'; -import {TargetedEntryPointFinder} from '../../src/entry_point_finder/targeted_entry_point_finder'; -import {NGCC_VERSION} from '../../src/packages/build_marker'; -import {NgccConfiguration, ProcessedNgccPackageConfig} from '../../src/packages/configuration'; -import {EntryPoint, EntryPointPackageJson} from '../../src/packages/entry_point'; -import {PathMappings} from '../../src/path_mappings'; - -runInEachFileSystem(() => { - describe('TargetedEntryPointFinder', () => { - let fs: FileSystem; - let resolver: DependencyResolver; - let logger: MockLogger; - let config: NgccConfiguration; - let _Abs: typeof absoluteFrom; - - beforeEach(() => { - fs = getFileSystem(); - _Abs = absoluteFrom; - logger = new MockLogger(); - const srcHost = new EsmDependencyHost(fs, new ModuleResolver(fs)); - const dtsHost = new DtsDependencyHost(fs); - config = new NgccConfiguration(fs, _Abs('/')); - resolver = new DependencyResolver(fs, logger, config, {esm2015: srcHost}, dtsHost); - }); - - describe('findEntryPoints()', () => { - it('should find a single entry-point with no dependencies', () => { - const basePath = _Abs('/sub_entry_points/node_modules'); - const targetPath = _Abs('/sub_entry_points/node_modules/common'); - loadTestFiles([ - ...createPackage(fs.resolve(basePath, ''), 'common'), - ...createPackage(fs.resolve(basePath, 'common'), 'http', ['@angular/common']), - ...createPackage( - fs.resolve(basePath, 'common/http'), 'testing', - ['@angular/common/http', '@angular/common/testing']), - ...createPackage(fs.resolve(basePath, 'common'), 'testing', ['@angular/common']), - ]); - const finder = new TargetedEntryPointFinder( - fs, config, logger, resolver, _Abs('/sub_entry_points/node_modules'), undefined, - targetPath); - const {entryPoints} = finder.findEntryPoints(); - expect(dumpEntryPointPaths(basePath, entryPoints)).toEqual([ - ['common', 'common'], - ]); - }); - - it('should find dependencies of secondary entry-points within a package', () => { - const basePath = _Abs('/sub_entry_points/node_modules'); - const targetPath = _Abs('/sub_entry_points/node_modules/common/http/testing'); - loadTestFiles([ - ...createPackage(fs.resolve(basePath, ''), 'common'), - ...createPackage(fs.resolve(basePath, 'common'), 'http', ['common']), - ...createPackage( - fs.resolve(basePath, 'common/http'), 'testing', ['common/http', 'common/testing']), - ...createPackage(fs.resolve(basePath, 'common'), 'testing', ['common']), - ]); - const finder = new TargetedEntryPointFinder( - fs, config, logger, resolver, _Abs('/sub_entry_points/node_modules'), undefined, - targetPath); - const {entryPoints} = finder.findEntryPoints(); - expect(dumpEntryPointPaths(basePath, entryPoints)).toEqual([ - ['common', 'common'], - ['common', 'common/http'], - ['common', 'common/testing'], - ['common', 'common/http/testing'], - ]); - }); - - it('should find dependencies inside a namespace', () => { - const basePath = _Abs('/namespaced/node_modules'); - const targetPath = _Abs('/namespaced/node_modules/@angular/common/http'); - loadTestFiles([ - ...createPackage(fs.resolve(basePath, '@angular'), 'common'), - ...createPackage(fs.resolve(basePath, '@angular/common'), 'http', ['@angular/common']), - ...createPackage( - fs.resolve(basePath, '@angular/common/http'), 'testing', - ['@angular/common/http', '@angular/common/testing']), - ...createPackage(fs.resolve(basePath, '@angular/common'), 'testing', ['@angular/common']), - ]); - const finder = new TargetedEntryPointFinder( - fs, config, logger, resolver, _Abs('/namespaced/node_modules'), undefined, targetPath); - const {entryPoints} = finder.findEntryPoints(); - expect(dumpEntryPointPaths(basePath, entryPoints)).toEqual([ - ['@angular/common', '@angular/common'], - ['@angular/common', '@angular/common/http'], - ]); - }); - - it('should return an empty array if the target path is not an entry-point', () => { - const targetPath = _Abs('/no_packages/node_modules/should_not_be_found'); - fs.ensureDir(_Abs('/no_packages/node_modules/should_not_be_found')); - const finder = new TargetedEntryPointFinder( - fs, config, logger, resolver, _Abs('/no_packages/node_modules'), undefined, targetPath); - const {entryPoints} = finder.findEntryPoints(); - expect(entryPoints).toEqual([]); - }); - - it('should return an empty array if the target path is an ignored entry-point', () => { - const basePath = _Abs('/project/node_modules'); - const targetPath = _Abs('/project/node_modules/some-package'); - const finder = new TargetedEntryPointFinder( - fs, config, logger, resolver, basePath, undefined, targetPath); - - loadTestFiles(createPackage(basePath, 'some-package')); - spyOn(config, 'getPackageConfig') - .and.returnValue( - new ProcessedNgccPackageConfig(fs, _Abs('/project/node_modules/some-package'), { - entryPoints: { - '.': {ignore: true}, - }, - })); - - const {entryPoints} = finder.findEntryPoints(); - expect(entryPoints).toEqual([]); - }); - - it('should return an empty array if the target path is not an Angular entry-point', () => { - const targetPath = _Abs('/no_valid_entry_points/node_modules/some_package'); - loadTestFiles([ - { - name: _Abs('/no_valid_entry_points/node_modules/some_package/package.json'), - contents: '{}' - }, - ]); - const finder = new TargetedEntryPointFinder( - fs, config, logger, resolver, _Abs('/no_valid_entry_points/node_modules'), undefined, - targetPath); - const {entryPoints} = finder.findEntryPoints(); - expect(entryPoints).toEqual([]); - }); - - // https://github.com/angular/angular/issues/32302 - it('should return an empty array if the target path is not an Angular entry-point with typings', - () => { - const targetPath = _Abs('/no_valid_entry_points/node_modules/some_package'); - loadTestFiles([ - { - name: _Abs('/no_valid_entry_points/node_modules/some_package/package.json'), - contents: '{"typings": "./index.d.ts"}' - }, - { - name: _Abs('/no_valid_entry_points/node_modules/some_package/index.d.ts'), - contents: 'export declare class MyClass {}' - }, - { - name: _Abs('/no_valid_entry_points/node_modules/some_package/index.js'), - contents: 'export class MyClass {}' - }, - ]); - const finder = new TargetedEntryPointFinder( - fs, config, logger, resolver, _Abs('/no_valid_entry_points/node_modules'), undefined, - targetPath); - const {entryPoints} = finder.findEntryPoints(); - expect(entryPoints).toEqual([]); - }); - - it('should handle nested node_modules folders', () => { - const targetPath = _Abs('/nested_node_modules/node_modules/outer'); - loadTestFiles([ - ...createPackage(_Abs('/nested_node_modules/node_modules'), 'outer', ['inner']), - ...createPackage(_Abs('/nested_node_modules/node_modules/outer/node_modules'), 'inner'), - ]); - const finder = new TargetedEntryPointFinder( - fs, config, logger, resolver, _Abs('/nested_node_modules/node_modules'), undefined, - targetPath); - const {entryPoints} = finder.findEntryPoints(); - expect(dumpEntryPointPaths(_Abs('/nested_node_modules/node_modules'), entryPoints)) - .toEqual([ - ['outer/node_modules/inner', 'outer/node_modules/inner'], - ['outer', 'outer'], - ]); - }); - - it('should handle external node_modules folders (e.g. in a yarn workspace)', () => { - // Note that neither the basePath and targetPath contain each other - const basePath = _Abs('/nested_node_modules/packages/app/node_modules'); - const targetPath = _Abs('/nested_node_modules/node_modules/package/entry-point'); - loadTestFiles([ - ...createPackage(_Abs('/nested_node_modules/node_modules'), 'package'), - ...createPackage(_Abs('/nested_node_modules/node_modules/package'), 'entry-point'), - ]); - const finder = new TargetedEntryPointFinder( - fs, config, logger, resolver, basePath, undefined, targetPath); - const {entryPoints} = finder.findEntryPoints(); - expect(dumpEntryPointPaths(_Abs('/nested_node_modules'), entryPoints)).toEqual([ - ['node_modules/package', 'node_modules/package/entry-point'], - ]); - }); - - it('should handle external node_modules folders (e.g. in a yarn workspace) for dependencies', - () => { - // The application being compiled is at `/project/packages/app` so the basePath sent to - // ngcc is the `node_modules` below it - const basePath = _Abs('/project/packages/app/node_modules'); - // `packages/app` depends upon lib1, which has a private dependency on lib2 in its - // own `node_modules` folder - const lib2 = createPackage( - _Abs('/project/node_modules/lib1/node_modules'), 'lib2', ['lib3/entry-point']); - // `lib2` depends upon `lib3/entry-point` which has been hoisted all the way up to the - // top level `node_modules` - const lib3 = createPackage(_Abs('/project/node_modules'), 'lib3'); - const lib3EntryPoint = createPackage(_Abs('/project/node_modules/lib3'), 'entry-point'); - loadTestFiles([...lib2, ...lib3, ...lib3EntryPoint]); - // The targetPath being processed is `lib2` and we expect it to find the correct - // entry-point info for the `lib3/entry-point` dependency. - const targetPath = _Abs('/project/node_modules/lib1/node_modules/lib2'); - const finder = new TargetedEntryPointFinder( - fs, config, logger, resolver, basePath, undefined, targetPath); - const {entryPoints} = finder.findEntryPoints(); - expect(dumpEntryPointPaths(_Abs('/project/node_modules'), entryPoints)).toEqual([ - ['lib3', 'lib3/entry-point'], - ['lib1/node_modules/lib2', 'lib1/node_modules/lib2'], - ]); - }); - - it('should handle external node_modules folders (e.g. in a yarn workspace) for scoped dependencies', - () => { - // The application being compiled is at `/project/packages/app` so the basePath sent to - // ngcc is the `node_modules` below it - const basePath = _Abs('/project/packages/app/node_modules'); - // `packages/app` depends upon lib1, which has a private dependency on lib2 in its - // own `node_modules` folder - const lib2 = createPackage( - _Abs('/project/node_modules/lib1/node_modules'), 'lib2', - ['@scope/lib3/entry-point']); - // `lib2` depends upon `lib3/entry-point` which has been hoisted all the way up to the - // top level `node_modules` - const lib3 = createPackage(_Abs('/project/node_modules/@scope'), 'lib3'); - const lib3EntryPoint = createPackage( - _Abs('/project/node_modules/@scope/lib3'), 'entry-point', ['lib4/entry-point']); - const lib4 = - createPackage(_Abs('/project/node_modules/@scope/lib3/node_modules'), 'lib4'); - const lib4EntryPoint = createPackage( - _Abs('/project/node_modules/@scope/lib3/node_modules/lib4'), 'entry-point'); - loadTestFiles([...lib2, ...lib3, ...lib3EntryPoint, ...lib4, ...lib4EntryPoint]); - // The targetPath being processed is `lib2` and we expect it to find the correct - // entry-point info for the `lib3/entry-point` dependency. - const targetPath = _Abs('/project/node_modules/lib1/node_modules/lib2'); - const finder = new TargetedEntryPointFinder( - fs, config, logger, resolver, basePath, undefined, targetPath); - const {entryPoints} = finder.findEntryPoints(); - expect(dumpEntryPointPaths(_Abs('/project/node_modules'), entryPoints)).toEqual([ - ['@scope/lib3/node_modules/lib4', '@scope/lib3/node_modules/lib4/entry-point'], - ['@scope/lib3', '@scope/lib3/entry-point'], - ['lib1/node_modules/lib2', 'lib1/node_modules/lib2'], - ]); - }); - - it('should handle dependencies via pathMappings', () => { - const basePath = _Abs('/path_mapped/node_modules'); - const targetPath = _Abs('/path_mapped/node_modules/test'); - const pathMappings: PathMappings = { - baseUrl: '/path_mapped/dist', - paths: { - '@x/*': ['*'], - '@y/*/test': ['lib/*/test'], - '@z/*': ['../dist/moo/../*'], - } - }; - loadTestFiles([ - ...createPackage( - _Abs('/path_mapped/node_modules'), 'test', - ['pkg1', '@x/pkg2', '@y/pkg3/test', '@z/pkg5']), - ...createPackage(_Abs('/path_mapped/node_modules'), 'pkg1'), - ...createPackage(_Abs('/path_mapped/dist'), 'pkg2', ['pkg4']), - ...createPackage(_Abs('/path_mapped/dist/pkg2/node_modules'), 'pkg4'), - ...createPackage(_Abs('/path_mapped/dist/lib/pkg3'), 'test'), - ...createPackage(_Abs('/path_mapped/dist'), 'pkg5'), - ]); - const srcHost = new EsmDependencyHost(fs, new ModuleResolver(fs, pathMappings)); - const dtsHost = new DtsDependencyHost(fs, pathMappings); - resolver = new DependencyResolver(fs, logger, config, {esm2015: srcHost}, dtsHost); - const finder = new TargetedEntryPointFinder( - fs, config, logger, resolver, basePath, pathMappings, targetPath); - const {entryPoints} = finder.findEntryPoints(); - expect(dumpEntryPointPaths(basePath, entryPoints)).toEqual([ - ['pkg1', 'pkg1'], - ['../dist/pkg2/node_modules/pkg4', '../dist/pkg2/node_modules/pkg4'], - ['../dist/pkg2', '../dist/pkg2'], - ['../dist/lib/pkg3/test', '../dist/lib/pkg3/test'], - ['../dist/pkg5', '../dist/pkg5'], - ['test', 'test'], - ]); - }); - - it('should correctly compute the packagePath of secondary entry-points via pathMappings', - () => { - const basePath = _Abs('/path_mapped/node_modules'); - const targetPath = _Abs('/path_mapped/dist/primary/secondary'); - const pathMappings: PathMappings = { - baseUrl: '/path_mapped/dist', - paths: {'libs': ['primary'], 'extras': ['primary/*']} - }; - loadTestFiles([ - ...createPackage(_Abs('/path_mapped/dist'), 'primary'), - ...createPackage(_Abs('/path_mapped/dist/primary'), 'secondary'), - ]); - const srcHost = new EsmDependencyHost(fs, new ModuleResolver(fs, pathMappings)); - const dtsHost = new DtsDependencyHost(fs, pathMappings); - resolver = new DependencyResolver(fs, logger, config, {esm2015: srcHost}, dtsHost); - const finder = new TargetedEntryPointFinder( - fs, config, logger, resolver, basePath, pathMappings, targetPath); - const {entryPoints} = finder.findEntryPoints(); - expect(entryPoints.length).toEqual(1); - const entryPoint = entryPoints[0]; - expect(entryPoint.name).toEqual('secondary'); - expect(entryPoint.path).toEqual(_Abs('/path_mapped/dist/primary/secondary')); - expect(entryPoint.packagePath).toEqual(_Abs('/path_mapped/dist/primary')); - }); - - it('should correctly compute an entry-point whose path starts with the same string as another entry-point, via pathMappings', - () => { - const basePath = _Abs('/path_mapped/node_modules'); - const targetPath = _Abs('/path_mapped/node_modules/test'); - const pathMappings: PathMappings = { - baseUrl: '/path_mapped/dist', - paths: { - 'lib1': ['my-lib/my-lib', 'my-lib'], - 'lib2': ['my-lib/a', 'my-lib/a'], - 'lib3': ['my-lib/b', 'my-lib/b'], - 'lib4': ['my-lib-other/my-lib-other', 'my-lib-other'] - } - }; - loadTestFiles([ - ...createPackage(_Abs('/path_mapped/node_modules'), 'test', ['lib2', 'lib4']), - ...createPackage(_Abs('/path_mapped/dist/my-lib'), 'my-lib'), - ...createPackage(_Abs('/path_mapped/dist/my-lib'), 'a'), - ...createPackage(_Abs('/path_mapped/dist/my-lib'), 'b'), - ...createPackage(_Abs('/path_mapped/dist/my-lib-other'), 'my-lib-other'), - ]); - const srcHost = new EsmDependencyHost(fs, new ModuleResolver(fs, pathMappings)); - const dtsHost = new DtsDependencyHost(fs, pathMappings); - resolver = new DependencyResolver(fs, logger, config, {esm2015: srcHost}, dtsHost); - const finder = new TargetedEntryPointFinder( - fs, config, logger, resolver, basePath, pathMappings, targetPath); - const {entryPoints} = finder.findEntryPoints(); - expect(dumpEntryPointPaths(basePath, entryPoints)).toEqual([ - ['../dist/my-lib/a', '../dist/my-lib/a'], - ['../dist/my-lib-other/my-lib-other', '../dist/my-lib-other/my-lib-other'], - ['test', 'test'], - ]); - }); - - it('should correctly compute the package path for a target whose name contains the string of another package', - () => { - // Create the "my-lib" package - it doesn't need to be a real entry-point - const myLibPath = _Abs('/project/dist/my-lib'); - loadTestFiles([{ - name: fs.resolve(myLibPath, 'package.json'), - contents: JSON.stringify({name: 'my-lib'}) - }]); - - // Create the "my-lib-other" Angular entry-point - const myLibOtherPath = _Abs('/project/dist/my-lib-other'); - loadTestFiles([ - { - name: fs.resolve(myLibOtherPath, 'package.json'), - contents: JSON.stringify({ - name: `my-lib-other`, - typings: `./my-lib-other.d.ts`, - fesm2015: `./fesm2015/my-lib-other.js`, - esm5: `./esm5/my-lib-other.js`, - main: `./common/my-lib-other.js`, - }) - }, - {name: fs.resolve(myLibOtherPath, 'my-lib-other.metadata.json'), contents: 'metadata'}, - {name: fs.resolve(myLibOtherPath, 'my-lib-other.d.ts'), contents: 'typings'}, - {name: fs.resolve(myLibOtherPath, 'fesm2015/my-lib-other.js'), contents: ''}, - {name: fs.resolve(myLibOtherPath, 'esm5/my-lib-other.js'), contents: ''}, - {name: fs.resolve(myLibOtherPath, 'commonjs/my-lib-other.js'), contents: ''}, - ]); - - const basePath = _Abs('/project/node_modules'); - const pathMappings: PathMappings = { - baseUrl: '/project', - paths: { - 'lib1': ['dist/my-lib'], - 'lib2': ['dist/my-lib-other'], - } - }; - - const srcHost = new EsmDependencyHost(fs, new ModuleResolver(fs, pathMappings)); - const dtsHost = new DtsDependencyHost(fs, pathMappings); - resolver = new DependencyResolver(fs, logger, config, {esm2015: srcHost}, dtsHost); - const finder = new TargetedEntryPointFinder( - fs, config, logger, resolver, basePath, pathMappings, myLibOtherPath); - const {entryPoints} = finder.findEntryPoints(); - - expect(dumpEntryPointPaths(basePath, entryPoints)).toEqual([ - ['../dist/my-lib-other', '../dist/my-lib-other'], - ]); - }); - - it('should handle pathMappings that map to files or non-existent directories', () => { - const basePath = _Abs('/path_mapped/node_modules'); - const targetPath = _Abs('/path_mapped/node_modules/test'); - const pathMappings: PathMappings = { - baseUrl: '/path_mapped/dist', - paths: { - '@test': ['pkg2/fesm2015/pkg2.js'], - '@missing': ['pkg3'], - } - }; - loadTestFiles([ - ...createPackage(_Abs('/path_mapped/node_modules'), 'test', []), - ...createPackage(_Abs('/path_mapped/dist'), 'pkg2'), - ]); - const srcHost = new EsmDependencyHost(fs, new ModuleResolver(fs, pathMappings)); - const dtsHost = new DtsDependencyHost(fs, pathMappings); - resolver = new DependencyResolver(fs, logger, config, {esm2015: srcHost}, dtsHost); - const finder = new TargetedEntryPointFinder( - fs, config, logger, resolver, basePath, pathMappings, targetPath); - const {entryPoints} = finder.findEntryPoints(); - expect(dumpEntryPointPaths(basePath, entryPoints)).toEqual([ - ['test', 'test'], - ]); - }); - - function dumpEntryPointPaths( - basePath: AbsoluteFsPath, entryPoints: EntryPoint[]): [string, string][] { - return entryPoints.map( - x => [fs.relative(basePath, x.packagePath), fs.relative(basePath, x.path)]); - } - }); - - describe('targetNeedsProcessingOrCleaning()', () => { - it('should return false if there is no entry-point', () => { - const targetPath = _Abs('/no_packages/node_modules/should_not_be_found'); - fs.ensureDir(targetPath); - const finder = new TargetedEntryPointFinder( - fs, config, logger, resolver, _Abs('/no_packages/node_modules'), undefined, targetPath); - expect(finder.targetNeedsProcessingOrCleaning(['fesm2015'], true)).toBe(false); - }); - - it('should return false if the target path is not a valid entry-point', () => { - const targetPath = _Abs('/no_valid_entry_points/node_modules/some_package'); - loadTestFiles([ - { - name: _Abs('/no_valid_entry_points/node_modules/some_package/package.json'), - contents: '{}' - }, - ]); - const finder = new TargetedEntryPointFinder( - fs, config, logger, resolver, _Abs('/no_valid_entry_points/node_modules'), undefined, - targetPath); - expect(finder.targetNeedsProcessingOrCleaning(['fesm2015'], true)).toBe(false); - }); - - it('should return false if the target path is ignored by the config', () => { - const basePath = _Abs('/project/node_modules'); - const targetPath = _Abs('/project/node_modules/some-package'); - const finder = new TargetedEntryPointFinder( - fs, config, logger, resolver, basePath, undefined, targetPath); - - loadTestFiles(createPackage(basePath, 'some-package')); - spyOn(config, 'getPackageConfig') - .and.returnValue( - new ProcessedNgccPackageConfig(fs, _Abs('/project/node_modules/some-package'), { - entryPoints: { - '.': {ignore: true}, - }, - })); - - expect(finder.targetNeedsProcessingOrCleaning(['fesm2015'], true)).toBe(false); - }); - - it('should false if the target path has no typings', () => { - const targetPath = _Abs('/no_valid_entry_points/node_modules/some_package'); - loadTestFiles([ - { - name: _Abs('/no_valid_entry_points/node_modules/some_package/package.json'), - contents: '{"fesm2015": "./index.js"}' - }, - { - name: - _Abs('/no_valid_entry_points/node_modules/some_package/some_package.metadata.json'), - contents: 'metadata info' - }, - { - name: _Abs('/no_valid_entry_points/node_modules/some_package/index.js'), - contents: 'export class MyClass {}' - }, - ]); - const finder = new TargetedEntryPointFinder( - fs, config, logger, resolver, _Abs('/no_valid_entry_points/node_modules'), undefined, - targetPath); - expect(finder.targetNeedsProcessingOrCleaning(['fesm2015'], true)).toBe(false); - }); - - it('should false if the target path is not compiled by Angular - i.e has no metadata file', - () => { - const targetPath = _Abs('/no_valid_entry_points/node_modules/some_package'); - loadTestFiles([ - { - name: _Abs('/no_valid_entry_points/node_modules/some_package/package.json'), - contents: '{"typings": "./index.d.ts", "fesm2015": "./index.js"}' - }, - { - name: _Abs('/no_valid_entry_points/node_modules/some_package/index.d.ts'), - contents: 'export declare class MyClass {}' - }, - { - name: _Abs('/no_valid_entry_points/node_modules/some_package/index.js'), - contents: 'export class MyClass {}' - }, - ]); - const finder = new TargetedEntryPointFinder( - fs, config, logger, resolver, _Abs('/no_valid_entry_points/node_modules'), undefined, - targetPath); - expect(finder.targetNeedsProcessingOrCleaning(['fesm2015'], true)).toBe(false); - }); - - describe('[compileAllFormats: true]', () => { - it('should return true if none of the properties to consider have been processed', () => { - const basePath = _Abs('/sub_entry_points/node_modules'); - const targetPath = _Abs('/sub_entry_points/node_modules/common/http/testing'); - loadTestFiles([ - ...createPackage(fs.resolve(basePath, ''), 'common'), - ...createPackage(fs.resolve(basePath, 'common'), 'http', ['common']), - ...createPackage( - fs.resolve(basePath, 'common/http'), 'testing', ['common/http', 'common/testing']), - ...createPackage(fs.resolve(basePath, 'common'), 'testing', ['common']), - ]); - const finder = new TargetedEntryPointFinder( - fs, config, logger, resolver, basePath, undefined, targetPath); - expect(finder.targetNeedsProcessingOrCleaning(['fesm2015', 'esm5'], true)).toBe(true); - }); - - it('should return true if at least one of the properties to consider has not been processed', - () => { - const basePath = _Abs('/sub_entry_points/node_modules'); - const targetPath = _Abs('/sub_entry_points/node_modules/common/http/testing'); - loadTestFiles([ - ...createPackage(fs.resolve(basePath, ''), 'common'), - ...createPackage(fs.resolve(basePath, 'common'), 'http', ['common']), - ...createPackage( - fs.resolve(basePath, 'common/http'), 'testing', - ['common/http', 'common/testing']), - ...createPackage(fs.resolve(basePath, 'common'), 'testing', ['common']), - ]); - - // Add a build marker to the package.json - const packageJsonPath = _Abs(`${targetPath}/package.json`); - const packageJson = JSON.parse(fs.readFile(packageJsonPath)) as EntryPointPackageJson; - packageJson.__processed_by_ivy_ngcc__ = { - esm5: NGCC_VERSION, - }; - fs.writeFile(packageJsonPath, JSON.stringify(packageJson)); - - const finder = new TargetedEntryPointFinder( - fs, config, logger, resolver, basePath, undefined, targetPath); - expect(finder.targetNeedsProcessingOrCleaning(['fesm2015', 'esm5'], true)).toBe(true); - }); - - it('should return false if all of the properties to consider have been processed', () => { - const basePath = _Abs('/sub_entry_points/node_modules'); - const targetPath = _Abs('/sub_entry_points/node_modules/common/http/testing'); - loadTestFiles([ - ...createPackage(fs.resolve(basePath, ''), 'common'), - ...createPackage(fs.resolve(basePath, 'common'), 'http', ['common']), - ...createPackage( - fs.resolve(basePath, 'common/http'), 'testing', ['common/http', 'common/testing']), - ...createPackage(fs.resolve(basePath, 'common'), 'testing', ['common']), - ]); - - // Add build markers to the package.json - const packageJsonPath = _Abs(`${targetPath}/package.json`); - const packageJson = JSON.parse(fs.readFile(packageJsonPath)) as EntryPointPackageJson; - packageJson.__processed_by_ivy_ngcc__ = { - fesm2015: NGCC_VERSION, - esm5: NGCC_VERSION, - main: NGCC_VERSION, - }; - fs.writeFile(packageJsonPath, JSON.stringify(packageJson)); - - const finder = new TargetedEntryPointFinder( - fs, config, logger, resolver, basePath, undefined, targetPath); - expect(finder.targetNeedsProcessingOrCleaning(['fesm2015', 'esm5'], true)).toBe(false); - }); - }); - - describe('[compileAllFormats: false]', () => { - it('should return true if none of the properties to consider have been processed', () => { - const basePath = _Abs('/sub_entry_points/node_modules'); - const targetPath = _Abs('/sub_entry_points/node_modules/common/http/testing'); - loadTestFiles([ - ...createPackage(fs.resolve(basePath, ''), 'common'), - ...createPackage(fs.resolve(basePath, 'common'), 'http', ['common']), - ...createPackage( - fs.resolve(basePath, 'common/http'), 'testing', ['common/http', 'common/testing']), - ...createPackage(fs.resolve(basePath, 'common'), 'testing', ['common']), - ]); - - const finder = new TargetedEntryPointFinder( - fs, config, logger, resolver, basePath, undefined, targetPath); - expect(finder.targetNeedsProcessingOrCleaning(['fesm2015', 'esm5'], false)).toBe(true); - }); - - it('should return true if the first of the properties to consider that is in the package.json has not been processed', - () => { - const basePath = _Abs('/sub_entry_points/node_modules'); - const targetPath = _Abs('/sub_entry_points/node_modules/common/http/testing'); - loadTestFiles([ - ...createPackage(fs.resolve(basePath, ''), 'common'), - ...createPackage(fs.resolve(basePath, 'common'), 'http', ['common']), - ...createPackage( - fs.resolve(basePath, 'common/http'), 'testing', - ['common/http', 'common/testing']), - ...createPackage(fs.resolve(basePath, 'common'), 'testing', ['common']), - ]); - - // Add build markers to the package.json - const packageJsonPath = _Abs(`${targetPath}/package.json`); - const packageJson = JSON.parse(fs.readFile(packageJsonPath)) as EntryPointPackageJson; - packageJson.__processed_by_ivy_ngcc__ = { - esm5: NGCC_VERSION, - }; - fs.writeFile(packageJsonPath, JSON.stringify(packageJson)); - - const finder = new TargetedEntryPointFinder( - fs, config, logger, resolver, basePath, undefined, targetPath); - expect(finder.targetNeedsProcessingOrCleaning(['fesm2015', 'esm5'], false)).toBe(true); - }); - - it('should return false if the first of the properties to consider (that actually appear in the package.json) has been processed', - () => { - const basePath = _Abs('/sub_entry_points/node_modules'); - const targetPath = _Abs('/sub_entry_points/node_modules/common/http/testing'); - loadTestFiles([ - ...createPackage(fs.resolve(basePath, ''), 'common'), - ...createPackage(fs.resolve(basePath, 'common'), 'http', ['common']), - ...createPackage( - fs.resolve(basePath, 'common/http'), 'testing', - ['common/http', 'common/testing']), - ...createPackage(fs.resolve(basePath, 'common'), 'testing', ['common']), - ]); - - // Add build markers to the package.json - const packageJsonPath = _Abs(`${targetPath}/package.json`); - const packageJson = JSON.parse(fs.readFile(packageJsonPath)) as EntryPointPackageJson; - packageJson.__processed_by_ivy_ngcc__ = { - fesm2015: NGCC_VERSION, - }; - fs.writeFile(packageJsonPath, JSON.stringify(packageJson)); - - const finder = new TargetedEntryPointFinder( - fs, config, logger, resolver, basePath, undefined, targetPath); - expect(finder.targetNeedsProcessingOrCleaning(['fesm2015', 'esm5'], false)) - .toBe(false); - }); - }); - }); - - function createPackage( - basePath: AbsoluteFsPath, packageName: string, deps: string[] = []): TestFile[] { - return [ - { - name: _Abs(`${basePath}/${packageName}/package.json`), - contents: JSON.stringify({ - name: packageName, - typings: `./${packageName}.d.ts`, - fesm2015: `./fesm2015/${packageName}.js`, - esm5: `./esm5/${packageName}.js`, - main: `./common/${packageName}.js`, - }) - }, - { - name: _Abs(`${basePath}/${packageName}/${packageName}.metadata.json`), - contents: 'metadata info' - }, - { - name: _Abs(`${basePath}/${packageName}/fesm2015/${packageName}.js`), - contents: deps.map((dep, i) => `import * as i${i} from '${dep}';`).join('\n'), - }, - { - name: _Abs(`${basePath}/${packageName}/esm5/${packageName}.js`), - contents: deps.map((dep, i) => `import * as i${i} from '${dep}';`).join('\n'), - }, - { - name: _Abs(`${basePath}/${packageName}/commonjs/${packageName}.js`), - contents: deps.map((dep, i) => `var i${i} = require('${dep}');`).join('\n'), - }, - ]; - } - }); -}); diff --git a/packages/compiler-cli/ngcc/test/entry_point_finder/utils_spec.ts b/packages/compiler-cli/ngcc/test/entry_point_finder/utils_spec.ts deleted file mode 100644 index 8d365844c5e..00000000000 --- a/packages/compiler-cli/ngcc/test/entry_point_finder/utils_spec.ts +++ /dev/null @@ -1,180 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import {absoluteFrom, getFileSystem} from '@angular/compiler-cli/src/ngtsc/file_system'; - -import {runInEachFileSystem} from '../../../src/ngtsc/file_system/testing'; -import {MockLogger} from '../../../src/ngtsc/logging/testing'; -import {getBasePaths} from '../../src/entry_point_finder/utils'; - -runInEachFileSystem(() => { - let _: typeof absoluteFrom; - let logger: MockLogger; - - beforeEach(() => { - _ = absoluteFrom; - logger = new MockLogger(); - }); - - describe('getBasePaths', () => { - it('should just return the `sourceDirectory if there are no `pathMappings', () => { - const sourceDirectory = _('/path/to/project/node_modules'); - const basePaths = getBasePaths(logger, sourceDirectory, undefined); - expect(basePaths).toEqual([sourceDirectory]); - }); - - it('should use each path mapping prefix', () => { - const projectDirectory = _('/path/to/project'); - const fs = getFileSystem(); - fs.ensureDir(fs.resolve(projectDirectory, 'dist-1')); - fs.ensureDir(fs.resolve(projectDirectory, 'sub-folder/dist-2')); - fs.ensureDir(fs.resolve(projectDirectory, 'libs')); - - const sourceDirectory = _('/path/to/project/node_modules'); - const pathMappings = { - baseUrl: projectDirectory, - paths: {'@dist': ['dist-1', 'sub-folder/dist-2'], '@lib/*': ['libs/*']} - }; - const basePaths = getBasePaths(logger, sourceDirectory, pathMappings); - expect(basePaths).toEqual([ - sourceDirectory, - fs.resolve(projectDirectory, 'dist-1'), - fs.resolve(projectDirectory, 'libs'), - fs.resolve(projectDirectory, 'sub-folder/dist-2'), - ]); - }); - - it('should find base-paths that start with a wildcard prefix', () => { - const projectDirectory = _('/path/to/project'); - const fs = getFileSystem(); - fs.ensureDir(fs.resolve(projectDirectory, 'dist')); - fs.ensureDir(fs.resolve(projectDirectory, 'dist-a')); - fs.ensureDir(fs.resolve(projectDirectory, 'dist-b')); - - const sourceDirectory = _('/path/to/project/node_modules'); - const pathMappings = {baseUrl: projectDirectory, paths: {'@dist*': ['dist*']}}; - const basePaths = getBasePaths(logger, sourceDirectory, pathMappings); - expect(basePaths).toEqual([ - sourceDirectory, - fs.resolve(projectDirectory, 'dist'), - fs.resolve(projectDirectory, 'dist-a'), - fs.resolve(projectDirectory, 'dist-b'), - ]); - }); - - it('should not be confused by folders that have the same starting string', () => { - const projectDirectory = _('/path/to/project'); - const fs = getFileSystem(); - fs.ensureDir(fs.resolve(projectDirectory, 'a/b')); - fs.ensureDir(fs.resolve(projectDirectory, 'a/b-2')); - fs.ensureDir(fs.resolve(projectDirectory, 'a/b/c')); - - const sourceDirectory = _('/path/to/project/node_modules'); - const pathMappings = {baseUrl: projectDirectory, paths: {'@dist': ['a/b', 'a/b-2', 'a/b/c']}}; - const basePaths = getBasePaths(logger, sourceDirectory, pathMappings); - expect(basePaths).toEqual([ - sourceDirectory, - fs.resolve(projectDirectory, 'a/b'), - fs.resolve(projectDirectory, 'a/b-2'), - ]); - }); - - it('should discard paths that are already contained by another path', () => { - const projectDirectory = _('/path/to/project'); - const fs = getFileSystem(); - fs.ensureDir(fs.resolve(projectDirectory, 'dist-1')); - fs.ensureDir(fs.resolve(projectDirectory, 'dist-1/sub-folder')); - fs.ensureDir(fs.resolve(projectDirectory, 'node_modules/libs')); - - const sourceDirectory = _('/path/to/project/node_modules'); - const pathMappings = { - baseUrl: projectDirectory, - paths: {'@dist': ['dist-1', 'dist-1/sub-folder'], '@lib/*': ['node_modules/libs/*']} - }; - const basePaths = getBasePaths(logger, sourceDirectory, pathMappings); - expect(basePaths).toEqual([ - sourceDirectory, - fs.resolve(projectDirectory, 'dist-1'), - ]); - }); - - it('should use the containing directory of path mapped files', () => { - const projectDirectory = _('/path/to/project'); - const fs = getFileSystem(); - fs.ensureDir(fs.resolve(projectDirectory, 'dist-1')); - fs.writeFile(fs.resolve(projectDirectory, 'dist-1/file.js'), 'dummy content'); - - const sourceDirectory = _('/path/to/project/node_modules'); - const pathMappings = {baseUrl: projectDirectory, paths: {'@dist': ['dist-1/file.js']}}; - const basePaths = getBasePaths(logger, sourceDirectory, pathMappings); - expect(basePaths).toEqual([ - sourceDirectory, - fs.resolve(projectDirectory, 'dist-1'), - ]); - }); - - it('should always include the `sourceDirectory` if it is a node_modules directory in the returned basePaths, even if it is contained by another basePath', - () => { - const projectDirectory = _('/path/to/project'); - const sourceDirectory = _('/path/to/project/node_modules'); - const fs = getFileSystem(); - fs.ensureDir(fs.resolve(sourceDirectory)); - - const pathMappings = {baseUrl: projectDirectory, paths: {'*': ['./*']}}; - const basePaths = getBasePaths(logger, sourceDirectory, pathMappings); - expect(basePaths).toEqual([ - sourceDirectory, - projectDirectory, - ]); - }); - - it('should log a warning if baseUrl is the root path', () => { - const fs = getFileSystem(); - fs.ensureDir(fs.resolve('/dist')); - - const sourceDirectory = _('/path/to/project/node_modules'); - const pathMappings = {baseUrl: _('/'), paths: {'@dist': ['dist']}}; - const basePaths = getBasePaths(logger, sourceDirectory, pathMappings); - expect(basePaths).toEqual([ - fs.resolve('/dist'), - sourceDirectory, - ]); - expect(logger.logs.warn).toEqual([ - [`The provided pathMappings baseUrl is the root path ${_('/')}.\n` + - `This is likely to mess up how ngcc finds entry-points and is probably not correct.\n` + - `Please check your path mappings configuration such as in the tsconfig.json file.`] - ]); - }); - - it('should discard basePaths that do not exists and log a debug message', () => { - const projectDirectory = _('/path/to/project'); - const fs = getFileSystem(); - fs.ensureDir(fs.resolve(projectDirectory, 'dist-1')); - fs.ensureDir(fs.resolve(projectDirectory, 'sub-folder')); - - const sourceDirectory = _('/path/to/project/node_modules'); - const pathMappings = { - baseUrl: projectDirectory, - paths: {'@dist': ['dist-1', 'sub-folder/dist-2'], '@lib/*': ['libs/*']} - }; - const basePaths = getBasePaths(logger, sourceDirectory, pathMappings); - expect(basePaths).toEqual([ - sourceDirectory, - fs.resolve(projectDirectory, 'dist-1'), - ]); - expect(logger.logs.debug).toEqual([ - [`The basePath "${ - fs.resolve(projectDirectory, 'sub-folder/dist-2')}" computed from baseUrl "${ - projectDirectory}" and path mapping "sub-folder/dist-2" does not exist in the file-system.\n` + - `It will not be scanned for entry-points.`], - [`The basePath "${fs.resolve(projectDirectory, 'libs')}" computed from baseUrl "${ - projectDirectory}" and path mapping "libs/*" does not exist in the file-system.\n` + - `It will not be scanned for entry-points.`], - ]); - }); - }); -}); diff --git a/packages/compiler-cli/ngcc/test/execution/cluster/executor_spec.ts b/packages/compiler-cli/ngcc/test/execution/cluster/executor_spec.ts deleted file mode 100644 index 511c9608ca1..00000000000 --- a/packages/compiler-cli/ngcc/test/execution/cluster/executor_spec.ts +++ /dev/null @@ -1,155 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ - -/// - -import {getFileSystem} from '@angular/compiler-cli/src/ngtsc/file_system'; -import cluster from 'cluster'; - -import {MockFileSystemNative, runInEachFileSystem} from '../../../../src/ngtsc/file_system/testing'; -import {MockLogger} from '../../../../src/ngtsc/logging/testing'; -import {ClusterExecutor} from '../../../src/execution/cluster/executor'; -import {ClusterMaster} from '../../../src/execution/cluster/master'; -import {AsyncLocker} from '../../../src/locking/async_locker'; -import {FileWriter} from '../../../src/writing/file_writer'; -import {PackageJsonUpdater} from '../../../src/writing/package_json_updater'; -import {MockLockFile} from '../../helpers/mock_lock_file'; -import {mockProperty} from '../../helpers/spy_utils'; -import {mockRequireResolveForWorkerScript} from '../../helpers/utils'; - -runInEachFileSystem(() => { - describe('ClusterExecutor', () => { - const runAsClusterMaster = mockProperty(cluster, 'isMaster'); - let masterRunSpy: jasmine.Spy; - let mockLogger: MockLogger; - let lockFileLog: string[]; - let mockLockFile: MockLockFile; - let locker: AsyncLocker; - let executor: ClusterExecutor; - let createTaskCompletedCallback: jasmine.Spy; - - beforeEach(() => { - mockRequireResolveForWorkerScript(); - - masterRunSpy = spyOn(ClusterMaster.prototype, 'run') - .and.returnValue(Promise.resolve('ClusterMaster#run()' as any)); - createTaskCompletedCallback = jasmine.createSpy('createTaskCompletedCallback'); - - mockLogger = new MockLogger(); - lockFileLog = []; - mockLockFile = new MockLockFile(new MockFileSystemNative(), lockFileLog); - locker = new AsyncLocker(mockLockFile, mockLogger, 200, 2); - executor = new ClusterExecutor( - 42, getFileSystem(), mockLogger, null as unknown as FileWriter, - null as unknown as PackageJsonUpdater, locker, createTaskCompletedCallback); - }); - - describe('execute()', () => { - beforeEach(() => runAsClusterMaster(true)); - - it('should log debug info about the executor', async () => { - const anyFn: () => any = () => undefined; - await executor.execute(anyFn, anyFn); - - expect(mockLogger.logs.debug).toEqual([ - ['Running ngcc on ClusterExecutor (using 42 worker processes).'], - ]); - }); - - it('should delegate to `ClusterMaster#run()`', async () => { - const analyzeEntryPointsSpy = jasmine.createSpy('analyzeEntryPoints'); - const createCompilerFnSpy = jasmine.createSpy('createCompilerFn'); - - expect(await executor.execute(analyzeEntryPointsSpy, createCompilerFnSpy)) - .toBe('ClusterMaster#run()' as any); - - expect(masterRunSpy).toHaveBeenCalledWith(); - - expect(analyzeEntryPointsSpy).toHaveBeenCalledWith(); - expect(createCompilerFnSpy).not.toHaveBeenCalled(); - }); - - it('should call LockFile.write() and LockFile.remove() if master runner completes successfully', - async () => { - const anyFn: () => any = () => undefined; - await executor.execute(anyFn, anyFn); - expect(lockFileLog).toEqual(['write()', 'remove()']); - }); - - it('should call LockFile.write() and LockFile.remove() if analyzeFn fails', async () => { - const analyzeEntryPointsSpy = - jasmine.createSpy('analyzeEntryPoints').and.throwError('analyze error'); - const createCompilerFnSpy = jasmine.createSpy('createCompilerFn'); - let error = ''; - try { - await executor.execute(analyzeEntryPointsSpy, createCompilerFnSpy); - } catch (e) { - error = (e as Error).message; - } - expect(analyzeEntryPointsSpy).toHaveBeenCalledWith(); - expect(createCompilerFnSpy).not.toHaveBeenCalled(); - expect(error).toEqual('analyze error'); - expect(lockFileLog).toEqual(['write()', 'remove()']); - }); - - it('should call LockFile.write() and LockFile.remove() if master runner fails', async () => { - const anyFn: () => any = () => undefined; - masterRunSpy.and.returnValue(Promise.reject(new Error('master runner error'))); - let error = ''; - try { - await executor.execute(anyFn, anyFn); - } catch (e) { - error = (e as Error).message; - } - expect(error).toEqual('master runner error'); - expect(lockFileLog).toEqual(['write()', 'remove()']); - }); - - it('should not call master runner if LockFile.write() fails', async () => { - const anyFn: () => any = () => undefined; - spyOn(mockLockFile, 'write').and.callFake(() => { - lockFileLog.push('write()'); - throw new Error('LockFile.write() error'); - }); - - executor = new ClusterExecutor( - 42, getFileSystem(), mockLogger, null as unknown as FileWriter, - null as unknown as PackageJsonUpdater, locker, createTaskCompletedCallback); - let error = ''; - try { - await executor.execute(anyFn, anyFn); - } catch (e) { - error = (e as Error).message; - } - expect(error).toEqual('LockFile.write() error'); - expect(masterRunSpy).not.toHaveBeenCalled(); - }); - - it('should fail if LockFile.remove() fails', async () => { - const anyFn: () => any = () => undefined; - spyOn(mockLockFile, 'remove').and.callFake(() => { - lockFileLog.push('remove()'); - throw new Error('LockFile.remove() error'); - }); - - executor = new ClusterExecutor( - 42, getFileSystem(), mockLogger, null as unknown as FileWriter, - null as unknown as PackageJsonUpdater, locker, createTaskCompletedCallback); - let error = ''; - try { - await executor.execute(anyFn, anyFn); - } catch (e) { - error = (e as Error).message; - } - expect(error).toEqual('LockFile.remove() error'); - expect(lockFileLog).toEqual(['write()', 'remove()']); - expect(masterRunSpy).toHaveBeenCalled(); - }); - }); - }); -}); diff --git a/packages/compiler-cli/ngcc/test/execution/cluster/package_json_updater_spec.ts b/packages/compiler-cli/ngcc/test/execution/cluster/package_json_updater_spec.ts deleted file mode 100644 index 03fad5bd7f1..00000000000 --- a/packages/compiler-cli/ngcc/test/execution/cluster/package_json_updater_spec.ts +++ /dev/null @@ -1,216 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ - -/// - -import cluster from 'cluster'; - -import {absoluteFrom as _} from '../../../../src/ngtsc/file_system'; -import {runInEachFileSystem} from '../../../../src/ngtsc/file_system/testing'; -import {ClusterWorkerPackageJsonUpdater} from '../../../src/execution/cluster/package_json_updater'; -import {JsonObject} from '../../../src/utils'; -import {PackageJsonPropertyPositioning, PackageJsonUpdate, PackageJsonUpdater} from '../../../src/writing/package_json_updater'; -import {mockProperty} from '../../helpers/spy_utils'; - - -runInEachFileSystem(() => { - describe('ClusterPackageJsonUpdater', () => { - const runAsClusterMaster = mockProperty(cluster, 'isMaster'); - const mockProcessSend = mockProperty(process, 'send'); - let processSendSpy: jasmine.Spy; - - beforeEach(() => { - processSendSpy = jasmine.createSpy('process.send'); - mockProcessSend(processSendSpy); - }); - - describe('constructor()', () => { - it('should throw an error if used on a cluster master', () => { - runAsClusterMaster(true); - expect(() => new ClusterWorkerPackageJsonUpdater()) - .toThrowError( - 'Tried to create cluster worker PackageJsonUpdater on the master process.'); - }); - }); - - describe('createUpdate()', () => { - let updater: ClusterWorkerPackageJsonUpdater; - beforeEach(() => { - runAsClusterMaster(false); - updater = new ClusterWorkerPackageJsonUpdater(); - }); - - it('should return a `PackageJsonUpdate` instance', () => { - expect(updater.createUpdate()).toEqual(jasmine.any(PackageJsonUpdate)); - }); - - it('should wire up the `PackageJsonUpdate` with its `writeChanges()` method', () => { - const writeChangesSpy = spyOn(updater, 'writeChanges'); - const jsonPath = _('/foo/package.json'); - const update = updater.createUpdate(); - - update.addChange(['foo'], 'updated'); - update.addChange(['baz'], 'updated 2', 'alphabetic'); - update.addChange(['bar'], 'updated 3', {before: 'bar'}); - update.writeChanges(jsonPath); - - expect(writeChangesSpy) - .toHaveBeenCalledWith( - [ - [['foo'], 'updated', 'unimportant'], - [['baz'], 'updated 2', 'alphabetic'], - [['bar'], 'updated 3', {before: 'bar'}], - ], - jsonPath, undefined); - }); - }); - - describe('writeChanges()', () => { - let updater: ClusterWorkerPackageJsonUpdater; - beforeEach(() => { - runAsClusterMaster(false); - updater = new ClusterWorkerPackageJsonUpdater(); - }); - - it('should send an `update-package-json` message to the master process', () => { - const jsonPath = _('/foo/package.json'); - - const writeToProp = - (propPath: string[], positioning?: PackageJsonPropertyPositioning, - parsed?: JsonObject) => updater.createUpdate() - .addChange(propPath, 'updated', positioning) - .writeChanges(jsonPath, parsed); - - writeToProp(['foo']); - expect(processSendSpy) - .toHaveBeenCalledWith( - { - type: 'update-package-json', - packageJsonPath: jsonPath, - changes: [[['foo'], 'updated', 'unimportant']], - }, - jasmine.any(Function)); - - writeToProp(['bar'], {before: 'foo'}); - expect(processSendSpy) - .toHaveBeenCalledWith( - { - type: 'update-package-json', - packageJsonPath: jsonPath, - changes: [[['bar'], 'updated', {before: 'foo'}]], - }, - jasmine.any(Function)); - - writeToProp(['bar', 'baz', 'qux'], 'alphabetic', {}); - expect(processSendSpy) - .toHaveBeenCalledWith( - { - type: 'update-package-json', - packageJsonPath: jsonPath, - changes: [[['bar', 'baz', 'qux'], 'updated', 'alphabetic']], - }, - jasmine.any(Function)); - }); - - it('should update an in-memory representation (if provided)', () => { - const jsonPath = _('/foo/package.json'); - const parsedJson: JsonObject = { - foo: true, - bar: {baz: 'OK'}, - }; - - const update = - updater.createUpdate().addChange(['foo'], false).addChange(['bar', 'baz'], 42); - - // Not updated yet. - expect(parsedJson).toEqual({ - foo: true, - bar: {baz: 'OK'}, - }); - - update.writeChanges(jsonPath, parsedJson); - - // Updated now. - expect(parsedJson).toEqual({ - foo: false, - bar: {baz: 42}, - }); - }); - - it('should create any missing ancestor objects', () => { - const jsonPath = _('/foo/package.json'); - const parsedJson: JsonObject = {foo: {}}; - - updater.createUpdate() - .addChange(['foo', 'bar', 'baz', 'qux'], 'updated') - .writeChanges(jsonPath, parsedJson); - - expect(parsedJson).toEqual({ - foo: { - bar: { - baz: { - qux: 'updated', - }, - }, - }, - }); - }); - - it('should throw, if a property-path is empty', () => { - const jsonPath = _('/foo/package.json'); - - expect(() => updater.createUpdate().addChange([], 'missing').writeChanges(jsonPath, {})) - .toThrowError(`Missing property path for writing value to '${jsonPath}'.`); - }); - - it('should throw, if a property-path points to a non-object intermediate value', () => { - const jsonPath = _('/foo/package.json'); - const parsedJson = {foo: null, bar: 42, baz: {qux: []}}; - - const writeToProp = (propPath: string[], parsed?: JsonObject) => - updater.createUpdate().addChange(propPath, 'updated').writeChanges(jsonPath, parsed); - - expect(() => writeToProp(['foo', 'child'], parsedJson)) - .toThrowError('Property path \'foo.child\' does not point to an object.'); - expect(() => writeToProp(['bar', 'child'], parsedJson)) - .toThrowError('Property path \'bar.child\' does not point to an object.'); - expect(() => writeToProp(['baz', 'qux', 'child'], parsedJson)) - .toThrowError('Property path \'baz.qux.child\' does not point to an object.'); - - // It should not throw, if no parsed representation is provided. - // (The error will still be thrown on the master process, but that is out of scope for - // this test.) - expect(() => writeToProp(['foo', 'child'])).not.toThrow(); - }); - - it('should throw, if trying to re-apply an already applied update', () => { - const update = updater.createUpdate().addChange(['foo'], 'updated'); - - expect(() => update.writeChanges(_('/foo/package.json'))).not.toThrow(); - expect(() => update.writeChanges(_('/foo/package.json'))) - .toThrowError('Trying to apply a `PackageJsonUpdate` that has already been applied.'); - expect(() => update.writeChanges(_('/bar/package.json'))) - .toThrowError('Trying to apply a `PackageJsonUpdate` that has already been applied.'); - }); - - it('should throw, if trying to update a synthesized `package.json` file', () => { - const update = updater.createUpdate().addChange(['foo'], 'updated'); - - expect(() => update.writeChanges(_('/foo/package.json'), { - synthesized: true - })).toThrowError('Trying to update a non-existent (synthesized) `package.json` file.'); - }); - }); - }); - - // Helpers - class MockPackageJsonUpdater implements PackageJsonUpdater { - createUpdate = jasmine.createSpy('MockPackageJsonUpdater#createUpdate'); - writeChanges = jasmine.createSpy('MockPackageJsonUpdater#writeChanges'); - } -}); diff --git a/packages/compiler-cli/ngcc/test/execution/cluster/worker_spec.ts b/packages/compiler-cli/ngcc/test/execution/cluster/worker_spec.ts deleted file mode 100644 index 712db71c118..00000000000 --- a/packages/compiler-cli/ngcc/test/execution/cluster/worker_spec.ts +++ /dev/null @@ -1,226 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ - -/// - -import cluster, {Worker} from 'cluster'; -import {EventEmitter} from 'events'; - -import {AbsoluteFsPath} from '../../../../src/ngtsc/file_system'; -import {MockLogger} from '../../../../src/ngtsc/logging/testing'; -import {CreateCompileFn} from '../../../src/execution/api'; -import {startWorker} from '../../../src/execution/cluster/worker'; -import {DtsProcessing, Task, TaskCompletedCallback, TaskProcessingOutcome} from '../../../src/execution/tasks/api'; -import {FileToWrite} from '../../../src/rendering/utils'; -import {mockProperty, spyProperty} from '../../helpers/spy_utils'; - - -describe('startWorker()', () => { - const runAsClusterMaster = mockProperty(cluster, 'isMaster'); - const mockProcessSend = mockProperty(process, 'send'); - let processSendSpy: jasmine.Spy; - let compileFnSpy: jasmine.Spy; - let createCompileFnSpy: jasmine.Spy; - let mockLogger: MockLogger; - - beforeEach(() => { - compileFnSpy = jasmine.createSpy('compileFn'); - createCompileFnSpy = jasmine.createSpy('createCompileFn').and.returnValue(compileFnSpy); - - processSendSpy = jasmine.createSpy('process.send'); - mockProcessSend(processSendSpy); - - mockLogger = new MockLogger(); - }); - - describe('(on cluster master)', () => { - beforeEach(() => runAsClusterMaster(true)); - - it('should throw an error', async () => { - await expectAsync(startWorker(mockLogger, createCompileFnSpy)) - .toBeRejectedWithError('Tried to run cluster worker on the master process.'); - expect(createCompileFnSpy).not.toHaveBeenCalled(); - }); - }); - - describe('(on cluster worker)', () => { - // The `cluster.worker` property is normally `undefined` on the master process and set to the - // current `cluster.worker` on worker processes. - const mockClusterWorker = mockProperty(cluster, 'worker'); - - beforeEach(() => { - runAsClusterMaster(false); - mockClusterWorker(Object.assign(new EventEmitter(), {id: 42}) as Worker); - }); - - it('should create the `compileFn()`', () => { - startWorker(mockLogger, createCompileFnSpy); - expect(createCompileFnSpy).toHaveBeenCalledWith(jasmine.any(Function), jasmine.any(Function)); - }); - - it('should notify the cluster master once ready', () => { - startWorker(mockLogger, createCompileFnSpy); - - expect(processSendSpy).toHaveBeenCalledTimes(1); - expect(processSendSpy).toHaveBeenCalledWith({type: 'ready'}, jasmine.any(Function)); - }); - - it('should set up `compileFn()` to send `transformed-files` messages to master', () => { - startWorker(mockLogger, createCompileFnSpy); - - expect(processSendSpy).toHaveBeenCalledTimes(1); - expect(processSendSpy).toHaveBeenCalledWith({type: 'ready'}, jasmine.any(Function)); - - const mockTransformedFiles: FileToWrite[] = [ - {path: '/foo' as AbsoluteFsPath, contents: 'FOO'}, - {path: '/bar' as AbsoluteFsPath, contents: 'BAR'}, - ]; - const beforeWritingFiles: Parameters[0] = - createCompileFnSpy.calls.argsFor(0)[0]; - - beforeWritingFiles(mockTransformedFiles); - - expect(processSendSpy).toHaveBeenCalledTimes(2); - expect(processSendSpy) - .toHaveBeenCalledWith( - {type: 'transformed-files', files: ['/foo', '/bar']}, jasmine.any(Function)); - }); - - it('should set up `compileFn()` to send `task-completed` messages to master', () => { - startWorker(mockLogger, createCompileFnSpy); - const onTaskCompleted: TaskCompletedCallback = createCompileFnSpy.calls.argsFor(0)[1]; - - // Reset as we are not interested in the initial messages. - processSendSpy.calls.reset(); - - onTaskCompleted(null as any, TaskProcessingOutcome.Processed, null); - expect(processSendSpy).toHaveBeenCalledTimes(1); - expect(processSendSpy) - .toHaveBeenCalledWith( - {type: 'task-completed', outcome: TaskProcessingOutcome.Processed, message: null}, - jasmine.any(Function)); - - processSendSpy.calls.reset(); - - onTaskCompleted(null as any, TaskProcessingOutcome.Failed, 'error message'); - expect(processSendSpy).toHaveBeenCalledTimes(1); - expect(processSendSpy) - .toHaveBeenCalledWith( - { - type: 'task-completed', - outcome: TaskProcessingOutcome.Failed, - message: 'error message', - }, - jasmine.any(Function)); - }); - - it('should return a promise (that is never resolved)', done => { - const promise = startWorker(mockLogger, createCompileFnSpy); - - expect(promise).toEqual(jasmine.any(Promise)); - - promise.then( - () => done.fail('Expected promise not to resolve'), - () => done.fail('Expected promise not to reject')); - - // We can't wait forever to verify that the promise is not resolved, but at least verify - // that it is not resolved immediately. - setTimeout(done, 100); - }); - - it('should handle `process-task` messages', () => { - const mockTask = { - entryPoint: {name: 'foo'}, - formatProperty: 'es2015', - processDts: DtsProcessing.Yes, - } as unknown as Task; - - startWorker(mockLogger, createCompileFnSpy); - cluster.worker!.emit('message', {type: 'process-task', task: mockTask}); - - expect(compileFnSpy).toHaveBeenCalledWith(mockTask); - expect(processSendSpy).toHaveBeenCalledOnceWith({type: 'ready'}, jasmine.any(Function)); - - expect(mockLogger.logs.debug[0]).toEqual([ - '[Worker #42] Processing task: {entryPoint: foo, formatProperty: es2015, processDts: Yes}', - ]); - }); - - it('should send errors during task processing back to the master process', () => { - const mockTask = { - entryPoint: {name: 'foo'}, - formatProperty: 'es2015', - processDts: DtsProcessing.Yes, - } as unknown as Task; - - let err: string|Error; - compileFnSpy.and.callFake(() => { - throw err; - }); - - startWorker(mockLogger, createCompileFnSpy); - - err = 'Error string.'; - cluster.worker!.emit('message', {type: 'process-task', task: mockTask}); - expect(processSendSpy) - .toHaveBeenCalledWith({type: 'error', error: err}, jasmine.any(Function)); - - err = new Error('Error object.'); - cluster.worker!.emit('message', {type: 'process-task', task: mockTask}); - expect(processSendSpy) - .toHaveBeenCalledWith({type: 'error', error: err.stack}, jasmine.any(Function)); - }); - - it('should exit on `ENOMEM` errors during task processing', () => { - const processExitSpy = jasmine.createSpy('process.exit'); - const { - setMockValue: mockProcessExit, - installSpies: installProcessExitSpies, - uninstallSpies: uninstallProcessExitSpies, - } = spyProperty(process, 'exit'); - - try { - installProcessExitSpies(); - mockProcessExit(processExitSpy as unknown as typeof process.exit); - - const mockTask = { - entryPoint: {name: 'foo'}, - formatProperty: 'es2015', - processDts: DtsProcessing.Yes, - } as unknown as Task; - - const noMemError = Object.assign(new Error('ENOMEM: not enough memory'), {code: 'ENOMEM'}); - compileFnSpy.and.throwError(noMemError); - - startWorker(mockLogger, createCompileFnSpy); - cluster.worker!.emit('message', {type: 'process-task', task: mockTask}); - - expect(mockLogger.logs.warn).toEqual([[`[Worker #42] ${noMemError.stack}`]]); - expect(processExitSpy).toHaveBeenCalledWith(1); - expect(processSendSpy).toHaveBeenCalledOnceWith({type: 'ready'}, jasmine.any(Function)); - } finally { - uninstallProcessExitSpies(); - } - }); - - it('should throw, when an unknown message type is received', () => { - startWorker(mockLogger, createCompileFnSpy); - cluster.worker!.emit('message', {type: 'unknown', foo: 'bar'}); - - expect(compileFnSpy).not.toHaveBeenCalled(); - expect(processSendSpy) - .toHaveBeenCalledWith( - { - type: 'error', - error: jasmine.stringMatching( - 'Error: \\[Worker #42\\] Invalid message received: {"type":"unknown","foo":"bar"}'), - }, - jasmine.any(Function)); - }); - }); -}); diff --git a/packages/compiler-cli/ngcc/test/execution/helpers.ts b/packages/compiler-cli/ngcc/test/execution/helpers.ts deleted file mode 100644 index 435f1993998..00000000000 --- a/packages/compiler-cli/ngcc/test/execution/helpers.ts +++ /dev/null @@ -1,85 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import {DepGraph} from 'dependency-graph'; - -import {DtsProcessing, PartiallyOrderedTasks} from '../../src/execution/tasks/api'; -import {EntryPoint, EntryPointJsonProperty} from '../../src/packages/entry_point'; - -/** - * Create a set of tasks and a graph of their interdependencies. - * - * NOTE 1: The first task for each entry-point generates typings (which is similar to what happens - * in the actual code). - * NOTE 2: The `computeTaskDependencies()` implementation relies on the fact that tasks are sorted - * in such a way that a task can only depend upon earlier tasks (i.e. dependencies always come - * before dependents in the list of tasks). - * To preserve this attribute, you need to ensure that entry-points will only depend on - * entry-points with a lower index. Take this into account when defining `entryPointDeps`. - * (Failing to do so, will result in an error.) - * - * @param entryPointCount The number of different entry-points to mock. - * @param tasksPerEntryPointCount The number of tasks to generate per entry-point (i.e. simulating - * processing multiple format properties). - * @param entryPointDeps An object mapping an entry-point to its dependencies. Keys are - * entry-point indices and values are arrays of entry-point indices that the - * entry-point corresponding to the key depends on. - * For example, if entry-point #2 depends on entry-points #0 and #1, - * `entryPointDeps` would be `{2: [0, 1]}`. - * @return An object with the following properties: - * - `tasks`: The (partially ordered) list of generated mock tasks. - * - `graph`: The dependency graph for the generated mock entry-point. - */ -export function createTasksAndGraph( - entryPointCount: number, tasksPerEntryPointCount = 1, - entryPointDeps: {[entryPointIndex: string]: number[]} = {}): - {tasks: PartiallyOrderedTasks, graph: DepGraph} { - const entryPoints: EntryPoint[] = []; - const tasks: PartiallyOrderedTasks = [] as any; - const graph = new DepGraph(); - - // Create the entry-points and the associated tasks. - for (let epIdx = 0; epIdx < entryPointCount; epIdx++) { - const entryPoint = { - name: `entry-point-${epIdx}`, - path: `/path/to/entry/point/${epIdx}`, - } as EntryPoint; - - entryPoints.push(entryPoint); - graph.addNode(entryPoint.path); - - for (let tIdx = 0; tIdx < tasksPerEntryPointCount; tIdx++) { - const processDts = tIdx === 0 ? DtsProcessing.Yes : DtsProcessing.No; - const formatProperty = `prop-${tIdx}` as unknown as EntryPointJsonProperty; - tasks.push({ - entryPoint, - formatProperty: formatProperty, - formatPropertiesToMarkAsProcessed: [], - processDts - }); - } - } - - // Define entry-point interdependencies. - for (const epIdx of Object.keys(entryPointDeps).map(strIdx => +strIdx)) { - const fromPath = entryPoints[epIdx].path; - for (const depIdx of entryPointDeps[epIdx]) { - // Ensure that each entry-point only depends on entry-points at a lower index. - if (depIdx >= epIdx) { - throw Error( - 'Invalid `entryPointDeps`: Entry-points can only depend on entry-points at a lower ' + - `index, but entry-point #${epIdx} depends on #${depIdx} in: ` + - JSON.stringify(entryPointDeps, null, 2)); - } - - const toPath = entryPoints[depIdx].path; - graph.addDependency(fromPath, toPath); - } - } - - return {tasks, graph}; -} diff --git a/packages/compiler-cli/ngcc/test/execution/single_processor_executor_spec.ts b/packages/compiler-cli/ngcc/test/execution/single_processor_executor_spec.ts deleted file mode 100644 index 5f365949654..00000000000 --- a/packages/compiler-cli/ngcc/test/execution/single_processor_executor_spec.ts +++ /dev/null @@ -1,146 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ - -/// - -import {MockFileSystemNative} from '../../../src/ngtsc/file_system/testing'; -import {MockLogger} from '../../../src/ngtsc/logging/testing'; -import {SingleProcessExecutorSync} from '../../src/execution/single_process_executor'; -import {Task, TaskQueue} from '../../src/execution/tasks/api'; -import {SyncLocker} from '../../src/locking/sync_locker'; -import {MockLockFile} from '../helpers/mock_lock_file'; - - -describe('SingleProcessExecutor', () => { - let mockLogger: MockLogger; - let lockFileLog: string[]; - let mockLockFile: MockLockFile; - let locker: SyncLocker; - let executor: SingleProcessExecutorSync; - let createTaskCompletedCallback: jasmine.Spy; - - beforeEach(() => { - mockLogger = new MockLogger(); - lockFileLog = []; - mockLockFile = new MockLockFile(new MockFileSystemNative(), lockFileLog); - locker = new SyncLocker(mockLockFile); - createTaskCompletedCallback = jasmine.createSpy('createTaskCompletedCallback'); - executor = new SingleProcessExecutorSync(mockLogger, locker, createTaskCompletedCallback); - }); - - const noTasks = () => ({allTasksCompleted: true, getNextTask: () => null} as TaskQueue); - const oneTask = () => { - let tasksCount = 1; - return { - get allTasksCompleted() { - return tasksCount === 0; - }, - getNextTask() { - tasksCount--; - return {}; - }, - markAsCompleted(_task: Task) {}, - }; - }; - - describe('execute()', () => { - it('should call LockFile.write() and LockFile.remove() if processing completes successfully', - () => { - const createCompileFn: () => any = () => undefined; - executor.execute(noTasks, createCompileFn); - expect(lockFileLog).toEqual(['write()', 'remove()']); - }); - - it('should call LockFile.write() and LockFile.remove() if `analyzeEntryPoints` fails', () => { - const errorFn: () => never = () => { - throw new Error('analyze error'); - }; - const createCompileFn: () => any = () => undefined; - let error: string = ''; - try { - executor.execute(errorFn, createCompileFn); - } catch (e) { - error = (e as Error).message; - } - expect(error).toEqual('analyze error'); - expect(lockFileLog).toEqual(['write()', 'remove()']); - }); - - it('should call LockFile.write() and LockFile.remove() if `createCompileFn` fails', () => { - const createErrorCompileFn: () => any = () => { - throw new Error('compile error'); - }; - let error: string = ''; - try { - executor.execute(oneTask, createErrorCompileFn); - } catch (e) { - error = (e as Error).message; - } - expect(error).toEqual('compile error'); - expect(lockFileLog).toEqual(['write()', 'remove()']); - }); - - it('should not call `analyzeEntryPoints` if LockFile.write() fails', () => { - spyOn(mockLockFile, 'write').and.callFake(() => { - lockFileLog.push('write()'); - throw new Error('LockFile.write() error'); - }); - - const analyzeFn: () => any = () => { - lockFileLog.push('analyzeFn'); - }; - const anyFn: () => any = () => undefined; - executor = new SingleProcessExecutorSync(mockLogger, locker, createTaskCompletedCallback); - let error = ''; - try { - executor.execute(analyzeFn, anyFn); - } catch (e) { - error = (e as Error).message; - } - expect(error).toEqual('LockFile.write() error'); - expect(lockFileLog).toEqual(['write()']); - }); - - it('should fail if LockFile.remove() fails', () => { - const anyFn: () => any = () => undefined; - spyOn(mockLockFile, 'remove').and.callFake(() => { - lockFileLog.push('remove()'); - throw new Error('LockFile.remove() error'); - }); - - executor = new SingleProcessExecutorSync(mockLogger, locker, createTaskCompletedCallback); - let error = ''; - try { - executor.execute(noTasks, anyFn); - } catch (e) { - error = (e as Error).message; - } - expect(error).toEqual('LockFile.remove() error'); - expect(lockFileLog).toEqual(['write()', 'remove()']); - }); - - it('should call createTaskCompletedCallback with the task queue', () => { - const createCompileFn = jasmine.createSpy('createCompileFn'); - executor.execute(noTasks, createCompileFn); - expect(createTaskCompletedCallback).toHaveBeenCalledTimes(1); - expect(createTaskCompletedCallback.calls.mostRecent().args).toEqual([jasmine.objectContaining( - {allTasksCompleted: true, getNextTask: jasmine.any(Function)})]); - }); - - it('should pass the necessary callbacks to createCompileFn', () => { - const beforeWritingFiles = jasmine.any(Function); - const onTaskCompleted = () => {}; - const createCompileFn = - jasmine.createSpy('createCompileFn').and.returnValue(function compileFn() {}); - createTaskCompletedCallback.and.returnValue(onTaskCompleted); - executor.execute(noTasks, createCompileFn); - expect(createCompileFn).toHaveBeenCalledTimes(1); - expect(createCompileFn).toHaveBeenCalledWith(beforeWritingFiles, onTaskCompleted); - }); - }); -}); diff --git a/packages/compiler-cli/ngcc/test/execution/tasks/queues/parallel_task_queue_spec.ts b/packages/compiler-cli/ngcc/test/execution/tasks/queues/parallel_task_queue_spec.ts deleted file mode 100644 index 30f466602b9..00000000000 --- a/packages/compiler-cli/ngcc/test/execution/tasks/queues/parallel_task_queue_spec.ts +++ /dev/null @@ -1,584 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ - -import {MockLogger} from '../../../../../src/ngtsc/logging/testing'; -import {DtsProcessing, PartiallyOrderedTasks, TaskQueue} from '../../../../src/execution/tasks/api'; -import {ParallelTaskQueue} from '../../../../src/execution/tasks/queues/parallel_task_queue'; -import {computeTaskDependencies} from '../../../../src/execution/tasks/utils'; -import {createTasksAndGraph} from '../../helpers'; - -describe('ParallelTaskQueue', () => { - /** - * Create a `TaskQueue` by generating mock tasks (optionally with interdependencies). - * - * See `createTasksAndGraph()` for important usage notes. - * - * @param entryPointCount The number of different entry-points to mock. - * @param tasksPerEntryPointCount The number of tasks to generate per entry-point (i.e. simulating - * processing multiple format properties). - * @param entryPointDeps An object mapping an entry-point to its dependencies. Keys are - * entry-point indices and values are arrays of entry-point indices that the - * entry-point corresponding to the key depends on. - * For example, if entry-point #2 depends on entry-points #0 and #1, - * `entryPointDeps` would be `{2: [0, 1]}`. - * @return An object with the following properties: - * - `tasks`: The (partially ordered) list of generated mock tasks. - * - `queue`: The created `TaskQueue`. - */ - function createQueue( - entryPointCount: number, tasksPerEntryPointCount = 1, - entryPointDeps: {[entryPointIndex: string]: number[]} = {}): - {tasks: PartiallyOrderedTasks, queue: TaskQueue} { - const {tasks, graph} = - createTasksAndGraph(entryPointCount, tasksPerEntryPointCount, entryPointDeps); - const dependencies = computeTaskDependencies(tasks, graph); - return {tasks, queue: new ParallelTaskQueue(new MockLogger(), tasks.slice(), dependencies)}; - } - - /** - * Simulate processing the next task: - * - Request the next task from the specified queue. - * - If a task was returned, mark it as completed. - * - Return the task (this allows making assertions against the picked tasks in tests). - * - * @param queue The `TaskQueue` to get the next task from. - * @return The "processed" task (if any). - */ - const processNextTask = (queue: TaskQueue): ReturnType => { - const task = queue.getNextTask(); - if (task !== null) queue.markAsCompleted(task); - return task; - }; - - describe('allTaskCompleted', () => { - it('should be `false`, when there are unprocessed tasks', () => { - const {queue} = createQueue(2); - expect(queue.allTasksCompleted).toBe(false); - - processNextTask(queue); - expect(queue.allTasksCompleted).toBe(false); - }); - - it('should be `false`, when there are tasks in progress', () => { - const {queue} = createQueue(2); - - queue.getNextTask(); - expect(queue.allTasksCompleted).toBe(false); - - processNextTask(queue); - expect(queue.allTasksCompleted).toBe(false); // The first task is still in progress. - }); - - it('should be `true`, when there are no unprocess or in-progress tasks', () => { - const {queue} = createQueue(3); - - const task1 = queue.getNextTask()!; - const task2 = queue.getNextTask()!; - const task3 = queue.getNextTask()!; - expect(queue.allTasksCompleted).toBe(false); - - queue.markAsCompleted(task1); - queue.markAsCompleted(task3); - expect(queue.allTasksCompleted).toBe(false); // The second task is still in progress. - - queue.markAsCompleted(task2); - expect(queue.allTasksCompleted).toBe(true); - }); - - it('should be `true`, if the queue was empty from the beginning', () => { - const {queue} = createQueue(0); - expect(queue.allTasksCompleted).toBe(true); - }); - - it('should remain `true` once the queue has been emptied', () => { - const {queue} = createQueue(1); - expect(queue.allTasksCompleted).toBe(false); - - processNextTask(queue); - expect(queue.allTasksCompleted).toBe(true); - - queue.getNextTask(); - expect(queue.allTasksCompleted).toBe(true); - }); - }); - - describe('getNextTask()', () => { - it('should return the tasks in order (when they are not blocked by other tasks)', () => { - const {tasks, queue} = createQueue(6, 1, {}); // 1 task per entry-point; no dependencies. - - expect(queue.getNextTask()).toBe(tasks[0]); - expect(queue.getNextTask()).toBe(tasks[1]); - expect(queue.getNextTask()).toBe(tasks[2]); - expect(queue.getNextTask()).toBe(tasks[3]); - expect(queue.getNextTask()).toBe(tasks[4]); - expect(queue.getNextTask()).toBe(tasks[5]); - }); - - it('should return `null`, when there are no more tasks', () => { - const {tasks, queue} = createQueue(3); - tasks.forEach(() => expect(queue.getNextTask()).not.toBe(null)); - - expect(queue.getNextTask()).toBe(null); - expect(queue.getNextTask()).toBe(null); - - const {tasks: tasks2, queue: queue2} = createQueue(0); - - expect(tasks2).toEqual([]); - expect(queue.getNextTask()).toBe(null); - expect(queue.getNextTask()).toBe(null); - }); - - it('should return `null`, if all unprocessed tasks are blocked', () => { - const {tasks, queue} = createQueue(2, 2, { - 0: [], // Entry-point #0 does not depend on anything. - 1: [0], // Entry-point #1 depends on #0. - }); - - // Verify that the first two tasks are for the first entry-point. - expect(tasks[0].entryPoint.name).toBe('entry-point-0'); - expect(tasks[0].processDts).toBe(DtsProcessing.Yes); - expect(tasks[1].entryPoint.name).toBe('entry-point-0'); - expect(tasks[1].processDts).toBe(DtsProcessing.No); - - // Verify that the last two tasks are for the second entry-point. - expect(tasks[2].entryPoint.name).toBe('entry-point-1'); - expect(tasks[3].entryPoint.name).toBe('entry-point-1'); - - // Return the first task, since it is not blocked. - expect(queue.getNextTask()).toBe(tasks[0]); - - // But the rest are blocked on the first task - expect(queue.getNextTask()).toBe(null); - - // Unblock typings task for entry-point #1 and non-typings task for entry-point #0 - queue.markAsCompleted(tasks[0]); - expect(queue.getNextTask()).toBe(tasks[2]); - expect(queue.getNextTask()).toBe(tasks[1]); - - // The non-typings task for entry-point #1 is blocked on the typings task - expect(queue.getNextTask()).toBe(null); - queue.markAsCompleted(tasks[1]); - // Still blocked because we only completed a non-blocking task - expect(queue.getNextTask()).toBe(null); - - // Finally, unblock non-typings task for entry-point #1 - queue.markAsCompleted(tasks[2]); - expect(queue.getNextTask()).toBe(tasks[3]); - }); - - it('should prefer tasks that are blocking many tasks', () => { - // Tasks by priority: #1, #0, #2, #3 - // - Entry-point #0 transitively blocks 1 entry-point(s): 2 - // - Entry-point #1 transitively blocks 2 entry-point(s): 2, 3 - // - Entry-point #2 transitively blocks 0 entry-point(s): - - // - Entry-point #3 transitively blocks 0 entry-point(s): - - const {tasks, queue} = createQueue(5, 1, { - 0: [], - 1: [], - 2: [0, 1], - 3: [1], - }); - - // First return task #1, since it blocks the most other tasks. - expect(processNextTask(queue)).toBe(tasks[1]); - - // Then return task #0, since it blocks the most other tasks after #1. - expect(processNextTask(queue)).toBe(tasks[0]); - - // Then return task #2, since it comes before #3. - expect(processNextTask(queue)).toBe(tasks[2]); - - // Finally return task #3. - expect(processNextTask(queue)).toBe(tasks[3]); - }); - - it('should return a lower priority task, if higher priority tasks are blocked', () => { - // Tasks by priority: #0, #1, #3, #2, #4 - // - Entry-point #0 transitively blocks 3 entry-point(s): 1, 2, 4 - // - Entry-point #1 transitively blocks 2 entry-point(s): 2, 4 - // - Entry-point #2 transitively blocks 0 entry-point(s): - - // - Entry-point #3 transitively blocks 1 entry-point(s): 4 - // - Entry-point #4 transitively blocks 0 entry-point(s): - - const deps = { - 0: [], - 1: [0], - 2: [0, 1], - 3: [], - 4: [0, 1, 3], - }; - - const {tasks, queue} = createQueue(5, 1, deps); - - // First return task #0, since that blocks the most other tasks. - expect(queue.getNextTask()).toBe(tasks[0]); - - // While task #0 is still in progress, return task #3. - // Despite task #3's having a lower priority than #1 (blocking 1 vs 2 other tasks), task #1 is - // currently blocked on #0 (while #3 is not blocked by anything). - expect(queue.getNextTask()).toBe(tasks[3]); - - // Use the same dependencies as above, but complete task #0 before asking for another task to - // verify that task #1 would be returned in this case. - const {tasks: tasks2, queue: queue2} = createQueue(5, 1, deps); - - expect(processNextTask(queue2)).toBe(tasks2[0]); - expect(processNextTask(queue2)).toBe(tasks2[1]); - }); - - it('should keep they initial relative order of tasks for tasks with the same priority', () => { - // Tasks by priority: #1, #3, #0, #2, #4 - // - Entry-point #0 transitively blocks 0 entry-point(s): - - // - Entry-point #1 transitively blocks 1 entry-point(s): 2 - // - Entry-point #2 transitively blocks 0 entry-point(s): - - // - Entry-point #3 transitively blocks 1 entry-point(s): 4 - // - Entry-point #4 transitively blocks 0 entry-point(s): - - const {tasks, queue} = createQueue(5, 1, { - 0: [], - 1: [], - 2: [1], - 3: [], - 4: [3], - }); - - // First return task #1 (even if it has the same priority as #3), because it comes before #3 - // in the initial task list. - // Note that task #0 is not returned (even if it comes first in the initial task list), - // because it has a lower priority (i.e. blocks fewer other tasks). - expect(processNextTask(queue)).toBe(tasks[1]); - - // Then return task #3 (skipping over both #0 and #2), becasue it blocks the most other tasks. - expect(processNextTask(queue)).toBe(tasks[3]); - - // The rest of the tasks (#0, #2, #4) block no tasks, so their initial relative order is - // preserved. - expect(queue.getNextTask()).toBe(tasks[0]); - expect(queue.getNextTask()).toBe(tasks[2]); - expect(queue.getNextTask()).toBe(tasks[4]); - }); - }); - - describe('markAsCompleted()', () => { - it('should mark a task as completed', () => { - const {queue} = createQueue(2); - - const task1 = queue.getNextTask()!; - const task2 = queue.getNextTask()!; - expect(queue.allTasksCompleted).toBe(false); - - queue.markAsCompleted(task1); - queue.markAsCompleted(task2); - expect(queue.allTasksCompleted).toBe(true); - }); - - it('should throw, if the specified task is not in progress', () => { - const {tasks, queue} = createQueue(3); - queue.getNextTask(); - - expect(() => queue.markAsCompleted(tasks[2])) - .toThrowError( - `Trying to mark task that was not in progress as completed: ` + - `{entryPoint: entry-point-2, formatProperty: prop-0, processDts: Yes}`); - }); - - it('should remove the completed task from the lists of blocking tasks (so other tasks can be unblocked)', - () => { - const {tasks, queue} = createQueue(3, 1, { - 0: [], // Entry-point #0 does not depend on anything. - 1: [0], // Entry-point #1 depends on #0. - 2: [0, 1], // Entry-point #2 depends on #0 and #1. - }); - - // Pick task #0 first, since it is the only one that is not blocked by other tasks. - expect(queue.getNextTask()).toBe(tasks[0]); - - // No task available, until task #0 is completed. - expect(queue.getNextTask()).toBe(null); - - // Once task #0 is completed, task #1 is unblocked. - queue.markAsCompleted(tasks[0]); - expect(queue.getNextTask()).toBe(tasks[1]); - - // Task #2 is still blocked on #1. - expect(queue.getNextTask()).toBe(null); - - // Once task #1 is completed, task #2 is unblocked. - queue.markAsCompleted(tasks[1]); - expect(queue.getNextTask()).toBe(tasks[2]); - }); - }); - - describe('markAsUnprocessed()', () => { - it('should mark an in-progress task as unprocessed, so that it can be picked again', () => { - const {queue} = createQueue(2); - - const task1 = queue.getNextTask()!; - const task2 = queue.getNextTask()!; - expect(queue.allTasksCompleted).toBe(false); - - queue.markAsUnprocessed(task1); - queue.markAsCompleted(task2); - expect(queue.allTasksCompleted).toBe(false); - - expect(queue.getNextTask()).toBe(task1); - expect(queue.allTasksCompleted).toBe(false); - - queue.markAsCompleted(task1); - expect(queue.allTasksCompleted).toBe(true); - }); - - it('should throw, if the specified task is not in progress', () => { - const {tasks, queue} = createQueue(3); - queue.getNextTask(); - queue.markAsCompleted(tasks[0]); - - // Try with a task that is already completed. - expect(() => queue.markAsUnprocessed(tasks[0])) - .toThrowError( - `Trying to mark task that was not in progress as unprocessed: ` + - `{entryPoint: entry-point-0, formatProperty: prop-0, processDts: Yes}`); - - // Try with a task that is not yet started. - expect(() => queue.markAsUnprocessed(tasks[2])) - .toThrowError( - `Trying to mark task that was not in progress as unprocessed: ` + - `{entryPoint: entry-point-2, formatProperty: prop-0, processDts: Yes}`); - }); - - it('should not remove the unprocessed task from the lists of blocking tasks', () => { - const {tasks, queue} = createQueue(3, 1, { - 0: [], // Entry-point #0 does not depend on anything. - 1: [0], // Entry-point #1 depends on #0. - 2: [0, 1], // Entry-point #2 depends on #0 and #1. - }); - - // Pick task #0 first, since it is the only one that is not blocked by other tasks. - expect(queue.getNextTask()).toBe(tasks[0]); - - // No task available, until task #0 is completed. - expect(queue.getNextTask()).toBe(null); - - // Put task #0 back to the unprocessed tasks. - queue.markAsUnprocessed(tasks[0]); - expect(queue.getNextTask()).toBe(tasks[0]); - - // Other tasks are still blocked on task #0. - expect(queue.getNextTask()).toBe(null); - }); - }); - - describe('toString()', () => { - it('should include the `TaskQueue` constructor\'s name', () => { - const {queue} = createQueue(0); - expect(queue.toString()).toMatch(/^ParallelTaskQueue\n/); - }); - - it('should include the value of `allTasksCompleted`', () => { - const {queue: queue1} = createQueue(0); - expect(queue1.toString()).toContain(' All tasks completed: true\n'); - - const {queue: queue2} = createQueue(3); - expect(queue2.toString()).toContain(' All tasks completed: false\n'); - - processNextTask(queue2); - processNextTask(queue2); - const task = queue2.getNextTask()!; - - expect(queue2.toString()).toContain(' All tasks completed: false\n'); - - queue2.markAsCompleted(task); - expect(queue2.toString()).toContain(' All tasks completed: true\n'); - }); - - it('should include the unprocessed tasks', () => { - const {queue} = createQueue(3); - expect(queue.toString()) - .toContain( - ' Unprocessed tasks (3): \n' + - ' - {entryPoint: entry-point-0, formatProperty: prop-0, processDts: Yes}\n' + - ' - {entryPoint: entry-point-1, formatProperty: prop-0, processDts: Yes}\n' + - ' - {entryPoint: entry-point-2, formatProperty: prop-0, processDts: Yes}\n'); - - const task1 = queue.getNextTask()!; - expect(queue.toString()) - .toContain( - ' Unprocessed tasks (2): \n' + - ' - {entryPoint: entry-point-1, formatProperty: prop-0, processDts: Yes}\n' + - ' - {entryPoint: entry-point-2, formatProperty: prop-0, processDts: Yes}\n'); - - queue.markAsCompleted(task1); - const task2 = queue.getNextTask()!; - expect(queue.toString()) - .toContain( - ' Unprocessed tasks (1): \n' + - ' - {entryPoint: entry-point-2, formatProperty: prop-0, processDts: Yes}\n'); - - queue.markAsCompleted(task2); - processNextTask(queue); - expect(queue.toString()).toContain(' Unprocessed tasks (0): \n'); - }); - - it('should include the in-progress tasks', () => { - const {queue} = createQueue(3); - expect(queue.toString()).toContain(' In-progress tasks (0): \n'); - - const task1 = queue.getNextTask()!; - expect(queue.toString()) - .toContain( - ' In-progress tasks (1): \n' + - ' - {entryPoint: entry-point-0, formatProperty: prop-0, processDts: Yes}\n'); - - queue.markAsCompleted(task1); - const task2 = queue.getNextTask()!; - expect(queue.toString()) - .toContain( - ' In-progress tasks (1): \n' + - ' - {entryPoint: entry-point-1, formatProperty: prop-0, processDts: Yes}\n'); - - queue.markAsCompleted(task2); - processNextTask(queue); - expect(queue.toString()).toContain(' In-progress tasks (0): \n'); - }); - - it('should include the blocked/blocking tasks', () => { - // Entry-point #0 transitively blocks 2 entry-point(s): 1, 3 - // Entry-point #1 transitively blocks 1 entry-point(s): 3 - // Entry-point #2 transitively blocks 1 entry-point(s): 3 - // Entry-point #3 transitively blocks 0 entry-point(s): - - const {tasks, queue} = createQueue(4, 2, { - 1: [0], - 3: [1, 2], - }); - - // Since there 4 entry-points and two tasks per entry-point (8 tasks in total), in comments - // below, tasks are denoted as `#X.Y` (where `X` is the entry-point index and Y is the task - // index). - // For example, the second task for the third entry-point would be `#2.1`. - - expect(queue.toString()) - .toContain( - ' Blocked tasks (6): \n' + - // #0.1 blocked by its typings #0.0 - ' - {entryPoint: entry-point-0, formatProperty: prop-1, processDts: No} (1): \n' + - ' - {entryPoint: entry-point-0, formatProperty: prop-0, processDts: Yes}\n' + - // #1.0 blocked by #0.0 - ' - {entryPoint: entry-point-1, formatProperty: prop-0, processDts: Yes} (1): \n' + - ' - {entryPoint: entry-point-0, formatProperty: prop-0, processDts: Yes}\n' + - // #1.1 blocked by #0.0 and its typings #1.0 - ' - {entryPoint: entry-point-1, formatProperty: prop-1, processDts: No} (2): \n' + - ' - {entryPoint: entry-point-0, formatProperty: prop-0, processDts: Yes}\n' + - ' - {entryPoint: entry-point-1, formatProperty: prop-0, processDts: Yes}\n' + - // #3.0 blocked by #0.0 (transitively), #1.0 and #2.0. - ' - {entryPoint: entry-point-3, formatProperty: prop-0, processDts: Yes} (3): \n' + - ' - {entryPoint: entry-point-0, formatProperty: prop-0, processDts: Yes}\n' + - ' - {entryPoint: entry-point-1, formatProperty: prop-0, processDts: Yes}\n' + - ' - {entryPoint: entry-point-2, formatProperty: prop-0, processDts: Yes}\n' + - // #3.1 blocked by #0.0 (transitively), #1.0 and #2.0, and its typings #3.0 - ' - {entryPoint: entry-point-3, formatProperty: prop-1, processDts: No} (4): \n' + - ' - {entryPoint: entry-point-0, formatProperty: prop-0, processDts: Yes}\n' + - ' - {entryPoint: entry-point-1, formatProperty: prop-0, processDts: Yes}\n' + - ' - {entryPoint: entry-point-2, formatProperty: prop-0, processDts: Yes}\n' + - ' - {entryPoint: entry-point-3, formatProperty: prop-0, processDts: Yes}\n' + - // #2.1 blocked by its typings #2.0 - ' - {entryPoint: entry-point-2, formatProperty: prop-1, processDts: No} (1): \n' + - ' - {entryPoint: entry-point-2, formatProperty: prop-0, processDts: Yes}'); - - expect(processNextTask(queue)).toBe(tasks[0]); // Process #0.0. - expect(processNextTask(queue)).toBe(tasks[2]); // Process #1.0. - expect(queue.toString()) - .toContain( - ' Blocked tasks (3): \n' + - // #3.0 blocked by #2.0. - ' - {entryPoint: entry-point-3, formatProperty: prop-0, processDts: Yes} (1): \n' + - ' - {entryPoint: entry-point-2, formatProperty: prop-0, processDts: Yes}\n' + - // #3.1 blocked by #2.0 and its typings #3.0 - ' - {entryPoint: entry-point-3, formatProperty: prop-1, processDts: No} (2): \n' + - ' - {entryPoint: entry-point-2, formatProperty: prop-0, processDts: Yes}\n' + - ' - {entryPoint: entry-point-3, formatProperty: prop-0, processDts: Yes}\n' + - // #2.1 blocked by its typings #2.0 - ' - {entryPoint: entry-point-2, formatProperty: prop-1, processDts: No} (1): \n' + - ' - {entryPoint: entry-point-2, formatProperty: prop-0, processDts: Yes}'); - - expect(processNextTask(queue)).toBe(tasks[4]); // Process #2.0. - expect(queue.toString()) - .toContain( - ' Blocked tasks (1): \n' + - // #3.1 blocked by its typings #3.0 - ' - {entryPoint: entry-point-3, formatProperty: prop-1, processDts: No} (1): \n' + - ' - {entryPoint: entry-point-3, formatProperty: prop-0, processDts: Yes}'); - expect(processNextTask(queue)).toBe(tasks[6]); // Process #3.0. - expect(queue.toString()).toContain(' Blocked tasks (0): '); - }); - - it('should display all info together', () => { - // An initially empty queue. - const {queue: queue1} = createQueue(0); - expect(queue1.toString()) - .toBe( - 'ParallelTaskQueue\n' + - ' All tasks completed: true\n' + - ' Unprocessed tasks (0): \n' + - ' In-progress tasks (0): \n' + - ' Blocked tasks (0): '); - - // A queue with three tasks (and one interdependency). - const {tasks: tasks2, queue: queue2} = createQueue(3, 1, {2: [1]}); - expect(queue2.toString()) - .toBe( - 'ParallelTaskQueue\n' + - ' All tasks completed: false\n' + - ' Unprocessed tasks (3): \n' + - ' - {entryPoint: entry-point-1, formatProperty: prop-0, processDts: Yes}\n' + - ' - {entryPoint: entry-point-0, formatProperty: prop-0, processDts: Yes}\n' + - ' - {entryPoint: entry-point-2, formatProperty: prop-0, processDts: Yes}\n' + - ' In-progress tasks (0): \n' + - ' Blocked tasks (1): \n' + - ' - {entryPoint: entry-point-2, formatProperty: prop-0, processDts: Yes} (1): \n' + - ' - {entryPoint: entry-point-1, formatProperty: prop-0, processDts: Yes}'); - - // Start processing tasks #1 and #0 (#2 is still blocked on #1). - expect(queue2.getNextTask()).toBe(tasks2[1]); - expect(queue2.getNextTask()).toBe(tasks2[0]); - expect(queue2.toString()) - .toBe( - 'ParallelTaskQueue\n' + - ' All tasks completed: false\n' + - ' Unprocessed tasks (1): \n' + - ' - {entryPoint: entry-point-2, formatProperty: prop-0, processDts: Yes}\n' + - ' In-progress tasks (2): \n' + - ' - {entryPoint: entry-point-1, formatProperty: prop-0, processDts: Yes}\n' + - ' - {entryPoint: entry-point-0, formatProperty: prop-0, processDts: Yes}\n' + - ' Blocked tasks (1): \n' + - ' - {entryPoint: entry-point-2, formatProperty: prop-0, processDts: Yes} (1): \n' + - ' - {entryPoint: entry-point-1, formatProperty: prop-0, processDts: Yes}'); - - // Complete task #1 nd start processing #2 (which is not unblocked). - queue2.markAsCompleted(tasks2[1]); - expect(queue2.getNextTask()).toBe(tasks2[2]); - expect(queue2.toString()) - .toBe( - 'ParallelTaskQueue\n' + - ' All tasks completed: false\n' + - ' Unprocessed tasks (0): \n' + - ' In-progress tasks (2): \n' + - ' - {entryPoint: entry-point-0, formatProperty: prop-0, processDts: Yes}\n' + - ' - {entryPoint: entry-point-2, formatProperty: prop-0, processDts: Yes}\n' + - ' Blocked tasks (0): '); - - // Complete tasks #2 and #0. All tasks are now completed. - queue2.markAsCompleted(tasks2[2]); - queue2.markAsCompleted(tasks2[0]); - expect(queue2.toString()) - .toBe( - 'ParallelTaskQueue\n' + - ' All tasks completed: true\n' + - ' Unprocessed tasks (0): \n' + - ' In-progress tasks (0): \n' + - ' Blocked tasks (0): '); - }); - }); -}); diff --git a/packages/compiler-cli/ngcc/test/execution/tasks/queues/serial_task_queue_spec.ts b/packages/compiler-cli/ngcc/test/execution/tasks/queues/serial_task_queue_spec.ts deleted file mode 100644 index 0ec481bd59e..00000000000 --- a/packages/compiler-cli/ngcc/test/execution/tasks/queues/serial_task_queue_spec.ts +++ /dev/null @@ -1,309 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import {DepGraph} from 'dependency-graph'; - -import {MockLogger} from '../../../../../src/ngtsc/logging/testing'; -import {DtsProcessing, PartiallyOrderedTasks, Task, TaskQueue} from '../../../../src/execution/tasks/api'; -import {SerialTaskQueue} from '../../../../src/execution/tasks/queues/serial_task_queue'; -import {computeTaskDependencies} from '../../../../src/execution/tasks/utils'; -import {EntryPoint, EntryPointJsonProperty} from '../../../../src/packages/entry_point'; - - -describe('SerialTaskQueue', () => { - // Helpers - /** - * Create a `TaskQueue` by generating mock tasks. - * - * NOTE: Tasks at even indices generate typings. - * - * @param taskCount The number of tasks to generate. - * @return An object with the following properties: - * - `tasks`: The (partially ordered) list of generated mock tasks. - * - `queue`: The created `TaskQueue`. - */ - const createQueue = (taskCount: number): {tasks: PartiallyOrderedTasks, queue: TaskQueue} => { - const tasks: PartiallyOrderedTasks = [] as any; - const graph = new DepGraph(); - for (let i = 0; i < taskCount; i++) { - const entryPoint = {name: `entry-point-${i}`, path: `/path/to/entry/point/${i}`} as - EntryPoint; - const processDts = i % 2 === 0 ? DtsProcessing.Yes : DtsProcessing.No; - tasks.push({ - entryPoint: entryPoint, - formatProperty: `prop-${i}` as unknown as EntryPointJsonProperty, - formatPropertiesToMarkAsProcessed: [], - processDts - } as Task); - graph.addNode(entryPoint.path); - } - const dependencies = computeTaskDependencies(tasks, graph); - return {tasks, queue: new SerialTaskQueue(new MockLogger(), tasks.slice(), dependencies)}; - }; - - /** - * Simulate processing the next task: - * - Request the next task from the specified queue. - * - If a task was returned, mark it as completed. - * - Return the task (this allows making assertions against the picked tasks in tests). - * - * @param queue The `TaskQueue` to get the next task from. - * @return The "processed" task (if any). - */ - const processNextTask = (queue: TaskQueue): ReturnType => { - const task = queue.getNextTask(); - if (task !== null) queue.markAsCompleted(task); - return task; - }; - - describe('allTasksCompleted', () => { - it('should be `false`, when there are unprocessed tasks', () => { - const {queue} = createQueue(2); - expect(queue.allTasksCompleted).toBe(false); - - processNextTask(queue); - expect(queue.allTasksCompleted).toBe(false); - }); - - it('should be `false`, when there are tasks in progress', () => { - const {queue} = createQueue(1); - queue.getNextTask(); - - expect(queue.allTasksCompleted).toBe(false); - }); - - it('should be `true`, when there are no unprocessed or in-progress tasks', () => { - const {queue} = createQueue(3); - - processNextTask(queue); - expect(queue.allTasksCompleted).toBe(false); - - processNextTask(queue); - expect(queue.allTasksCompleted).toBe(false); - - processNextTask(queue); - expect(queue.allTasksCompleted).toBe(true); - }); - - it('should be `true`, if the queue was empty from the beginning', () => { - const {queue} = createQueue(0); - expect(queue.allTasksCompleted).toBe(true); - }); - - it('should remain `true` once the queue has been emptied', () => { - const {queue} = createQueue(1); - expect(queue.allTasksCompleted).toBe(false); - - processNextTask(queue); - expect(queue.allTasksCompleted).toBe(true); - - processNextTask(queue); - expect(queue.allTasksCompleted).toBe(true); - }); - }); - - describe('getNextTask()', () => { - it('should return the tasks in order', () => { - const {tasks, queue} = createQueue(3); - - expect(processNextTask(queue)).toBe(tasks[0]); - expect(processNextTask(queue)).toBe(tasks[1]); - expect(processNextTask(queue)).toBe(tasks[2]); - }); - - it('should return `null`, when there are no more tasks', () => { - const {tasks, queue} = createQueue(3); - tasks.forEach(() => expect(processNextTask(queue)).not.toBe(null)); - - expect(processNextTask(queue)).toBe(null); - expect(processNextTask(queue)).toBe(null); - - const {tasks: tasks2, queue: queue2} = createQueue(0); - - expect(tasks2).toEqual([]); - expect(processNextTask(queue2)).toBe(null); - expect(processNextTask(queue2)).toBe(null); - }); - - it('should throw, if a task is already in progress', () => { - const {queue} = createQueue(3); - queue.getNextTask(); - - expect(() => queue.getNextTask()) - .toThrowError( - `Trying to get next task, while there is already a task in progress: ` + - `{entryPoint: entry-point-0, formatProperty: prop-0, processDts: Yes}`); - }); - }); - - describe('markAsCompleted()', () => { - it('should mark a task as completed, so that the next task can be picked', () => { - const {queue} = createQueue(3); - const task = queue.getNextTask()!; - - expect(() => queue.getNextTask()).toThrow(); - - queue.markAsCompleted(task); - expect(() => queue.getNextTask()).not.toThrow(); - }); - - it('should throw, if the specified task is not in progress', () => { - const {tasks, queue} = createQueue(3); - queue.getNextTask(); - - expect(() => queue.markAsCompleted(tasks[2])) - .toThrowError( - `Trying to mark task that was not in progress as completed: ` + - `{entryPoint: entry-point-2, formatProperty: prop-2, processDts: Yes}`); - }); - }); - - describe('markAsUnprocessed()', () => { - it('should mark an in-progress task as unprocessed, so that it can be picked again', () => { - const {queue} = createQueue(3); - const task = queue.getNextTask()!; - - expect(() => queue.getNextTask()).toThrow(); - - queue.markAsUnprocessed(task); - expect(queue.getNextTask()).toBe(task); - }); - - it('should throw, if the specified task is not in progress', () => { - const {tasks, queue} = createQueue(3); - queue.getNextTask(); - queue.markAsCompleted(tasks[0]); - - // Try with a task that is already completed. - expect(() => queue.markAsUnprocessed(tasks[0])) - .toThrowError( - `Trying to mark task that was not in progress as unprocessed: ` + - `{entryPoint: entry-point-0, formatProperty: prop-0, processDts: Yes}`); - - // Try with a task that is not yet started. - expect(() => queue.markAsUnprocessed(tasks[2])) - .toThrowError( - `Trying to mark task that was not in progress as unprocessed: ` + - `{entryPoint: entry-point-2, formatProperty: prop-2, processDts: Yes}`); - }); - }); - - describe('toString()', () => { - it('should include the `TaskQueue` constructor\'s name', () => { - const {queue} = createQueue(0); - expect(queue.toString()).toMatch(/^SerialTaskQueue\n/); - }); - - it('should include the value of `allTasksCompleted`', () => { - const {queue: queue1} = createQueue(0); - expect(queue1.toString()).toContain(' All tasks completed: true\n'); - - const {queue: queue2} = createQueue(3); - expect(queue2.toString()).toContain(' All tasks completed: false\n'); - - processNextTask(queue2); - processNextTask(queue2); - const task = queue2.getNextTask()!; - - expect(queue2.toString()).toContain(' All tasks completed: false\n'); - - queue2.markAsCompleted(task); - expect(queue2.toString()).toContain(' All tasks completed: true\n'); - }); - - it('should include the unprocessed tasks', () => { - const {queue} = createQueue(3); - expect(queue.toString()) - .toContain( - ' Unprocessed tasks (3): \n' + - ' - {entryPoint: entry-point-0, formatProperty: prop-0, processDts: Yes}\n' + - ' - {entryPoint: entry-point-1, formatProperty: prop-1, processDts: No}\n' + - ' - {entryPoint: entry-point-2, formatProperty: prop-2, processDts: Yes}\n'); - - const task1 = queue.getNextTask()!; - expect(queue.toString()) - .toContain( - ' Unprocessed tasks (2): \n' + - ' - {entryPoint: entry-point-1, formatProperty: prop-1, processDts: No}\n' + - ' - {entryPoint: entry-point-2, formatProperty: prop-2, processDts: Yes}\n'); - - queue.markAsCompleted(task1); - const task2 = queue.getNextTask()!; - expect(queue.toString()) - .toContain( - ' Unprocessed tasks (1): \n' + - ' - {entryPoint: entry-point-2, formatProperty: prop-2, processDts: Yes}\n'); - - queue.markAsCompleted(task2); - processNextTask(queue); - expect(queue.toString()).toContain(' Unprocessed tasks (0): \n'); - }); - - it('should include the in-progress tasks', () => { - const {queue} = createQueue(3); - expect(queue.toString()).toContain(' In-progress tasks (0): '); - - const task1 = queue.getNextTask()!; - expect(queue.toString()) - .toContain( - ' In-progress tasks (1): \n' + - ' - {entryPoint: entry-point-0, formatProperty: prop-0, processDts: Yes}'); - - queue.markAsCompleted(task1); - const task2 = queue.getNextTask()!; - expect(queue.toString()) - .toContain( - ' In-progress tasks (1): \n' + - ' - {entryPoint: entry-point-1, formatProperty: prop-1, processDts: No}'); - - queue.markAsCompleted(task2); - processNextTask(queue); - expect(queue.toString()).toContain(' In-progress tasks (0): '); - }); - - it('should display all info together', () => { - const {queue: queue1} = createQueue(0); - expect(queue1.toString()) - .toBe( - 'SerialTaskQueue\n' + - ' All tasks completed: true\n' + - ' Unprocessed tasks (0): \n' + - ' In-progress tasks (0): '); - - const {queue: queue2} = createQueue(3); - expect(queue2.toString()) - .toBe( - 'SerialTaskQueue\n' + - ' All tasks completed: false\n' + - ' Unprocessed tasks (3): \n' + - ' - {entryPoint: entry-point-0, formatProperty: prop-0, processDts: Yes}\n' + - ' - {entryPoint: entry-point-1, formatProperty: prop-1, processDts: No}\n' + - ' - {entryPoint: entry-point-2, formatProperty: prop-2, processDts: Yes}\n' + - ' In-progress tasks (0): '); - - processNextTask(queue2); - const task = queue2.getNextTask()!; - expect(queue2.toString()) - .toBe( - 'SerialTaskQueue\n' + - ' All tasks completed: false\n' + - ' Unprocessed tasks (1): \n' + - ' - {entryPoint: entry-point-2, formatProperty: prop-2, processDts: Yes}\n' + - ' In-progress tasks (1): \n' + - ' - {entryPoint: entry-point-1, formatProperty: prop-1, processDts: No}'); - - queue2.markAsCompleted(task); - processNextTask(queue2); - expect(queue2.toString()) - .toBe( - 'SerialTaskQueue\n' + - ' All tasks completed: true\n' + - ' Unprocessed tasks (0): \n' + - ' In-progress tasks (0): '); - }); - }); -}); diff --git a/packages/compiler-cli/ngcc/test/execution/utils_spec.ts b/packages/compiler-cli/ngcc/test/execution/utils_spec.ts deleted file mode 100644 index a50684e663f..00000000000 --- a/packages/compiler-cli/ngcc/test/execution/utils_spec.ts +++ /dev/null @@ -1,84 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import {DtsProcessing} from '../../src/execution/tasks/api'; -import {computeTaskDependencies, sortTasksByPriority} from '../../src/execution/tasks/utils'; -import {createTasksAndGraph} from './helpers'; - -describe('execution utils', () => { - describe('computeTaskDependencies()', () => { - it('should throw, if there are multiple tasks that generate typings for a single entry-point', - () => { - const {tasks, graph} = createTasksAndGraph(2, 2, { - 0: [], // Entry-point #0 does not depend on anything. - 1: [0], // Entry-point #1 depends on #0. - }); - tasks[1].processDts = DtsProcessing.Yes; // Tweak task #1 to also generate typings. - - expect(() => computeTaskDependencies(tasks, graph)) - .toThrowError( - 'Invariant violated: Multiple tasks are assigned generating typings for ' + - '\'/path/to/entry/point/0\':\n' + - ' - {entryPoint: entry-point-0, formatProperty: prop-0, processDts: Yes}\n' + - ' - {entryPoint: entry-point-0, formatProperty: prop-1, processDts: Yes}'); - }); - - it('should add non-typings tasks to the dependents of typings tasks', () => { - const {tasks, graph} = createTasksAndGraph(2, 2, { - 0: [], // entry-point-0 does not depend on anything. - 1: [0], // entry-point-1 depends on entry-point-0. - }); - - const dependents = computeTaskDependencies(tasks, graph); - // entry-point-0 - expect(dependents.get(tasks[0])).toEqual(new Set([ - tasks[1], // non-typings task for entry-point-0 - tasks[2], // typings task for entry-point-1, which depends upon entry-point-0 - tasks[3], // non-typings task for entry-point-1, which depends upon entry-point-0 - ])); - expect(dependents.get(tasks[1])).toBeUndefined(); - - // entry-point-1 - expect(dependents.get(tasks[2])).toEqual(new Set([ - tasks[3], // non-typings task for entry-point-1 - ])); - expect(dependents.get(tasks[3])).toBeUndefined(); - }); - }); - - describe('sortTasksByPriority', () => { - it('should return the tasks in their original order if there are no dependencies', () => { - const {tasks} = createTasksAndGraph(3, 1); - const sortedTasks = sortTasksByPriority(tasks, new Map()); - expect(sortedTasks).toEqual(tasks); - }); - - it('should return the tasks ordered by how many tasks depend upon them', () => { - // Before sort: - // 0 blocks [3] - // 1 blocks [2, 3] - // 2 blocks [] - // 3 blocks [4] - // 4 blocks [] - const {tasks, graph} = createTasksAndGraph(5, 1, {0: [], 1: [], 2: [1], 3: [0, 1], 4: [3]}); - const sortedTasks = sortTasksByPriority(tasks, computeTaskDependencies(tasks, graph)); - // After sort: - // 1 blocks [2, 3] - // 0 blocks [3] - // 3 blocks [4] - // 2 blocks [] - // 4 blocks [] - expect(sortedTasks).toEqual([ - tasks[1], - tasks[0], - tasks[3], - tasks[2], - tasks[4], - ]); - }); - }); -}); diff --git a/packages/compiler-cli/ngcc/test/helpers/BUILD.bazel b/packages/compiler-cli/ngcc/test/helpers/BUILD.bazel deleted file mode 100644 index 8eeb12d0093..00000000000 --- a/packages/compiler-cli/ngcc/test/helpers/BUILD.bazel +++ /dev/null @@ -1,17 +0,0 @@ -load("//tools:defaults.bzl", "ts_library") - -package(default_visibility = ["//packages/compiler-cli/ngcc:__subpackages__"]) - -ts_library( - name = "helpers", - testonly = True, - srcs = glob([ - "*.ts", - ]), - deps = [ - "//packages/compiler-cli/ngcc", - "//packages/compiler-cli/src/ngtsc/file_system", - "//packages/compiler-cli/src/ngtsc/file_system/testing", - "@npm//typescript", - ], -) diff --git a/packages/compiler-cli/ngcc/test/helpers/mock_lock_file.ts b/packages/compiler-cli/ngcc/test/helpers/mock_lock_file.ts deleted file mode 100644 index ca3b483f767..00000000000 --- a/packages/compiler-cli/ngcc/test/helpers/mock_lock_file.ts +++ /dev/null @@ -1,28 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import {PathManipulation} from '../../../src/ngtsc/file_system'; -import {LockFile} from '../../src/locking/lock_file'; - -/** - * A mock implementation of `LockFile` that just logs its calls. - */ -export class MockLockFile implements LockFile { - constructor( - fs: PathManipulation, private log: string[] = [], public path = fs.resolve('/lockfile'), - private pid = '1234') {} - write() { - this.log.push('write()'); - } - read(): string { - this.log.push('read()'); - return this.pid; - } - remove() { - this.log.push('remove()'); - } -} diff --git a/packages/compiler-cli/ngcc/test/helpers/spy_utils.ts b/packages/compiler-cli/ngcc/test/helpers/spy_utils.ts deleted file mode 100644 index 95de698ecec..00000000000 --- a/packages/compiler-cli/ngcc/test/helpers/spy_utils.ts +++ /dev/null @@ -1,112 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ - -/** An object with helpers for mocking/spying on an object's property. */ -export interface IPropertySpyHelpers { - /** - * A `jasmine.Spy` for `get` operations on the property (i.e. reading the current property value). - * (This is useful in case one needs to make assertions against property reads.) - */ - getSpy: jasmine.Spy; - - /** - * A `jasmine.Spy` for `set` operations on the property (i.e. setting a new property value). - * (This is useful in case one needs to make assertions against property writes.) - */ - setSpy: jasmine.Spy; - - /** Install the getter/setter spies for the property. */ - installSpies(): void; - - /** - * Uninstall the property spies and restore the original value (from before installing the - * spies), including the property descriptor. - */ - uninstallSpies(): void; - - /** Update the current value of the mocked property. */ - setMockValue(value: T[P]): void; -} - -/** - * Set up mocking an object's property (using spies) and return a function for updating the mocked - * property's value during tests. - * - * This is, essentially, a wrapper around `spyProperty()` which additionally takes care of - * installing the spies before each test (via `beforeEach()`) and uninstalling them after each test - * (via `afterEach()`). - * - * Example usage: - * - * ```ts - * describe('something', () => { - * // Assuming `window.foo` is an object... - * const mockWindowFooBar = mockProperty(window.foo, 'bar'); - * - * it('should do this', () => { - * mockWindowFooBar('baz'); - * expect(window.foo.bar).toBe('baz'); - * - * mockWindowFooBar('qux'); - * expect(window.foo.bar).toBe('qux'); - * }); - * }); - * ``` - * - * @param ctx The object whose property needs to be spied on. - * @param prop The name of the property to spy on. - * - * @return A function for updating the current value of the mocked property. - */ -export const mockProperty = - (ctx: T, prop: P): IPropertySpyHelpers['setMockValue'] => { - const {setMockValue, installSpies, uninstallSpies} = spyProperty(ctx, prop); - - beforeEach(installSpies); - afterEach(uninstallSpies); - - return setMockValue; - }; - -/** - * Return utility functions to help mock and spy on an object's property. - * - * It supports spying on properties that are either defined on the object instance itself or on its - * prototype. It also supports spying on non-writable properties (as long as they are configurable). - * - * NOTE: Unlike `jasmine`'s spying utilities, spies are not automatically installed/uninstalled, so - * the caller is responsible for manually taking care of that (by calling - * `installSpies()`/`uninstallSpies()` as necessary). - * - * @param ctx The object whose property needs to be spied on. - * @param prop The name of the property to spy on. - * - * @return An object with helpers for mocking/spying on an object's property. - */ -export const spyProperty = (ctx: T, prop: P): IPropertySpyHelpers => { - const originalDescriptor = Object.getOwnPropertyDescriptor(ctx, prop); - - let value = ctx[prop]; - const setMockValue = (mockValue: typeof value) => value = mockValue; - const setSpy = jasmine.createSpy(`set ${String(prop)}`).and.callFake(setMockValue); - const getSpy = jasmine.createSpy(`get ${String(prop)}`).and.callFake(() => value); - - const installSpies = () => { - value = ctx[prop]; - Object.defineProperty(ctx, prop, { - configurable: true, - enumerable: originalDescriptor ? originalDescriptor.enumerable : true, - get: getSpy, - set: setSpy, - }); - }; - const uninstallSpies = () => - originalDescriptor ? Object.defineProperty(ctx, prop, originalDescriptor) : delete ctx[prop]; - - return {installSpies, uninstallSpies, setMockValue, getSpy, setSpy}; -}; diff --git a/packages/compiler-cli/ngcc/test/helpers/umd_utils.ts b/packages/compiler-cli/ngcc/test/helpers/umd_utils.ts deleted file mode 100644 index c1aafdb7e65..00000000000 --- a/packages/compiler-cli/ngcc/test/helpers/umd_utils.ts +++ /dev/null @@ -1,173 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ - -export interface AdditionalFormatOptions { - /** - * The index of the `exports` parameter in the factory function (and thus the corresponding - * argument in the various factory calls). Passing -1 will cause the `exports` parameter/argument - * to be omitted altogether. - * (This option only affects the `Rollup` format.) - * - * Defaults to `0` (i.e. `exports` being the first argument). - */ - exportsParamIndex?: number; - - /** - * Whether to include an initializer for the `global` variable (`global = global || self`) before - * the global factory call. - * (This option only affects the `Rollup` format.) - * - * Defaults to `false` (i.e. not include an initialier for `global`). - */ - hasGlobalInitializer?: boolean; - - /** - * A set of dependencies that are not used in the factory function (i.e. for which factory - * function parameters are omitted). - * Unused dependencies must always follow used ones (if any). In other words, unused dependencies - * can only appear at the end of the dependency list. - * - * Defaults to an empty set (i.e. all dependencies are used). - */ - unusedDependencies?: Set; -} - -export enum ParenthesisFormat { - AroundFunction = 'parenthesis around function declaration', - AroundIife = 'parenthesis around IIFE', -} - -export enum WrapperFunctionFormat { - Rollup = 'wrapper function as emitted by Rollup', - Webpack = 'wrapper function as emitted by Webpack', -} - -export function createUmdModuleFactory( - wrapperFunctionFormat: WrapperFunctionFormat, - parenthesisFormat: ParenthesisFormat = ParenthesisFormat.AroundIife) { - return function createUmdModule( - moduleName: string, dependencies: string[], factoryBody: string, - {exportsParamIndex = 0, hasGlobalInitializer = false, unusedDependencies = new Set()}: - AdditionalFormatOptions = {}) { - // Ensure that any unused dependencies are at the end. - const firstUnusedDepIndex = dependencies.findIndex(dep => unusedDependencies.has(dep)); - const subsequentUsedDepIndex = - dependencies.slice(firstUnusedDepIndex + 1).findIndex(dep => !unusedDependencies.has(dep)); - if (firstUnusedDepIndex !== -1 && subsequentUsedDepIndex !== -1) { - throw new Error( - `Used dependencies cannot follow unused ones (dependencies: ${dependencies.join(', ')} ` + - `| unused: ${[...unusedDependencies].join(', ')}).`); - } - - let wrapperFunctionDeclaration: string = ''; - let wrapperFunctionCall: string = ''; - - const depsIdentifiers = dependencies.map( - dep => dep.replace(/^@angular\//, 'ng.') - .replace(/^\.?\//, '') - .replace(/[-/](.?)/g, (_, g1) => g1.toUpperCase())); - const depsParamNames = dependencies.filter(dep => !unusedDependencies.has(dep)) - .map((dep, i) => depsIdentifiers[i].replace(/^.*\./, '')); - const insertItem = (item: string, arr: string[]) => (exportsParamIndex < 0) ? - arr : - [...arr.slice(0, exportsParamIndex), item, ...arr.slice(exportsParamIndex)]; - - switch (wrapperFunctionFormat) { - case WrapperFunctionFormat.Rollup: - const cjsArgs = insertItem('exports', dependencies.map(d => `require('${d}')`)); - const amdArgs = insertItem('exports', dependencies).map(d => `'${d}'`); - const globalArgs = insertItem(moduleName, depsIdentifiers).map(id => `global.${id}`); - const factoryParams = insertItem('exports', depsParamNames); - - wrapperFunctionDeclaration = stripIndentation(` - function (global, factory) { - typeof exports === 'object' && typeof module !== 'undefined' ? - factory(${cjsArgs.join(', ')}) : - typeof define === 'function' && define.amd ? - define('${moduleName}', ${amdArgs.length ? `[${amdArgs.join(', ')}], ` : ''}factory) : - (${hasGlobalInitializer ? 'global = global || self, ' : ''}factory(${ - globalArgs.join(', ')})); - } - `); - wrapperFunctionCall = stripIndentation(` - (this, (function (${factoryParams.join(', ')}) { - 'use strict'; - - })) - `); - break; - case WrapperFunctionFormat.Webpack: - wrapperFunctionDeclaration = stripIndentation(` - function (root, factory) { - if (typeof exports === 'object' && typeof module === 'object') - module.exports = factory(${dependencies.map(d => `require('${d}')`).join(', ')}); - else if (typeof define === 'function' && define.amd) - define([${dependencies.map(d => `'${d}'`).join(', ')}], factory); - else if (typeof exports === 'object') - exports['${moduleName}'] = factory(${ - dependencies.map(d => `require('${d}')`).join(', ')}); - else - root['${moduleName}'] = factory(${ - depsIdentifiers.map(id => `root${id.split('.').map(x => `['${x}']`).join('')}`) - .join(', ')}); - } - `); - wrapperFunctionCall = stripIndentation(` - (global, function (${depsParamNames.join(', ')}) { - 'use strict'; - - }) - `); - break; - default: - throw new Error(`Unknown UMD wrapper function format: ${wrapperFunctionFormat}`); - } - - // Replace the placeholder with the actual function body outside a `stripIndentation()` call to - // avoid altering the original function body indentation. - // (This keeps the tests more predictable in case the original indentation is taken into account - // in test assertions.) - wrapperFunctionCall = wrapperFunctionCall.replace('', factoryBody); - - switch (parenthesisFormat) { - case ParenthesisFormat.AroundFunction: - return `(${wrapperFunctionDeclaration})${wrapperFunctionCall};`; - case ParenthesisFormat.AroundIife: - return `(${wrapperFunctionDeclaration}${wrapperFunctionCall});`; - default: - throw new Error(`Unknown UMD parenthesis format: ${wrapperFunctionFormat}`); - } - }; -} - -export function testForEachUmdFormat(testSuite: (data: { - createUmdModule: ReturnType, - wrapperFunctionFormat: WrapperFunctionFormat, - parenthesisFormat: ParenthesisFormat - }) => void) { - return () => { - for (const wrapperFunctionFormat - of [WrapperFunctionFormat.Rollup, WrapperFunctionFormat.Webpack]) { - for (const parenthesisFormat - of [ParenthesisFormat.AroundFunction, ParenthesisFormat.AroundIife]) { - const createUmdModule = createUmdModuleFactory(wrapperFunctionFormat, parenthesisFormat); - describe(`(with ${wrapperFunctionFormat} and ${parenthesisFormat})`, () => { - testSuite({createUmdModule, wrapperFunctionFormat, parenthesisFormat}); - }); - } - } - }; -} - -function stripIndentation(text: string): string { - const lines = text.replace(/^ *\r?\n/, '').replace(/\r?\n *$/, '').split('\n'); - const minIndentation = - Math.min(...lines.filter(l => !/^ *\r?$/.test(l)).map(l => /^ */.exec(l)![0].length)); - - return lines.map(l => l.substring(minIndentation)).join('\n'); -} diff --git a/packages/compiler-cli/ngcc/test/helpers/utils.ts b/packages/compiler-cli/ngcc/test/helpers/utils.ts deleted file mode 100644 index 2f2d4220581..00000000000 --- a/packages/compiler-cli/ngcc/test/helpers/utils.ts +++ /dev/null @@ -1,151 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import ts from 'typescript'; - -import {absoluteFrom, AbsoluteFsPath, getFileSystem, NgtscCompilerHost} from '../../../src/ngtsc/file_system'; -import {TestFile} from '../../../src/ngtsc/file_system/testing'; -import {ClusterWorkerScriptResolver} from '../../src/execution/cluster/master'; -import {DtsProcessing} from '../../src/execution/tasks/api'; -import {LockFilePathResolver} from '../../src/locking/lock_file'; -import {LockFileUnlockerScriptResolver} from '../../src/locking/lock_file_with_child_process'; -import {BundleProgram, makeBundleProgram} from '../../src/packages/bundle_program'; -import {NgccEntryPointConfig} from '../../src/packages/configuration'; -import {EntryPoint, EntryPointFormat} from '../../src/packages/entry_point'; -import {EntryPointBundle} from '../../src/packages/entry_point_bundle'; -import {NgccSourcesCompilerHost} from '../../src/packages/ngcc_compiler_host'; -import {createModuleResolutionCache, EntryPointFileCache, SharedFileCache} from '../../src/packages/source_file_cache'; - -export type TestConfig = Pick; - -export function makeTestEntryPoint( - entryPointName: string, packageName: string = entryPointName, config?: TestConfig): EntryPoint { - return { - name: entryPointName, - path: absoluteFrom(`/node_modules/${entryPointName}`), - packageName, - packagePath: absoluteFrom(`/node_modules/${packageName}`), - repositoryUrl: `https://github.com/${packageName}`, - packageJson: {name: entryPointName}, - typings: absoluteFrom(`/node_modules/${entryPointName}/index.d.ts`), - compiledByAngular: true, - ignoreMissingDependencies: false, - generateDeepReexports: config !== undefined ? !!config.generateDeepReexports : false, - }; -} - -/** - * - * @param format The format of the bundle. - * @param files The source files to include in the bundle. - * @param dtsFiles The typings files to include the bundle. - */ -export function makeTestEntryPointBundle( - packageName: string, format: EntryPointFormat, isCore: boolean, srcRootNames: AbsoluteFsPath[], - dtsRootNames?: AbsoluteFsPath[], config?: TestConfig, - enableI18nLegacyMessageIdFormat = false): EntryPointBundle { - const entryPoint = makeTestEntryPoint(packageName, packageName, config); - const src = makeTestBundleProgram(srcRootNames[0], isCore); - const dts = dtsRootNames ? - makeTestDtsBundleProgram(dtsRootNames[0], entryPoint.packagePath, isCore) : - null; - const isFlatCore = isCore && src.r3SymbolsFile === null; - return { - entryPoint, - format, - rootDirs: [absoluteFrom('/')], - src, - dts, - dtsProcessing: dtsRootNames ? DtsProcessing.Yes : DtsProcessing.No, - isCore, - isFlatCore, - enableI18nLegacyMessageIdFormat - }; -} - -export function makeTestBundleProgram( - path: AbsoluteFsPath, isCore: boolean = false, - additionalFiles?: AbsoluteFsPath[]): BundleProgram { - const fs = getFileSystem(); - const entryPointPath = fs.dirname(path); - const rootDir = fs.dirname(entryPointPath); - const options: ts.CompilerOptions = - {allowJs: true, maxNodeModuleJsDepth: Infinity, checkJs: false, rootDir, rootDirs: [rootDir]}; - const moduleResolutionCache = createModuleResolutionCache(fs); - const entryPointFileCache = - new EntryPointFileCache(fs, new SharedFileCache(fs), sourceText => sourceText); - const host = - new NgccSourcesCompilerHost(fs, options, entryPointFileCache, moduleResolutionCache, rootDir); - return makeBundleProgram( - fs, isCore, rootDir, path, 'r3_symbols.js', options, host, additionalFiles); -} - -export function makeTestDtsBundleProgram( - path: AbsoluteFsPath, packagePath: AbsoluteFsPath, isCore: boolean = false): BundleProgram { - const fs = getFileSystem(); - const options = {}; - const host = new NgtscCompilerHost(fs, options); - return makeBundleProgram(fs, isCore, packagePath, path, 'r3_symbols.d.ts', options, host); -} - -export function convertToDirectTsLibImport(filesystem: TestFile[]) { - return filesystem.map(file => { - const contents = - file.contents - .replace( - `import * as tslib_1 from 'tslib';`, - `import { __decorate, __metadata, __read, __values, __param, __extends, __assign } from 'tslib';`) - .replace(/tslib_1\./g, ''); - return {...file, contents}; - }); -} - -export function convertToInlineTsLib(filesystem: TestFile[], suffix: string = '') { - return filesystem.map(file => { - const contents = file.contents - .replace(`import * as tslib_1 from 'tslib';`, ` -var __decorate${suffix} = null; -var __metadata${suffix} = null; -var __read${suffix} = null; -var __values${suffix} = null; -var __param${suffix} = null; -var __extends${suffix} = null; -var __assign${suffix} = null; -`).replace(/tslib_1\.([_a-z]+)/gi, '$1' + suffix.replace('$', '$$')); - return {...file, contents}; - }); -} - -export function getRootFiles(testFiles: TestFile[]): AbsoluteFsPath[] { - return testFiles.filter(f => f.isRoot !== false).map(f => absoluteFrom(f.name)); -} - -/** - * Mock out the lockfile path resolution, which uses `require.resolve()`. - */ -export function mockRequireResolveForLockfile() { - spyOn(LockFilePathResolver, 'resolve') - .and.returnValue(absoluteFrom('/node_modules/@angular/compiler-cli/package.json')); -} - -/** - * Mock out the worker script resolution, which uses `require.resolve()`. - */ -export function mockRequireResolveForWorkerScript() { - spyOn(ClusterWorkerScriptResolver, 'resolve') - .and.returnValue(absoluteFrom( - '/node_modules/@angular/compiler-cli/ngcc/src/execution/cluster/ngcc_cluster_worker')); -} - -/** - * Mock out the lock file unlocker script resolution, which uses `require.resolve()`. - */ -export function mockRequireResolveForLockFileUnlockerScript() { - spyOn(LockFileUnlockerScriptResolver, 'resolve') - .and.returnValue(absoluteFrom( - '/node_modules/@angular/compiler-cli/ngcc/src/locking/lock_file_with_child_process/ngcc_lock_unlocker')); -} diff --git a/packages/compiler-cli/ngcc/test/host/commonjs_host_import_helper_spec.ts b/packages/compiler-cli/ngcc/test/host/commonjs_host_import_helper_spec.ts deleted file mode 100644 index 1d8881fc03d..00000000000 --- a/packages/compiler-cli/ngcc/test/host/commonjs_host_import_helper_spec.ts +++ /dev/null @@ -1,127 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import {absoluteFrom, getFileSystem} from '../../../src/ngtsc/file_system'; -import {runInEachFileSystem, TestFile} from '../../../src/ngtsc/file_system/testing'; -import {MockLogger} from '../../../src/ngtsc/logging/testing'; -import {isNamedVariableDeclaration} from '../../../src/ngtsc/reflection'; -import {getDeclaration, loadFakeCore, loadTestFiles} from '../../../src/ngtsc/testing'; -import {CommonJsReflectionHost} from '../../src/host/commonjs_host'; -import {makeTestBundleProgram} from '../helpers/utils'; - -runInEachFileSystem(() => { - describe('CommonJsReflectionHost [import helper style]', () => { - let _: typeof absoluteFrom; - let TOPLEVEL_DECORATORS_FILE: TestFile; - - beforeEach(() => { - _ = absoluteFrom; - - TOPLEVEL_DECORATORS_FILE = { - name: _('/toplevel_decorators.cjs.js'), - contents: ` -var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { - var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; - if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); - else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; - return c > 3 && r && Object.defineProperty(target, key, r), r; -}; -var core = require('@angular/core'); - -var INJECTED_TOKEN = new InjectionToken('injected'); -var ViewContainerRef = {}; -var TemplateRef = {}; - -var SomeDirective = (function() { - function SomeDirective(_viewContainer, _template, injected) {} - return SomeDirective; -}()); -SomeDirective = __decorate([ - core.Directive({ selector: '[someDirective]' }), - __metadata("design:paramtypes", [core.ViewContainerRef, core.TemplateRef]) -], SomeDirective); -__decorate([ - core.Input(), -], SomeDirective.prototype, "input1", void 0); -__decorate([ - core.Input(), -], SomeDirective.prototype, "input2", void 0); -exports.SomeDirective = SomeDirective; - -var OtherDirective = (function() { - function OtherDirective(_viewContainer, _template, injected) {} - return OtherDirective; -}()); -OtherDirective = __decorate([ - core.Directive({ selector: '[OtherDirective]' }), - __metadata("design:paramtypes", [core.ViewContainerRef, core.TemplateRef]) -], OtherDirective); -__decorate([ - core.Input(), -], OtherDirective.prototype, "input1", void 0); -__decorate([ - core.Input(), -], OtherDirective.prototype, "input2", void 0); -exports.OtherDirective = OtherDirective; - -var AliasedDirective$1 = (function () { - function AliasedDirective() {} - return AliasedDirective; -}()); -AliasedDirective$1 = __decorate([ - core.Directive({ selector: '[someDirective]' }), -], AliasedDirective$1); -exports.AliasedDirective$1 = AliasedDirective$1; -` - }; - }); - - describe('getDecoratorsOfDeclaration()', () => { - it('should find the decorators on a class at the top level', () => { - loadFakeCore(getFileSystem()); - loadTestFiles([TOPLEVEL_DECORATORS_FILE]); - const bundle = makeTestBundleProgram(TOPLEVEL_DECORATORS_FILE.name); - const host = new CommonJsReflectionHost(new MockLogger(), false, bundle); - const classNode = getDeclaration( - bundle.program, TOPLEVEL_DECORATORS_FILE.name, 'SomeDirective', - isNamedVariableDeclaration); - const decorators = host.getDecoratorsOfDeclaration(classNode)!; - - expect(decorators.length).toEqual(1); - - const decorator = decorators[0]; - expect(decorator.name).toEqual('Directive'); - expect(decorator.identifier!.getText()).toEqual('core.Directive'); - expect(decorator.import).toEqual({name: 'Directive', from: '@angular/core'}); - expect(decorator.args!.map(arg => arg.getText())).toEqual([ - '{ selector: \'[someDirective]\' }', - ]); - }); - - it('should find the decorators on an aliased class at the top level', () => { - loadFakeCore(getFileSystem()); - loadTestFiles([TOPLEVEL_DECORATORS_FILE]); - const bundle = makeTestBundleProgram(TOPLEVEL_DECORATORS_FILE.name); - const host = new CommonJsReflectionHost(new MockLogger(), false, bundle); - const classNode = getDeclaration( - bundle.program, TOPLEVEL_DECORATORS_FILE.name, 'AliasedDirective$1', - isNamedVariableDeclaration); - const decorators = host.getDecoratorsOfDeclaration(classNode)!; - - expect(decorators.length).toEqual(1); - - const decorator = decorators[0]; - expect(decorator.name).toEqual('Directive'); - expect(decorator.identifier!.getText()).toEqual('core.Directive'); - expect(decorator.import).toEqual({name: 'Directive', from: '@angular/core'}); - expect(decorator.args!.map(arg => arg.getText())).toEqual([ - '{ selector: \'[someDirective]\' }', - ]); - }); - }); - }); -}); diff --git a/packages/compiler-cli/ngcc/test/host/commonjs_host_spec.ts b/packages/compiler-cli/ngcc/test/host/commonjs_host_spec.ts deleted file mode 100644 index 1b192d1978a..00000000000 --- a/packages/compiler-cli/ngcc/test/host/commonjs_host_spec.ts +++ /dev/null @@ -1,3140 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import ts from 'typescript'; - -import {absoluteFrom, getFileSystem, getSourceFileOrError} from '../../../src/ngtsc/file_system'; -import {runInEachFileSystem, TestFile} from '../../../src/ngtsc/file_system/testing'; -import {MockLogger} from '../../../src/ngtsc/logging/testing'; -import {ClassMemberKind, ConcreteDeclaration, CtorParameter, DeclarationKind, DownleveledEnum, InlineDeclaration, isNamedClassDeclaration, isNamedFunctionDeclaration, isNamedVariableDeclaration, KnownDeclaration, TypeScriptReflectionHost, TypeValueReferenceKind} from '../../../src/ngtsc/reflection'; -import {getDeclaration, loadFakeCore, loadTestFiles} from '../../../src/ngtsc/testing'; -import {CommonJsReflectionHost} from '../../src/host/commonjs_host'; -import {DelegatingReflectionHost} from '../../src/host/delegating_host'; -import {getIifeBody} from '../../src/host/esm2015_host'; -import {NgccReflectionHost} from '../../src/host/ngcc_host'; -import {BundleProgram} from '../../src/packages/bundle_program'; -import {getRootFiles, makeTestBundleProgram, makeTestDtsBundleProgram} from '../helpers/utils'; - -import {expectTypeValueReferencesForParameters} from './util'; - -runInEachFileSystem(() => { - describe('CommonJsReflectionHost', () => { - let _: typeof absoluteFrom; - - let SOME_DIRECTIVE_FILE: TestFile; - let TOPLEVEL_DECORATORS_FILE: TestFile; - let CTOR_DECORATORS_ARRAY_FILE: TestFile; - let SIMPLE_ES2015_CLASS_FILE: TestFile; - let SIMPLE_CLASS_FILE: TestFile; - let FOO_FUNCTION_FILE: TestFile; - let INLINE_EXPORT_FILE: TestFile; - let INVALID_DECORATORS_FILE: TestFile; - let INVALID_DECORATOR_ARGS_FILE: TestFile; - let INVALID_PROP_DECORATORS_FILE: TestFile; - let INVALID_PROP_DECORATOR_ARGS_FILE: TestFile; - let INVALID_CTOR_DECORATORS_FILE: TestFile; - let INVALID_CTOR_DECORATOR_ARGS_FILE: TestFile; - let IMPORTS_FILES: TestFile[]; - let EXPORTS_FILES: TestFile[]; - let FUNCTION_BODY_FILE: TestFile; - let DECORATED_FILES: TestFile[]; - let TYPINGS_SRC_FILES: TestFile[]; - let TYPINGS_DTS_FILES: TestFile[]; - - // Helpers - const createHost = (bundle: BundleProgram, ngccHost: CommonJsReflectionHost) => { - const tsHost = new TypeScriptReflectionHost(bundle.program.getTypeChecker()); - return new DelegatingReflectionHost(tsHost, ngccHost); - }; - - beforeEach(() => { - _ = absoluteFrom; - - SOME_DIRECTIVE_FILE = { - name: _('/some_directive.cjs.js'), - contents: ` -var core = require('@angular/core'); - -var INJECTED_TOKEN = new InjectionToken('injected'); -var ViewContainerRef = {}; -var TemplateRef = {}; - -var SomeDirective = (function() { - function SomeDirective(_viewContainer, _template, injected) { - this.instanceProperty = 'instance'; - } - SomeDirective.prototype = { - instanceMethod: function() {}, - }; - SomeDirective.staticMethod = function() {}; - SomeDirective.staticProperty = 'static'; - SomeDirective.decorators = [ - { type: core.Directive, args: [{ selector: '[someDirective]' },] } - ]; - SomeDirective.ctorParameters = function() { return [ - { type: ViewContainerRef, }, - { type: TemplateRef, }, - { type: undefined, decorators: [{ type: core.Inject, args: [INJECTED_TOKEN,] },] }, - ]; }; - SomeDirective.propDecorators = { - "input1": [{ type: core.Input },], - "input2": [{ type: core.Input },], - }; - return SomeDirective; -}()); -exports.SomeDirective = SomeDirective; -` - }; - - TOPLEVEL_DECORATORS_FILE = { - name: _('/toplevel_decorators.cjs.js'), - contents: ` -var core = require('@angular/core'); - -var INJECTED_TOKEN = new InjectionToken('injected'); -var ViewContainerRef = {}; -var TemplateRef = {}; - -var SomeDirective = (function() { - function SomeDirective(_viewContainer, _template, injected) {} - return SomeDirective; -}()); -SomeDirective.decorators = [ - { type: core.Directive, args: [{ selector: '[someDirective]' },] } -]; -SomeDirective.ctorParameters = function() { return [ - { type: ViewContainerRef, }, - { type: TemplateRef, }, - { type: undefined, decorators: [{ type: core.Inject, args: [INJECTED_TOKEN,] },] }, -]; }; -SomeDirective.propDecorators = { - "input1": [{ type: core.Input },], - "input2": [{ type: core.Input },], -}; -exports.SomeDirective = SomeDirective; -` - }; - - CTOR_DECORATORS_ARRAY_FILE = { - name: _('/ctor_decorated_as_array.js'), - contents: ` - var core = require('@angular/core'); - var CtorDecoratedAsArray = (function() { - function CtorDecoratedAsArray(arg1) { - } - CtorDecoratedAsArray.ctorParameters = [{ type: ParamType, decorators: [{ type: Inject },] }]; - return CtorDecoratedAsArray; - }()); - exports.SomeDirective = SomeDirective; - `, - }; - - SIMPLE_ES2015_CLASS_FILE = { - name: _('/simple_es2015_class.d.ts'), - contents: ` - export class EmptyClass {} - `, - }; - - SIMPLE_CLASS_FILE = { - name: _('/simple_class.js'), - contents: ` -var EmptyClass = (function() { - function EmptyClass() { - } - return EmptyClass; -}()); -var NoParensClass = function() { - function EmptyClass() { - } - return EmptyClass; -}(); -var InnerParensClass = (function() { - function EmptyClass() { - } - return EmptyClass; -})(); -var NoDecoratorConstructorClass = (function() { - function NoDecoratorConstructorClass(foo) { - } - return NoDecoratorConstructorClass; -}()); -var OuterClass1 = (function() { - function InnerClass1() { - } - return InnerClass1; -}()); -var OuterClass2 = (function() { - function InnerClass2() { - } - InnerClass2_1 = InnerClass12 - var InnerClass2_1; - return InnerClass2; -}()); -var SuperClass = (function() { function SuperClass() {} return SuperClass; }()); -var ChildClass = /** @class */ (function (_super) { - __extends(InnerChildClass, _super); - function InnerChildClass() {} - return InnerChildClass; -}(SuperClass); -exports.EmptyClass = EmptyClass; -exports.NoDecoratorConstructorClass = NoDecoratorConstructorClass; -`, - }; - - FOO_FUNCTION_FILE = { - name: _('/foo_function.js'), - contents: ` -var core = require('@angular/core'); -function foo() {} -foo.decorators = [ - { type: core.Directive, args: [{ selector: '[ignored]' },] } -]; -exports.foo = foo; -`, - }; - - INLINE_EXPORT_FILE = { - name: _('/inline_export.js'), - contents: ` -var core = require('@angular/core'); -function foo() {} -foo.decorators = [ - { type: core.Directive, args: [{ selector: '[ignored]' },] } -]; -exports.directives = [foo]; -exports.Inline = (function() { function Inline() {} return Inline; })(); -`, - }; - - INVALID_DECORATORS_FILE = { - name: _('/invalid_decorators.js'), - contents: ` -var core = require('@angular/core'); -var NotArrayLiteral = (function() { - function NotArrayLiteral() { - } - NotArrayLiteral.decorators = () => [ - { type: core.Directive, args: [{ selector: '[ignored]' },] }, - ]; - return NotArrayLiteral; -}()); - -var NotObjectLiteral = (function() { - function NotObjectLiteral() { - } - NotObjectLiteral.decorators = [ - "This is not an object literal", - { type: core.Directive }, - ]; - return NotObjectLiteral; -}()); - -var NoTypeProperty = (function() { - function NoTypeProperty() { - } - NoTypeProperty.decorators = [ - { notType: core.Directive }, - { type: core.Directive }, - ]; - return NoTypeProperty; -}()); - -var NotIdentifier = (function() { - function NotIdentifier() { - } - NotIdentifier.decorators = [ - { type: 'StringsLiteralsAreNotIdentifiers' }, - { type: core.Directive }, - ]; - return NotIdentifier; -}()); -`, - }; - - INVALID_DECORATOR_ARGS_FILE = { - name: _('/invalid_decorator_args.js'), - contents: ` -var core = require('@angular/core'); -var NoArgsProperty = (function() { - function NoArgsProperty() { - } - NoArgsProperty.decorators = [ - { type: core.Directive }, - ]; - return NoArgsProperty; -}()); - -var args = [{ selector: '[ignored]' },]; -var NoPropertyAssignment = (function() { - function NoPropertyAssignment() { - } - NoPropertyAssignment.decorators = [ - { type: core.Directive, args }, - ]; - return NoPropertyAssignment; -}()); - -var NotArrayLiteral = (function() { - function NotArrayLiteral() { - } - NotArrayLiteral.decorators = [ - { type: core.Directive, args: () => [{ selector: '[ignored]' },] }, - ]; - return NotArrayLiteral; -}()); -`, - }; - - INVALID_PROP_DECORATORS_FILE = { - name: _('/invalid_prop_decorators.js'), - contents: ` -var core = require('@angular/core'); -var NotObjectLiteral = (function() { - function NotObjectLiteral() { - } - NotObjectLiteral.propDecorators = () => ({ - "prop": [{ type: core.Directive },] - }); - return NotObjectLiteral; -}()); - -var NotObjectLiteralProp = (function() { - function NotObjectLiteralProp() { - } - NotObjectLiteralProp.propDecorators = { - "prop": [ - "This is not an object literal", - { type: core.Directive }, - ] - }; - return NotObjectLiteralProp; -}()); - -var NoTypeProperty = (function() { - function NoTypeProperty() { - } - NoTypeProperty.propDecorators = { - "prop": [ - { notType: core.Directive }, - { type: core.Directive }, - ] - }; - return NoTypeProperty; -}()); - -var NotIdentifier = (function() { - function NotIdentifier() { - } - NotIdentifier.propDecorators = { - "prop": [ - { type: 'StringsLiteralsAreNotIdentifiers' }, - { type: core.Directive }, - ] - }; - return NotIdentifier; -}()); -`, - }; - - INVALID_PROP_DECORATOR_ARGS_FILE = { - name: _('/invalid_prop_decorator_args.js'), - contents: ` -var core = require('@angular/core'); -var NoArgsProperty = (function() { - function NoArgsProperty() { - } - NoArgsProperty.propDecorators = { - "prop": [{ type: core.Input },] - }; - return NoArgsProperty; -}()); - -var args = [{ selector: '[ignored]' },]; -var NoPropertyAssignment = (function() { - function NoPropertyAssignment() { - } - NoPropertyAssignment.propDecorators = { - "prop": [{ type: core.Input, args },] - }; - return NoPropertyAssignment; -}()); - -var NotArrayLiteral = (function() { - function NotArrayLiteral() { - } - NotArrayLiteral.propDecorators = { - "prop": [{ type: core.Input, args: () => [{ selector: '[ignored]' },] },], - }; - return NotArrayLiteral; -}()); -`, - }; - - INVALID_CTOR_DECORATORS_FILE = { - name: _('/invalid_ctor_decorators.js'), - contents: ` -var core = require('@angular/core'); -var NoParameters = (function() { - function NoParameters() {} - return NoParameters; -}()); - -var ArrowFunction = (function() { - function ArrowFunction(arg1) { - } - ArrowFunction.ctorParameters = () => [ - { type: 'ParamType', decorators: [{ type: core.Inject },] } - ]; - return ArrowFunction; -}()); - -var NotArrayLiteral = (function() { - function NotArrayLiteral(arg1) { - } - NotArrayLiteral.ctorParameters = function() { return 'StringsAreNotArrayLiterals'; }; - return NotArrayLiteral; -}()); - -var NotObjectLiteral = (function() { - function NotObjectLiteral(arg1, arg2) { - } - NotObjectLiteral.ctorParameters = function() { return [ - "This is not an object literal", - { type: 'ParamType', decorators: [{ type: core.Inject },] }, - ]; }; - return NotObjectLiteral; -}()); - -var NoTypeProperty = (function() { - function NoTypeProperty(arg1, arg2) { - } - NoTypeProperty.ctorParameters = function() { return [ - { - type: 'ParamType', - decorators: [ - { notType: core.Inject }, - { type: core.Inject }, - ] - }, - ]; }; - return NoTypeProperty; -}()); - -var NotIdentifier = (function() { - function NotIdentifier(arg1, arg2) { - } - NotIdentifier.ctorParameters = function() { return [ - { - type: 'ParamType', - decorators: [ - { type: 'StringsLiteralsAreNotIdentifiers' }, - { type: core.Inject }, - ] - }, - ]; }; - return NotIdentifier; -}()); -`, - }; - - INVALID_CTOR_DECORATOR_ARGS_FILE = { - name: _('/invalid_ctor_decorator_args.js'), - contents: ` -var core = require('@angular/core'); -var NoArgsProperty = (function() { - function NoArgsProperty(arg1) { - } - NoArgsProperty.ctorParameters = function() { return [ - { type: 'ParamType', decorators: [{ type: core.Inject },] }, - ]; }; - return NoArgsProperty; -}()); - -var args = [{ selector: '[ignored]' },]; -var NoPropertyAssignment = (function() { - function NoPropertyAssignment(arg1) { - } - NoPropertyAssignment.ctorParameters = function() { return [ - { type: 'ParamType', decorators: [{ type: core.Inject, args },] }, - ]; }; - return NoPropertyAssignment; -}()); - -var NotArrayLiteral = (function() { - function NotArrayLiteral(arg1) { - } - NotArrayLiteral.ctorParameters = function() { return [ - { type: 'ParamType', decorators: [{ type: core.Inject, args: () => [{ selector: '[ignored]' },] },] }, - ]; }; - return NotArrayLiteral; -}()); -`, - }; - - IMPORTS_FILES = [ - { - name: _('/index.js'), - contents: ` - var file_a = require('./file_a'); - var file_b = require('./file_b'); - var file_c = require('./file_c'); - ` - }, - { - name: _('/file_a.js'), - contents: ` -var a = 'a'; -exports.a = a; -`, - }, - { - name: _('/file_b.js'), - contents: ` -var file_a = require('./file_a'); -var b = file_a.a; -var c = 'c'; -var d = c; -`, - }, - { - name: _('/file_c.js'), - contents: ` -var file_a = require('./file_a'); -var c = file_a.a; -`, - }, - ]; - - EXPORTS_FILES = [ - { - name: _('/index.js'), - contents: `var a_module = require('./a_module');\n` + - `var b_module = require('./b_module');\n` + - `var xtra_module = require('./xtra_module');\n` + - `var wildcard_reexports_emitted_helpers = require('./wildcard_reexports_emitted_helpers');\n` + - `var wildcard_reexports_imported_helpers = require('./wildcard_reexports_imported_helpers');\n` + - `var define_property_reexports = require('./define_property_reexports');\n` - }, - { - name: _('/a_module.js'), - contents: `// In TS 3.9 exports are initialized to undefined at the top of the file\n` + - `exports.a = void 0;\n` + - `var a = 'a';\n` + - `exports.a = a;\n`, - }, - { - name: _('/b_module.js'), - contents: `var core = require('@angular/core');\n` + - `var a_module = require('./a_module');\n` + - `var b = a_module.a;\n` + - `var e = 'e';\n` + - `var SomeClass = (function() {\n` + - ` function SomeClass() {}\n` + - ` return SomeClass;\n` + - `}());\n` + - `\n` + - `exports.Directive = core.Directive;\n` + - `exports.a = a_module.a;\n` + - `exports.b = b;\n` + - `exports.c = a_module.a;\n` + - `exports.d = b;\n` + - `exports.e = e;\n` + - `exports.DirectiveX = core.Directive;\n` + - `exports.SomeClass = SomeClass;\n`, - }, - { - name: _('/xtra_module.js'), - contents: `var xtra1 = 'xtra1';\n` + - `var xtra2 = 'xtra2';\n` + - `exports.xtra1 = xtra1;\n` + - `exports.xtra2 = xtra2;\n`, - }, - { - name: _('/wildcard_reexports_emitted_helpers.js'), - contents: `function __export(m) {\n` + - ` for (var p in m) if (!exports.hasOwnProperty(p)) exports[p] = m[p];\n` + - `}\n` + - `var b_module = require("./b_module");\n` + - `__export(b_module);\n` + - `__export(require("./xtra_module"));\n`, - }, - { - name: _('/wildcard_reexports_imported_helpers.js'), - contents: `var tslib_1 = require("tslib");\n` + - `var b_module = require("./b_module");\n` + - `tslib_1.__exportStar(b_module, exports);\n` + - `tslib_1.__exportStar(require("./xtra_module"), exports);\n`, - }, - { - name: _('/define_property_reexports.js'), - contents: `var moduleA = require("./a_module");\n` + - `Object.defineProperty(exports, "newA", { enumerable: true, get: function () { return moduleA.a; } });`, - } - ]; - - FUNCTION_BODY_FILE = { - name: _('/function_body.js'), - contents: ` -function foo(x) { - return x; -} -function bar(x, y) { - if (y === void 0) { y = 42; } - return x + y; -} -function complex() { - var x = 42; - return 42; -} -function baz(x) { - var y; - if (x === void 0) { y = 42; } - return y; -} -var y; -function qux(x) { - if (x === void 0) { y = 42; } - return y; -} -function moo() { - var x; - if (x === void 0) { x = 42; } - return x; -} -var x; -function juu() { - if (x === void 0) { x = 42; } - return x; -} -` - }; - - DECORATED_FILES = [ - { - name: _('/primary.js'), - contents: ` -var core = require('@angular/core'); -var secondary = require('./secondary'); -var A = (function() { - function A() {} - A.decorators = [ - { type: core.Directive, args: [{ selector: '[a]' }] } - ]; - return A; -}()); - var B = (function() { - function B() {} - B.decorators = [ - { type: core.Directive, args: [{ selector: '[b]' }] } - ]; - return B; -}()); - function x() {} - function y() {} - var C = (function() { - function C() {} - return C; -}); -exports.A = A; -exports.x = x; -exports.C = C; -` - }, - { - name: _('/secondary.js'), - contents: ` -var core = require('@angular/core'); -var D = (function() { - function D() {} - D.decorators = [ - { type: core.Directive, args: [{ selector: '[d]' }] } - ]; - return D; -}()); -exports.D = D; - ` - } - ]; - - TYPINGS_SRC_FILES = [ - { - name: _('/ep/src/index.js'), - contents: ` -var internal = require('./internal'); -var class1 = require('./class1'); -var class2 = require('./class2'); -var missing = require('./missing-class'); -var flatFile = require('./flat-file'); -var func1 = require('./func1'); -function __export(m) { - for (var p in m) if (!exports.hasOwnProperty(p)) exports[p] = m[p]; -} -var InternalClass = internal.InternalClass; -__export(class1); -__export(class2); -` - }, - { - name: _('/ep/src/class1.js'), - contents: ` -var Class1 = (function() { - function Class1() {} - return Class1; -}()); -var MissingClass1 = (function() { - function MissingClass1() {} - return MissingClass1; -}()); -exports.Class1 = Class1; -exports.MissingClass1 = MissingClass1; -` - }, - { - name: _('/ep/src/class2.js'), - contents: ` -var Class2 = (function() { - function Class2() {} - return Class2; -}()); -exports.Class2 = Class2; -` - }, - {name: _('/ep/src/func1.js'), contents: 'function mooFn() {} export {mooFn}'}, { - name: _('/ep/src/internal.js'), - contents: ` -var InternalClass = (function() { - function InternalClass() {} - return InternalClass; -}()); -var Class2 = (function() { - function Class2() {} - return Class2; -}()); -exports.InternalClass =InternalClass; -exports.Class2 = Class2; -` - }, - { - name: _('/ep/src/missing-class.js'), - contents: ` -var MissingClass2 = (function() { - function MissingClass2() {} - return MissingClass2; -}()); -exports. MissingClass2 = MissingClass2; -` - }, - { - name: _('/ep/src/flat-file.js'), - contents: ` -var Class1 = (function() { - function Class1() {} - return Class1; -}()); -var MissingClass1 = (function() { - function MissingClass1() {} - return MissingClass1; -}()); -var MissingClass2 = (function() { - function MissingClass2() {} - return MissingClass2; -}()); -var SourceClass = (function() { - function SourceClass() {} - return SourceClass; -}()); -exports.Class1 = Class1; -exports.AliasedClass = SourceClass; -exports.MissingClass1 = MissingClass1; -exports.MissingClass2 = MissingClass2; -` - } - ]; - - TYPINGS_DTS_FILES = [ - { - name: _('/ep/typings/index.d.ts'), - contents: ` - import '../../an_external_lib/index'; - import {InternalClass} from './internal'; - import {mooFn} from './func1'; - export * from './class1'; - export * from './class2'; - ` - }, - { - name: _('/ep/typings/class1.d.ts'), - contents: `export declare class Class1 {}\nexport declare class OtherClass {}` - }, - { - name: _('/ep/typings/class2.d.ts'), - contents: ` - export declare class Class2 {} - export declare interface SomeInterface {} - export {TypingsClass as AliasedClass} from './typings-class'; - ` - }, - {name: _('/ep/typings/func1.d.ts'), contents: 'export declare function mooFn(): void;'}, - { - name: _('/ep/typings/internal.d.ts'), - contents: `export declare class InternalClass {}\nexport declare class Class2 {}` - }, - { - name: _('/ep/typings/typings-class.d.ts'), - contents: `export declare class TypingsClass {}` - }, - {name: _('/ep/typings/shadow-class.d.ts'), contents: `export declare class ShadowClass {}`}, - {name: _('/an_external_lib/index.d.ts'), contents: 'export declare class ShadowClass {}'}, - ]; - }); - - describe('CommonJsReflectionHost', () => { - describe('getDecoratorsOfDeclaration()', () => { - it('should find the decorators on a class', () => { - loadTestFiles([SOME_DIRECTIVE_FILE]); - const bundle = makeTestBundleProgram(SOME_DIRECTIVE_FILE.name); - const host = - createHost(bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, SOME_DIRECTIVE_FILE.name, 'SomeDirective', - isNamedVariableDeclaration); - const decorators = host.getDecoratorsOfDeclaration(classNode)!; - - expect(decorators).toBeDefined(); - expect(decorators.length).toEqual(1); - - const decorator = decorators[0]; - expect(decorator.name).toEqual('Directive'); - expect(decorator.import).toEqual({name: 'Directive', from: '@angular/core'}); - expect(decorator.args!.map(arg => arg.getText())).toEqual([ - '{ selector: \'[someDirective]\' }', - ]); - }); - - it('should find the decorators on a class at the top level', () => { - loadTestFiles([TOPLEVEL_DECORATORS_FILE]); - const bundle = makeTestBundleProgram(TOPLEVEL_DECORATORS_FILE.name); - const host = - createHost(bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, TOPLEVEL_DECORATORS_FILE.name, 'SomeDirective', - isNamedVariableDeclaration); - const decorators = host.getDecoratorsOfDeclaration(classNode)!; - - expect(decorators).toBeDefined(); - expect(decorators.length).toEqual(1); - - const decorator = decorators[0]; - expect(decorator.name).toEqual('Directive'); - expect(decorator.import).toEqual({name: 'Directive', from: '@angular/core'}); - expect(decorator.args!.map(arg => arg.getText())).toEqual([ - '{ selector: \'[someDirective]\' }', - ]); - }); - - it('should return null if the symbol is not a class', () => { - loadTestFiles([FOO_FUNCTION_FILE]); - const bundle = makeTestBundleProgram(FOO_FUNCTION_FILE.name); - const host = - createHost(bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle)); - const functionNode = getDeclaration( - bundle.program, FOO_FUNCTION_FILE.name, 'foo', isNamedFunctionDeclaration); - const decorators = host.getDecoratorsOfDeclaration(functionNode); - expect(decorators).toBe(null); - }); - - it('should return null if there are no decorators', () => { - loadTestFiles([SIMPLE_CLASS_FILE]); - const bundle = makeTestBundleProgram(SIMPLE_CLASS_FILE.name); - const host = - createHost(bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, SIMPLE_CLASS_FILE.name, 'EmptyClass', isNamedVariableDeclaration); - const decorators = host.getDecoratorsOfDeclaration(classNode); - expect(decorators).toBe(null); - }); - - it('should ignore `decorators` if it is not an array literal', () => { - loadTestFiles([INVALID_DECORATORS_FILE]); - const bundle = makeTestBundleProgram(INVALID_DECORATORS_FILE.name); - const host = - createHost(bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, INVALID_DECORATORS_FILE.name, 'NotArrayLiteral', - isNamedVariableDeclaration); - const decorators = host.getDecoratorsOfDeclaration(classNode); - expect(decorators).toEqual([]); - }); - - it('should ignore decorator elements that are not object literals', () => { - loadTestFiles([INVALID_DECORATORS_FILE]); - const bundle = makeTestBundleProgram(INVALID_DECORATORS_FILE.name); - const host = - createHost(bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, INVALID_DECORATORS_FILE.name, 'NotObjectLiteral', - isNamedVariableDeclaration); - const decorators = host.getDecoratorsOfDeclaration(classNode)!; - - expect(decorators.length).toBe(1); - expect(decorators[0]).toEqual(jasmine.objectContaining({name: 'Directive'})); - }); - - it('should ignore decorator elements that have no `type` property', () => { - loadTestFiles([INVALID_DECORATORS_FILE]); - const bundle = makeTestBundleProgram(INVALID_DECORATORS_FILE.name); - const host = - createHost(bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, INVALID_DECORATORS_FILE.name, 'NoTypeProperty', - isNamedVariableDeclaration); - const decorators = host.getDecoratorsOfDeclaration(classNode)!; - - expect(decorators.length).toBe(1); - expect(decorators[0]).toEqual(jasmine.objectContaining({name: 'Directive'})); - }); - - it('should ignore decorator elements whose `type` value is not an identifier', () => { - loadTestFiles([INVALID_DECORATORS_FILE]); - const bundle = makeTestBundleProgram(INVALID_DECORATORS_FILE.name); - const host = - createHost(bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, INVALID_DECORATORS_FILE.name, 'NotIdentifier', - isNamedVariableDeclaration); - const decorators = host.getDecoratorsOfDeclaration(classNode)!; - - expect(decorators.length).toBe(1); - expect(decorators[0]).toEqual(jasmine.objectContaining({name: 'Directive'})); - }); - - describe('(returned decorators `args`)', () => { - it('should be an empty array if decorator has no `args` property', () => { - loadTestFiles([INVALID_DECORATOR_ARGS_FILE]); - const bundle = makeTestBundleProgram(INVALID_DECORATOR_ARGS_FILE.name); - const host = - createHost(bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, INVALID_DECORATOR_ARGS_FILE.name, 'NoArgsProperty', - isNamedVariableDeclaration); - const decorators = host.getDecoratorsOfDeclaration(classNode)!; - - expect(decorators.length).toBe(1); - expect(decorators[0].name).toBe('Directive'); - expect(decorators[0].args).toEqual([]); - }); - - it('should be an empty array if decorator\'s `args` has no property assignment', () => { - loadTestFiles([INVALID_DECORATOR_ARGS_FILE]); - const bundle = makeTestBundleProgram(INVALID_DECORATOR_ARGS_FILE.name); - const host = - createHost(bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, INVALID_DECORATOR_ARGS_FILE.name, 'NoPropertyAssignment', - isNamedVariableDeclaration); - const decorators = host.getDecoratorsOfDeclaration(classNode)!; - - expect(decorators.length).toBe(1); - expect(decorators[0].name).toBe('Directive'); - expect(decorators[0].args).toEqual([]); - }); - - it('should be an empty array if `args` property value is not an array literal', () => { - loadTestFiles([INVALID_DECORATOR_ARGS_FILE]); - const bundle = makeTestBundleProgram(INVALID_DECORATOR_ARGS_FILE.name); - const host = - createHost(bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, INVALID_DECORATOR_ARGS_FILE.name, 'NotArrayLiteral', - isNamedVariableDeclaration); - const decorators = host.getDecoratorsOfDeclaration(classNode)!; - - expect(decorators.length).toBe(1); - expect(decorators[0].name).toBe('Directive'); - expect(decorators[0].args).toEqual([]); - }); - }); - }); - - describe('getMembersOfClass()', () => { - it('should find decorated members on a class', () => { - loadTestFiles([SOME_DIRECTIVE_FILE]); - const bundle = makeTestBundleProgram(SOME_DIRECTIVE_FILE.name); - const host = - createHost(bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, SOME_DIRECTIVE_FILE.name, 'SomeDirective', - isNamedVariableDeclaration); - const members = host.getMembersOfClass(classNode); - - const input1 = members.find(member => member.name === 'input1')!; - expect(input1.kind).toEqual(ClassMemberKind.Property); - expect(input1.isStatic).toEqual(false); - expect(input1.decorators!.map(d => d.name)).toEqual(['Input']); - - const input2 = members.find(member => member.name === 'input2')!; - expect(input2.kind).toEqual(ClassMemberKind.Property); - expect(input2.isStatic).toEqual(false); - expect(input1.decorators!.map(d => d.name)).toEqual(['Input']); - }); - - it('should find decorated members on a class at the top level', () => { - loadTestFiles([TOPLEVEL_DECORATORS_FILE]); - const bundle = makeTestBundleProgram(TOPLEVEL_DECORATORS_FILE.name); - const host = - createHost(bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, TOPLEVEL_DECORATORS_FILE.name, 'SomeDirective', - isNamedVariableDeclaration); - const members = host.getMembersOfClass(classNode); - - const input1 = members.find(member => member.name === 'input1')!; - expect(input1.kind).toEqual(ClassMemberKind.Property); - expect(input1.isStatic).toEqual(false); - expect(input1.decorators!.map(d => d.name)).toEqual(['Input']); - - const input2 = members.find(member => member.name === 'input2')!; - expect(input2.kind).toEqual(ClassMemberKind.Property); - expect(input2.isStatic).toEqual(false); - expect(input1.decorators!.map(d => d.name)).toEqual(['Input']); - }); - - it('should find non decorated properties on a class', () => { - loadTestFiles([SOME_DIRECTIVE_FILE]); - const bundle = makeTestBundleProgram(SOME_DIRECTIVE_FILE.name); - const host = - createHost(bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, SOME_DIRECTIVE_FILE.name, 'SomeDirective', - isNamedVariableDeclaration); - const members = host.getMembersOfClass(classNode); - - const instanceProperty = members.find(member => member.name === 'instanceProperty')!; - expect(instanceProperty.kind).toEqual(ClassMemberKind.Property); - expect(instanceProperty.isStatic).toEqual(false); - expect(ts.isBinaryExpression(instanceProperty.implementation!)).toEqual(true); - expect(instanceProperty.value!.getText()).toEqual(`'instance'`); - }); - - it('should find static methods on a class', () => { - loadTestFiles([SOME_DIRECTIVE_FILE]); - const bundle = makeTestBundleProgram(SOME_DIRECTIVE_FILE.name); - const host = - createHost(bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, SOME_DIRECTIVE_FILE.name, 'SomeDirective', - isNamedVariableDeclaration); - const members = host.getMembersOfClass(classNode); - - const staticMethod = members.find(member => member.name === 'staticMethod')!; - expect(staticMethod.kind).toEqual(ClassMemberKind.Method); - expect(staticMethod.isStatic).toEqual(true); - expect(ts.isFunctionExpression(staticMethod.implementation!)).toEqual(true); - }); - - it('should find static properties on a class', () => { - loadTestFiles([SOME_DIRECTIVE_FILE]); - const bundle = makeTestBundleProgram(SOME_DIRECTIVE_FILE.name); - const host = - createHost(bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, SOME_DIRECTIVE_FILE.name, 'SomeDirective', - isNamedVariableDeclaration); - const members = host.getMembersOfClass(classNode); - - const staticProperty = members.find(member => member.name === 'staticProperty')!; - expect(staticProperty.kind).toEqual(ClassMemberKind.Property); - expect(staticProperty.isStatic).toEqual(true); - expect(ts.isPropertyAccessExpression(staticProperty.implementation!)).toEqual(true); - expect(staticProperty.value!.getText()).toEqual(`'static'`); - }); - - it('should throw if the symbol is not a class', () => { - loadTestFiles([FOO_FUNCTION_FILE]); - const bundle = makeTestBundleProgram(FOO_FUNCTION_FILE.name); - const host = - createHost(bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle)); - const functionNode = getDeclaration( - bundle.program, FOO_FUNCTION_FILE.name, 'foo', isNamedFunctionDeclaration); - expect(() => { - host.getMembersOfClass(functionNode); - }).toThrowError(`Attempted to get members of a non-class: "function foo() {}"`); - }); - - it('should return an empty array if there are no prop decorators', () => { - loadTestFiles([SIMPLE_CLASS_FILE]); - const bundle = makeTestBundleProgram(SIMPLE_CLASS_FILE.name); - const host = - createHost(bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, SIMPLE_CLASS_FILE.name, 'EmptyClass', isNamedVariableDeclaration); - const members = host.getMembersOfClass(classNode); - - expect(members).toEqual([]); - }); - - it('should not process decorated properties in `propDecorators` if it is not an object literal', - () => { - loadTestFiles([INVALID_PROP_DECORATORS_FILE]); - const bundle = makeTestBundleProgram(INVALID_PROP_DECORATORS_FILE.name); - const host = - createHost(bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, INVALID_PROP_DECORATORS_FILE.name, 'NotObjectLiteral', - isNamedVariableDeclaration); - const members = host.getMembersOfClass(classNode); - - expect(members.map(member => member.name)).not.toContain('prop'); - }); - - it('should ignore prop decorator elements that are not object literals', () => { - loadTestFiles([INVALID_PROP_DECORATORS_FILE]); - const bundle = makeTestBundleProgram(INVALID_PROP_DECORATORS_FILE.name); - const host = - createHost(bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, INVALID_PROP_DECORATORS_FILE.name, 'NotObjectLiteralProp', - isNamedVariableDeclaration); - const members = host.getMembersOfClass(classNode); - const prop = members.find(m => m.name === 'prop')!; - const decorators = prop.decorators!; - - expect(decorators.length).toBe(1); - expect(decorators[0]).toEqual(jasmine.objectContaining({name: 'Directive'})); - }); - - it('should ignore prop decorator elements that have no `type` property', () => { - loadTestFiles([INVALID_PROP_DECORATORS_FILE]); - const bundle = makeTestBundleProgram(INVALID_PROP_DECORATORS_FILE.name); - const host = - createHost(bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, INVALID_PROP_DECORATORS_FILE.name, 'NoTypeProperty', - isNamedVariableDeclaration); - const members = host.getMembersOfClass(classNode); - const prop = members.find(m => m.name === 'prop')!; - const decorators = prop.decorators!; - - expect(decorators.length).toBe(1); - expect(decorators[0]).toEqual(jasmine.objectContaining({name: 'Directive'})); - }); - - it('should ignore prop decorator elements whose `type` value is not an identifier', () => { - loadTestFiles([INVALID_PROP_DECORATORS_FILE]); - const bundle = makeTestBundleProgram(INVALID_PROP_DECORATORS_FILE.name); - const host = - createHost(bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, INVALID_PROP_DECORATORS_FILE.name, 'NotIdentifier', - isNamedVariableDeclaration); - const members = host.getMembersOfClass(classNode); - const prop = members.find(m => m.name === 'prop')!; - const decorators = prop.decorators!; - - expect(decorators.length).toBe(1); - expect(decorators[0]).toEqual(jasmine.objectContaining({name: 'Directive'})); - }); - - it('should have import information on decorators', () => { - loadTestFiles([SOME_DIRECTIVE_FILE]); - const bundle = makeTestBundleProgram(SOME_DIRECTIVE_FILE.name); - const host = - createHost(bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle)); - - const classNode = getDeclaration( - bundle.program, SOME_DIRECTIVE_FILE.name, 'SomeDirective', - isNamedVariableDeclaration); - const decorators = host.getDecoratorsOfDeclaration(classNode)!; - - expect(decorators.length).toEqual(1); - expect(decorators[0].import).toEqual({name: 'Directive', from: '@angular/core'}); - }); - - describe('(returned prop decorators `args`)', () => { - it('should be an empty array if prop decorator has no `args` property', () => { - loadTestFiles([INVALID_PROP_DECORATOR_ARGS_FILE]); - const bundle = makeTestBundleProgram(INVALID_PROP_DECORATOR_ARGS_FILE.name); - const host = - createHost(bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, INVALID_PROP_DECORATOR_ARGS_FILE.name, 'NoArgsProperty', - isNamedVariableDeclaration); - const members = host.getMembersOfClass(classNode); - const prop = members.find(m => m.name === 'prop')!; - const decorators = prop.decorators!; - - expect(decorators.length).toBe(1); - expect(decorators[0].name).toBe('Input'); - expect(decorators[0].args).toEqual([]); - }); - - it('should be an empty array if prop decorator\'s `args` has no property assignment', - () => { - loadTestFiles([INVALID_PROP_DECORATOR_ARGS_FILE]); - const bundle = makeTestBundleProgram(INVALID_PROP_DECORATOR_ARGS_FILE.name); - const host = - createHost(bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, INVALID_PROP_DECORATOR_ARGS_FILE.name, 'NoPropertyAssignment', - isNamedVariableDeclaration); - const members = host.getMembersOfClass(classNode); - const prop = members.find(m => m.name === 'prop')!; - const decorators = prop.decorators!; - - expect(decorators.length).toBe(1); - expect(decorators[0].name).toBe('Input'); - expect(decorators[0].args).toEqual([]); - }); - - it('should be an empty array if `args` property value is not an array literal', () => { - loadTestFiles([INVALID_PROP_DECORATOR_ARGS_FILE]); - const bundle = makeTestBundleProgram(INVALID_PROP_DECORATOR_ARGS_FILE.name); - const host = - createHost(bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, INVALID_PROP_DECORATOR_ARGS_FILE.name, 'NotArrayLiteral', - isNamedVariableDeclaration); - const members = host.getMembersOfClass(classNode); - const prop = members.find(m => m.name === 'prop')!; - const decorators = prop.decorators!; - - expect(decorators.length).toBe(1); - expect(decorators[0].name).toBe('Input'); - expect(decorators[0].args).toEqual([]); - }); - }); - }); - - describe('getConstructorParameters', () => { - it('should retain imported name for type value references for decorated constructor parameter types', - () => { - const files = [ - { - name: _('/node_modules/shared-lib/foo.d.ts'), - contents: ` - declare class Foo {} - export {Foo as Bar}; - `, - }, - { - name: _('/node_modules/shared-lib/index.d.ts'), - contents: ` - export {Bar as Baz} from './foo'; - `, - }, - { - name: _('/local.js'), - contents: ` - var Internal = (function() { - function Internal() { - } - return Internal; - }()); - exports.External = Internal; - ` - }, - { - name: _('/main.js'), - contents: ` - var shared = require('shared-lib'); - var local = require('./local'); - var SameFile = (function() { - function SameFile() { - } - return SameFile; - }()); - exports.SameFile = SameFile; - - var SomeClass = (function() { - function SomeClass(arg1, arg2, arg3) {} - return SomeClass; - }()); - SomeClass.ctorParameters = function() { return [{ type: shared.Baz }, { type: local.External }, { type: SameFile }]; }; - exports.SomeClass = SomeClass; - `, - }, - ]; - - loadTestFiles(files); - const bundle = makeTestBundleProgram(_('/main.js')); - const host = - createHost(bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, _('/main.js'), 'SomeClass', isNamedVariableDeclaration); - - const parameters = host.getConstructorParameters(classNode)!; - - expect(parameters.map(p => p.name)).toEqual(['arg1', 'arg2', 'arg3']); - expectTypeValueReferencesForParameters( - parameters, ['Baz', 'External', 'SameFile'], ['shared-lib', './local', null]); - }); - - it('should find the decorated constructor parameters', () => { - loadTestFiles([SOME_DIRECTIVE_FILE]); - const bundle = makeTestBundleProgram(SOME_DIRECTIVE_FILE.name); - const host = - createHost(bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, SOME_DIRECTIVE_FILE.name, 'SomeDirective', - isNamedVariableDeclaration); - const parameters = host.getConstructorParameters(classNode); - - expect(parameters).toBeDefined(); - expect(parameters!.map(parameter => parameter.name)).toEqual([ - '_viewContainer', '_template', 'injected' - ]); - expectTypeValueReferencesForParameters(parameters!, [ - 'ViewContainerRef', - 'TemplateRef', - null, - ]); - }); - - it('should find the decorated constructor parameters at the top level', () => { - loadTestFiles([TOPLEVEL_DECORATORS_FILE]); - const bundle = makeTestBundleProgram(TOPLEVEL_DECORATORS_FILE.name); - const host = - createHost(bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, TOPLEVEL_DECORATORS_FILE.name, 'SomeDirective', - isNamedVariableDeclaration); - const parameters = host.getConstructorParameters(classNode); - - expect(parameters).toBeDefined(); - expect(parameters!.map(parameter => parameter.name)).toEqual([ - '_viewContainer', '_template', 'injected' - ]); - expectTypeValueReferencesForParameters(parameters!, [ - 'ViewContainerRef', - 'TemplateRef', - null, - ]); - }); - - it('should accept `ctorParameters` as an array', () => { - loadTestFiles([CTOR_DECORATORS_ARRAY_FILE]); - const bundle = makeTestBundleProgram(CTOR_DECORATORS_ARRAY_FILE.name); - const host = - createHost(bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, CTOR_DECORATORS_ARRAY_FILE.name, 'CtorDecoratedAsArray', - isNamedVariableDeclaration); - const parameters = host.getConstructorParameters(classNode)!; - - expect(parameters).toBeDefined(); - expect(parameters.map(parameter => parameter.name)).toEqual(['arg1']); - expectTypeValueReferencesForParameters(parameters, ['ParamType']); - }); - - it('should throw if the symbol is not a class', () => { - loadTestFiles([FOO_FUNCTION_FILE]); - const bundle = makeTestBundleProgram(FOO_FUNCTION_FILE.name); - const host = - createHost(bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle)); - const functionNode = getDeclaration( - bundle.program, FOO_FUNCTION_FILE.name, 'foo', isNamedFunctionDeclaration); - expect(() => { - host.getConstructorParameters(functionNode); - }) - .toThrowError( - 'Attempted to get constructor parameters of a non-class: "function foo() {}"'); - }); - - // In ES5 there is no such thing as a constructor-less class - // it('should return `null` if there is no constructor', () => { }); - - it('should return an array even if there are no decorators', () => { - loadTestFiles([SIMPLE_CLASS_FILE]); - const bundle = makeTestBundleProgram(SIMPLE_CLASS_FILE.name); - const host = - createHost(bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, SIMPLE_CLASS_FILE.name, 'NoDecoratorConstructorClass', - isNamedVariableDeclaration); - const parameters = host.getConstructorParameters(classNode); - - expect(parameters).toEqual(jasmine.any(Array)); - expect(parameters!.length).toEqual(1); - expect(parameters![0].name).toEqual('foo'); - expect(parameters![0].decorators).toBe(null); - }); - - it('should return an empty array if there are no constructor parameters', () => { - loadTestFiles([INVALID_CTOR_DECORATORS_FILE]); - const bundle = makeTestBundleProgram(INVALID_CTOR_DECORATORS_FILE.name); - const host = - createHost(bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, INVALID_CTOR_DECORATORS_FILE.name, 'NoParameters', - isNamedVariableDeclaration); - const parameters = host.getConstructorParameters(classNode); - - expect(parameters).toEqual([]); - }); - - // In ES5 there are no arrow functions - // it('should ignore `ctorParameters` if it is an arrow function', () => { }); - - it('should ignore `ctorParameters` if it does not return an array literal', () => { - loadTestFiles([INVALID_CTOR_DECORATORS_FILE]); - const bundle = makeTestBundleProgram(INVALID_CTOR_DECORATORS_FILE.name); - const host = - createHost(bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, INVALID_CTOR_DECORATORS_FILE.name, 'NotArrayLiteral', - isNamedVariableDeclaration); - const parameters = host.getConstructorParameters(classNode); - - expect(parameters!.length).toBe(1); - expect(parameters![0]).toEqual(jasmine.objectContaining({ - name: 'arg1', - decorators: null, - })); - }); - - describe('(returned parameters `decorators`)', () => { - it('should ignore param decorator elements that are not object literals', () => { - loadTestFiles([INVALID_CTOR_DECORATORS_FILE]); - const bundle = makeTestBundleProgram(INVALID_CTOR_DECORATORS_FILE.name); - const host = - createHost(bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, INVALID_CTOR_DECORATORS_FILE.name, 'NotObjectLiteral', - isNamedVariableDeclaration); - const parameters = host.getConstructorParameters(classNode); - - expect(parameters!.length).toBe(2); - expect(parameters![0]).toEqual(jasmine.objectContaining({ - name: 'arg1', - decorators: null, - })); - expect(parameters![1]).toEqual(jasmine.objectContaining({ - name: 'arg2', - decorators: jasmine.any(Array) as any - })); - }); - - it('should ignore param decorator elements that have no `type` property', () => { - loadTestFiles([INVALID_CTOR_DECORATORS_FILE]); - const bundle = makeTestBundleProgram(INVALID_CTOR_DECORATORS_FILE.name); - const host = - createHost(bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, INVALID_CTOR_DECORATORS_FILE.name, 'NoTypeProperty', - isNamedVariableDeclaration); - const parameters = host.getConstructorParameters(classNode); - const decorators = parameters![0].decorators!; - - expect(decorators.length).toBe(1); - expect(decorators[0]).toEqual(jasmine.objectContaining({name: 'Inject'})); - }); - - it('should ignore param decorator elements whose `type` value is not an identifier', - () => { - loadTestFiles([INVALID_CTOR_DECORATORS_FILE]); - const bundle = makeTestBundleProgram(INVALID_CTOR_DECORATORS_FILE.name); - const host = - createHost(bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, INVALID_CTOR_DECORATORS_FILE.name, 'NotIdentifier', - isNamedVariableDeclaration); - const parameters = host.getConstructorParameters(classNode); - const decorators = parameters![0].decorators!; - - expect(decorators.length).toBe(1); - expect(decorators[0]).toEqual(jasmine.objectContaining({name: 'Inject'})); - }); - - it('should have import information on decorators', () => { - loadTestFiles([SOME_DIRECTIVE_FILE]); - const bundle = makeTestBundleProgram(SOME_DIRECTIVE_FILE.name); - const host = - createHost(bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, SOME_DIRECTIVE_FILE.name, 'SomeDirective', - isNamedVariableDeclaration); - - const parameters = host.getConstructorParameters(classNode); - const decorators = parameters![2].decorators!; - - expect(decorators.length).toEqual(1); - expect(decorators[0].name).toBe('Inject'); - expect(decorators[0].import).toEqual({name: 'Inject', from: '@angular/core'}); - }); - }); - - describe('(returned parameters `decorators.args`)', () => { - it('should be an empty array if param decorator has no `args` property', () => { - loadTestFiles([INVALID_CTOR_DECORATOR_ARGS_FILE]); - const bundle = makeTestBundleProgram(INVALID_CTOR_DECORATOR_ARGS_FILE.name); - const host = - createHost(bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, INVALID_CTOR_DECORATOR_ARGS_FILE.name, 'NoArgsProperty', - isNamedVariableDeclaration); - const parameters = host.getConstructorParameters(classNode); - expect(parameters!.length).toBe(1); - const decorators = parameters![0].decorators!; - - expect(decorators.length).toBe(1); - expect(decorators[0].name).toBe('Inject'); - expect(decorators[0].args).toEqual([]); - }); - - it('should be an empty array if param decorator\'s `args` has no property assignment', - () => { - loadTestFiles([INVALID_CTOR_DECORATOR_ARGS_FILE]); - const bundle = makeTestBundleProgram(INVALID_CTOR_DECORATOR_ARGS_FILE.name); - const host = - createHost(bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, INVALID_CTOR_DECORATOR_ARGS_FILE.name, 'NoPropertyAssignment', - isNamedVariableDeclaration); - const parameters = host.getConstructorParameters(classNode); - const decorators = parameters![0].decorators!; - - expect(decorators.length).toBe(1); - expect(decorators[0].name).toBe('Inject'); - expect(decorators[0].args).toEqual([]); - }); - - it('should be an empty array if `args` property value is not an array literal', () => { - loadTestFiles([INVALID_CTOR_DECORATOR_ARGS_FILE]); - const bundle = makeTestBundleProgram(INVALID_CTOR_DECORATOR_ARGS_FILE.name); - const host = - createHost(bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, INVALID_CTOR_DECORATOR_ARGS_FILE.name, 'NotArrayLiteral', - isNamedVariableDeclaration); - const parameters = host.getConstructorParameters(classNode); - const decorators = parameters![0].decorators!; - - expect(decorators.length).toBe(1); - expect(decorators[0].name).toBe('Inject'); - expect(decorators[0].args).toEqual([]); - }); - }); - - function getConstructorParameters( - constructor: string, mode?: 'inlined'|'inlined_with_suffix'|'imported') { - let fileHeader = ''; - - switch (mode) { - case 'imported': - fileHeader = `const tslib = require('tslib');`; - break; - case 'inlined': - fileHeader = - `var __spread = (this && this.__spread) || function (...args) { /* ... */ };\n` + - `var __spreadArray = (this && this.__spreadArray) || function (...args) { /* ... */ };\n` + - `var __read = (this && this.__read) || function (...args) { /* ... */ };\n`; - break; - case 'inlined_with_suffix': - fileHeader = - `var __spread$1 = (this && this.__spread$1) || function (...args) { /* ... */ };\n` + - `var __spreadArray$1 = (this && this.__spreadArray$1) || function (...args) { /* ... */ };\n` + - `var __read$2 = (this && this.__read$2) || function (...args) { /* ... */ };\n`; - break; - } - const file = { - name: _('/synthesized_constructors.js'), - contents: ` - ${fileHeader} - - var TestClass = /** @class */ (function (_super) { - __extends(TestClass, _super); - ${constructor} - return TestClass; - }(null)); - - exports.TestClass = TestClass;`, - }; - - loadTestFiles([file]); - const bundle = makeTestBundleProgram(file.name); - const host = - createHost(bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle)); - const classNode = - getDeclaration(bundle.program, file.name, 'TestClass', isNamedVariableDeclaration); - return host.getConstructorParameters(classNode); - } - - describe('TS -> ES5: synthesized constructors', () => { - it('recognizes _this assignment from super call', () => { - const parameters = getConstructorParameters(` - function TestClass() { - var _this = _super !== null && _super.apply(this, arguments) || this; - _this.synthesizedProperty = null; - return _this; - } - `); - - expect(parameters).toBeNull(); - }); - - it('recognizes super call as return statement', () => { - const parameters = getConstructorParameters(` - function TestClass() { - return _super !== null && _super.apply(this, arguments) || this; - } - `); - - expect(parameters).toBeNull(); - }); - - it('handles the case where a unique name was generated for _super or _this', () => { - const parameters = getConstructorParameters(` - function TestClass() { - var _this_1 = _super_1 !== null && _super_1.apply(this, arguments) || this; - _this_1._this = null; - _this_1._super = null; - return _this_1; - } - `); - - expect(parameters).toBeNull(); - }); - - it('does not consider constructors with parameters as synthesized', () => { - const parameters = getConstructorParameters(` - function TestClass(arg) { - return _super !== null && _super.apply(this, arguments) || this; - } - `); - - expect(parameters!.length).toBe(1); - }); - - it('does not consider manual super calls as synthesized', () => { - const parameters = getConstructorParameters(` - function TestClass() { - return _super.call(this) || this; - } - `); - - expect(parameters!.length).toBe(0); - }); - - it('does not consider empty constructors as synthesized', () => { - const parameters = getConstructorParameters(`function TestClass() {}`); - expect(parameters!.length).toBe(0); - }); - }); - - // See: https://github.com/angular/angular/issues/38453. - describe('ES2015 -> ES5: synthesized constructors through TSC downleveling', () => { - it('recognizes delegate super call using inline spread helper', () => { - const parameters = getConstructorParameters( - ` - function TestClass() { - return _super.apply(this, __spread(arguments)) || this; - }`, - 'inlined'); - - expect(parameters).toBeNull(); - }); - - it('recognizes delegate super call using inline spreadArray helper', () => { - const parameters = getConstructorParameters( - ` - function TestClass() { - return _super.apply(this, __spreadArray([], __read(arguments))) || this; - }`, - 'inlined'); - - expect(parameters).toBeNull(); - }); - - it('recognizes delegate super call using inline spread helper with suffix', () => { - const parameters = getConstructorParameters( - ` - function TestClass() { - return _super.apply(this, __spread$1(arguments)) || this; - }`, - 'inlined_with_suffix'); - - expect(parameters).toBeNull(); - }); - - it('recognizes delegate super call using inline spreadArray helper with suffix', () => { - const parameters = getConstructorParameters( - ` - function TestClass() { - return _super.apply(this, __spreadArray$1([], __read$2(arguments))) || this; - }`, - 'inlined_with_suffix'); - - expect(parameters).toBeNull(); - }); - - it('recognizes delegate super call using imported spread helper', () => { - const parameters = getConstructorParameters( - ` - function TestClass() { - return _super.apply(this, tslib.__spread(arguments)) || this; - }`, - 'imported'); - - expect(parameters).toBeNull(); - }); - - it('recognizes delegate super call using imported spreadArray helper', () => { - const parameters = getConstructorParameters( - ` - function TestClass() { - return _super.apply(this, tslib.__spreadArray([], tslib.__read(arguments))) || this; - }`, - 'imported'); - - expect(parameters).toBeNull(); - }); - - describe('with class member assignment', () => { - it('recognizes delegate super call using inline spread helper', () => { - const parameters = getConstructorParameters( - ` - function TestClass() { - var _this = _super.apply(this, __spread(arguments)) || this; - _this.synthesizedProperty = null; - return _this; - }`, - 'inlined'); - - expect(parameters).toBeNull(); - }); - - it('recognizes delegate super call using inline spreadArray helper', () => { - const parameters = getConstructorParameters( - ` - function TestClass() { - var _this = _super.apply(this, __spreadArray([], __read(arguments))) || this; - _this.synthesizedProperty = null; - return _this; - }`, - 'inlined'); - - expect(parameters).toBeNull(); - }); - - it('recognizes delegate super call using inline spread helper with suffix', () => { - const parameters = getConstructorParameters( - ` - function TestClass() { - var _this = _super.apply(this, __spread$1(arguments)) || this; - _this.synthesizedProperty = null; - return _this; - }`, - 'inlined_with_suffix'); - - expect(parameters).toBeNull(); - }); - - it('recognizes delegate super call using inline spreadArray helper with suffix', () => { - const parameters = getConstructorParameters( - ` - function TestClass() { - var _this = _super.apply(this, __spreadArray$1([], __read$2(arguments))) || this; - _this.synthesizedProperty = null; - return _this; - }`, - 'inlined_with_suffix'); - - expect(parameters).toBeNull(); - }); - - it('recognizes delegate super call using imported spread helper', () => { - const parameters = getConstructorParameters( - ` - function TestClass() { - var _this = _super.apply(this, tslib.__spread(arguments)) || this; - _this.synthesizedProperty = null; - return _this; - }`, - 'imported'); - - expect(parameters).toBeNull(); - }); - - it('recognizes delegate super call using imported spreadArray helper', () => { - const parameters = getConstructorParameters( - ` - function TestClass() { - var _this = _super.apply(this, tslib.__spreadArray([], tslib.__read(arguments))) || this; - _this.synthesizedProperty = null; - return _this; - }`, - 'imported'); - - expect(parameters).toBeNull(); - }); - }); - - it('handles the case where a unique name was generated for _super or _this', () => { - const parameters = getConstructorParameters( - ` - function TestClass() { - var _this_1 = _super_1.apply(this, __spread(arguments)) || this; - _this_1._this = null; - _this_1._super = null; - return _this_1; - }`, - 'inlined'); - - expect(parameters).toBeNull(); - }); - - it('does not consider constructors with parameters as synthesized', () => { - const parameters = getConstructorParameters( - ` - function TestClass(arg) { - return _super.apply(this, __spread(arguments)) || this; - }`, - 'inlined'); - - expect(parameters!.length).toBe(1); - }); - }); - }); - - describe('getDefinitionOfFunction()', () => { - it('should return an object describing the function declaration passed as an argument', - () => { - loadTestFiles([FUNCTION_BODY_FILE]); - const bundle = makeTestBundleProgram(FUNCTION_BODY_FILE.name); - const host = - createHost(bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle)); - - const fooNode = getDeclaration( - bundle.program, FUNCTION_BODY_FILE.name, 'foo', isNamedFunctionDeclaration)!; - const fooDef = host.getDefinitionOfFunction(fooNode)!; - expect(fooDef.node).toBe(fooNode); - expect(fooDef.body!.length).toEqual(1); - expect(fooDef.body![0].getText()).toEqual(`return x;`); - expect(fooDef.parameters.length).toEqual(1); - expect(fooDef.parameters[0].name).toEqual('x'); - expect(fooDef.parameters[0].initializer).toBe(null); - - const barNode = getDeclaration( - bundle.program, FUNCTION_BODY_FILE.name, 'bar', isNamedFunctionDeclaration)!; - const barDef = host.getDefinitionOfFunction(barNode)!; - expect(barDef.node).toBe(barNode); - expect(barDef.body!.length).toEqual(1); - expect(ts.isReturnStatement(barDef.body![0])).toBeTruthy(); - expect(barDef.body![0].getText()).toEqual(`return x + y;`); - expect(barDef.parameters.length).toEqual(2); - expect(barDef.parameters[0].name).toEqual('x'); - expect(fooDef.parameters[0].initializer).toBe(null); - expect(barDef.parameters[1].name).toEqual('y'); - expect(barDef.parameters[1].initializer!.getText()).toEqual('42'); - - const bazNode = getDeclaration( - bundle.program, FUNCTION_BODY_FILE.name, 'baz', isNamedFunctionDeclaration)!; - const bazDef = host.getDefinitionOfFunction(bazNode)!; - expect(bazDef.node).toBe(bazNode); - expect(bazDef.body!.length).toEqual(3); - expect(bazDef.parameters.length).toEqual(1); - expect(bazDef.parameters[0].name).toEqual('x'); - expect(bazDef.parameters[0].initializer).toBe(null); - - const quxNode = getDeclaration( - bundle.program, FUNCTION_BODY_FILE.name, 'qux', isNamedFunctionDeclaration)!; - const quxDef = host.getDefinitionOfFunction(quxNode)!; - expect(quxDef.node).toBe(quxNode); - expect(quxDef.body!.length).toEqual(2); - expect(quxDef.parameters.length).toEqual(1); - expect(quxDef.parameters[0].name).toEqual('x'); - expect(quxDef.parameters[0].initializer).toBe(null); - }); - }); - - describe('getImportOfIdentifier', () => { - it('should find the import of an identifier', () => { - loadTestFiles(IMPORTS_FILES); - const bundle = makeTestBundleProgram(_('/index.js')); - const host = - createHost(bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle)); - const variableNode = - getDeclaration(bundle.program, _('/file_b.js'), 'b', isNamedVariableDeclaration); - const identifier = (variableNode.initializer && - ts.isPropertyAccessExpression(variableNode.initializer) && - ts.isIdentifier(variableNode.initializer.name)) ? - variableNode.initializer.name : - null; - - expect(identifier).not.toBe(null); - const importOfIdent = host.getImportOfIdentifier(identifier!); - expect(importOfIdent).toEqual({name: 'a', from: './file_a'}); - }); - - it('should return null if the identifier was not imported', () => { - loadTestFiles(IMPORTS_FILES); - const bundle = makeTestBundleProgram(_('/index.js')); - const host = - createHost(bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle)); - const variableNode = - getDeclaration(bundle.program, _('/file_b.js'), 'd', isNamedVariableDeclaration); - const importOfIdent = - host.getImportOfIdentifier(variableNode.initializer as ts.Identifier); - - expect(importOfIdent).toBeNull(); - }); - - it('should handle factory functions not wrapped in parentheses', () => { - loadTestFiles(IMPORTS_FILES); - const bundle = makeTestBundleProgram(_('/index.js')); - const host = - createHost(bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle)); - const variableNode = - getDeclaration(bundle.program, _('/file_c.js'), 'c', isNamedVariableDeclaration); - const identifier = (variableNode.initializer && - ts.isPropertyAccessExpression(variableNode.initializer) && - ts.isIdentifier(variableNode.initializer.name)) ? - variableNode.initializer.name : - null; - - expect(identifier).not.toBe(null); - const importOfIdent = host.getImportOfIdentifier(identifier!); - expect(importOfIdent).toEqual({name: 'a', from: './file_a'}); - }); - }); - - describe('getDeclarationOfIdentifier', () => { - // Helpers - const createTestForTsHelper = - (program: ts.Program, host: NgccReflectionHost, srcFile: TestFile, - getHelperDeclaration: (name: string) => ts.Declaration) => - (varName: string, helperName: string, knownAs: KnownDeclaration, - viaModule: string|null = null) => { - const node = - getDeclaration(program, srcFile.name, varName, ts.isVariableDeclaration); - const helperIdentifier = getIdentifierFromCallExpression(node); - const helperDeclaration = host.getDeclarationOfIdentifier(helperIdentifier); - - expect(helperDeclaration).toEqual({ - kind: DeclarationKind.Concrete, - known: knownAs, - node: getHelperDeclaration(helperName), - viaModule, - identity: null, - }); - }; - - const getIdentifierFromCallExpression = (decl: ts.VariableDeclaration) => { - if (decl.initializer !== undefined && ts.isCallExpression(decl.initializer)) { - const expr = decl.initializer.expression; - if (ts.isIdentifier(expr)) return expr; - if (ts.isPropertyAccessExpression(expr) && ts.isIdentifier(expr.name)) return expr.name; - } - throw new Error(`Unable to extract identifier from declaration '${decl.getText()}'.`); - }; - - it('should return the declaration of a locally defined identifier', () => { - loadTestFiles([SOME_DIRECTIVE_FILE]); - const bundle = makeTestBundleProgram(SOME_DIRECTIVE_FILE.name); - const host = - createHost(bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, SOME_DIRECTIVE_FILE.name, 'SomeDirective', - isNamedVariableDeclaration); - const ctrDecorators = host.getConstructorParameters(classNode)!; - const identifierOfViewContainerRef = (ctrDecorators[0].typeValueReference! as { - kind: TypeValueReferenceKind.LOCAL, - expression: ts.Identifier, - defaultImportStatement: null, - }).expression; - - const expectedDeclarationNode = getDeclaration( - bundle.program, SOME_DIRECTIVE_FILE.name, 'ViewContainerRef', - isNamedVariableDeclaration); - const actualDeclaration = host.getDeclarationOfIdentifier(identifierOfViewContainerRef); - expect(actualDeclaration).not.toBe(null); - expect(actualDeclaration!.node).toBe(expectedDeclarationNode); - expect(actualDeclaration!.viaModule).toBe(null); - expect((actualDeclaration as ConcreteDeclaration).identity).toBe(null); - }); - - it('should return the correct declaration for an outer alias identifier', () => { - const PROGRAM_FILE: TestFile = { - name: _('/test.js'), - contents: ` - var AliasedClass = AliasedClass_1 = (function () { - function InnerClass() { - } - return InnerClass; - }()); - var AliasedClass_1; - `, - }; - - loadTestFiles([PROGRAM_FILE]); - const bundle = makeTestBundleProgram(PROGRAM_FILE.name); - const host = - createHost(bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle)); - - const expectedDeclaration = getDeclaration( - bundle.program, PROGRAM_FILE.name, 'AliasedClass', isNamedVariableDeclaration); - // Grab the `AliasedClass_1` identifier (which is an alias for `AliasedClass`). - const aliasIdentifier = - (expectedDeclaration.initializer as ts.BinaryExpression).left as ts.Identifier; - const actualDeclaration = host.getDeclarationOfIdentifier(aliasIdentifier); - - expect(aliasIdentifier.getText()).toBe('AliasedClass_1'); - expect(actualDeclaration).not.toBe(null); - expect(actualDeclaration!.node).toBe(expectedDeclaration); - }); - - it('should return the source-file of an import namespace', () => { - loadFakeCore(getFileSystem()); - loadTestFiles([SOME_DIRECTIVE_FILE]); - const bundle = makeTestBundleProgram(SOME_DIRECTIVE_FILE.name); - const host = - createHost(bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, SOME_DIRECTIVE_FILE.name, 'SomeDirective', - isNamedVariableDeclaration); - const classDecorators = host.getDecoratorsOfDeclaration(classNode)!; - const namespaceIdentifier = - (((classDecorators[0].node as ts.ObjectLiteralExpression).properties[0] as - ts.PropertyAssignment) - .initializer as ts.PropertyAccessExpression) - .expression as ts.Identifier; - - const expectedDeclarationNode = - getSourceFileOrError(bundle.program, _('/node_modules/@angular/core/index.d.ts')); - const actualDeclaration = host.getDeclarationOfIdentifier(namespaceIdentifier); - expect(actualDeclaration).not.toBe(null); - expect(actualDeclaration!.node).toBe(expectedDeclarationNode); - expect(actualDeclaration!.viaModule).toBe('@angular/core'); - }); - - it('should return viaModule: null for relative imports', () => { - loadTestFiles([ - { - name: _('/index.js'), - contents: ` - const a = require('./a'); - var b = a.b; - `, - }, - { - name: _('/a.js'), - contents: ` - exports.b = 1; - ` - } - ]); - - const bundle = makeTestBundleProgram(_('/index.js')); - const host = - createHost(bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle)); - const variableNode = - getDeclaration(bundle.program, _('/index.js'), 'b', isNamedVariableDeclaration); - const identifier = variableNode.name as ts.Identifier; - - const importOfIdent = host.getDeclarationOfIdentifier(identifier!)!; - expect(importOfIdent.node).not.toBeNull(); - expect(importOfIdent.viaModule).toBeNull(); - }); - - it('should return a viaModule for absolute imports', () => { - loadTestFiles([ - { - name: _('/index.js'), - contents: ` - var a = require('lib'); - var b = a.x; - `, - }, - { - name: _('/node_modules/lib/index.d.ts'), - contents: 'export declare const x: number;', - }, - ]); - - const bundle = makeTestBundleProgram(_('/index.js')); - const host = - createHost(bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle)); - const variableNode = - getDeclaration(bundle.program, _('/index.js'), 'b', isNamedVariableDeclaration); - const identifier = - (variableNode.initializer! as ts.PropertyAccessExpression).name as ts.Identifier; - - const importOfIdent = host.getDeclarationOfIdentifier(identifier!)!; - expect(importOfIdent.viaModule).toBe('lib'); - }); - - it('should recognize TypeScript helpers (as function declarations)', () => { - const file: TestFile = { - name: _('/test.js'), - contents: ` - function __assign(t, ...sources) { /* ... */ } - function __spread(...args) { /* ... */ } - function __spreadArrays(...args) { /* ... */ } - function __spreadArray(to, from) { /* ... */ } - function __read(o) { /* ... */ } - - var a = __assign({foo: 'bar'}, {baz: 'qux'}); - var b = __spread(['foo', 'bar'], ['baz', 'qux']); - var c = __spreadArrays(['foo', 'bar'], ['baz', 'qux']); - var d = __spreadArray(['foo', 'bar'], ['baz', 'qux']); - var e = __read(['foo', 'bar']); - `, - }; - loadTestFiles([file]); - const bundle = makeTestBundleProgram(file.name); - const host = - createHost(bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle)); - - const testForHelper = createTestForTsHelper( - bundle.program, host, file, - helperName => - getDeclaration(bundle.program, file.name, helperName, ts.isFunctionDeclaration)); - - testForHelper('a', '__assign', KnownDeclaration.TsHelperAssign); - testForHelper('b', '__spread', KnownDeclaration.TsHelperSpread); - testForHelper('c', '__spreadArrays', KnownDeclaration.TsHelperSpreadArrays); - testForHelper('d', '__spreadArray', KnownDeclaration.TsHelperSpreadArray); - testForHelper('e', '__read', KnownDeclaration.TsHelperRead); - }); - - it('should recognize suffixed TypeScript helpers (as function declarations)', () => { - const file: TestFile = { - name: _('/test.js'), - contents: ` - function __assign$1(t, ...sources) { /* ... */ } - function __spread$2(...args) { /* ... */ } - function __spreadArrays$3(...args) { /* ... */ } - function __spreadArray$3(to, from) { /* ... */ } - function __read$3(o) { /* ... */ } - - var a = __assign$1({foo: 'bar'}, {baz: 'qux'}); - var b = __spread$2(['foo', 'bar'], ['baz', 'qux']); - var c = __spreadArrays$3(['foo', 'bar'], ['baz', 'qux']); - var d = __spreadArray$3(['foo', 'bar'], ['baz', 'qux']); - var e = __read$3(['foo', 'bar']); - `, - }; - loadTestFiles([file]); - const bundle = makeTestBundleProgram(file.name); - const host = - createHost(bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle)); - - const testForHelper = createTestForTsHelper( - bundle.program, host, file, - helperName => - getDeclaration(bundle.program, file.name, helperName, ts.isFunctionDeclaration)); - - testForHelper('a', '__assign$1', KnownDeclaration.TsHelperAssign); - testForHelper('b', '__spread$2', KnownDeclaration.TsHelperSpread); - testForHelper('c', '__spreadArrays$3', KnownDeclaration.TsHelperSpreadArrays); - testForHelper('d', '__spreadArray$3', KnownDeclaration.TsHelperSpreadArray); - testForHelper('e', '__read$3', KnownDeclaration.TsHelperRead); - }); - - it('should recognize TypeScript helpers (as variable declarations)', () => { - const file: TestFile = { - name: _('/test.js'), - contents: ` - var __assign = (this && this.__assign) || function (t, ...sources) { /* ... */ } - var __spread = (this && this.__spread) || function (...args) { /* ... */ } - var __spreadArrays = (this && this.__spreadArrays) || function (...args) { /* ... */ } - var __spreadArray = (this && this.__spreadArray) || function (to, from) { /* ... */ } - var __read = (this && this.__read) || function (o) { /* ... */ } - - var a = __assign({foo: 'bar'}, {baz: 'qux'}); - var b = __spread(['foo', 'bar'], ['baz', 'qux']); - var c = __spreadArrays(['foo', 'bar'], ['baz', 'qux']); - var d = __spreadArray(['foo', 'bar'], ['baz', 'qux']); - var e = __read(['foo', 'bar']); - `, - }; - loadTestFiles([file]); - const bundle = makeTestBundleProgram(file.name); - const host = - createHost(bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle)); - - const testForHelper = createTestForTsHelper( - bundle.program, host, file, - helperName => - getDeclaration(bundle.program, file.name, helperName, ts.isVariableDeclaration)); - - testForHelper('a', '__assign', KnownDeclaration.TsHelperAssign); - testForHelper('b', '__spread', KnownDeclaration.TsHelperSpread); - testForHelper('c', '__spreadArrays', KnownDeclaration.TsHelperSpreadArrays); - testForHelper('d', '__spreadArray', KnownDeclaration.TsHelperSpreadArray); - testForHelper('e', '__read', KnownDeclaration.TsHelperRead); - }); - - it('should recognize suffixed TypeScript helpers (as variable declarations)', () => { - const file: TestFile = { - name: _('/test.js'), - contents: ` - var __assign$1 = (this && this.__assign$1) || function (t, ...sources) { /* ... */ } - var __spread$2 = (this && this.__spread$2) || function (...args) { /* ... */ } - var __spreadArrays$3 = (this && this.__spreadArrays$3) || function (...args) { /* ... */ } - var __spreadArray$3 = (this && this.__spreadArray$3) || function (to, from) { /* ... */ } - var __read$3 = (this && this.__read$3) || function (o) { /* ... */ } - - var a = __assign$1({foo: 'bar'}, {baz: 'qux'}); - var b = __spread$2(['foo', 'bar'], ['baz', 'qux']); - var c = __spreadArrays$3(['foo', 'bar'], ['baz', 'qux']); - var d = __spreadArray$3(['foo', 'bar'], ['baz', 'qux']); - var e = __read$3(['foo', 'bar']); - `, - }; - loadTestFiles([file]); - const bundle = makeTestBundleProgram(file.name); - const host = - createHost(bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle)); - - const testForHelper = createTestForTsHelper( - bundle.program, host, file, - helperName => - getDeclaration(bundle.program, file.name, helperName, ts.isVariableDeclaration)); - - testForHelper('a', '__assign$1', KnownDeclaration.TsHelperAssign); - testForHelper('b', '__spread$2', KnownDeclaration.TsHelperSpread); - testForHelper('c', '__spreadArrays$3', KnownDeclaration.TsHelperSpreadArrays); - testForHelper('d', '__spreadArray$3', KnownDeclaration.TsHelperSpreadArray); - testForHelper('e', '__read$3', KnownDeclaration.TsHelperRead); - }); - - it('should recognize imported TypeScript helpers', () => { - const files: TestFile[] = [ - { - name: _('/test.js'), - contents: ` - var tslib_1 = require('tslib'); - - var a = tslib_1.__assign({foo: 'bar'}, {baz: 'qux'}); - var b = tslib_1.__spread(['foo', 'bar'], ['baz', 'qux']); - var c = tslib_1.__spreadArrays(['foo', 'bar'], ['baz', 'qux']); - var d = tslib_1.__spreadArray(['foo', 'bar'], ['baz', 'qux']); - var e = tslib_1.__read(['foo', 'bar']); - `, - }, - { - name: _('/node_modules/tslib/index.d.ts'), - contents: ` - export declare function __assign(t: any, ...sources: any[]): any; - export declare function __spread(...args: any[][]): any[]; - export declare function __spreadArrays(...args: any[][]): any[]; - export declare function __spreadArray(to: any[], from: any[]): any[]; - export declare function __read(o: any, n?: number): any[]; - `, - }, - ]; - loadTestFiles(files); - - const [testFile, tslibFile] = files; - const bundle = makeTestBundleProgram(testFile.name); - const host = - createHost(bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle)); - - const testForHelper = createTestForTsHelper( - bundle.program, host, testFile, - helperName => getDeclaration( - bundle.program, tslibFile.name, helperName, ts.isFunctionDeclaration)); - - testForHelper('a', '__assign', KnownDeclaration.TsHelperAssign, 'tslib'); - testForHelper('b', '__spread', KnownDeclaration.TsHelperSpread, 'tslib'); - testForHelper('c', '__spreadArrays', KnownDeclaration.TsHelperSpreadArrays, 'tslib'); - testForHelper('d', '__spreadArray', KnownDeclaration.TsHelperSpreadArray, 'tslib'); - testForHelper('e', '__read', KnownDeclaration.TsHelperRead, 'tslib'); - }); - - it('should recognize undeclared, unimported TypeScript helpers (by name)', () => { - const file: TestFile = { - name: _('/test.js'), - contents: ` - var a = __assign({foo: 'bar'}, {baz: 'qux'}); - var b = __spread(['foo', 'bar'], ['baz', 'qux']); - var c = __spreadArrays(['foo', 'bar'], ['baz', 'qux']); - var d = __spreadArray(['foo', 'bar'], ['baz', 'qux']); - var e = __read(['foo', 'bar']); - `, - }; - loadTestFiles([file]); - const bundle = makeTestBundleProgram(file.name); - const host = - createHost(bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle)); - - const testForHelper = - (varName: string, helperName: string, knownAs: KnownDeclaration) => { - const node = - getDeclaration(bundle.program, file.name, varName, ts.isVariableDeclaration); - const helperIdentifier = getIdentifierFromCallExpression(node); - const helperDeclaration = host.getDeclarationOfIdentifier(helperIdentifier); - - expect(helperDeclaration).toEqual({ - kind: DeclarationKind.Inline, - known: knownAs, - node: helperIdentifier, - viaModule: null, - }); - }; - - testForHelper('a', '__assign', KnownDeclaration.TsHelperAssign); - testForHelper('b', '__spread', KnownDeclaration.TsHelperSpread); - testForHelper('c', '__spreadArrays', KnownDeclaration.TsHelperSpreadArrays); - testForHelper('d', '__spreadArray', KnownDeclaration.TsHelperSpreadArray); - testForHelper('e', '__read', KnownDeclaration.TsHelperRead); - }); - - it('should recognize suffixed, undeclared, unimported TypeScript helpers (by name)', () => { - const file: TestFile = { - name: _('/test.js'), - contents: ` - var a = __assign$1({foo: 'bar'}, {baz: 'qux'}); - var b = __spread$2(['foo', 'bar'], ['baz', 'qux']); - var c = __spreadArrays$3(['foo', 'bar'], ['baz', 'qux']); - var d = __spreadArray$3(['foo', 'bar'], ['baz', 'qux']); - var e = __read$3(['foo', 'bar']); - `, - }; - loadTestFiles([file]); - const bundle = makeTestBundleProgram(file.name); - const host = - createHost(bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle)); - - const testForHelper = - (varName: string, helperName: string, knownAs: KnownDeclaration) => { - const node = - getDeclaration(bundle.program, file.name, varName, ts.isVariableDeclaration); - const helperIdentifier = getIdentifierFromCallExpression(node); - const helperDeclaration = host.getDeclarationOfIdentifier(helperIdentifier); - - expect(helperDeclaration).toEqual({ - kind: DeclarationKind.Inline, - known: knownAs, - node: helperIdentifier, - viaModule: null, - }); - }; - - testForHelper('a', '__assign$1', KnownDeclaration.TsHelperAssign); - testForHelper('b', '__spread$2', KnownDeclaration.TsHelperSpread); - testForHelper('c', '__spreadArrays$3', KnownDeclaration.TsHelperSpreadArrays); - testForHelper('d', '__spreadArray$3', KnownDeclaration.TsHelperSpreadArray); - testForHelper('e', '__read$3', KnownDeclaration.TsHelperRead); - }); - - it('should recognize enum declarations with string values', () => { - const testFile: TestFile = { - name: _('/node_modules/test-package/some/file.js'), - contents: ` - var Enum; - (function (Enum) { - Enum["ValueA"] = "1"; - Enum["ValueB"] = "2"; - })(exports.Enum || (exports.Enum = {})); - - var value = Enum;` - }; - loadTestFiles([testFile]); - const bundle = makeTestBundleProgram(testFile.name); - const host = - createHost(bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle)); - const valueDecl = getDeclaration( - bundle.program, _('/node_modules/test-package/some/file.js'), 'value', - ts.isVariableDeclaration); - const declaration = host.getDeclarationOfIdentifier( - valueDecl.initializer as ts.Identifier) as ConcreteDeclaration; - - const enumMembers = (declaration.identity as DownleveledEnum).enumMembers; - expect(declaration.node.parent.parent.getText()).toBe('var Enum;'); - expect(enumMembers!.length).toBe(2); - expect(enumMembers![0].name.getText()).toBe('"ValueA"'); - expect(enumMembers![0].initializer!.getText()).toBe('"1"'); - expect(enumMembers![1].name.getText()).toBe('"ValueB"'); - expect(enumMembers![1].initializer!.getText()).toBe('"2"'); - }); - - it('should recognize enum declarations with numeric values', () => { - const testFile: TestFile = { - name: _('/node_modules/test-package/some/file.js'), - contents: ` - var Enum; - (function (Enum) { - Enum[Enum["ValueA"] = "1"] = "ValueA"; - Enum[Enum["ValueB"] = "2"] = "ValueB"; - })(exports.Enum || (exports.Enum = {})); - - var value = Enum;` - }; - loadTestFiles([testFile]); - const bundle = makeTestBundleProgram(testFile.name); - const host = - createHost(bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle)); - const valueDecl = getDeclaration( - bundle.program, _('/node_modules/test-package/some/file.js'), 'value', - ts.isVariableDeclaration); - const declaration = host.getDeclarationOfIdentifier( - valueDecl.initializer as ts.Identifier) as ConcreteDeclaration; - - const enumMembers = (declaration.identity as DownleveledEnum).enumMembers; - expect(declaration.node.parent.parent.getText()).toBe('var Enum;'); - expect(enumMembers!.length).toBe(2); - expect(enumMembers![0].name.getText()).toBe('"ValueA"'); - expect(enumMembers![0].initializer!.getText()).toBe('"1"'); - expect(enumMembers![1].name.getText()).toBe('"ValueB"'); - expect(enumMembers![1].initializer!.getText()).toBe('"2"'); - }); - - it('should not consider IIFEs that do no assign members to the parameter as an enum declaration', - () => { - const testFile: TestFile = { - name: _('/node_modules/test-package/some/file.js'), - contents: ` - var Enum; - (function (E) { - Enum["ValueA"] = "1"; - Enum["ValueB"] = "2"; - })(exports.Enum || (exports.Enum = {})); - - var value = Enum;` - }; - loadTestFiles([testFile]); - const bundle = makeTestBundleProgram(testFile.name); - const host = - createHost(bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle)); - const valueDecl = getDeclaration( - bundle.program, _('/node_modules/test-package/some/file.js'), 'value', - ts.isVariableDeclaration); - const declaration = host.getDeclarationOfIdentifier( - valueDecl.initializer as ts.Identifier) as ConcreteDeclaration; - - expect(declaration.node.parent.parent.getText()).toBe('var Enum;'); - expect(declaration.identity).toBe(null); - }); - - it('should not consider IIFEs without call argument as an enum declaration', () => { - const testFile: TestFile = { - name: _('/node_modules/test-package/some/file.js'), - contents: ` - var Enum; - (function (Enum) { - Enum["ValueA"] = "1"; - Enum["ValueB"] = "2"; - })(); - - var value = Enum;` - }; - loadTestFiles([testFile]); - const bundle = makeTestBundleProgram(testFile.name); - const host = - createHost(bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle)); - const valueDecl = getDeclaration( - bundle.program, _('/node_modules/test-package/some/file.js'), 'value', - ts.isVariableDeclaration); - const declaration = host.getDeclarationOfIdentifier( - valueDecl.initializer as ts.Identifier) as ConcreteDeclaration; - - expect(declaration.node.parent.parent.getText()).toBe('var Enum;'); - expect(declaration.identity).toBe(null); - }); - }); - - describe('getExportsOfModule()', () => { - it('should return a map of all the exports from a given module', () => { - loadFakeCore(getFileSystem()); - loadTestFiles(EXPORTS_FILES); - const bundle = makeTestBundleProgram(_('/index.js')); - const host = - createHost(bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle)); - const file = getSourceFileOrError(bundle.program, _('/b_module.js')); - const exportDeclarations = host.getExportsOfModule(file); - expect(exportDeclarations).not.toBe(null); - expect(Array.from(exportDeclarations!.entries()) - .map(entry => [entry[0], entry[1].node!.getText(), entry[1].viaModule])) - .toEqual([ - ['Directive', `Directive: FnWithArg<(clazz: any) => any>`, '@angular/core'], - ['a', `a = 'a'`, null], - ['b', `b = a_module.a`, null], - ['c', `a = 'a'`, null], - ['d', `b = a_module.a`, null], - ['e', `e = 'e'`, null], - ['DirectiveX', `Directive: FnWithArg<(clazz: any) => any>`, '@angular/core'], - [ - 'SomeClass', - `SomeClass = (function() {\n function SomeClass() {}\n return SomeClass;\n}())`, - null - ], - ]); - }); - - it('should handle wildcard re-exports of other modules (with emitted helpers)', () => { - loadFakeCore(getFileSystem()); - loadTestFiles(EXPORTS_FILES); - const bundle = makeTestBundleProgram(_('/index.js')); - const host = - createHost(bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle)); - const file = - getSourceFileOrError(bundle.program, _('/wildcard_reexports_emitted_helpers.js')); - const exportDeclarations = host.getExportsOfModule(file); - expect(exportDeclarations).not.toBe(null); - expect(Array.from(exportDeclarations!.entries()) - .map(entry => [entry[0], entry[1].node!.getText(), entry[1].viaModule])) - .toEqual([ - ['Directive', `Directive: FnWithArg<(clazz: any) => any>`, '@angular/core'], - ['a', `a = 'a'`, null], - ['b', `b = a_module.a`, null], - ['c', `a = 'a'`, null], - ['d', `b = a_module.a`, null], - ['e', `e = 'e'`, null], - ['DirectiveX', `Directive: FnWithArg<(clazz: any) => any>`, '@angular/core'], - [ - 'SomeClass', - `SomeClass = (function() {\n function SomeClass() {}\n return SomeClass;\n}())`, - null - ], - ['xtra1', `xtra1 = 'xtra1'`, null], - ['xtra2', `xtra2 = 'xtra2'`, null], - ]); - }); - - it('should handle wildcard re-exports of other modules (with imported helpers)', () => { - loadFakeCore(getFileSystem()); - loadTestFiles(EXPORTS_FILES); - const bundle = makeTestBundleProgram(_('/index.js')); - const host = - createHost(bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle)); - const file = - getSourceFileOrError(bundle.program, _('/wildcard_reexports_imported_helpers.js')); - const exportDeclarations = host.getExportsOfModule(file); - expect(exportDeclarations).not.toBe(null); - expect(Array.from(exportDeclarations!.entries()) - .map(entry => [entry[0], entry[1].node!.getText(), entry[1].viaModule])) - .toEqual([ - ['Directive', `Directive: FnWithArg<(clazz: any) => any>`, '@angular/core'], - ['a', `a = 'a'`, null], - ['b', `b = a_module.a`, null], - ['c', `a = 'a'`, null], - ['d', `b = a_module.a`, null], - ['e', `e = 'e'`, null], - ['DirectiveX', `Directive: FnWithArg<(clazz: any) => any>`, '@angular/core'], - [ - 'SomeClass', - `SomeClass = (function() {\n function SomeClass() {}\n return SomeClass;\n}())`, - null - ], - ['xtra1', `xtra1 = 'xtra1'`, null], - ['xtra2', `xtra2 = 'xtra2'`, null], - ]); - }); - - it('should handle inline exports', () => { - loadTestFiles([INLINE_EXPORT_FILE]); - const bundle = makeTestBundleProgram(_('/inline_export.js')); - const host = - createHost(bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle)); - const file = getSourceFileOrError(bundle.program, _('/inline_export.js')); - const exportDeclarations = host.getExportsOfModule(file); - expect(exportDeclarations).not.toBeNull(); - const entries: [string, InlineDeclaration][] = - Array.from(exportDeclarations!.entries()) as any; - expect( - entries.map( - ([name, decl]) => - [name, decl.node!.getText(), decl.implementation!.getText(), decl.viaModule])) - .toEqual([ - ['directives', 'exports.directives', '[foo]', null], - ['Inline', 'exports.Inline', 'function Inline() {}', null], - ]); - }); - - it('should recognize declarations of known TypeScript helpers', () => { - const tslib = { - name: _('/tslib.d.ts'), - contents: ` - export declare function __assign(t: any, ...sources: any[]): any; - export declare function __spread(...args: any[][]): any[]; - export declare function __spreadArrays(...args: any[][]): any[]; - export declare function __spreadArray(to: any[], from: any[]): any[]; - export declare function __read(o: any, n?: number): any[]; - export declare function __unknownHelper(...args: any[]): any; - `, - }; - loadTestFiles([tslib]); - const bundle = makeTestBundleProgram(tslib.name); - const host = - createHost(bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle)); - const sf = getSourceFileOrError(bundle.program, tslib.name); - const exportDeclarations = host.getExportsOfModule(sf)!; - - expect([...exportDeclarations].map(([exportName, {known}]) => [exportName, known])) - .toEqual([ - ['__assign', KnownDeclaration.TsHelperAssign], - ['__spread', KnownDeclaration.TsHelperSpread], - ['__spreadArrays', KnownDeclaration.TsHelperSpreadArrays], - ['__spreadArray', KnownDeclaration.TsHelperSpreadArray], - ['__read', KnownDeclaration.TsHelperRead], - ['__unknownHelper', null], - ]); - }); - - it('should define property exports from a module', () => { - loadFakeCore(getFileSystem()); - loadTestFiles(EXPORTS_FILES); - const bundle = makeTestBundleProgram(_('/index.js')); - const host = - createHost(bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle)); - const file = getSourceFileOrError(bundle.program, _('/define_property_reexports.js')); - const exportDeclarations = host.getExportsOfModule(file); - expect(exportDeclarations).not.toBe(null); - expect(Array.from(exportDeclarations!.entries()) - .map(entry => [entry[0], entry[1].node!.getText(), entry[1].viaModule])) - .toEqual([ - ['newA', `a = 'a'`, null], - ]); - }); - }); - - describe('getClassSymbol()', () => { - it('should return the class symbol for an ES2015 class', () => { - loadTestFiles([SIMPLE_ES2015_CLASS_FILE]); - const bundle = makeTestBundleProgram(SIMPLE_ES2015_CLASS_FILE.name); - const host = - createHost(bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle)); - const node = getDeclaration( - bundle.program, SIMPLE_ES2015_CLASS_FILE.name, 'EmptyClass', isNamedClassDeclaration); - const classSymbol = host.getClassSymbol(node); - - expect(classSymbol).toBeDefined(); - expect(classSymbol!.declaration.valueDeclaration).toBe(node); - expect(classSymbol!.implementation.valueDeclaration).toBe(node); - }); - - it('should return the class symbol for an ES5 class (outer variable declaration)', () => { - loadTestFiles([SIMPLE_CLASS_FILE]); - const bundle = makeTestBundleProgram(SIMPLE_CLASS_FILE.name); - const host = - createHost(bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle)); - const outerNode = getDeclaration( - bundle.program, SIMPLE_CLASS_FILE.name, 'EmptyClass', isNamedVariableDeclaration); - const innerNode = (getIifeBody(outerNode.initializer!) as ts.Block) - .statements.find(isNamedFunctionDeclaration)!; - const classSymbol = host.getClassSymbol(outerNode); - - expect(classSymbol).toBeDefined(); - expect(classSymbol!.declaration.valueDeclaration).toBe(outerNode); - expect(classSymbol!.implementation.valueDeclaration).toBe(innerNode); - }); - - it('should return the class symbol for an ES5 class (inner function declaration)', () => { - loadTestFiles([SIMPLE_CLASS_FILE]); - const bundle = makeTestBundleProgram(SIMPLE_CLASS_FILE.name); - const host = - createHost(bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle)); - const outerNode = getDeclaration( - bundle.program, SIMPLE_CLASS_FILE.name, 'EmptyClass', isNamedVariableDeclaration); - const innerNode = (getIifeBody(outerNode.initializer!) as ts.Block) - .statements.find(isNamedFunctionDeclaration)!; - const classSymbol = host.getClassSymbol(innerNode); - - expect(classSymbol).toBeDefined(); - expect(classSymbol!.declaration.valueDeclaration).toBe(outerNode); - expect(classSymbol!.implementation.valueDeclaration).toBe(innerNode); - }); - - it('should return the same class symbol (of the outer declaration) for outer and inner declarations', - () => { - loadTestFiles([SIMPLE_CLASS_FILE]); - const bundle = makeTestBundleProgram(SIMPLE_CLASS_FILE.name); - const host = - createHost(bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle)); - const outerNode = getDeclaration( - bundle.program, SIMPLE_CLASS_FILE.name, 'EmptyClass', isNamedVariableDeclaration); - const innerNode = (getIifeBody(outerNode.initializer!) as ts.Block) - .statements.find(isNamedFunctionDeclaration)!; - - const innerSymbol = host.getClassSymbol(innerNode)!; - const outerSymbol = host.getClassSymbol(outerNode)!; - expect(innerSymbol.declaration).toBe(outerSymbol.declaration); - expect(innerSymbol.implementation).toBe(outerSymbol.implementation); - }); - - it('should return the class symbol for an ES5 class whose IIFE is not wrapped in parens', - () => { - loadTestFiles([SIMPLE_CLASS_FILE]); - const bundle = makeTestBundleProgram(SIMPLE_CLASS_FILE.name); - const host = - createHost(bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle)); - const outerNode = getDeclaration( - bundle.program, SIMPLE_CLASS_FILE.name, 'NoParensClass', - isNamedVariableDeclaration); - const innerNode = (getIifeBody(outerNode.initializer!) as ts.Block) - .statements.find(isNamedFunctionDeclaration)!; - const classSymbol = host.getClassSymbol(outerNode); - - expect(classSymbol).toBeDefined(); - expect(classSymbol!.declaration.valueDeclaration).toBe(outerNode); - expect(classSymbol!.implementation.valueDeclaration).toBe(innerNode); - }); - - it('should return the class symbol for an ES5 class whose IIFE is not wrapped with inner parens', - () => { - loadTestFiles([SIMPLE_CLASS_FILE]); - const bundle = makeTestBundleProgram(SIMPLE_CLASS_FILE.name); - const host = - createHost(bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle)); - const outerNode = getDeclaration( - bundle.program, SIMPLE_CLASS_FILE.name, 'InnerParensClass', - isNamedVariableDeclaration); - const innerNode = (getIifeBody(outerNode.initializer!) as ts.Block) - .statements.find(isNamedFunctionDeclaration)!; - const classSymbol = host.getClassSymbol(outerNode); - - expect(classSymbol).toBeDefined(); - expect(classSymbol!.declaration.valueDeclaration).toBe(outerNode); - expect(classSymbol!.implementation.valueDeclaration).toBe(innerNode); - }); - - it('should return undefined if node is not an ES5 class', () => { - loadTestFiles([FOO_FUNCTION_FILE]); - const bundle = makeTestBundleProgram(FOO_FUNCTION_FILE.name); - const host = - createHost(bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle)); - const node = getDeclaration( - bundle.program, FOO_FUNCTION_FILE.name, 'foo', isNamedFunctionDeclaration); - const classSymbol = host.getClassSymbol(node); - - expect(classSymbol).toBeUndefined(); - }); - - it('should return undefined if variable declaration is not initialized using an IIFE', - () => { - const testFile = { - name: _('/test.js'), - contents: `var MyClass = null;`, - }; - loadTestFiles([testFile]); - const bundle = makeTestBundleProgram(testFile.name); - const host = - createHost(bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle)); - const node = getDeclaration( - bundle.program, testFile.name, 'MyClass', isNamedVariableDeclaration); - const classSymbol = host.getClassSymbol(node); - - expect(classSymbol).toBeUndefined(); - }); - }); - - describe('isClass()', () => { - it('should return true if a given node is a TS class declaration', () => { - loadTestFiles([SIMPLE_ES2015_CLASS_FILE]); - const bundle = makeTestBundleProgram(SIMPLE_ES2015_CLASS_FILE.name); - const host = - createHost(bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle)); - const node = getDeclaration( - bundle.program, SIMPLE_ES2015_CLASS_FILE.name, 'EmptyClass', isNamedClassDeclaration); - expect(host.isClass(node)).toBe(true); - }); - - it('should return true if a given node is the outer variable declaration of a class', - () => { - loadTestFiles([SIMPLE_CLASS_FILE]); - const bundle = makeTestBundleProgram(SIMPLE_CLASS_FILE.name); - const host = - createHost(bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle)); - const node = getDeclaration( - bundle.program, SIMPLE_CLASS_FILE.name, 'EmptyClass', ts.isVariableDeclaration); - expect(host.isClass(node)).toBe(true); - }); - - it('should return true if a given node is the inner variable declaration of a class', - () => { - loadTestFiles([SIMPLE_CLASS_FILE]); - const bundle = makeTestBundleProgram(SIMPLE_CLASS_FILE.name); - const host = - createHost(bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle)); - const outerNode = getDeclaration( - bundle.program, SIMPLE_CLASS_FILE.name, 'EmptyClass', ts.isVariableDeclaration); - const innerNode = (getIifeBody(outerNode.initializer!) as ts.Block) - .statements.find(isNamedFunctionDeclaration)!; - expect(host.isClass(innerNode)).toBe(true); - }); - - it('should return false if a given node is a function declaration', () => { - loadTestFiles([FOO_FUNCTION_FILE]); - const bundle = makeTestBundleProgram(FOO_FUNCTION_FILE.name); - const host = - createHost(bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle)); - const node = getDeclaration( - bundle.program, FOO_FUNCTION_FILE.name, 'foo', isNamedFunctionDeclaration); - expect(host.isClass(node)).toBe(false); - }); - }); - - describe('hasBaseClass()', () => { - function hasBaseClass(source: string) { - const file = { - name: _('/synthesized_constructors.js'), - contents: source, - }; - - loadTestFiles([file]); - const bundle = makeTestBundleProgram(file.name); - const host = - createHost(bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle)); - const classNode = - getDeclaration(bundle.program, file.name, 'TestClass', isNamedVariableDeclaration); - return host.hasBaseClass(classNode); - } - - it('should consider an IIFE with _super parameter as having a base class', () => { - const result = hasBaseClass(` - var TestClass = /** @class */ (function (_super) { - __extends(TestClass, _super); - function TestClass() {} - return TestClass; - }(null));`); - expect(result).toBe(true); - }); - - it('should consider an IIFE with a unique name generated for the _super parameter as having a base class', - () => { - const result = hasBaseClass(` - var TestClass = /** @class */ (function (_super_1) { - __extends(TestClass, _super_1); - function TestClass() {} - return TestClass; - }(null));`); - expect(result).toBe(true); - }); - - it('should not consider an IIFE without parameter as having a base class', () => { - const result = hasBaseClass(` - var TestClass = /** @class */ (function () { - __extends(TestClass, _super); - function TestClass() {} - return TestClass; - }(null));`); - expect(result).toBe(false); - }); - }); - - describe('getBaseClassExpression()', () => { - function getBaseClassIdentifier(source: string): ts.Identifier|null { - const file = { - name: _('/synthesized_constructors.js'), - contents: source, - }; - - loadTestFiles([file]); - const bundle = makeTestBundleProgram(file.name); - const host = - createHost(bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle)); - const classNode = - getDeclaration(bundle.program, file.name, 'TestClass', isNamedVariableDeclaration); - const expression = host.getBaseClassExpression(classNode); - if (expression !== null && !ts.isIdentifier(expression)) { - throw new Error( - 'Expected class to inherit via an identifier but got: ' + expression.getText()); - } - return expression; - } - - it('should find the base class of an IIFE with _super parameter', () => { - const identifier = getBaseClassIdentifier(` - var BaseClass = /** @class */ (function () { - function BaseClass() {} - return BaseClass; - }()); - var TestClass = /** @class */ (function (_super) { - __extends(TestClass, _super); - function TestClass() {} - return TestClass; - }(BaseClass));`); - expect(identifier!.text).toBe('BaseClass'); - }); - - it('should find the base class of an IIFE with a unique name generated for the _super parameter', - () => { - const identifier = getBaseClassIdentifier(` - var BaseClass = /** @class */ (function () { - function BaseClass() {} - return BaseClass; - }()); - var TestClass = /** @class */ (function (_super_1) { - __extends(TestClass, _super_1); - function TestClass() {} - return TestClass; - }(BaseClass));`); - expect(identifier!.text).toBe('BaseClass'); - }); - - it('should not find a base class for an IIFE without parameter', () => { - const identifier = getBaseClassIdentifier(` - var BaseClass = /** @class */ (function () { - function BaseClass() {} - return BaseClass; - }()); - var TestClass = /** @class */ (function () { - __extends(TestClass, _super); - function TestClass() {} - return TestClass; - }(BaseClass));`); - expect(identifier).toBe(null); - }); - - it('should find a dynamic base class expression of an IIFE', () => { - const file = { - name: _('/synthesized_constructors.js'), - contents: ` - var BaseClass = /** @class */ (function () { - function BaseClass() {} - return BaseClass; - }()); - function foo() { return BaseClass; } - var TestClass = /** @class */ (function (_super) { - __extends(TestClass, _super); - function TestClass() {} - return TestClass; - }(foo()));`, - }; - - loadTestFiles([file]); - const bundle = makeTestBundleProgram(file.name); - const host = - createHost(bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle)); - const classNode = - getDeclaration(bundle.program, file.name, 'TestClass', isNamedVariableDeclaration); - const expression = host.getBaseClassExpression(classNode)!; - expect(expression.getText()).toBe('foo()'); - }); - }); - - describe('findClassSymbols()', () => { - it('should return an array of all classes in the given source file', () => { - loadTestFiles(DECORATED_FILES); - const bundle = makeTestBundleProgram(getRootFiles(DECORATED_FILES)[0]); - const host = - createHost(bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle)); - const primaryFile = getSourceFileOrError(bundle.program, DECORATED_FILES[0].name); - const secondaryFile = getSourceFileOrError(bundle.program, DECORATED_FILES[1].name); - - const classSymbolsPrimary = host.findClassSymbols(primaryFile); - expect(classSymbolsPrimary.length).toEqual(2); - expect(classSymbolsPrimary.map(c => c.name)).toEqual(['A', 'B']); - - const classSymbolsSecondary = host.findClassSymbols(secondaryFile); - expect(classSymbolsSecondary.length).toEqual(1); - expect(classSymbolsSecondary.map(c => c.name)).toEqual(['D']); - }); - }); - - describe('getDecoratorsOfSymbol()', () => { - it('should return decorators of class symbol', () => { - loadTestFiles(DECORATED_FILES); - const bundle = makeTestBundleProgram(getRootFiles(DECORATED_FILES)[0]); - const host = - createHost(bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle)); - const primaryFile = getSourceFileOrError(bundle.program, DECORATED_FILES[0].name); - const secondaryFile = getSourceFileOrError(bundle.program, DECORATED_FILES[1].name); - - const classSymbolsPrimary = host.findClassSymbols(primaryFile); - const classDecoratorsPrimary = - classSymbolsPrimary.map(s => host.getDecoratorsOfSymbol(s)); - expect(classDecoratorsPrimary.length).toEqual(2); - expect(classDecoratorsPrimary[0]!.map(d => d.name)).toEqual(['Directive']); - expect(classDecoratorsPrimary[1]!.map(d => d.name)).toEqual(['Directive']); - - const classSymbolsSecondary = host.findClassSymbols(secondaryFile); - const classDecoratorsSecondary = - classSymbolsSecondary.map(s => host.getDecoratorsOfSymbol(s)); - expect(classDecoratorsSecondary.length).toEqual(1); - expect(classDecoratorsSecondary[0]!.map(d => d.name)).toEqual(['Directive']); - }); - }); - - describe('getDtsDeclaration()', () => { - it('should find the dts declaration that has the same relative path to the source file', - () => { - loadTestFiles(TYPINGS_SRC_FILES); - loadTestFiles(TYPINGS_DTS_FILES); - const bundle = makeTestBundleProgram(getRootFiles(TYPINGS_SRC_FILES)[0]); - const dts = makeTestBundleProgram(getRootFiles(TYPINGS_DTS_FILES)[0]); - const class1 = getDeclaration( - bundle.program, _('/ep/src/class1.js'), 'Class1', ts.isVariableDeclaration); - const host = createHost( - bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle, dts)); - - const dtsDeclaration = host.getDtsDeclaration(class1); - expect(dtsDeclaration!.getSourceFile().fileName).toEqual(_('/ep/typings/class1.d.ts')); - }); - - it('should find the dts declaration for exported functions', () => { - loadTestFiles(TYPINGS_SRC_FILES); - loadTestFiles(TYPINGS_DTS_FILES); - const bundle = makeTestBundleProgram(_('/ep/src/func1.js')); - const dts = makeTestDtsBundleProgram(_('/ep/typings/func1.d.ts'), _('/')); - const mooFn = getDeclaration( - bundle.program, _('/ep/src/func1.js'), 'mooFn', ts.isFunctionDeclaration); - const host = - createHost(bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle, dts)); - const dtsDeclaration = host.getDtsDeclaration(mooFn); - expect(dtsDeclaration!.getSourceFile().fileName).toEqual(_('/ep/typings/func1.d.ts')); - }); - - it('should return null if there is no matching class in the matching dts file', () => { - loadTestFiles(TYPINGS_SRC_FILES); - loadTestFiles(TYPINGS_DTS_FILES); - const bundle = makeTestBundleProgram(_('/ep/src/index.js')); - const dts = makeTestDtsBundleProgram(_('/ep/typings/index.d.ts'), _('/')); - const missingClass = getDeclaration( - bundle.program, _('/ep/src/class1.js'), 'MissingClass1', ts.isVariableDeclaration); - const host = - createHost(bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle, dts)); - - expect(host.getDtsDeclaration(missingClass)).toBe(null); - }); - - it('should return null if there is no matching dts file', () => { - loadTestFiles(TYPINGS_SRC_FILES); - loadTestFiles(TYPINGS_DTS_FILES); - const bundle = makeTestBundleProgram(_('/ep/src/index.js')); - const dts = makeTestDtsBundleProgram(_('/ep/typings/index.d.ts'), _('/')); - const missingClass = getDeclaration( - bundle.program, _('/ep/src/missing-class.js'), 'MissingClass2', - ts.isVariableDeclaration); - const host = - createHost(bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle, dts)); - - expect(host.getDtsDeclaration(missingClass)).toBe(null); - }); - - it('should find the dts file that contains a matching class declaration, even if the source files do not match', - () => { - loadTestFiles(TYPINGS_SRC_FILES); - loadTestFiles(TYPINGS_DTS_FILES); - const bundle = makeTestBundleProgram(_('/ep/src/flat-file.js')); - const dts = makeTestBundleProgram(getRootFiles(TYPINGS_DTS_FILES)[0]); - const class1 = getDeclaration( - bundle.program, _('/ep/src/flat-file.js'), 'Class1', ts.isVariableDeclaration); - const host = createHost( - bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle, dts)); - - const dtsDeclaration = host.getDtsDeclaration(class1); - expect(dtsDeclaration!.getSourceFile().fileName).toEqual(_('/ep/typings/class1.d.ts')); - }); - - it('should find aliased exports', () => { - loadTestFiles(TYPINGS_SRC_FILES); - loadTestFiles(TYPINGS_DTS_FILES); - const bundle = makeTestBundleProgram(_('/ep/src/flat-file.js')); - const dts = makeTestBundleProgram(getRootFiles(TYPINGS_DTS_FILES)[0]); - const sourceClass = getDeclaration( - bundle.program, _('/ep/src/flat-file.js'), 'SourceClass', ts.isVariableDeclaration); - const host = - createHost(bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle, dts)); - - const dtsDeclaration = host.getDtsDeclaration(sourceClass); - if (dtsDeclaration === null) { - return fail('Expected dts class to be found'); - } - if (!isNamedClassDeclaration(dtsDeclaration)) { - return fail('Expected a named class to be found.'); - } - expect(dtsDeclaration.name.text).toEqual('TypingsClass'); - expect(_(dtsDeclaration.getSourceFile().fileName)) - .toEqual(_('/ep/typings/typings-class.d.ts')); - }); - - it('should find the dts file that contains a matching class declaration, even if the class is not publicly exported', - () => { - loadTestFiles(TYPINGS_SRC_FILES); - loadTestFiles(TYPINGS_DTS_FILES); - const bundle = makeTestBundleProgram(getRootFiles(TYPINGS_SRC_FILES)[0]); - const dts = makeTestBundleProgram(getRootFiles(TYPINGS_DTS_FILES)[0]); - const internalClass = getDeclaration( - bundle.program, _('/ep/src/internal.js'), 'InternalClass', - ts.isVariableDeclaration); - const host = createHost( - bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle, dts)); - - const dtsDeclaration = host.getDtsDeclaration(internalClass); - expect(dtsDeclaration!.getSourceFile().fileName) - .toEqual(_('/ep/typings/internal.d.ts')); - }); - - it('should match publicly and internal exported classes correctly, even if they have the same name', - () => { - loadTestFiles(TYPINGS_SRC_FILES); - loadTestFiles(TYPINGS_DTS_FILES); - const bundle = makeTestBundleProgram(getRootFiles(TYPINGS_SRC_FILES)[0]); - const dts = makeTestDtsBundleProgram(getRootFiles(TYPINGS_DTS_FILES)[0], _('/ep')); - const host = createHost( - bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle, dts)); - - const class2 = getDeclaration( - bundle.program, _('/ep/src/class2.js'), 'Class2', isNamedVariableDeclaration); - const class2DtsDeclaration = host.getDtsDeclaration(class2); - expect(class2DtsDeclaration!.getSourceFile().fileName) - .toEqual(_('/ep/typings/class2.d.ts')); - - const internalClass2 = getDeclaration( - bundle.program, _('/ep/src/internal.js'), 'Class2', isNamedVariableDeclaration); - const internalClass2DtsDeclaration = host.getDtsDeclaration(internalClass2); - expect(internalClass2DtsDeclaration!.getSourceFile().fileName) - .toEqual(_('/ep/typings/internal.d.ts')); - }); - }); - - describe('getInternalNameOfClass()', () => { - it('should return the name of the inner class declaration', () => { - loadTestFiles([SIMPLE_CLASS_FILE]); - const bundle = makeTestBundleProgram(SIMPLE_CLASS_FILE.name); - const host = - createHost(bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle)); - - const emptyClass = getDeclaration( - bundle.program, SIMPLE_CLASS_FILE.name, 'EmptyClass', isNamedVariableDeclaration); - expect(host.getInternalNameOfClass(emptyClass).text).toEqual('EmptyClass'); - - const class1 = getDeclaration( - bundle.program, SIMPLE_CLASS_FILE.name, 'OuterClass1', isNamedVariableDeclaration); - expect(host.getInternalNameOfClass(class1).text).toEqual('InnerClass1'); - - const class2 = getDeclaration( - bundle.program, SIMPLE_CLASS_FILE.name, 'OuterClass2', isNamedVariableDeclaration); - expect(host.getInternalNameOfClass(class2).text).toEqual('InnerClass2'); - - const childClass = getDeclaration( - bundle.program, SIMPLE_CLASS_FILE.name, 'ChildClass', isNamedVariableDeclaration); - expect(host.getInternalNameOfClass(childClass).text).toEqual('InnerChildClass'); - }); - }); - - describe('getAdjacentNameOfClass()', () => { - it('should return the name of the inner class declaration', () => { - loadTestFiles([SIMPLE_CLASS_FILE]); - const bundle = makeTestBundleProgram(SIMPLE_CLASS_FILE.name); - const host = - createHost(bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle)); - - const emptyClass = getDeclaration( - bundle.program, SIMPLE_CLASS_FILE.name, 'EmptyClass', isNamedVariableDeclaration); - expect(host.getAdjacentNameOfClass(emptyClass).text).toEqual('EmptyClass'); - - const class1 = getDeclaration( - bundle.program, SIMPLE_CLASS_FILE.name, 'OuterClass1', isNamedVariableDeclaration); - expect(host.getAdjacentNameOfClass(class1).text).toEqual('InnerClass1'); - - const class2 = getDeclaration( - bundle.program, SIMPLE_CLASS_FILE.name, 'OuterClass2', isNamedVariableDeclaration); - expect(host.getAdjacentNameOfClass(class2).text).toEqual('InnerClass2'); - - const childClass = getDeclaration( - bundle.program, SIMPLE_CLASS_FILE.name, 'ChildClass', isNamedVariableDeclaration); - expect(host.getAdjacentNameOfClass(childClass).text).toEqual('InnerChildClass'); - }); - }); - }); - }); -}); diff --git a/packages/compiler-cli/ngcc/test/host/esm2015_host_import_helper_spec.ts b/packages/compiler-cli/ngcc/test/host/esm2015_host_import_helper_spec.ts deleted file mode 100644 index 31d8c5d96e5..00000000000 --- a/packages/compiler-cli/ngcc/test/host/esm2015_host_import_helper_spec.ts +++ /dev/null @@ -1,584 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ - -import ts from 'typescript'; - -import {absoluteFrom, getFileSystem, getSourceFileOrError} from '../../../src/ngtsc/file_system'; -import {runInEachFileSystem, TestFile} from '../../../src/ngtsc/file_system/testing'; -import {MockLogger} from '../../../src/ngtsc/logging/testing'; -import {ClassMemberKind, isNamedVariableDeclaration, TypeValueReferenceKind} from '../../../src/ngtsc/reflection'; -import {getDeclaration, loadFakeCore, loadTestFiles, loadTsLib} from '../../../src/ngtsc/testing'; -import {Esm2015ReflectionHost} from '../../src/host/esm2015_host'; -import {convertToDirectTsLibImport, convertToInlineTsLib, makeTestBundleProgram} from '../helpers/utils'; - -import {expectTypeValueReferencesForParameters} from './util'; - -runInEachFileSystem(() => { - describe('Fesm2015ReflectionHost [import helper style]', () => { - let _: typeof absoluteFrom; - let FILES: {[label: string]: TestFile[]}; - - beforeEach(() => { - _ = absoluteFrom; - const NAMESPACED_IMPORT_FILES = [ - { - name: _('/some_directive.js'), - contents: ` - import * as tslib_1 from 'tslib'; - import { Directive, Inject, InjectionToken, Input } from '@angular/core'; - const INJECTED_TOKEN = new InjectionToken('injected'); - class ViewContainerRef { - } - class TemplateRef { - } - let SomeDirective = class SomeDirective { - constructor(_viewContainer, _template, injected) { - this.instanceProperty = 'instance'; - this.input1 = ''; - this.input2 = 0; - } - instanceMethod() { } - static staticMethod() { } - }; - SomeDirective.staticProperty = 'static'; - tslib_1.__decorate([ - Input(), - tslib_1.__metadata("design:type", String) - ], SomeDirective.prototype, "input1", void 0); - tslib_1.__decorate([ - Input(), - tslib_1.__metadata("design:type", Number) - ], SomeDirective.prototype, "input2", void 0); - SomeDirective = tslib_1.__decorate([ - Directive({ selector: '[someDirective]' }), - tslib_1.__param(2, Inject(INJECTED_TOKEN)), - tslib_1.__metadata("design:paramtypes", [ViewContainerRef, - TemplateRef, String]) - ], SomeDirective); - export { SomeDirective }; - `, - }, - { - name: _('/some_directive_ctor_parameters.js'), - contents: ` - import * as tslib_1 from 'tslib'; - import { Directive, Inject, InjectionToken, Input } from '@angular/core'; - const INJECTED_TOKEN = new InjectionToken('injected'); - class ViewContainerRef { - } - class TemplateRef { - } - let SomeDirective = class SomeDirective { - constructor(_viewContainer, _template, injected) { - this.input1 = ''; - } - }; - SomeDirective.ctorParameters = () => [ - { type: ViewContainerRef, }, - { type: TemplateRef, }, - { type: undefined, decorators: [{ type: Inject, args: [INJECTED_TOKEN,] },] }, - ]; - tslib_1.__decorate([ - Input(), - ], SomeDirective.prototype, "input1", void 0); - SomeDirective = tslib_1.__decorate([ - Directive({ selector: '[someDirective]' }), - tslib_1.__param(2, Inject(INJECTED_TOKEN)), - ], SomeDirective); - export { SomeDirective }; - `, - }, - { - name: _('/some_directive_ctor_parameters_iife.js'), - contents: ` - import * as tslib_1 from 'tslib'; - import { Directive, Inject, InjectionToken, Input } from '@angular/core'; - const INJECTED_TOKEN = new InjectionToken('injected'); - let ViewContainerRef = /** class */ (() => { class ViewContainerRef {} return ViewContainerRef; })(); - let TemplateRef = /** class */ (() => { class TemplateRef {} return TemplateRef; })(); - let SomeDirective = /** @class */ (() => { - let SomeDirective = class SomeDirective { - constructor(_viewContainer, _template, injected) { - this.input1 = ''; - } - }; - SomeDirective.ctorParameters = () => [ - { type: ViewContainerRef, }, - { type: TemplateRef, }, - { type: undefined, decorators: [{ type: Inject, args: [INJECTED_TOKEN,] },] }, - ]; - tslib_1.__decorate([ - Input(), - ], SomeDirective.prototype, "input1", void 0); - SomeDirective = tslib_1.__decorate([ - Directive({ selector: '[someDirective]' }), - tslib_1.__param(2, Inject(INJECTED_TOKEN)), - ], SomeDirective); - })(); - export { SomeDirective }; - `, - }, - { - name: _('/node_modules/@angular/core/some_directive.js'), - contents: ` - import * as tslib_1 from 'tslib'; - import { Directive, Input } from './directives'; - let SomeDirective = class SomeDirective { - constructor() { this.input1 = ''; } - }; - tslib_1.__decorate([ - Input(), - tslib_1.__metadata("design:type", String) - ], SomeDirective.prototype, "input1", void 0); - SomeDirective = tslib_1.__decorate([ - Directive({ selector: '[someDirective]' }), - ], SomeDirective); - export { SomeDirective }; - `, - }, - { - name: _('/ngmodule.js'), - contents: ` - import * as tslib_1 from 'tslib'; - import { NgModule } from './directives'; - var HttpClientXsrfModule_1; - let HttpClientXsrfModule = HttpClientXsrfModule_1 = class HttpClientXsrfModule { - static withOptions(options = {}) { - return { - ngModule: HttpClientXsrfModule_1, - providers: [], - }; - } - }; - HttpClientXsrfModule.staticProperty = 'static'; - HttpClientXsrfModule = HttpClientXsrfModule_1 = tslib_1.__decorate([ - NgModule({ - providers: [], - }) - ], HttpClientXsrfModule); - let missingValue; - let nonDecoratedVar; - nonDecoratedVar = 43; - export { HttpClientXsrfModule }; - ` - }, - ]; - - const DIRECT_IMPORT_FILES = convertToDirectTsLibImport(NAMESPACED_IMPORT_FILES); - const INLINE_FILES = convertToInlineTsLib(NAMESPACED_IMPORT_FILES); - const INLINE_SUFFIXED_FILES = convertToInlineTsLib(NAMESPACED_IMPORT_FILES, '$2'); - - FILES = { - 'namespaced': NAMESPACED_IMPORT_FILES, - 'direct import': DIRECT_IMPORT_FILES, - 'inline': INLINE_FILES, - 'inline suffixed': INLINE_SUFFIXED_FILES, - }; - }); - - ['namespaced', 'direct import', 'inline', 'inline suffixed'].forEach(label => { - describe(`[${label}]`, () => { - beforeEach(() => { - const fs = getFileSystem(); - loadTsLib(fs); - loadFakeCore(fs); - loadTestFiles(FILES[label]); - }); - - describe('getDecoratorsOfDeclaration()', () => { - it('should find the decorators on a class', () => { - const bundle = makeTestBundleProgram(_('/some_directive.js')); - const host = new Esm2015ReflectionHost(new MockLogger(), false, bundle); - const classNode = getDeclaration( - bundle.program, _('/some_directive.js'), 'SomeDirective', - isNamedVariableDeclaration); - const decorators = host.getDecoratorsOfDeclaration(classNode)!; - - expect(decorators).toBeDefined(); - expect(decorators.length).toEqual(1); - - const decorator = decorators[0]; - expect(decorator.name).toEqual('Directive'); - expect(decorator.identifier!.getText()).toEqual('Directive'); - expect(decorator.import).toEqual({name: 'Directive', from: '@angular/core'}); - expect(decorator.args!.map(arg => arg.getText())).toEqual([ - '{ selector: \'[someDirective]\' }', - ]); - }); - - it('should find the decorators on a class when mixing `ctorParameters` and `__decorate`', - () => { - const bundle = makeTestBundleProgram(_('/some_directive_ctor_parameters.js')); - const host = new Esm2015ReflectionHost(new MockLogger(), false, bundle); - const classNode = getDeclaration( - bundle.program, _('/some_directive_ctor_parameters.js'), 'SomeDirective', - isNamedVariableDeclaration); - const decorators = host.getDecoratorsOfDeclaration(classNode)!; - - expect(decorators).toBeDefined(); - expect(decorators.length).toEqual(1); - - const decorator = decorators[0]; - expect(decorator.name).toEqual('Directive'); - expect(decorator.identifier!.getText()).toEqual('Directive'); - expect(decorator.import).toEqual({name: 'Directive', from: '@angular/core'}); - expect(decorator.args!.map(arg => arg.getText())).toEqual([ - '{ selector: \'[someDirective]\' }', - ]); - }); - - it('should find the decorators on an IIFE wrapped class when mixing `ctorParameters` and `__decorate`', - () => { - const bundle = makeTestBundleProgram(_('/some_directive_ctor_parameters_iife.js')); - const host = new Esm2015ReflectionHost(new MockLogger(), false, bundle); - const classNode = getDeclaration( - bundle.program, _('/some_directive_ctor_parameters_iife.js'), 'SomeDirective', - isNamedVariableDeclaration); - const decorators = host.getDecoratorsOfDeclaration(classNode)!; - - expect(decorators).toBeDefined(); - expect(decorators.length).toEqual(1); - - const decorator = decorators[0]; - expect(decorator.name).toEqual('Directive'); - expect(decorator.identifier!.getText()).toEqual('Directive'); - expect(decorator.import).toEqual({name: 'Directive', from: '@angular/core'}); - expect(decorator.args!.map(arg => arg.getText())).toEqual([ - '{ selector: \'[someDirective]\' }', - ]); - }); - - it('should support decorators being used inside @angular/core', () => { - const bundle = - makeTestBundleProgram(_('/node_modules/@angular/core/some_directive.js')); - const host = new Esm2015ReflectionHost(new MockLogger(), true, bundle); - const classNode = getDeclaration( - bundle.program, _('/node_modules/@angular/core/some_directive.js'), 'SomeDirective', - isNamedVariableDeclaration); - const decorators = host.getDecoratorsOfDeclaration(classNode)!; - - expect(decorators).toBeDefined(); - expect(decorators.length).toEqual(1); - - const decorator = decorators[0]; - expect(decorator.name).toEqual('Directive'); - expect(decorator.identifier!.getText()).toEqual('Directive'); - expect(decorator.import).toEqual({name: 'Directive', from: './directives'}); - expect(decorator.args!.map(arg => arg.getText())).toEqual([ - '{ selector: \'[someDirective]\' }', - ]); - }); - }); - - describe('getMembersOfClass()', () => { - it('should find decorated members on a class', () => { - const bundle = makeTestBundleProgram(_('/some_directive.js')); - const host = new Esm2015ReflectionHost(new MockLogger(), false, bundle); - const classNode = getDeclaration( - bundle.program, _('/some_directive.js'), 'SomeDirective', - isNamedVariableDeclaration); - const members = host.getMembersOfClass(classNode); - - const input1 = members.find(member => member.name === 'input1')!; - expect(input1.kind).toEqual(ClassMemberKind.Property); - expect(input1.isStatic).toEqual(false); - expect(input1.decorators!.map(d => d.name)).toEqual(['Input']); - - const input2 = members.find(member => member.name === 'input2')!; - expect(input2.kind).toEqual(ClassMemberKind.Property); - expect(input2.isStatic).toEqual(false); - expect(input1.decorators!.map(d => d.name)).toEqual(['Input']); - }); - - it('should find decorated members on a class when mixing `ctorParameters` and `__decorate`', - () => { - const bundle = makeTestBundleProgram(_('/some_directive_ctor_parameters.js')); - const host = new Esm2015ReflectionHost(new MockLogger(), false, bundle); - const classNode = getDeclaration( - bundle.program, _('/some_directive_ctor_parameters.js'), 'SomeDirective', - isNamedVariableDeclaration); - const members = host.getMembersOfClass(classNode); - - const input1 = members.find(member => member.name === 'input1')!; - expect(input1.kind).toEqual(ClassMemberKind.Property); - expect(input1.isStatic).toEqual(false); - expect(input1.decorators!.map(d => d.name)).toEqual(['Input']); - }); - - it('should find decorated members on an IIFE wrapped class when mixing `ctorParameters` and `__decorate`', - () => { - const bundle = makeTestBundleProgram(_('/some_directive_ctor_parameters_iife.js')); - const host = new Esm2015ReflectionHost(new MockLogger(), false, bundle); - const classNode = getDeclaration( - bundle.program, _('/some_directive_ctor_parameters_iife.js'), 'SomeDirective', - isNamedVariableDeclaration); - const members = host.getMembersOfClass(classNode); - - const input1 = members.find(member => member.name === 'input1')!; - expect(input1.kind).toEqual(ClassMemberKind.Property); - expect(input1.isStatic).toEqual(false); - expect(input1.decorators!.map(d => d.name)).toEqual(['Input']); - }); - - it('should find non decorated properties on a class', () => { - const bundle = makeTestBundleProgram(_('/some_directive.js')); - const host = new Esm2015ReflectionHost(new MockLogger(), false, bundle); - const classNode = getDeclaration( - bundle.program, _('/some_directive.js'), 'SomeDirective', - isNamedVariableDeclaration); - const members = host.getMembersOfClass(classNode); - - const instanceProperty = members.find(member => member.name === 'instanceProperty')!; - expect(instanceProperty.kind).toEqual(ClassMemberKind.Property); - expect(instanceProperty.isStatic).toEqual(false); - expect(ts.isBinaryExpression(instanceProperty.implementation!)).toEqual(true); - expect(instanceProperty.value!.getText()).toEqual(`'instance'`); - }); - - it('should find static methods on a class', () => { - const bundle = makeTestBundleProgram(_('/some_directive.js')); - const host = new Esm2015ReflectionHost(new MockLogger(), false, bundle); - const classNode = getDeclaration( - bundle.program, _('/some_directive.js'), 'SomeDirective', - isNamedVariableDeclaration); - const members = host.getMembersOfClass(classNode); - - const staticMethod = members.find(member => member.name === 'staticMethod')!; - expect(staticMethod.kind).toEqual(ClassMemberKind.Method); - expect(staticMethod.isStatic).toEqual(true); - expect(ts.isMethodDeclaration(staticMethod.implementation!)).toEqual(true); - }); - - it('should find static properties on a class', () => { - const bundle = makeTestBundleProgram(_('/some_directive.js')); - const host = new Esm2015ReflectionHost(new MockLogger(), false, bundle); - const classNode = getDeclaration( - bundle.program, _('/some_directive.js'), 'SomeDirective', - isNamedVariableDeclaration); - - const members = host.getMembersOfClass(classNode); - const staticProperty = members.find(member => member.name === 'staticProperty')!; - expect(staticProperty.kind).toEqual(ClassMemberKind.Property); - expect(staticProperty.isStatic).toEqual(true); - expect(ts.isPropertyAccessExpression(staticProperty.implementation!)).toEqual(true); - expect(staticProperty.value!.getText()).toEqual(`'static'`); - }); - - it('should find static properties on a class that has an intermediate variable assignment', - () => { - const bundle = makeTestBundleProgram(_('/ngmodule.js')); - const host = new Esm2015ReflectionHost(new MockLogger(), false, bundle); - const classNode = getDeclaration( - bundle.program, _('/ngmodule.js'), 'HttpClientXsrfModule', - isNamedVariableDeclaration); - - const members = host.getMembersOfClass(classNode); - const staticProperty = members.find(member => member.name === 'staticProperty')!; - expect(staticProperty.kind).toEqual(ClassMemberKind.Property); - expect(staticProperty.isStatic).toEqual(true); - expect(ts.isPropertyAccessExpression(staticProperty.implementation!)).toEqual(true); - expect(staticProperty.value!.getText()).toEqual(`'static'`); - }); - - it('should support decorators being used inside @angular/core', () => { - const bundle = - makeTestBundleProgram(_('/node_modules/@angular/core/some_directive.js')); - const host = new Esm2015ReflectionHost(new MockLogger(), true, bundle); - const classNode = getDeclaration( - bundle.program, _('/node_modules/@angular/core/some_directive.js'), 'SomeDirective', - isNamedVariableDeclaration); - const members = host.getMembersOfClass(classNode); - - const input1 = members.find(member => member.name === 'input1')!; - expect(input1.kind).toEqual(ClassMemberKind.Property); - expect(input1.isStatic).toEqual(false); - expect(input1.decorators!.map(d => d.name)).toEqual(['Input']); - }); - }); - - describe('getConstructorParameters', () => { - it('should find the decorated constructor parameters', () => { - const bundle = makeTestBundleProgram(_('/some_directive.js')); - const host = new Esm2015ReflectionHost(new MockLogger(), false, bundle); - const classNode = getDeclaration( - bundle.program, _('/some_directive.js'), 'SomeDirective', - isNamedVariableDeclaration); - const parameters = host.getConstructorParameters(classNode); - - expect(parameters).toBeDefined(); - expect(parameters!.map(parameter => parameter.name)).toEqual([ - '_viewContainer', '_template', 'injected' - ]); - expectTypeValueReferencesForParameters(parameters!, [ - 'ViewContainerRef', - 'TemplateRef', - 'String', - ]); - }); - - it('should find the decorated constructor parameters when mixing `ctorParameters` and `__decorate`', - () => { - const bundle = makeTestBundleProgram(_('/some_directive_ctor_parameters.js')); - const host = new Esm2015ReflectionHost(new MockLogger(), false, bundle); - const classNode = getDeclaration( - bundle.program, _('/some_directive_ctor_parameters.js'), 'SomeDirective', - isNamedVariableDeclaration); - const parameters = host.getConstructorParameters(classNode); - - expect(parameters).toBeDefined(); - expect(parameters!.map(parameter => parameter.name)).toEqual([ - '_viewContainer', '_template', 'injected' - ]); - expectTypeValueReferencesForParameters(parameters!, [ - 'ViewContainerRef', - 'TemplateRef', - null, - ]); - - const decorators = parameters![2].decorators!; - expect(decorators.length).toEqual(1); - expect(decorators[0].name).toBe('Inject'); - expect(decorators[0].import!.from).toBe('@angular/core'); - expect(decorators[0].import!.name).toBe('Inject'); - }); - }); - - it('should find the decorated constructor parameters on an IIFE wrapped class when mixing `ctorParameters` and `__decorate`', - () => { - const bundle = makeTestBundleProgram(_('/some_directive_ctor_parameters_iife.js')); - const host = new Esm2015ReflectionHost(new MockLogger(), false, bundle); - const classNode = getDeclaration( - bundle.program, _('/some_directive_ctor_parameters_iife.js'), 'SomeDirective', - isNamedVariableDeclaration); - const parameters = host.getConstructorParameters(classNode); - - expect(parameters).toBeDefined(); - expect(parameters!.map(parameter => parameter.name)).toEqual([ - '_viewContainer', '_template', 'injected' - ]); - expectTypeValueReferencesForParameters(parameters!, [ - 'ViewContainerRef', - 'TemplateRef', - null, - ]); - - const decorators = parameters![2].decorators!; - expect(decorators.length).toEqual(1); - expect(decorators[0].name).toBe('Inject'); - expect(decorators[0].import!.from).toBe('@angular/core'); - expect(decorators[0].import!.name).toBe('Inject'); - }); - - describe('getDeclarationOfIdentifier', () => { - it('should return the declaration of a locally defined identifier', () => { - const bundle = makeTestBundleProgram(_('/some_directive.js')); - const host = new Esm2015ReflectionHost(new MockLogger(), false, bundle); - const classNode = getDeclaration( - bundle.program, _('/some_directive.js'), 'SomeDirective', - isNamedVariableDeclaration); - const ctrDecorators = host.getConstructorParameters(classNode)!; - const identifierOfViewContainerRef = (ctrDecorators[0].typeValueReference! as { - kind: TypeValueReferenceKind.LOCAL, - expression: ts.Identifier, - defaultImportStatement: null, - }).expression; - - const expectedDeclarationNode = getDeclaration( - bundle.program, _('/some_directive.js'), 'ViewContainerRef', ts.isClassDeclaration); - const actualDeclaration = host.getDeclarationOfIdentifier(identifierOfViewContainerRef); - expect(actualDeclaration).not.toBe(null); - expect(actualDeclaration!.node).toBe(expectedDeclarationNode); - expect(actualDeclaration!.viaModule).toBe(null); - }); - - it('should return the declaration of an externally defined identifier', () => { - const bundle = makeTestBundleProgram(_('/some_directive.js')); - const host = new Esm2015ReflectionHost(new MockLogger(), false, bundle); - const classNode = getDeclaration( - bundle.program, _('/some_directive.js'), 'SomeDirective', - isNamedVariableDeclaration); - const classDecorators = host.getDecoratorsOfDeclaration(classNode)!; - const decoratorNode = classDecorators[0].node!; - const identifierOfDirective = - ts.isCallExpression(decoratorNode) && ts.isIdentifier(decoratorNode.expression) ? - decoratorNode.expression : - null; - - const expectedDeclarationNode = getDeclaration( - bundle.program, _('/node_modules/@angular/core/index.d.ts'), 'Directive', - isNamedVariableDeclaration); - const actualDeclaration = host.getDeclarationOfIdentifier(identifierOfDirective!); - expect(actualDeclaration).not.toBe(null); - expect(actualDeclaration!.node).toBe(expectedDeclarationNode); - expect(actualDeclaration!.viaModule).toBe('@angular/core'); - }); - }); - - describe('getVariableValue', () => { - it('should find the "actual" declaration of an aliased variable identifier', () => { - const bundle = makeTestBundleProgram(_('/ngmodule.js')); - const host = new Esm2015ReflectionHost(new MockLogger(), false, bundle); - const ngModuleRef = findVariableDeclaration( - getSourceFileOrError(bundle.program, _('/ngmodule.js')), 'HttpClientXsrfModule_1'); - - const value = host.getVariableValue(ngModuleRef!); - expect(value).not.toBe(null); - if (!value || !ts.isClassExpression(value)) { - throw new Error( - `Expected value to be a class expression: ${value && value.getText()}.`); - } - expect(value.name!.text).toBe('HttpClientXsrfModule'); - }); - - it('should return null if the variable has no assignment', () => { - const bundle = makeTestBundleProgram(_('/ngmodule.js')); - const host = new Esm2015ReflectionHost(new MockLogger(), false, bundle); - const missingValue = findVariableDeclaration( - getSourceFileOrError(bundle.program, _('/ngmodule.js')), 'missingValue'); - const value = host.getVariableValue(missingValue!); - expect(value).toBe(null); - }); - - it('should return null if the variable is not assigned from a call to __decorate', () => { - const bundle = makeTestBundleProgram(_('/ngmodule.js')); - const host = new Esm2015ReflectionHost(new MockLogger(), false, bundle); - const nonDecoratedVar = findVariableDeclaration( - getSourceFileOrError(bundle.program, _('/ngmodule.js')), 'nonDecoratedVar'); - const value = host.getVariableValue(nonDecoratedVar!); - expect(value).toBe(null); - }); - }); - - describe('getEndOfClass()', () => { - it('should return the last statement related to the class', () => { - const bundle = makeTestBundleProgram(_('/ngmodule.js')); - const host = new Esm2015ReflectionHost(new MockLogger(), false, bundle); - const classSymbol = - host.findClassSymbols(bundle.program.getSourceFile(_('/ngmodule.js'))!)[0]; - const endOfClass = host.getEndOfClass(classSymbol); - expect(endOfClass.getText()) - .toMatch( - /HttpClientXsrfModule = HttpClientXsrfModule_1 = .*__decorate.*\(\[\n\s*NgModule\(\{\n\s*providers: \[],\n\s*}\)\n\s*], HttpClientXsrfModule\);/); - }); - }); - }); - }); - - function findVariableDeclaration( - node: ts.Node|undefined, variableName: string): ts.VariableDeclaration|undefined { - if (!node) { - return; - } - if (ts.isVariableDeclaration(node) && ts.isIdentifier(node.name) && - node.name.text === variableName) { - return node; - } - return node.forEachChild(node => findVariableDeclaration(node, variableName)); - } - }); -}); diff --git a/packages/compiler-cli/ngcc/test/host/esm2015_host_spec.ts b/packages/compiler-cli/ngcc/test/host/esm2015_host_spec.ts deleted file mode 100644 index fff33c75b97..00000000000 --- a/packages/compiler-cli/ngcc/test/host/esm2015_host_spec.ts +++ /dev/null @@ -1,2696 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ - -import ts from 'typescript'; - -import {absoluteFrom, getFileSystem, getSourceFileOrError} from '../../../src/ngtsc/file_system'; -import {runInEachFileSystem, TestFile} from '../../../src/ngtsc/file_system/testing'; -import {MockLogger} from '../../../src/ngtsc/logging/testing'; -import {ClassMemberKind, ConcreteDeclaration, CtorParameter, DownleveledEnum, isNamedClassDeclaration, isNamedFunctionDeclaration, isNamedVariableDeclaration, TypeScriptReflectionHost} from '../../../src/ngtsc/reflection'; -import {getDeclaration, loadFakeCore, loadTestFiles, walkForDeclarations} from '../../../src/ngtsc/testing'; -import {DelegatingReflectionHost} from '../../src/host/delegating_host'; -import {Esm2015ReflectionHost} from '../../src/host/esm2015_host'; -import {BundleProgram} from '../../src/packages/bundle_program'; -import {getRootFiles, makeTestBundleProgram, makeTestDtsBundleProgram} from '../helpers/utils'; - -import {expectTypeValueReferencesForParameters} from './util'; - -runInEachFileSystem(() => { - describe('Esm2015ReflectionHost', () => { - let _: typeof absoluteFrom; - - let SOME_DIRECTIVE_FILE: TestFile; - let CTOR_DECORATORS_ARRAY_FILE: TestFile; - let ACCESSORS_FILE: TestFile; - let SIMPLE_CLASS_FILE: TestFile; - let CLASS_EXPRESSION_FILE: TestFile; - let WRAPPED_CLASS_EXPRESSION_FILE: TestFile; - let FOO_FUNCTION_FILE: TestFile; - let INVALID_DECORATORS_FILE: TestFile; - let INVALID_DECORATOR_ARGS_FILE: TestFile; - let INVALID_PROP_DECORATORS_FILE: TestFile; - let INVALID_PROP_DECORATOR_ARGS_FILE: TestFile; - let INVALID_CTOR_DECORATORS_FILE: TestFile; - let INVALID_CTOR_DECORATOR_ARGS_FILE: TestFile; - let IMPORTS_FILES: TestFile[]; - let EXPORTS_FILES: TestFile[]; - let FUNCTION_BODY_FILE: TestFile; - let DECORATED_FILES: TestFile[]; - let ARITY_CLASSES: TestFile[]; - let TYPINGS_SRC_FILES: TestFile[]; - let TYPINGS_DTS_FILES: TestFile[]; - let NAMESPACED_IMPORT_FILE: TestFile; - let INDEX_SIGNATURE_PROP_FILE: TestFile; - - // Helpers - const createHost = (bundle: BundleProgram, ngccHost: Esm2015ReflectionHost) => { - const tsHost = new TypeScriptReflectionHost(bundle.program.getTypeChecker()); - return new DelegatingReflectionHost(tsHost, ngccHost); - }; - - beforeEach(() => { - _ = absoluteFrom; - - SOME_DIRECTIVE_FILE = { - name: _('/some_directive.js'), - contents: ` - import { Directive, Inject, InjectionToken, Input, HostListener, HostBinding, ViewContainerRef, TemplateRef } from '@angular/core'; - - const INJECTED_TOKEN = new InjectionToken('injected'); - const TestToken = {}; - - class SomeDirective { - constructor(_viewContainer, _template, injected) { - this.instanceProperty = 'instance'; - } - instanceMethod() {} - - onClick() {} - - @HostBinding('class.foo') - get isClassFoo() { return false; } - - static staticMethod() {} - } - SomeDirective.staticProperty = 'static'; - SomeDirective.decorators = [ - { type: Directive, args: [{ selector: '[someDirective]' },] } - ]; - SomeDirective.ctorParameters = () => [ - { type: ViewContainerRef, }, - { type: TemplateRef, }, - { type: undefined, decorators: [{ type: Inject, args: [INJECTED_TOKEN,] },] }, - ]; - SomeDirective.propDecorators = { - "input1": [{ type: Input },], - "input2": [{ type: Input },], - "target": [{ type: HostBinding, args: ['attr.target',] }, { type: Input },], - "onClick": [{ type: HostListener, args: ['click',] },], - }; - `, - }; - - CTOR_DECORATORS_ARRAY_FILE = { - name: _('/ctor_decorated_as_array.js'), - contents: ` - class CtorDecoratedAsArray { - constructor(arg1) { - } - } - CtorDecoratedAsArray.ctorParameters = [{ type: ParamType, decorators: [{ type: Inject },] }]; - `, - }; - - ACCESSORS_FILE = { - name: _('/accessors.js'), - contents: ` - import { Directive, Input, Output } from '@angular/core'; - - class SomeDirective { - set setterAndGetter(value) { this.value = value; } - get setterAndGetter() { return null; } - } - SomeDirective.decorators = [ - { type: Directive, args: [{ selector: '[someDirective]' },] } - ]; - SomeDirective.propDecorators = { - "setterAndGetter": [{ type: Input },], - }; - `, - }; - - SIMPLE_CLASS_FILE = { - name: _('/simple_class.js'), - contents: ` - class EmptyClass {} - class NoDecoratorConstructorClass { - constructor(foo) {} - } - let SimpleWrappedClass = /** @class */ (() => { - class SimpleWrappedClassInner {} - return SimpleWrappedClassInner; - })(); - let AliasedWrappedClass = /** @class */ (() => { - let AliasedWrappedClassAdjacent = class AliasedWrappedClassInner {}; - return AliasWrappedClassAdjacent; - })(); - `, - }; - - CLASS_EXPRESSION_FILE = { - name: _('/class_expression.js'), - contents: ` - import {Directive} from '@angular/core'; - var AliasedClass_1; - let EmptyClass = class EmptyClass {}; - let AliasedClass = AliasedClass_1 = class AliasedClass {} - AliasedClass.decorators = [ - { type: Directive, args: [{ selector: '[someDirective]' },] } - ]; - let usageOfAliasedClass = AliasedClass_1; - `, - }; - - WRAPPED_CLASS_EXPRESSION_FILE = { - name: _('/wrapped_class_expression.js'), - contents: ` - import {Directive} from '@angular/core'; - var AliasedWrappedClass_1; - let SimpleWrappedClass = /** @class */ (() => { - class SimpleWrappedClass {} - return SimpleWrappedClass; - })(); - let AliasedWrappedClass = AliasedWrappedClass_1 = /** @class */ (() => { - class AliasedWrappedClass {} - AliasedWrappedClass.decorators = [ - { type: Directive, args: [{ selector: '[someDirective]' },] } - ]; - return AliasedWrappedClass; - })(); - let usageOfWrappedClass = AliasedWrappedClass_1; - let DecoratedWrappedClass = /** @class */ (() => { - var DecoratedWrappedClass_1; - let AdjacentDecoratedWrappedClass = DecoratedWrappedClass_1 = class InnerDecoratedWrappedClass { - static forRoot() { - return new DecoratedWrappedClass_1(); - } - }; - AdjacentDecoratedWrappedClass = DecoratedWrappedClass_1 = __decorate([ - Decorator() - ], AdjacentDecoratedWrappedClass); - return AdjacentDecoratedWrappedClass; - })(); - `, - }; - - FOO_FUNCTION_FILE = { - name: _('/foo_function.js'), - contents: ` - import { Directive } from '@angular/core'; - - function foo() {} - foo.decorators = [ - { type: Directive, args: [{ selector: '[ignored]' },] } - ]; - `, - }; - - INVALID_DECORATORS_FILE = { - name: _('/invalid_decorators.js'), - contents: ` - import {Directive} from '@angular/core'; - class NotArrayLiteral { - } - NotArrayLiteral.decorators = () => [ - { type: Directive, args: [{ selector: '[ignored]' },] }, - ]; - - class NotObjectLiteral { - } - NotObjectLiteral.decorators = [ - "This is not an object literal", - { type: Directive }, - ]; - - class NoTypeProperty { - } - NoTypeProperty.decorators = [ - { notType: Directive }, - { type: Directive }, - ]; - - class NotIdentifier { - } - NotIdentifier.decorators = [ - { type: 'StringsLiteralsAreNotIdentifiers' }, - { type: Directive }, - ]; - `, - }; - - INVALID_DECORATOR_ARGS_FILE = { - name: _('/invalid_decorator_args.js'), - contents: ` - import {Directive} from '@angular/core'; - class NoArgsProperty { - } - NoArgsProperty.decorators = [ - - { type: Directive }, - ]; - - const args = [{ selector: '[ignored]' },]; - class NoPropertyAssignment { - } - NoPropertyAssignment.decorators = [ - { type: Directive, args }, - ]; - - class NotArrayLiteral { - } - NotArrayLiteral.decorators = [ - { type: Directive, args: () => [{ selector: '[ignored]' },] }, - ]; - `, - }; - - INVALID_PROP_DECORATORS_FILE = { - name: _('/invalid_prop_decorators.js'), - contents: ` - import {Input} from '@angular/core'; - class NotObjectLiteral { - } - NotObjectLiteral.propDecorators = () => ({ - "prop": [{ type: Input },] - }); - - class NotObjectLiteralProp { - } - NotObjectLiteralProp.propDecorators = { - "prop": [ - "This is not an object literal", - { type: Input }, - ] - }; - - class NoTypeProperty { - } - NoTypeProperty.propDecorators = { - "prop": [ - { notType: Input }, - { type: Input }, - ] - }; - - class NotIdentifier { - } - NotIdentifier.propDecorators = { - "prop": [ - { type: 'StringsLiteralsAreNotIdentifiers' }, - { type: Input }, - ] - }; - `, - }; - - INVALID_PROP_DECORATOR_ARGS_FILE = { - name: _('/invalid_prop_decorator_args.js'), - contents: ` - import {Input} from '@angular/core'; - class NoArgsProperty { - } - NoArgsProperty.propDecorators = { - "prop": [{ type: Input },] - }; - - const args = [{ selector: '[ignored]' },]; - class NoPropertyAssignment { - } - NoPropertyAssignment.propDecorators = { - "prop": [{ type: Input, args },] - }; - - class NotArrayLiteral { - } - NotArrayLiteral.propDecorators = { - "prop": [{ type: Input, args: () => [{ selector: '[ignored]' },] },], - }; - `, - }; - - INVALID_CTOR_DECORATORS_FILE = { - name: _('/invalid_ctor_decorators.js'), - contents: ` - import {Inject} from '@angular/core'; - class NoParameters { - constructor() { - } - } - - const NotFromCoreDecorator = {}; - class NotFromCore { - constructor(arg1) { - } - } - NotFromCore.ctorParameters = () => [ - { type: 'ParamType', decorators: [{ type: NotFromCoreDecorator },] }, - ] - - class NotArrowFunction { - constructor(arg1) { - } - } - NotArrowFunction.ctorParameters = function() { - return { type: 'ParamType', decorators: [{ type: Inject },] }; - }; - - class NotArrayLiteral { - constructor(arg1) { - } - } - NotArrayLiteral.ctorParameters = () => 'StringsAreNotArrayLiterals'; - - class NotObjectLiteral { - constructor(arg1, arg2) { - } - } - NotObjectLiteral.ctorParameters = () => [ - "This is not an object literal", - { type: 'ParamType', decorators: [{ type: Inject },] }, - ]; - - class NoTypeProperty { - constructor(arg1, arg2) { - } - } - NoTypeProperty.ctorParameters = () => [ - { - type: 'ParamType', - decorators: [ - { notType: Inject }, - { type: Inject }, - ] - }, - ]; - - class NotIdentifier { - constructor(arg1, arg2) { - } - } - NotIdentifier.ctorParameters = () => [ - { - type: 'ParamType', - decorators: [ - { type: 'StringsLiteralsAreNotIdentifiers' }, - { type: Inject }, - ] - }, - ]; - `, - }; - - INVALID_CTOR_DECORATOR_ARGS_FILE = { - name: _('/invalid_ctor_decorator_args.js'), - contents: ` - import {Inject} from '@angular/core'; - class NoArgsProperty { - constructor(arg1) { - } - } - NoArgsProperty.ctorParameters = () => [ - { type: 'ParamType', decorators: [{ type: Inject },] }, - ]; - - const args = [{ selector: '[ignored]' },]; - class NoPropertyAssignment { - constructor(arg1) { - } - } - NoPropertyAssignment.ctorParameters = () => [ - { type: 'ParamType', decorators: [{ type: Inject, args },] }, - ]; - - class NotArrayLiteral { - constructor(arg1) { - } - } - NotArrayLiteral.ctorParameters = () => [ - { type: 'ParamType', decorators: [{ type: Inject, args: () => [{ selector: '[ignored]' },] },] }, - ]; - `, - }; - - IMPORTS_FILES = [ - { - name: _('/index.js'), - contents: ` - import * as a from './a'; - import * as b from './b'; - import * as c from './c'; - ` - }, - { - name: _('/a.js'), - contents: ` - export const a = 'a'; - `, - }, - { - name: _('/b.js'), - contents: ` - import {a} from './a.js'; - import {a as foo} from './a.js'; - - const b = a; - const c = foo; - const d = b; - `, - }, - ]; - - EXPORTS_FILES = [ - { - name: _('/index.js'), - contents: ` - import * as a from './a'; - import * as b from './b'; - ` - }, - { - name: _('/a.js'), - contents: ` - export const a = 'a'; - `, - }, - { - name: _('/b.js'), - contents: ` - import {Directive} from '@angular/core'; - import {a} from './a'; - import {a as foo} from './a'; - export {Directive} from '@angular/core'; - export {a} from './a'; - export const b = a; - export const c = foo; - export const d = b; - export const e = 'e'; - export const DirectiveX = Directive; - export class SomeClass {} - `, - }, - ]; - - FUNCTION_BODY_FILE = { - name: _('/function_body.js'), - contents: ` - function foo(x) { - return x; - } - function bar(x, y = 42) { - return x + y; - } - function baz(x) { - let y; - if (y === void 0) { y = 42; } - return x; - } - let y; - function qux(x) { - if (x === void 0) { y = 42; } - return y; - } - function moo() { - let x; - if (x === void 0) { x = 42; } - return x; - } - let x; - function juu() { - if (x === void 0) { x = 42; } - return x; - } - ` - }; - - DECORATED_FILES = [ - { - name: _('/primary.js'), - contents: ` - import {Directive} from '@angular/core'; - import {D} from './secondary'; - class A {} - A.decorators = [ - { type: Directive, args: [{ selector: '[a]' }] } - ]; - function x() {} - function y() {} - class B {} - B.decorators = [ - { type: Directive, args: [{ selector: '[b]' }] } - ]; - class C {} - var AliasedClass_1; - let AliasedClass = AliasedClass_1 = class AliasedClass {} - AliasedClass.decorators = [ - { type: Directive, args: [{ selector: '[aliased]' },] } - ]; - let Wrapped1 = /** @class */ (() => { - let Wrapped1 = class Wrapped1 { - }; - Wrapped1 = __decorate([ - Directive({selector: '[wrapped-1]'}) - ], Wrapped1); - return Wrapped1; - })(); - let Wrapped2 = /** @class */ (() => { - class Wrapped2 {} - Wrapped2.decorators = [ - { type: Directive, args: [{ selector: '[wrapped-2]' },] } - ]; - return Wrapped2; - })(); - export { A, x, C, AliasedClass, Wrapped1, Wrapped2 }; - ` - }, - { - name: _('/secondary.js'), - contents: ` - import {Directive} from '@angular/core'; - class D {} - D.decorators = [ - { type: Directive, args: [{ selector: '[d]' }] } - ]; - export {D}; - ` - } - ]; - - ARITY_CLASSES = [ - { - name: _('/src/class.js'), - contents: ` - export class NoTypeParam {} - export class OneTypeParam {} - export class TwoTypeParams {} - `, - }, - { - name: _('/typings/class.d.ts'), - contents: ` - export declare class NoTypeParam {} - export declare class OneTypeParam {} - export declare class TwoTypeParams {} - `, - }, - ]; - - TYPINGS_SRC_FILES = [ - { - name: _('/ep/src/index.js'), - contents: ` - import 'an_external_lib'; - import 'an_external_lib_without_typings'; - import {InternalClass} from './internal'; - import * as func1 from './func1'; - import * as missing from './missing-class'; - import * as flatFile from './flat-file'; - export * from './class1'; - export * from './class2'; - ` - }, - { - name: _('/ep/src/class1.js'), - contents: 'export class Class1 {}\nexport class MissingClass1 {}' - }, - {name: _('/ep/src/class2.js'), contents: 'export class Class2 {}'}, - {name: _('/ep/src/func1.js'), contents: 'export function mooFn() {}'}, - { - name: _('/ep/src/internal.js'), - contents: 'export class InternalClass {}\nexport class Class2 {}' - }, - {name: _('/ep/src/missing-class.js'), contents: 'export class MissingClass2 {}'}, - { - name: _('/ep/src/flat-file.js'), - contents: ` - export class Class1 {} - export class MissingClass1 {} - export class MissingClass2 {} - class SourceClass {} - export {SourceClass as AliasedClass};`, - }, - { - name: _('/ep/src/shadow-class.js'), - contents: 'export class ShadowClass {}', - }, - { - name: _('/an_external_lib_without_typings/index.js'), - contents: '// Some content.', - }, - ]; - - TYPINGS_DTS_FILES = [ - { - name: _('/ep/typings/index.d.ts'), - contents: ` - import '../../an_external_lib/index'; - import {InternalClass} from './internal'; - import {mooFn} from './func1'; - export * from './class1'; - export * from './class2'; - ` - }, - { - name: _('/ep/typings/class1.d.ts'), - contents: `export declare class Class1 {}\nexport declare class OtherClass {}` - }, - { - name: _('/ep/typings/class2.d.ts'), - contents: ` - export declare class Class2 {} - export declare interface SomeInterface {} - export {TypingsClass as AliasedClass} from './typings-class'; - ` - }, - {name: _('/ep/typings/func1.d.ts'), contents: 'export declare function mooFn(): void;'}, - { - name: _('/ep/typings/internal.d.ts'), - contents: `export declare class InternalClass {}\nexport declare class Class2 {}` - }, - { - name: _('/ep/typings/typings-class.d.ts'), - contents: `export declare class TypingsClass {}` - }, - {name: _('/ep/typings/shadow-class.d.ts'), contents: `export declare class ShadowClass {}`}, - {name: _('/an_external_lib/index.d.ts'), contents: 'export declare class ShadowClass {}'}, - ]; - - NAMESPACED_IMPORT_FILE = { - name: _('/some_directive.js'), - contents: ` - import * as core from '@angular/core'; - - class SomeDirective { - } - SomeDirective.decorators = [ - { type: core.Directive, args: [{ selector: '[someDirective]' },] } - ]; - ` - }; - - INDEX_SIGNATURE_PROP_FILE = { - name: _('/index_signature_prop.d.ts'), - contents: ` - abstract class IndexSignatureClass { - [key: string]: any; - } - `, - }; - }); - - describe('getDecoratorsOfDeclaration()', () => { - it('should find the decorators on a class', () => { - loadTestFiles([SOME_DIRECTIVE_FILE]); - const bundle = makeTestBundleProgram(SOME_DIRECTIVE_FILE.name); - const host = createHost(bundle, new Esm2015ReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, SOME_DIRECTIVE_FILE.name, 'SomeDirective', isNamedClassDeclaration); - const decorators = host.getDecoratorsOfDeclaration(classNode)!; - - expect(decorators).toBeDefined(); - expect(decorators.length).toEqual(1); - - const decorator = decorators[0]; - expect(decorator.name).toEqual('Directive'); - expect(decorator.import).toEqual({name: 'Directive', from: '@angular/core'}); - expect(decorator.args!.map(arg => arg.getText())).toEqual([ - '{ selector: \'[someDirective]\' }', - ]); - }); - - it('should find the decorators on an aliased class', () => { - loadTestFiles([CLASS_EXPRESSION_FILE]); - const bundle = makeTestBundleProgram(CLASS_EXPRESSION_FILE.name); - const host = createHost(bundle, new Esm2015ReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, CLASS_EXPRESSION_FILE.name, 'AliasedClass', isNamedVariableDeclaration); - const decorators = host.getDecoratorsOfDeclaration(classNode)!; - - expect(decorators).toBeDefined(); - expect(decorators.length).toEqual(1); - - const decorator = decorators[0]; - expect(decorator.name).toEqual('Directive'); - expect(decorator.import).toEqual({name: 'Directive', from: '@angular/core'}); - expect(decorator.args!.map(arg => arg.getText())).toEqual([ - '{ selector: \'[someDirective]\' }', - ]); - }); - - it('should find the decorators on an aliased wrapped class', () => { - loadTestFiles([WRAPPED_CLASS_EXPRESSION_FILE]); - const bundle = makeTestBundleProgram(WRAPPED_CLASS_EXPRESSION_FILE.name); - const host = createHost(bundle, new Esm2015ReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, WRAPPED_CLASS_EXPRESSION_FILE.name, 'AliasedWrappedClass', - isNamedVariableDeclaration); - const decorators = host.getDecoratorsOfDeclaration(classNode)!; - - expect(decorators).not.toBe(null!); - expect(decorators.length).toEqual(1); - - const decorator = decorators[0]; - expect(decorator.name).toEqual('Directive'); - expect(decorator.import).toEqual({name: 'Directive', from: '@angular/core'}); - expect(decorator.args!.map(arg => arg.getText())).toEqual([ - '{ selector: \'[someDirective]\' }', - ]); - }); - - it('should return null if the symbol is not a class', () => { - loadTestFiles([FOO_FUNCTION_FILE]); - const bundle = makeTestBundleProgram(FOO_FUNCTION_FILE.name); - const host = createHost(bundle, new Esm2015ReflectionHost(new MockLogger(), false, bundle)); - const functionNode = getDeclaration( - bundle.program, FOO_FUNCTION_FILE.name, 'foo', isNamedFunctionDeclaration); - const decorators = host.getDecoratorsOfDeclaration(functionNode); - expect(decorators).toBe(null); - }); - - it('should return null if there are no decorators', () => { - loadTestFiles([SIMPLE_CLASS_FILE]); - const bundle = makeTestBundleProgram(SIMPLE_CLASS_FILE.name); - const host = createHost(bundle, new Esm2015ReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, SIMPLE_CLASS_FILE.name, 'EmptyClass', isNamedClassDeclaration); - const decorators = host.getDecoratorsOfDeclaration(classNode); - expect(decorators).toBe(null); - }); - - it('should ignore `decorators` if it is not an array literal', () => { - loadTestFiles([INVALID_DECORATORS_FILE]); - const bundle = makeTestBundleProgram(INVALID_DECORATORS_FILE.name); - const host = createHost(bundle, new Esm2015ReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, INVALID_DECORATORS_FILE.name, 'NotArrayLiteral', - isNamedClassDeclaration); - const decorators = host.getDecoratorsOfDeclaration(classNode); - expect(decorators).toEqual([]); - }); - - it('should ignore decorator elements that are not object literals', () => { - loadTestFiles([INVALID_DECORATORS_FILE]); - const bundle = makeTestBundleProgram(INVALID_DECORATORS_FILE.name); - const host = createHost(bundle, new Esm2015ReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, INVALID_DECORATORS_FILE.name, 'NotObjectLiteral', - isNamedClassDeclaration); - const decorators = host.getDecoratorsOfDeclaration(classNode)!; - - expect(decorators.length).toBe(1); - expect(decorators[0]).toEqual(jasmine.objectContaining({name: 'Directive'})); - }); - - it('should ignore decorator elements that have no `type` property', () => { - loadTestFiles([INVALID_DECORATORS_FILE]); - const bundle = makeTestBundleProgram(INVALID_DECORATORS_FILE.name); - const host = createHost(bundle, new Esm2015ReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, INVALID_DECORATORS_FILE.name, 'NoTypeProperty', - isNamedClassDeclaration); - const decorators = host.getDecoratorsOfDeclaration(classNode)!; - - expect(decorators.length).toBe(1); - expect(decorators[0]).toEqual(jasmine.objectContaining({name: 'Directive'})); - }); - - it('should ignore decorator elements whose `type` value is not an identifier', () => { - loadTestFiles([INVALID_DECORATORS_FILE]); - const bundle = makeTestBundleProgram(INVALID_DECORATORS_FILE.name); - const host = createHost(bundle, new Esm2015ReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, INVALID_DECORATORS_FILE.name, 'NotIdentifier', isNamedClassDeclaration); - const decorators = host.getDecoratorsOfDeclaration(classNode)!; - - expect(decorators.length).toBe(1); - expect(decorators[0]).toEqual(jasmine.objectContaining({name: 'Directive'})); - }); - - it('should have import information on decorators', () => { - loadTestFiles([SOME_DIRECTIVE_FILE]); - const bundle = makeTestBundleProgram(SOME_DIRECTIVE_FILE.name); - const host = createHost(bundle, new Esm2015ReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, SOME_DIRECTIVE_FILE.name, 'SomeDirective', isNamedClassDeclaration); - const decorators = host.getDecoratorsOfDeclaration(classNode)!; - - expect(decorators.length).toEqual(1); - expect(decorators[0].import).toEqual({name: 'Directive', from: '@angular/core'}); - }); - - describe('(returned decorators `args`)', () => { - it('should be an empty array if decorator has no `args` property', () => { - loadTestFiles([INVALID_DECORATOR_ARGS_FILE]); - const bundle = makeTestBundleProgram(INVALID_DECORATOR_ARGS_FILE.name); - const host = - createHost(bundle, new Esm2015ReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, INVALID_DECORATOR_ARGS_FILE.name, 'NoArgsProperty', - isNamedClassDeclaration); - const decorators = host.getDecoratorsOfDeclaration(classNode)!; - - expect(decorators.length).toBe(1); - expect(decorators[0].name).toBe('Directive'); - expect(decorators[0].args).toEqual([]); - }); - - it('should be an empty array if decorator\'s `args` has no property assignment', () => { - loadTestFiles([INVALID_DECORATOR_ARGS_FILE]); - const bundle = makeTestBundleProgram(INVALID_DECORATOR_ARGS_FILE.name); - const host = - createHost(bundle, new Esm2015ReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, INVALID_DECORATOR_ARGS_FILE.name, 'NoPropertyAssignment', - isNamedClassDeclaration); - const decorators = host.getDecoratorsOfDeclaration(classNode)!; - - expect(decorators.length).toBe(1); - expect(decorators[0].name).toBe('Directive'); - expect(decorators[0].args).toEqual([]); - }); - - it('should be an empty array if `args` property value is not an array literal', () => { - loadTestFiles([INVALID_DECORATOR_ARGS_FILE]); - const bundle = makeTestBundleProgram(INVALID_DECORATOR_ARGS_FILE.name); - const host = - createHost(bundle, new Esm2015ReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, INVALID_DECORATOR_ARGS_FILE.name, 'NotArrayLiteral', - isNamedClassDeclaration); - const decorators = host.getDecoratorsOfDeclaration(classNode)!; - - expect(decorators.length).toBe(1); - expect(decorators[0].name).toBe('Directive'); - expect(decorators[0].args).toEqual([]); - }); - }); - }); - - describe('getMembersOfClass()', () => { - it('should find decorated properties on a class', () => { - loadTestFiles([SOME_DIRECTIVE_FILE]); - const bundle = makeTestBundleProgram(SOME_DIRECTIVE_FILE.name); - const host = createHost(bundle, new Esm2015ReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, SOME_DIRECTIVE_FILE.name, 'SomeDirective', isNamedClassDeclaration); - const members = host.getMembersOfClass(classNode); - - const input1 = members.find(member => member.name === 'input1')!; - expect(input1.kind).toEqual(ClassMemberKind.Property); - expect(input1.isStatic).toEqual(false); - expect(input1.decorators!.map(d => d.name)).toEqual(['Input']); - expect(input1.decorators![0].import).toEqual({name: 'Input', from: '@angular/core'}); - - const input2 = members.find(member => member.name === 'input2')!; - expect(input2.kind).toEqual(ClassMemberKind.Property); - expect(input2.isStatic).toEqual(false); - expect(input2.decorators!.map(d => d.name)).toEqual(['Input']); - expect(input2.decorators![0].import).toEqual({name: 'Input', from: '@angular/core'}); - }); - - it('should find non decorated properties on a class', () => { - loadTestFiles([SOME_DIRECTIVE_FILE]); - const bundle = makeTestBundleProgram(SOME_DIRECTIVE_FILE.name); - const host = createHost(bundle, new Esm2015ReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, SOME_DIRECTIVE_FILE.name, 'SomeDirective', isNamedClassDeclaration); - const members = host.getMembersOfClass(classNode); - - const instanceProperty = members.find(member => member.name === 'instanceProperty')!; - expect(instanceProperty.kind).toEqual(ClassMemberKind.Property); - expect(instanceProperty.isStatic).toEqual(false); - expect(ts.isBinaryExpression(instanceProperty.implementation!)).toEqual(true); - expect(instanceProperty.value!.getText()).toEqual(`'instance'`); - }); - - it('should handle equally named getter/setter pairs correctly', () => { - loadTestFiles([ACCESSORS_FILE]); - const bundle = makeTestBundleProgram(ACCESSORS_FILE.name); - const host = createHost(bundle, new Esm2015ReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, ACCESSORS_FILE.name, 'SomeDirective', isNamedClassDeclaration); - const members = host.getMembersOfClass(classNode); - - const [combinedSetter, combinedGetter] = - members.filter(member => member.name === 'setterAndGetter'); - expect(combinedSetter.kind).toEqual(ClassMemberKind.Setter); - expect(combinedSetter.isStatic).toEqual(false); - expect(ts.isSetAccessor(combinedSetter.implementation!)).toEqual(true); - expect(combinedSetter.value).toBeNull(); - expect(combinedSetter.decorators!.map(d => d.name)).toEqual(['Input']); - expect(combinedGetter.kind).toEqual(ClassMemberKind.Getter); - expect(combinedGetter.isStatic).toEqual(false); - expect(ts.isGetAccessor(combinedGetter.implementation!)).toEqual(true); - expect(combinedGetter.value).toBeNull(); - expect(combinedGetter.decorators!.map(d => d.name)).toEqual([]); - }); - - it('should find static methods on a class', () => { - loadTestFiles([SOME_DIRECTIVE_FILE]); - const bundle = makeTestBundleProgram(SOME_DIRECTIVE_FILE.name); - const host = createHost(bundle, new Esm2015ReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, SOME_DIRECTIVE_FILE.name, 'SomeDirective', isNamedClassDeclaration); - const members = host.getMembersOfClass(classNode); - - const staticMethod = members.find(member => member.name === 'staticMethod')!; - expect(staticMethod.kind).toEqual(ClassMemberKind.Method); - expect(staticMethod.isStatic).toEqual(true); - expect(ts.isMethodDeclaration(staticMethod.implementation!)).toEqual(true); - }); - - it('should find static properties on a class', () => { - loadTestFiles([SOME_DIRECTIVE_FILE]); - const bundle = makeTestBundleProgram(SOME_DIRECTIVE_FILE.name); - const host = createHost(bundle, new Esm2015ReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, SOME_DIRECTIVE_FILE.name, 'SomeDirective', isNamedClassDeclaration); - const members = host.getMembersOfClass(classNode); - - const staticProperty = members.find(member => member.name === 'staticProperty')!; - expect(staticProperty.kind).toEqual(ClassMemberKind.Property); - expect(staticProperty.isStatic).toEqual(true); - expect(ts.isPropertyAccessExpression(staticProperty.implementation!)).toEqual(true); - expect(staticProperty.value!.getText()).toEqual(`'static'`); - }); - - it('should ignore index signature properties', () => { - loadTestFiles([INDEX_SIGNATURE_PROP_FILE]); - const logger = new MockLogger(); - const bundle = makeTestBundleProgram(INDEX_SIGNATURE_PROP_FILE.name); - const host = createHost(bundle, new Esm2015ReflectionHost(logger, false, bundle)); - const classNode = getDeclaration( - bundle.program, INDEX_SIGNATURE_PROP_FILE.name, 'IndexSignatureClass', - isNamedClassDeclaration); - const members = host.getMembersOfClass(classNode); - - expect(members).toEqual([]); - expect(logger.logs.warn).toEqual([]); - }); - - it('should throw if the symbol is not a class', () => { - loadTestFiles([FOO_FUNCTION_FILE]); - const bundle = makeTestBundleProgram(FOO_FUNCTION_FILE.name); - const host = createHost(bundle, new Esm2015ReflectionHost(new MockLogger(), false, bundle)); - const functionNode = getDeclaration( - bundle.program, FOO_FUNCTION_FILE.name, 'foo', isNamedFunctionDeclaration); - expect(() => { - host.getMembersOfClass(functionNode); - }).toThrowError(`Attempted to get members of a non-class: "function foo() {}"`); - }); - - it('should return an empty array if there are no prop decorators', () => { - loadTestFiles([SIMPLE_CLASS_FILE]); - const bundle = makeTestBundleProgram(SIMPLE_CLASS_FILE.name); - const host = createHost(bundle, new Esm2015ReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, SIMPLE_CLASS_FILE.name, 'EmptyClass', isNamedClassDeclaration); - const members = host.getMembersOfClass(classNode); - - expect(members).toEqual([]); - }); - - it('should not process decorated properties in `propDecorators` if it is not an object literal', - () => { - loadTestFiles([INVALID_PROP_DECORATORS_FILE]); - const bundle = makeTestBundleProgram(INVALID_PROP_DECORATORS_FILE.name); - const host = - createHost(bundle, new Esm2015ReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, INVALID_PROP_DECORATORS_FILE.name, 'NotObjectLiteral', - isNamedClassDeclaration); - const members = host.getMembersOfClass(classNode); - - expect(members.map(member => member.name)).not.toContain('prop'); - }); - - it('should ignore prop decorator elements that are not object literals', () => { - loadTestFiles([INVALID_PROP_DECORATORS_FILE]); - const bundle = makeTestBundleProgram(INVALID_PROP_DECORATORS_FILE.name); - const host = createHost(bundle, new Esm2015ReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, INVALID_PROP_DECORATORS_FILE.name, 'NotObjectLiteralProp', - isNamedClassDeclaration); - const members = host.getMembersOfClass(classNode); - const prop = members.find(m => m.name === 'prop')!; - const decorators = prop.decorators!; - - expect(decorators.length).toBe(1); - expect(decorators[0]).toEqual(jasmine.objectContaining({name: 'Input'})); - }); - - it('should ignore prop decorator elements that have no `type` property', () => { - loadTestFiles([INVALID_PROP_DECORATORS_FILE]); - const bundle = makeTestBundleProgram(INVALID_PROP_DECORATORS_FILE.name); - const host = createHost(bundle, new Esm2015ReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, INVALID_PROP_DECORATORS_FILE.name, 'NoTypeProperty', - isNamedClassDeclaration); - const members = host.getMembersOfClass(classNode); - const prop = members.find(m => m.name === 'prop')!; - const decorators = prop.decorators!; - - expect(decorators.length).toBe(1); - expect(decorators[0]).toEqual(jasmine.objectContaining({name: 'Input'})); - }); - - it('should ignore prop decorator elements whose `type` value is not an identifier', () => { - loadTestFiles([INVALID_PROP_DECORATORS_FILE]); - const bundle = makeTestBundleProgram(INVALID_PROP_DECORATORS_FILE.name); - const host = createHost(bundle, new Esm2015ReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, INVALID_PROP_DECORATORS_FILE.name, 'NotIdentifier', - isNamedClassDeclaration); - const members = host.getMembersOfClass(classNode); - const prop = members.find(m => m.name === 'prop')!; - const decorators = prop.decorators!; - - expect(decorators.length).toBe(1); - expect(decorators[0]).toEqual(jasmine.objectContaining({name: 'Input'})); - }); - - describe('(returned prop decorators `args`)', () => { - it('should be an empty array if prop decorator has no `args` property', () => { - loadTestFiles([INVALID_PROP_DECORATOR_ARGS_FILE]); - const bundle = makeTestBundleProgram(INVALID_PROP_DECORATOR_ARGS_FILE.name); - const host = - createHost(bundle, new Esm2015ReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, INVALID_PROP_DECORATOR_ARGS_FILE.name, 'NoArgsProperty', - isNamedClassDeclaration); - const members = host.getMembersOfClass(classNode); - const prop = members.find(m => m.name === 'prop')!; - const decorators = prop.decorators!; - - expect(decorators.length).toBe(1); - expect(decorators[0].name).toBe('Input'); - expect(decorators[0].args).toEqual([]); - }); - - it('should be an empty array if prop decorator\'s `args` has no property assignment', - () => { - loadTestFiles([INVALID_PROP_DECORATOR_ARGS_FILE]); - const bundle = makeTestBundleProgram(INVALID_PROP_DECORATOR_ARGS_FILE.name); - const host = - createHost(bundle, new Esm2015ReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, INVALID_PROP_DECORATOR_ARGS_FILE.name, 'NoPropertyAssignment', - isNamedClassDeclaration); - const members = host.getMembersOfClass(classNode); - const prop = members.find(m => m.name === 'prop')!; - const decorators = prop.decorators!; - - expect(decorators.length).toBe(1); - expect(decorators[0].name).toBe('Input'); - expect(decorators[0].args).toEqual([]); - }); - - it('should be an empty array if `args` property value is not an array literal', () => { - loadTestFiles([INVALID_PROP_DECORATOR_ARGS_FILE]); - const bundle = makeTestBundleProgram(INVALID_PROP_DECORATOR_ARGS_FILE.name); - const host = - createHost(bundle, new Esm2015ReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, INVALID_PROP_DECORATOR_ARGS_FILE.name, 'NotArrayLiteral', - isNamedClassDeclaration); - const members = host.getMembersOfClass(classNode); - const prop = members.find(m => m.name === 'prop')!; - const decorators = prop.decorators!; - - expect(decorators.length).toBe(1); - expect(decorators[0].name).toBe('Input'); - expect(decorators[0].args).toEqual([]); - }); - }); - }); - - describe('getConstructorParameters()', () => { - it('should retain imported name for type value references for decorated constructor parameter types', - () => { - const files = [ - { - name: _('/node_modules/shared-lib/foo.d.ts'), - contents: ` - declare class Foo {} - export {Foo as Bar}; - `, - }, - { - name: _('/node_modules/shared-lib/index.d.ts'), - contents: ` - export {Bar as Baz} from './foo'; - `, - }, - { - name: _('/local.js'), - contents: ` - class Internal {} - export {Internal as External}; - ` - }, - { - name: _('/main.js'), - contents: ` - import {Baz} from 'shared-lib'; - import {External} from './local'; - export class SameFile {} - - export class SomeClass { - constructor(arg1, arg2, arg3) {} - } - SomeClass.ctorParameters = [{ type: Baz }, { type: External }, { type: SameFile }]; - `, - }, - ]; - - loadTestFiles(files); - const bundle = makeTestBundleProgram(_('/main.js')); - const host = - createHost(bundle, new Esm2015ReflectionHost(new MockLogger(), false, bundle)); - const classNode = - getDeclaration(bundle.program, _('/main.js'), 'SomeClass', isNamedClassDeclaration); - - const parameters = host.getConstructorParameters(classNode)!; - - expect(parameters.map(p => p.name)).toEqual(['arg1', 'arg2', 'arg3']); - expectTypeValueReferencesForParameters( - parameters, ['Baz', 'External', 'SameFile'], ['shared-lib', './local', null]); - }); - - it('should find the decorated constructor parameters', () => { - loadFakeCore(getFileSystem()); - loadTestFiles([SOME_DIRECTIVE_FILE]); - const bundle = makeTestBundleProgram(SOME_DIRECTIVE_FILE.name); - const host = createHost(bundle, new Esm2015ReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, SOME_DIRECTIVE_FILE.name, 'SomeDirective', isNamedClassDeclaration); - const parameters = host.getConstructorParameters(classNode)!; - - expect(parameters).toBeDefined(); - expect(parameters.map(parameter => parameter.name)).toEqual([ - '_viewContainer', '_template', 'injected' - ]); - expectTypeValueReferencesForParameters( - parameters, ['ViewContainerRef', 'TemplateRef', null], - ['@angular/core', '@angular/core', null]); - }); - - it('should accept `ctorParameters` as an array', () => { - loadTestFiles([CTOR_DECORATORS_ARRAY_FILE]); - const bundle = makeTestBundleProgram(CTOR_DECORATORS_ARRAY_FILE.name); - const host = createHost(bundle, new Esm2015ReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, CTOR_DECORATORS_ARRAY_FILE.name, 'CtorDecoratedAsArray', - isNamedClassDeclaration); - const parameters = host.getConstructorParameters(classNode)!; - - expect(parameters).toBeDefined(); - expect(parameters.map(parameter => parameter.name)).toEqual(['arg1']); - expectTypeValueReferencesForParameters(parameters, ['ParamType']); - }); - - it('should throw if the symbol is not a class', () => { - loadTestFiles([FOO_FUNCTION_FILE]); - const bundle = makeTestBundleProgram(FOO_FUNCTION_FILE.name); - const host = createHost(bundle, new Esm2015ReflectionHost(new MockLogger(), false, bundle)); - const functionNode = getDeclaration( - bundle.program, FOO_FUNCTION_FILE.name, 'foo', isNamedFunctionDeclaration); - expect(() => { - host.getConstructorParameters(functionNode); - }) - .toThrowError( - 'Attempted to get constructor parameters of a non-class: "function foo() {}"'); - }); - - it('should return `null` if there is no constructor', () => { - loadTestFiles([SIMPLE_CLASS_FILE]); - const bundle = makeTestBundleProgram(SIMPLE_CLASS_FILE.name); - const host = createHost(bundle, new Esm2015ReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, SIMPLE_CLASS_FILE.name, 'EmptyClass', isNamedClassDeclaration); - const parameters = host.getConstructorParameters(classNode); - expect(parameters).toBe(null); - }); - - it('should return an array even if there are no decorators', () => { - loadTestFiles([SIMPLE_CLASS_FILE]); - const bundle = makeTestBundleProgram(SIMPLE_CLASS_FILE.name); - const host = createHost(bundle, new Esm2015ReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, SIMPLE_CLASS_FILE.name, 'NoDecoratorConstructorClass', - isNamedClassDeclaration); - const parameters = host.getConstructorParameters(classNode)!; - - expect(parameters).toEqual(jasmine.any(Array)); - expect(parameters.length).toEqual(1); - expect(parameters[0].name).toEqual('foo'); - expect(parameters[0].decorators).toBe(null); - }); - - it('should return an empty array if there are no constructor parameters', () => { - loadTestFiles([INVALID_CTOR_DECORATORS_FILE]); - const bundle = makeTestBundleProgram(INVALID_CTOR_DECORATORS_FILE.name); - const host = createHost(bundle, new Esm2015ReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, INVALID_CTOR_DECORATORS_FILE.name, 'NoParameters', - isNamedClassDeclaration); - const parameters = host.getConstructorParameters(classNode); - - expect(parameters).toEqual([]); - }); - - it('should ignore decorators that are not imported from core', () => { - loadTestFiles([INVALID_CTOR_DECORATORS_FILE]); - const bundle = makeTestBundleProgram(INVALID_CTOR_DECORATORS_FILE.name); - const host = createHost(bundle, new Esm2015ReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, INVALID_CTOR_DECORATORS_FILE.name, 'NotFromCore', - isNamedClassDeclaration); - const parameters = host.getConstructorParameters(classNode)!; - - expect(parameters.length).toBe(1); - expect(parameters[0]).toEqual(jasmine.objectContaining({ - name: 'arg1', - decorators: [], - })); - }); - - it('should ignore `ctorParameters` if it is not an arrow function', () => { - loadTestFiles([INVALID_CTOR_DECORATORS_FILE]); - const bundle = makeTestBundleProgram(INVALID_CTOR_DECORATORS_FILE.name); - const host = createHost(bundle, new Esm2015ReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, INVALID_CTOR_DECORATORS_FILE.name, 'NotArrowFunction', - isNamedClassDeclaration); - const parameters = host.getConstructorParameters(classNode)!; - - expect(parameters.length).toBe(1); - expect(parameters[0]).toEqual(jasmine.objectContaining({ - name: 'arg1', - decorators: null, - })); - }); - - it('should ignore `ctorParameters` if it does not return an array literal', () => { - loadTestFiles([INVALID_CTOR_DECORATORS_FILE]); - const bundle = makeTestBundleProgram(INVALID_CTOR_DECORATORS_FILE.name); - const host = createHost(bundle, new Esm2015ReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, INVALID_CTOR_DECORATORS_FILE.name, 'NotArrayLiteral', - isNamedClassDeclaration); - const parameters = host.getConstructorParameters(classNode)!; - - expect(parameters.length).toBe(1); - expect(parameters[0]).toEqual(jasmine.objectContaining({ - name: 'arg1', - decorators: null, - })); - }); - - describe('synthesized constructors', () => { - function getConstructorParameters(constructor: string) { - const file = { - name: _('/synthesized_constructors.js'), - contents: ` - class BaseClass {} - class TestClass extends BaseClass { - ${constructor} - } - `, - }; - - loadTestFiles([file]); - const bundle = makeTestBundleProgram(file.name); - const host = - createHost(bundle, new Esm2015ReflectionHost(new MockLogger(), false, bundle)); - const classNode = - getDeclaration(bundle.program, file.name, 'TestClass', isNamedClassDeclaration); - return host.getConstructorParameters(classNode); - } - - it('recognizes super call as first statement', () => { - const parameters = getConstructorParameters(` - constructor() { - super(...arguments); - this.synthesizedProperty = null; - }`); - - expect(parameters).toBeNull(); - }); - - it('does not consider super call without spread element as synthesized', () => { - const parameters = getConstructorParameters(` - constructor() { - super(arguments); - }`); - - expect(parameters!.length).toBe(0); - }); - - it('does not consider constructors with parameters as synthesized', () => { - const parameters = getConstructorParameters(` - constructor(arg) { - super(...arguments); - }`); - - expect(parameters!.length).toBe(1); - }); - - it('does not consider manual super calls as synthesized', () => { - const parameters = getConstructorParameters(` - constructor() { - super(); - }`); - - expect(parameters!.length).toBe(0); - }); - - it('does not consider empty constructors as synthesized', () => { - const parameters = getConstructorParameters(` - constructor() { - }`); - - expect(parameters!.length).toBe(0); - }); - }); - - describe('(returned parameters `decorators`)', () => { - it('should ignore param decorator elements that are not object literals', () => { - loadTestFiles([INVALID_CTOR_DECORATORS_FILE]); - const bundle = makeTestBundleProgram(INVALID_CTOR_DECORATORS_FILE.name); - const host = - createHost(bundle, new Esm2015ReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, INVALID_CTOR_DECORATORS_FILE.name, 'NotObjectLiteral', - isNamedClassDeclaration); - const parameters = host.getConstructorParameters(classNode); - - expect(parameters!.length).toBe(2); - expect(parameters![0]).toEqual(jasmine.objectContaining({ - name: 'arg1', - decorators: null, - })); - expect(parameters![1]).toEqual(jasmine.objectContaining({ - name: 'arg2', - decorators: jasmine.any(Array) as any - })); - }); - - it('should ignore param decorator elements that have no `type` property', () => { - loadTestFiles([INVALID_CTOR_DECORATORS_FILE]); - const bundle = makeTestBundleProgram(INVALID_CTOR_DECORATORS_FILE.name); - const host = - createHost(bundle, new Esm2015ReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, INVALID_CTOR_DECORATORS_FILE.name, 'NoTypeProperty', - isNamedClassDeclaration); - const parameters = host.getConstructorParameters(classNode); - const decorators = parameters![0].decorators!; - - expect(decorators.length).toBe(1); - expect(decorators[0]).toEqual(jasmine.objectContaining({name: 'Inject'})); - }); - - it('should ignore param decorator elements whose `type` value is not an identifier', () => { - loadTestFiles([INVALID_CTOR_DECORATORS_FILE]); - const bundle = makeTestBundleProgram(INVALID_CTOR_DECORATORS_FILE.name); - const host = - createHost(bundle, new Esm2015ReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, INVALID_CTOR_DECORATORS_FILE.name, 'NotIdentifier', - isNamedClassDeclaration); - const parameters = host.getConstructorParameters(classNode); - const decorators = parameters![0].decorators!; - - expect(decorators.length).toBe(1); - expect(decorators[0]).toEqual(jasmine.objectContaining({name: 'Inject'})); - }); - - it('should have import information on decorators', () => { - loadTestFiles([SOME_DIRECTIVE_FILE]); - const bundle = makeTestBundleProgram(SOME_DIRECTIVE_FILE.name); - const host = - createHost(bundle, new Esm2015ReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, SOME_DIRECTIVE_FILE.name, 'SomeDirective', isNamedClassDeclaration); - const parameters = host.getConstructorParameters(classNode)!; - const decorators = parameters[2].decorators!; - - expect(decorators.length).toEqual(1); - expect(decorators[0].import).toEqual({name: 'Inject', from: '@angular/core'}); - }); - }); - - describe('(returned parameters `decorators.args`)', () => { - it('should be an empty array if param decorator has no `args` property', () => { - loadTestFiles([INVALID_CTOR_DECORATOR_ARGS_FILE]); - const bundle = makeTestBundleProgram(INVALID_CTOR_DECORATOR_ARGS_FILE.name); - const host = - createHost(bundle, new Esm2015ReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, INVALID_CTOR_DECORATOR_ARGS_FILE.name, 'NoArgsProperty', - isNamedClassDeclaration); - const parameters = host.getConstructorParameters(classNode); - expect(parameters!.length).toBe(1); - const decorators = parameters![0].decorators!; - - expect(decorators.length).toBe(1); - expect(decorators[0].name).toBe('Inject'); - expect(decorators[0].args).toEqual([]); - }); - - it('should be an empty array if param decorator\'s `args` has no property assignment', - () => { - loadTestFiles([INVALID_CTOR_DECORATOR_ARGS_FILE]); - const bundle = makeTestBundleProgram(INVALID_CTOR_DECORATOR_ARGS_FILE.name); - const host = - createHost(bundle, new Esm2015ReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, INVALID_CTOR_DECORATOR_ARGS_FILE.name, 'NoPropertyAssignment', - isNamedClassDeclaration); - const parameters = host.getConstructorParameters(classNode); - const decorators = parameters![0].decorators!; - - expect(decorators.length).toBe(1); - expect(decorators[0].name).toBe('Inject'); - expect(decorators[0].args).toEqual([]); - }); - - it('should be an empty array if `args` property value is not an array literal', () => { - loadTestFiles([INVALID_CTOR_DECORATOR_ARGS_FILE]); - const bundle = makeTestBundleProgram(INVALID_CTOR_DECORATOR_ARGS_FILE.name); - const host = - createHost(bundle, new Esm2015ReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, INVALID_CTOR_DECORATOR_ARGS_FILE.name, 'NotArrayLiteral', - isNamedClassDeclaration); - const parameters = host.getConstructorParameters(classNode); - const decorators = parameters![0].decorators!; - - expect(decorators.length).toBe(1); - expect(decorators[0].name).toBe('Inject'); - expect(decorators[0].args).toEqual([]); - }); - }); - }); - - describe('getDefinitionOfFunction()', () => { - it('should return an object describing the function declaration passed as an argument', - () => { - loadTestFiles([FUNCTION_BODY_FILE]); - const bundle = makeTestBundleProgram(FUNCTION_BODY_FILE.name); - const host = - createHost(bundle, new Esm2015ReflectionHost(new MockLogger(), false, bundle)); - - const fooNode = getDeclaration( - bundle.program, FUNCTION_BODY_FILE.name, 'foo', isNamedFunctionDeclaration)!; - const fooDef = host.getDefinitionOfFunction(fooNode)!; - expect(fooDef.node).toBe(fooNode); - expect(fooDef.body!.length).toEqual(1); - expect(fooDef.body![0].getText()).toEqual(`return x;`); - expect(fooDef.parameters.length).toEqual(1); - expect(fooDef.parameters[0].name).toEqual('x'); - expect(fooDef.parameters[0].initializer).toBe(null); - - const barNode = getDeclaration( - bundle.program, FUNCTION_BODY_FILE.name, 'bar', isNamedFunctionDeclaration)!; - const barDef = host.getDefinitionOfFunction(barNode)!; - expect(barDef.node).toBe(barNode); - expect(barDef.body!.length).toEqual(1); - expect(ts.isReturnStatement(barDef.body![0])).toBeTruthy(); - expect(barDef.body![0].getText()).toEqual(`return x + y;`); - expect(barDef.parameters.length).toEqual(2); - expect(barDef.parameters[0].name).toEqual('x'); - expect(fooDef.parameters[0].initializer).toBe(null); - expect(barDef.parameters[1].name).toEqual('y'); - expect(barDef.parameters[1].initializer!.getText()).toEqual('42'); - - const bazNode = getDeclaration( - bundle.program, FUNCTION_BODY_FILE.name, 'baz', isNamedFunctionDeclaration)!; - const bazDef = host.getDefinitionOfFunction(bazNode)!; - expect(bazDef.node).toBe(bazNode); - expect(bazDef.body!.length).toEqual(3); - expect(bazDef.parameters.length).toEqual(1); - expect(bazDef.parameters[0].name).toEqual('x'); - expect(bazDef.parameters[0].initializer).toBe(null); - - const quxNode = getDeclaration( - bundle.program, FUNCTION_BODY_FILE.name, 'qux', isNamedFunctionDeclaration)!; - const quxDef = host.getDefinitionOfFunction(quxNode)!; - expect(quxDef.node).toBe(quxNode); - expect(quxDef.body!.length).toEqual(2); - expect(quxDef.parameters.length).toEqual(1); - expect(quxDef.parameters[0].name).toEqual('x'); - expect(quxDef.parameters[0].initializer).toBe(null); - - const mooNode = getDeclaration( - bundle.program, FUNCTION_BODY_FILE.name, 'moo', isNamedFunctionDeclaration)!; - const mooDef = host.getDefinitionOfFunction(mooNode)!; - expect(mooDef.node).toBe(mooNode); - expect(mooDef.body!.length).toEqual(3); - expect(mooDef.parameters).toEqual([]); - - const juuNode = getDeclaration( - bundle.program, FUNCTION_BODY_FILE.name, 'juu', isNamedFunctionDeclaration)!; - const juuDef = host.getDefinitionOfFunction(juuNode)!; - expect(juuDef.node).toBe(juuNode); - expect(juuDef.body!.length).toEqual(2); - expect(juuDef.parameters).toEqual([]); - }); - }); - - describe('getImportOfIdentifier()', () => { - it('should find the import of an identifier', () => { - loadTestFiles(IMPORTS_FILES); - const bundle = makeTestBundleProgram(_('/index.js')); - const host = createHost(bundle, new Esm2015ReflectionHost(new MockLogger(), false, bundle)); - const variableNode = - getDeclaration(bundle.program, _('/b.js'), 'b', isNamedVariableDeclaration); - const importOfIdent = host.getImportOfIdentifier(variableNode.initializer as ts.Identifier); - - expect(importOfIdent).toEqual({name: 'a', from: './a.js'}); - }); - - it('should find the name by which the identifier was exported, not imported', () => { - loadTestFiles(IMPORTS_FILES); - const bundle = makeTestBundleProgram(_('/index.js')); - const host = createHost(bundle, new Esm2015ReflectionHost(new MockLogger(), false, bundle)); - const variableNode = - getDeclaration(bundle.program, _('/b.js'), 'c', isNamedVariableDeclaration); - const importOfIdent = host.getImportOfIdentifier(variableNode.initializer as ts.Identifier); - - expect(importOfIdent).toEqual({name: 'a', from: './a.js'}); - }); - - it('should return null if the identifier was not imported', () => { - loadTestFiles(IMPORTS_FILES); - const bundle = makeTestBundleProgram(_('/index.js')); - const host = createHost(bundle, new Esm2015ReflectionHost(new MockLogger(), false, bundle)); - const variableNode = - getDeclaration(bundle.program, _('/b.js'), 'd', isNamedVariableDeclaration); - const importOfIdent = host.getImportOfIdentifier(variableNode.initializer as ts.Identifier); - - expect(importOfIdent).toBeNull(); - }); - }); - - describe('getDeclarationOfIdentifier()', () => { - it('should return the declaration of a locally defined identifier', () => { - loadFakeCore(getFileSystem()); - loadTestFiles([SOME_DIRECTIVE_FILE]); - const bundle = makeTestBundleProgram(SOME_DIRECTIVE_FILE.name); - const host = createHost(bundle, new Esm2015ReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, SOME_DIRECTIVE_FILE.name, 'SomeDirective', isNamedClassDeclaration); - const actualDeclaration = host.getDeclarationOfIdentifier(classNode.name); - expect(actualDeclaration).not.toBe(null); - expect(actualDeclaration!.node).toBe(classNode); - expect(actualDeclaration!.viaModule).toBe(null); - expect((actualDeclaration as ConcreteDeclaration).identity).toBe(null); - }); - - it('should return the declaration of an externally defined identifier', () => { - loadFakeCore(getFileSystem()); - loadTestFiles([SOME_DIRECTIVE_FILE]); - const bundle = makeTestBundleProgram(SOME_DIRECTIVE_FILE.name); - const host = createHost(bundle, new Esm2015ReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, SOME_DIRECTIVE_FILE.name, 'SomeDirective', isNamedClassDeclaration); - const classDecorators = host.getDecoratorsOfDeclaration(classNode)!; - const identifierOfDirective = - ((classDecorators[0].node as ts.ObjectLiteralExpression).properties[0] as - ts.PropertyAssignment) - .initializer as ts.Identifier; - - const expectedDeclarationNode = getDeclaration( - bundle.program, _('/node_modules/@angular/core/index.d.ts'), 'Directive', - isNamedVariableDeclaration); - const actualDeclaration = host.getDeclarationOfIdentifier(identifierOfDirective); - expect(actualDeclaration).not.toBe(null); - expect(actualDeclaration!.node).toBe(expectedDeclarationNode); - expect(actualDeclaration!.viaModule).toBe('@angular/core'); - }); - - it('should return the source-file of an import namespace', () => { - loadFakeCore(getFileSystem()); - loadTestFiles([NAMESPACED_IMPORT_FILE]); - const bundle = makeTestBundleProgram(NAMESPACED_IMPORT_FILE.name); - const host = createHost(bundle, new Esm2015ReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, NAMESPACED_IMPORT_FILE.name, 'SomeDirective', ts.isClassDeclaration); - const classDecorators = host.getDecoratorsOfDeclaration(classNode)!; - const identifier = - (((classDecorators[0].node as ts.ObjectLiteralExpression).properties[0] as - ts.PropertyAssignment) - .initializer as ts.PropertyAccessExpression) - .expression as ts.Identifier; - - const expectedDeclarationNode = - getSourceFileOrError(bundle.program, _('/node_modules/@angular/core/index.d.ts')); - const actualDeclaration = host.getDeclarationOfIdentifier(identifier); - expect(actualDeclaration).not.toBe(null); - expect(actualDeclaration!.node).toBe(expectedDeclarationNode); - expect(actualDeclaration!.viaModule).toBe('@angular/core'); - }); - - it('should return the original declaration of an aliased class', () => { - loadTestFiles([CLASS_EXPRESSION_FILE]); - const bundle = makeTestBundleProgram(CLASS_EXPRESSION_FILE.name); - const host = createHost(bundle, new Esm2015ReflectionHost(new MockLogger(), false, bundle)); - const classDeclaration = getDeclaration( - bundle.program, CLASS_EXPRESSION_FILE.name, 'AliasedClass', ts.isVariableDeclaration); - const usageOfAliasedClass = getDeclaration( - bundle.program, CLASS_EXPRESSION_FILE.name, 'usageOfAliasedClass', - ts.isVariableDeclaration); - const aliasedClassIdentifier = usageOfAliasedClass.initializer as ts.Identifier; - expect(aliasedClassIdentifier.text).toBe('AliasedClass_1'); - expect(host.getDeclarationOfIdentifier(aliasedClassIdentifier)!.node) - .toBe(classDeclaration); - }); - - it('should return the original declaration of a wrapped aliased class', () => { - loadTestFiles([WRAPPED_CLASS_EXPRESSION_FILE]); - const bundle = makeTestBundleProgram(WRAPPED_CLASS_EXPRESSION_FILE.name); - const host = createHost(bundle, new Esm2015ReflectionHost(new MockLogger(), false, bundle)); - const classDeclaration = getDeclaration( - bundle.program, WRAPPED_CLASS_EXPRESSION_FILE.name, 'AliasedWrappedClass', - ts.isVariableDeclaration); - const usageOfWrappedClass = getDeclaration( - bundle.program, WRAPPED_CLASS_EXPRESSION_FILE.name, 'usageOfWrappedClass', - ts.isVariableDeclaration); - const aliasedClassIdentifier = usageOfWrappedClass.initializer as ts.Identifier; - expect(aliasedClassIdentifier.text).toBe('AliasedWrappedClass_1'); - expect(host.getDeclarationOfIdentifier(aliasedClassIdentifier)!.node) - .toBe(classDeclaration); - }); - - it('should return the correct declaration for an inner class identifier inside an IIFE', - () => { - loadTestFiles([SIMPLE_CLASS_FILE]); - const bundle = makeTestBundleProgram(SIMPLE_CLASS_FILE.name); - const host = - createHost(bundle, new Esm2015ReflectionHost(new MockLogger(), false, bundle)); - - const outerDeclaration = getDeclaration( - bundle.program, SIMPLE_CLASS_FILE.name, 'SimpleWrappedClass', - isNamedVariableDeclaration); - const innerDeclaration = - ((((outerDeclaration.initializer as ts.CallExpression).expression as - ts.ParenthesizedExpression) - .expression as ts.ArrowFunction) - .body as ts.Block) - .statements[0] as ts.ClassDeclaration; - - const outerIdentifier = outerDeclaration.name as ts.Identifier; - const innerIdentifier = innerDeclaration.name as ts.Identifier; - - expect(host.getDeclarationOfIdentifier(outerIdentifier)!.node).toBe(outerDeclaration); - expect(host.getDeclarationOfIdentifier(innerIdentifier)!.node).toBe(outerDeclaration); - }); - - it('should return the correct declaration for an aliased class identifier inside an IIFE', - () => { - loadTestFiles([WRAPPED_CLASS_EXPRESSION_FILE]); - const bundle = makeTestBundleProgram(WRAPPED_CLASS_EXPRESSION_FILE.name); - const host = - createHost(bundle, new Esm2015ReflectionHost(new MockLogger(), false, bundle)); - const classDeclaration = getDeclaration( - bundle.program, WRAPPED_CLASS_EXPRESSION_FILE.name, 'DecoratedWrappedClass', - ts.isVariableDeclaration); - const innerClassDeclarations = - walkForDeclarations('InnerDecoratedWrappedClass', classDeclaration); - if (innerClassDeclarations.length === 0) { - throw new Error('Expected InnerDecoratedWrappedClass to exist'); - } - const aliasedClassIdentifier = - (innerClassDeclarations[0].parent as ts.BinaryExpression).left as ts.Identifier; - expect(aliasedClassIdentifier.text).toBe('DecoratedWrappedClass_1'); - const d = host.getDeclarationOfIdentifier(aliasedClassIdentifier); - expect(d!.node).toBe(classDeclaration); - }); - - it('should recognize enum declarations with string values', () => { - const testFile: TestFile = { - name: _('/node_modules/test-package/some/file.js'), - contents: ` - export var Enum; - (function (Enum) { - Enum["ValueA"] = "1"; - Enum["ValueB"] = "2"; - })(Enum || (Enum = {})); - - var value = Enum;` - }; - loadTestFiles([testFile]); - const bundle = makeTestBundleProgram(testFile.name); - const host = createHost(bundle, new Esm2015ReflectionHost(new MockLogger(), false, bundle)); - const valueDecl = getDeclaration( - bundle.program, _('/node_modules/test-package/some/file.js'), 'value', - ts.isVariableDeclaration); - const declaration = host.getDeclarationOfIdentifier( - valueDecl.initializer as ts.Identifier) as ConcreteDeclaration; - - expect(declaration.node.parent.parent.getText()).toBe('export var Enum;'); - - const enumMembers = (declaration.identity as DownleveledEnum).enumMembers; - expect(enumMembers!.length).toBe(2); - expect(enumMembers![0].name.getText()).toBe('"ValueA"'); - expect(enumMembers![0].initializer!.getText()).toBe('"1"'); - expect(enumMembers![1].name.getText()).toBe('"ValueB"'); - expect(enumMembers![1].initializer!.getText()).toBe('"2"'); - }); - - it('should recognize enum declarations with numeric values', () => { - const testFile: TestFile = { - name: _('/node_modules/test-package/some/file.js'), - contents: ` - export var Enum; - (function (Enum) { - Enum[Enum["ValueA"] = "1"] = "ValueA"; - Enum[Enum["ValueB"] = "2"] = "ValueB"; - })(Enum || (Enum = {})); - - var value = Enum;` - }; - loadTestFiles([testFile]); - const bundle = makeTestBundleProgram(testFile.name); - const host = createHost(bundle, new Esm2015ReflectionHost(new MockLogger(), false, bundle)); - const valueDecl = getDeclaration( - bundle.program, _('/node_modules/test-package/some/file.js'), 'value', - ts.isVariableDeclaration); - const declaration = host.getDeclarationOfIdentifier( - valueDecl.initializer as ts.Identifier) as ConcreteDeclaration; - - const enumMembers = (declaration.identity as DownleveledEnum).enumMembers; - expect(declaration.node.parent.parent.getText()).toBe('export var Enum;'); - expect(enumMembers!.length).toBe(2); - expect(enumMembers![0].name.getText()).toBe('"ValueA"'); - expect(enumMembers![0].initializer!.getText()).toBe('"1"'); - expect(enumMembers![1].name.getText()).toBe('"ValueB"'); - expect(enumMembers![1].initializer!.getText()).toBe('"2"'); - }); - - it('should not consider IIFEs that do no assign members to the parameter as an enum declaration', - () => { - const testFile: TestFile = { - name: _('/node_modules/test-package/some/file.js'), - contents: ` - export var Enum; - (function (E) { - Enum["ValueA"] = "1"; - Enum["ValueB"] = "2"; - })(Enum || (Enum = {})); - - var value = Enum;` - }; - loadTestFiles([testFile]); - const bundle = makeTestBundleProgram(testFile.name); - const host = - createHost(bundle, new Esm2015ReflectionHost(new MockLogger(), false, bundle)); - const valueDecl = getDeclaration( - bundle.program, _('/node_modules/test-package/some/file.js'), 'value', - ts.isVariableDeclaration); - const declaration = host.getDeclarationOfIdentifier( - valueDecl.initializer as ts.Identifier) as ConcreteDeclaration; - - expect(declaration.node.parent.parent.getText()).toBe('export var Enum;'); - expect(declaration.identity).toBe(null); - }); - - it('should not consider IIFEs without call argument as an enum declaration', () => { - const testFile: TestFile = { - name: _('/node_modules/test-package/some/file.js'), - contents: ` - export var Enum; - (function (Enum) { - Enum["ValueA"] = "1"; - Enum["ValueB"] = "2"; - })(); - - var value = Enum;` - }; - loadTestFiles([testFile]); - const bundle = makeTestBundleProgram(testFile.name); - const host = createHost(bundle, new Esm2015ReflectionHost(new MockLogger(), false, bundle)); - const valueDecl = getDeclaration( - bundle.program, _('/node_modules/test-package/some/file.js'), 'value', - ts.isVariableDeclaration); - const declaration = host.getDeclarationOfIdentifier( - valueDecl.initializer as ts.Identifier) as ConcreteDeclaration; - - expect(declaration.node.parent.parent.getText()).toBe('export var Enum;'); - expect(declaration.identity).toBe(null); - }); - }); - - describe('getExportsOfModule()', () => { - it('should return a map of all the exports from a given module', () => { - loadFakeCore(getFileSystem()); - loadTestFiles(EXPORTS_FILES); - const bundle = makeTestBundleProgram(_('/index.js')); - const host = createHost(bundle, new Esm2015ReflectionHost(new MockLogger(), false, bundle)); - const file = getSourceFileOrError(bundle.program, _('/b.js')); - const exportDeclarations = host.getExportsOfModule(file); - expect(exportDeclarations).not.toBe(null); - expect(Array.from(exportDeclarations!.keys())).toEqual([ - 'Directive', - 'a', - 'b', - 'c', - 'd', - 'e', - 'DirectiveX', - 'SomeClass', - ]); - - const values = - Array.from(exportDeclarations!.values()) - .map(declaration => [declaration.node!.getText(), declaration.viaModule]); - expect(values).toEqual([ - [`Directive: FnWithArg<(clazz: any) => any>`, null], - [`a = 'a'`, null], - [`b = a`, null], - [`c = foo`, null], - [`d = b`, null], - [`e = 'e'`, null], - [`DirectiveX = Directive`, null], - ['export class SomeClass {}', null], - ]); - }); - }); - - describe('getClassSymbol()', () => { - it('should return the class symbol for an ES2015 class', () => { - loadTestFiles([SIMPLE_CLASS_FILE]); - const bundle = makeTestBundleProgram(SIMPLE_CLASS_FILE.name); - const host = createHost(bundle, new Esm2015ReflectionHost(new MockLogger(), false, bundle)); - const node = getDeclaration( - bundle.program, SIMPLE_CLASS_FILE.name, 'EmptyClass', isNamedClassDeclaration); - const classSymbol = host.getClassSymbol(node); - - expect(classSymbol).toBeDefined(); - expect(classSymbol!.declaration.valueDeclaration).toBe(node); - expect(classSymbol!.implementation.valueDeclaration).toBe(node); - expect(classSymbol!.adjacent).toBeUndefined(); - }); - - it('should return the class symbol for a class expression (outer variable declaration)', - () => { - loadTestFiles([CLASS_EXPRESSION_FILE]); - const bundle = makeTestBundleProgram(CLASS_EXPRESSION_FILE.name); - const host = - createHost(bundle, new Esm2015ReflectionHost(new MockLogger(), false, bundle)); - const outerNode = getDeclaration( - bundle.program, CLASS_EXPRESSION_FILE.name, 'EmptyClass', - isNamedVariableDeclaration); - const innerNode = (outerNode.initializer as ts.ClassExpression); - const classSymbol = host.getClassSymbol(outerNode); - - expect(classSymbol).toBeDefined(); - expect(classSymbol!.declaration.valueDeclaration).toBe(outerNode); - expect(classSymbol!.implementation.valueDeclaration).toBe(innerNode); - expect(classSymbol!.adjacent).toBeUndefined(); - }); - - it('should return the class symbol for a class expression (inner class expression)', () => { - loadTestFiles([CLASS_EXPRESSION_FILE]); - const bundle = makeTestBundleProgram(CLASS_EXPRESSION_FILE.name); - const host = createHost(bundle, new Esm2015ReflectionHost(new MockLogger(), false, bundle)); - const outerNode = getDeclaration( - bundle.program, CLASS_EXPRESSION_FILE.name, 'EmptyClass', isNamedVariableDeclaration); - const innerNode = (outerNode.initializer as ts.ClassExpression); - const classSymbol = host.getClassSymbol(innerNode); - - expect(classSymbol).toBeDefined(); - expect(classSymbol!.declaration.valueDeclaration).toBe(outerNode); - expect(classSymbol!.implementation.valueDeclaration).toBe(innerNode); - expect(classSymbol!.adjacent).toBeUndefined(); - }); - - it('should return the same class symbol (of the outer declaration) for outer and inner declarations', - () => { - loadTestFiles([CLASS_EXPRESSION_FILE]); - const bundle = makeTestBundleProgram(CLASS_EXPRESSION_FILE.name); - const host = - createHost(bundle, new Esm2015ReflectionHost(new MockLogger(), false, bundle)); - const outerNode = getDeclaration( - bundle.program, CLASS_EXPRESSION_FILE.name, 'EmptyClass', - isNamedVariableDeclaration); - const innerNode = (outerNode.initializer as ts.ClassExpression); - - const innerSymbol = host.getClassSymbol(innerNode)!; - const outerSymbol = host.getClassSymbol(outerNode)!; - expect(innerSymbol.declaration).toBe(outerSymbol.declaration); - expect(innerSymbol.implementation).toBe(outerSymbol.implementation); - }); - - it('should return the class symbol for a wrapped class expression (outer variable declaration)', - () => { - loadTestFiles([WRAPPED_CLASS_EXPRESSION_FILE]); - const bundle = makeTestBundleProgram(WRAPPED_CLASS_EXPRESSION_FILE.name); - const host = - createHost(bundle, new Esm2015ReflectionHost(new MockLogger(), false, bundle)); - const outerNode = getDeclaration( - bundle.program, WRAPPED_CLASS_EXPRESSION_FILE.name, 'SimpleWrappedClass', - isNamedVariableDeclaration); - const classSymbol = host.getClassSymbol(outerNode); - - if (classSymbol === undefined) { - return fail('Expected classSymbol to be defined'); - } - expect(classSymbol.name).toEqual('SimpleWrappedClass'); - expect(classSymbol.declaration.valueDeclaration).toBe(outerNode); - if (!isNamedClassDeclaration(classSymbol.implementation.valueDeclaration)) { - return fail('Expected a named class declaration'); - } - expect(classSymbol.implementation.valueDeclaration.name.text).toBe('SimpleWrappedClass'); - expect(classSymbol.adjacent).toBeUndefined(); - }); - - it('should return the class symbol for a wrapped class expression (inner class expression)', - () => { - loadTestFiles([WRAPPED_CLASS_EXPRESSION_FILE]); - const bundle = makeTestBundleProgram(WRAPPED_CLASS_EXPRESSION_FILE.name); - const host = - createHost(bundle, new Esm2015ReflectionHost(new MockLogger(), false, bundle)); - const outerNode = getDeclaration( - bundle.program, WRAPPED_CLASS_EXPRESSION_FILE.name, 'SimpleWrappedClass', - isNamedVariableDeclaration); - const innerNode = ((outerNode as any).initializer.expression.expression.body as ts.Block) - .statements[0]; - const classSymbol = host.getClassSymbol(innerNode); - - if (classSymbol === undefined) { - return fail('Expected classSymbol to be defined'); - } - expect(classSymbol.name).toEqual('SimpleWrappedClass'); - expect(classSymbol.declaration.valueDeclaration).toBe(outerNode); - if (!isNamedClassDeclaration(classSymbol.implementation.valueDeclaration)) { - return fail('Expected a named class declaration'); - } - expect(classSymbol.implementation.valueDeclaration.name.text).toBe('SimpleWrappedClass'); - expect(classSymbol.adjacent).toBeUndefined(); - }); - - it('should return the same class symbol (of the outer declaration) for wrapped outer and inner declarations', - () => { - loadTestFiles([WRAPPED_CLASS_EXPRESSION_FILE]); - const bundle = makeTestBundleProgram(WRAPPED_CLASS_EXPRESSION_FILE.name); - const host = - createHost(bundle, new Esm2015ReflectionHost(new MockLogger(), false, bundle)); - const outerNode = getDeclaration( - bundle.program, WRAPPED_CLASS_EXPRESSION_FILE.name, 'SimpleWrappedClass', - isNamedVariableDeclaration); - const innerNode = ((outerNode as any).initializer.expression.expression.body as ts.Block) - .statements[0]; - - const innerSymbol = host.getClassSymbol(innerNode)!; - const outerSymbol = host.getClassSymbol(outerNode)!; - expect(innerSymbol.declaration).toBe(outerSymbol.declaration); - expect(innerSymbol.implementation).toBe(outerSymbol.implementation); - }); - - it('should return the class symbol for a decorated wrapped class expression (from the outer variable declaration)', - () => { - loadTestFiles([WRAPPED_CLASS_EXPRESSION_FILE]); - const bundle = makeTestBundleProgram(WRAPPED_CLASS_EXPRESSION_FILE.name); - const host = - createHost(bundle, new Esm2015ReflectionHost(new MockLogger(), false, bundle)); - const outerNode = getDeclaration( - bundle.program, WRAPPED_CLASS_EXPRESSION_FILE.name, 'DecoratedWrappedClass', - isNamedVariableDeclaration); - const classSymbol = host.getClassSymbol(outerNode); - - if (classSymbol === undefined) { - return fail('Expected classSymbol to be defined'); - } - expect(classSymbol.name).toEqual('DecoratedWrappedClass'); - expect(classSymbol.declaration.valueDeclaration).toBe(outerNode); - - if (!ts.isClassExpression(classSymbol.implementation.valueDeclaration)) { - return fail('Expected a named class declaration'); - } - expect(classSymbol.implementation.valueDeclaration.name!.text) - .toBe('InnerDecoratedWrappedClass'); - - if (classSymbol.adjacent === undefined || - !isNamedVariableDeclaration(classSymbol.adjacent.valueDeclaration)) { - return fail('Expected a named variable declaration for the adjacent symbol'); - } - expect(classSymbol.adjacent.valueDeclaration.name.text) - .toBe('AdjacentDecoratedWrappedClass'); - }); - - it('should return the class symbol for a decorated wrapped class expression (from the inner class expression)', - () => { - loadTestFiles([WRAPPED_CLASS_EXPRESSION_FILE]); - const bundle = makeTestBundleProgram(WRAPPED_CLASS_EXPRESSION_FILE.name); - const host = - createHost(bundle, new Esm2015ReflectionHost(new MockLogger(), false, bundle)); - const outerNode = getDeclaration( - bundle.program, WRAPPED_CLASS_EXPRESSION_FILE.name, 'DecoratedWrappedClass', - isNamedVariableDeclaration); - const innerNodes = walkForDeclarations('InnerDecoratedWrappedClass', outerNode); - if (innerNodes.length === 0) { - throw new Error('Expected to find InnerDecoratedWrappedClass'); - } - const classSymbol = host.getClassSymbol(innerNodes[0]); - - if (classSymbol === undefined) { - return fail('Expected classSymbol to be defined'); - } - expect(classSymbol.name).toEqual('DecoratedWrappedClass'); - expect(classSymbol.declaration.valueDeclaration).toBe(outerNode); - expect(classSymbol.implementation.valueDeclaration) - .toBe(innerNodes[0] as ts.Declaration); - - if (classSymbol.adjacent === undefined || - !isNamedVariableDeclaration(classSymbol.adjacent.valueDeclaration)) { - return fail('Expected a named variable declaration for the adjacent symbol'); - } - expect(classSymbol.adjacent.valueDeclaration.name.text) - .toBe('AdjacentDecoratedWrappedClass'); - }); - - - it('should return the class symbol for a decorated wrapped class expression (from the adjacent class expression)', - () => { - loadTestFiles([WRAPPED_CLASS_EXPRESSION_FILE]); - const bundle = makeTestBundleProgram(WRAPPED_CLASS_EXPRESSION_FILE.name); - const host = - createHost(bundle, new Esm2015ReflectionHost(new MockLogger(), false, bundle)); - const outerNode = getDeclaration( - bundle.program, WRAPPED_CLASS_EXPRESSION_FILE.name, 'DecoratedWrappedClass', - isNamedVariableDeclaration); - const innerNodes = walkForDeclarations('InnerDecoratedWrappedClass', outerNode); - if (innerNodes.length === 0) { - throw new Error('Expected to find InnerDecoratedWrappedClass'); - } - const adjacentNode: ts.ClassExpression = - (outerNode as any) - .initializer.expression.expression.body.statements[0] - .declarationList.declarations[0] - .name; - const classSymbol = host.getClassSymbol(adjacentNode); - - if (classSymbol === undefined) { - return fail('Expected classSymbol to be defined'); - } - expect(classSymbol.name).toEqual('DecoratedWrappedClass'); - expect(classSymbol.declaration.valueDeclaration).toBe(outerNode); - expect(classSymbol.implementation.valueDeclaration) - .toBe(innerNodes[0] as ts.Declaration); - - if (classSymbol.adjacent === undefined || - !isNamedVariableDeclaration(classSymbol.adjacent.valueDeclaration)) { - return fail('Expected a named variable declaration for the adjacent symbol'); - } - expect(classSymbol.adjacent.valueDeclaration.name.text) - .toBe('AdjacentDecoratedWrappedClass'); - }); - - it('should return the same class symbol (of the outer declaration) for decorated wrapped outer and inner declarations', - () => { - loadTestFiles([WRAPPED_CLASS_EXPRESSION_FILE]); - const bundle = makeTestBundleProgram(WRAPPED_CLASS_EXPRESSION_FILE.name); - const host = - createHost(bundle, new Esm2015ReflectionHost(new MockLogger(), false, bundle)); - const outerNode = getDeclaration( - bundle.program, WRAPPED_CLASS_EXPRESSION_FILE.name, 'DecoratedWrappedClass', - isNamedVariableDeclaration); - const innerNodes = walkForDeclarations('InnerDecoratedWrappedClass', outerNode); - if (innerNodes.length === 0) { - throw new Error('Expected to find InnerDecoratedWrappedClass'); - } - - const innerSymbol = host.getClassSymbol(innerNodes[0])!; - const outerSymbol = host.getClassSymbol(outerNode)!; - expect(innerSymbol.declaration).toBe(outerSymbol.declaration); - expect(innerSymbol.implementation).toBe(outerSymbol.implementation); - }); - - it('should return undefined if node is not a class', () => { - loadTestFiles([FOO_FUNCTION_FILE]); - const bundle = makeTestBundleProgram(FOO_FUNCTION_FILE.name); - const host = createHost(bundle, new Esm2015ReflectionHost(new MockLogger(), false, bundle)); - const node = getDeclaration( - bundle.program, FOO_FUNCTION_FILE.name, 'foo', isNamedFunctionDeclaration); - const classSymbol = host.getClassSymbol(node); - - expect(classSymbol).toBeUndefined(); - }); - - it('should return undefined if variable declaration is not initialized to a valid class definition', - () => { - const testFile = { - name: _('/test.js'), - contents: `var MyClass = null;`, - }; - loadTestFiles([testFile]); - const bundle = makeTestBundleProgram(testFile.name); - const host = - createHost(bundle, new Esm2015ReflectionHost(new MockLogger(), false, bundle)); - const node = - getDeclaration(bundle.program, testFile.name, 'MyClass', isNamedVariableDeclaration); - const classSymbol = host.getClassSymbol(node); - - expect(classSymbol).toBeUndefined(); - }); - }); - - describe('isClass()', () => { - it('should return true if a given node is a TS class declaration', () => { - loadTestFiles([SIMPLE_CLASS_FILE]); - const bundle = makeTestBundleProgram(SIMPLE_CLASS_FILE.name); - const host = createHost(bundle, new Esm2015ReflectionHost(new MockLogger(), false, bundle)); - const node = getDeclaration( - bundle.program, SIMPLE_CLASS_FILE.name, 'EmptyClass', isNamedClassDeclaration); - expect(host.isClass(node)).toBe(true); - }); - - it('should return true if a given node is a class expression assigned into a variable', - () => { - loadTestFiles([CLASS_EXPRESSION_FILE]); - const bundle = makeTestBundleProgram(CLASS_EXPRESSION_FILE.name); - const host = - createHost(bundle, new Esm2015ReflectionHost(new MockLogger(), false, bundle)); - const node = getDeclaration( - bundle.program, CLASS_EXPRESSION_FILE.name, 'EmptyClass', ts.isVariableDeclaration); - expect(host.isClass(node)).toBe(true); - }); - - it('should return true if a given node is a class expression assigned into two variables', - () => { - loadTestFiles([CLASS_EXPRESSION_FILE]); - const bundle = makeTestBundleProgram(CLASS_EXPRESSION_FILE.name); - const host = - createHost(bundle, new Esm2015ReflectionHost(new MockLogger(), false, bundle)); - const node = getDeclaration( - bundle.program, CLASS_EXPRESSION_FILE.name, 'AliasedClass', - ts.isVariableDeclaration); - expect(host.isClass(node)).toBe(true); - }); - - it('should return false if a given node is a TS function declaration', () => { - loadTestFiles([FOO_FUNCTION_FILE]); - const bundle = makeTestBundleProgram(FOO_FUNCTION_FILE.name); - const host = createHost(bundle, new Esm2015ReflectionHost(new MockLogger(), false, bundle)); - const node = getDeclaration( - bundle.program, FOO_FUNCTION_FILE.name, 'foo', isNamedFunctionDeclaration); - expect(host.isClass(node)).toBe(false); - }); - }); - - describe('hasBaseClass()', () => { - it('should not consider a class without extends clause as having a base class', () => { - const file = { - name: _('/base_class.js'), - contents: `class TestClass {}`, - }; - loadTestFiles([file]); - const bundle = makeTestBundleProgram(file.name); - const host = createHost(bundle, new Esm2015ReflectionHost(new MockLogger(), false, bundle)); - const classNode = - getDeclaration(bundle.program, file.name, 'TestClass', isNamedClassDeclaration); - expect(host.hasBaseClass(classNode)).toBe(false); - }); - - it('should consider a class with extends clause as having a base class', () => { - const file = { - name: _('/base_class.js'), - contents: ` - class BaseClass {} - class TestClass extends BaseClass {}`, - }; - loadTestFiles([file]); - const bundle = makeTestBundleProgram(file.name); - const host = createHost(bundle, new Esm2015ReflectionHost(new MockLogger(), false, bundle)); - const classNode = - getDeclaration(bundle.program, file.name, 'TestClass', isNamedClassDeclaration); - expect(host.hasBaseClass(classNode)).toBe(true); - }); - - it('should consider an aliased class with extends clause as having a base class', () => { - const file = { - name: _('/base_class.js'), - contents: ` - let TestClass_1; - class BaseClass {} - let TestClass = TestClass_1 = class TestClass extends BaseClass {}`, - }; - loadTestFiles([file]); - const bundle = makeTestBundleProgram(file.name); - const host = createHost(bundle, new Esm2015ReflectionHost(new MockLogger(), false, bundle)); - const classNode = - getDeclaration(bundle.program, file.name, 'TestClass', isNamedVariableDeclaration); - expect(host.hasBaseClass(classNode)).toBe(true); - }); - }); - - describe('getBaseClassExpression()', () => { - it('should not consider a class without extends clause as having a base class', () => { - const file = { - name: _('/base_class.js'), - contents: `class TestClass {}`, - }; - loadTestFiles([file]); - const bundle = makeTestBundleProgram(file.name); - const host = createHost(bundle, new Esm2015ReflectionHost(new MockLogger(), false, bundle)); - const classNode = - getDeclaration(bundle.program, file.name, 'TestClass', isNamedClassDeclaration); - expect(host.getBaseClassExpression(classNode)).toBe(null); - }); - - it('should find the base class of a class with an `extends` clause', () => { - const file = { - name: _('/base_class.js'), - contents: ` - class BaseClass {} - class TestClass extends BaseClass {}`, - }; - loadTestFiles([file]); - const bundle = makeTestBundleProgram(file.name); - const host = createHost(bundle, new Esm2015ReflectionHost(new MockLogger(), false, bundle)); - const classNode = - getDeclaration(bundle.program, file.name, 'TestClass', isNamedClassDeclaration); - const baseIdentifier = host.getBaseClassExpression(classNode)!; - if (!ts.isIdentifier(baseIdentifier)) { - throw new Error(`Expected ${baseIdentifier.getText()} to be an identifier.`); - } - expect(baseIdentifier.text).toEqual('BaseClass'); - }); - - it('should find the base class of an aliased class with an `extends` clause', () => { - const file = { - name: _('/base_class.js'), - contents: ` - let TestClass_1; - class BaseClass {} - let TestClass = TestClass_1 = class TestClass extends BaseClass {}`, - }; - loadTestFiles([file]); - const bundle = makeTestBundleProgram(file.name); - const host = createHost(bundle, new Esm2015ReflectionHost(new MockLogger(), false, bundle)); - const classNode = - getDeclaration(bundle.program, file.name, 'TestClass', isNamedVariableDeclaration); - const baseIdentifier = host.getBaseClassExpression(classNode)!; - if (!ts.isIdentifier(baseIdentifier)) { - throw new Error(`Expected ${baseIdentifier.getText()} to be an identifier.`); - } - expect(baseIdentifier.text).toEqual('BaseClass'); - }); - - it('should find the base class expression of a class with a dynamic `extends` expression', - () => { - const file = { - name: _('/base_class.js'), - contents: ` - class BaseClass {} - function foo() { return BaseClass; } - class TestClass extends foo() {}`, - }; - loadTestFiles([file]); - const bundle = makeTestBundleProgram(file.name); - const host = - createHost(bundle, new Esm2015ReflectionHost(new MockLogger(), false, bundle)); - const classNode = - getDeclaration(bundle.program, file.name, 'TestClass', isNamedClassDeclaration); - const baseExpression = host.getBaseClassExpression(classNode)!; - expect(baseExpression.getText()).toEqual('foo()'); - }); - }); - - describe('getGenericArityOfClass()', () => { - it('should properly count type parameters', () => { - loadTestFiles(ARITY_CLASSES); - const bundle = makeTestBundleProgram(ARITY_CLASSES[0].name); - const dts = makeTestBundleProgram(ARITY_CLASSES[1].name); - const host = - createHost(bundle, new Esm2015ReflectionHost(new MockLogger(), false, bundle, dts)); - const noTypeParamClass = getDeclaration( - bundle.program, _('/src/class.js'), 'NoTypeParam', isNamedClassDeclaration); - expect(host.getGenericArityOfClass(noTypeParamClass)).toBe(0); - const oneTypeParamClass = getDeclaration( - bundle.program, _('/src/class.js'), 'OneTypeParam', isNamedClassDeclaration); - expect(host.getGenericArityOfClass(oneTypeParamClass)).toBe(1); - const twoTypeParamsClass = getDeclaration( - bundle.program, _('/src/class.js'), 'TwoTypeParams', isNamedClassDeclaration); - expect(host.getGenericArityOfClass(twoTypeParamsClass)).toBe(2); - }); - }); - - describe('findClassSymbols()', () => { - it('should return an array of all classes in the given source file', () => { - loadTestFiles(DECORATED_FILES); - const bundle = makeTestBundleProgram(getRootFiles(DECORATED_FILES)[0]); - const host = createHost(bundle, new Esm2015ReflectionHost(new MockLogger(), false, bundle)); - const primaryFile = getSourceFileOrError(bundle.program, DECORATED_FILES[0].name); - const secondaryFile = getSourceFileOrError(bundle.program, DECORATED_FILES[1].name); - - const classSymbolsPrimary = host.findClassSymbols(primaryFile); - expect(classSymbolsPrimary.map(c => c.name)).toEqual([ - 'A', 'B', 'C', 'AliasedClass', 'Wrapped1', 'Wrapped2' - ]); - - const classSymbolsSecondary = host.findClassSymbols(secondaryFile); - expect(classSymbolsSecondary.length).toEqual(1); - expect(classSymbolsSecondary.map(c => c.name)).toEqual(['D']); - }); - }); - - describe('getDecoratorsOfSymbol()', () => { - it('should return decorators of class symbol', () => { - loadTestFiles(DECORATED_FILES); - const bundle = makeTestBundleProgram(getRootFiles(DECORATED_FILES)[0]); - const host = createHost(bundle, new Esm2015ReflectionHost(new MockLogger(), false, bundle)); - const primaryFile = getSourceFileOrError(bundle.program, DECORATED_FILES[0].name); - const secondaryFile = getSourceFileOrError(bundle.program, DECORATED_FILES[1].name); - - const classSymbolsPrimary = host.findClassSymbols(primaryFile); - const classDecoratorsPrimary = classSymbolsPrimary.map(s => host.getDecoratorsOfSymbol(s)); - - expect(classDecoratorsPrimary.length).toEqual(6); - expect(classDecoratorsPrimary[0]!.map(d => d.name)).toEqual(['Directive']); - expect(classDecoratorsPrimary[1]!.map(d => d.name)).toEqual(['Directive']); - expect(classDecoratorsPrimary[2]).toBe(null); - expect(classDecoratorsPrimary[3]!.map(d => d.name)).toEqual(['Directive']); - expect(classDecoratorsPrimary[4]!.map(d => d.name)).toEqual(['Directive']); - expect(classDecoratorsPrimary[5]!.map(d => d.name)).toEqual(['Directive']); - - const classSymbolsSecondary = host.findClassSymbols(secondaryFile); - const classDecoratorsSecondary = - classSymbolsSecondary.map(s => host.getDecoratorsOfSymbol(s)); - expect(classDecoratorsSecondary.length).toEqual(1); - expect(classDecoratorsSecondary[0]!.map(d => d.name)).toEqual(['Directive']); - }); - - it('should return a cloned array on each invocation', () => { - loadTestFiles(DECORATED_FILES); - const bundle = makeTestBundleProgram(getRootFiles(DECORATED_FILES)[0]); - const host = createHost(bundle, new Esm2015ReflectionHost(new MockLogger(), false, bundle)); - const classDecl = - getDeclaration(bundle.program, DECORATED_FILES[0].name, 'A', ts.isClassDeclaration)!; - const classSymbol = host.getClassSymbol(classDecl)!; - - const firstResult = host.getDecoratorsOfSymbol(classSymbol); - const secondResult = host.getDecoratorsOfSymbol(classSymbol); - - expect(firstResult).not.toBe(secondResult); - }); - }); - - describe('getDtsDeclaration()', () => { - it('should find the dts declaration that has the same relative path to the source file', - () => { - loadTestFiles(TYPINGS_SRC_FILES); - loadTestFiles(TYPINGS_DTS_FILES); - const bundle = makeTestBundleProgram(getRootFiles(TYPINGS_SRC_FILES)[0]); - const dts = makeTestBundleProgram(getRootFiles(TYPINGS_DTS_FILES)[0]); - const class1 = getDeclaration( - bundle.program, _('/ep/src/class1.js'), 'Class1', isNamedClassDeclaration); - const host = - createHost(bundle, new Esm2015ReflectionHost(new MockLogger(), false, bundle, dts)); - - const dtsDeclaration = host.getDtsDeclaration(class1); - expect(dtsDeclaration!.getSourceFile().fileName).toEqual(_('/ep/typings/class1.d.ts')); - }); - - it('should find the dts declaration for exported functions', () => { - loadTestFiles(TYPINGS_SRC_FILES); - loadTestFiles(TYPINGS_DTS_FILES); - const bundle = makeTestBundleProgram(_('/ep/src/func1.js')); - const dts = makeTestDtsBundleProgram(_('/ep/typings/func1.d.ts'), _('/')); - const mooFn = getDeclaration( - bundle.program, _('/ep/src/func1.js'), 'mooFn', isNamedFunctionDeclaration); - const host = - createHost(bundle, new Esm2015ReflectionHost(new MockLogger(), false, bundle, dts)); - - const dtsDeclaration = host.getDtsDeclaration(mooFn); - expect(dtsDeclaration!.getSourceFile().fileName).toEqual(_('/ep/typings/func1.d.ts')); - }); - - it('should return null if there is no matching class in the matching dts file', () => { - loadTestFiles(TYPINGS_SRC_FILES); - loadTestFiles(TYPINGS_DTS_FILES); - const bundle = makeTestBundleProgram(getRootFiles(TYPINGS_SRC_FILES)[0]); - const dts = makeTestBundleProgram(getRootFiles(TYPINGS_DTS_FILES)[0]); - const missingClass = getDeclaration( - bundle.program, _('/ep/src/missing-class.js'), 'MissingClass2', - isNamedClassDeclaration); - const host = - createHost(bundle, new Esm2015ReflectionHost(new MockLogger(), false, bundle, dts)); - - expect(host.getDtsDeclaration(missingClass)).toBe(null); - }); - - it('should return null if there is no matching dts file', () => { - loadTestFiles(TYPINGS_SRC_FILES); - loadTestFiles(TYPINGS_DTS_FILES); - const bundle = makeTestBundleProgram(getRootFiles(TYPINGS_SRC_FILES)[0]); - const dts = makeTestBundleProgram(getRootFiles(TYPINGS_DTS_FILES)[0]); - const missingClass = getDeclaration( - bundle.program, _('/ep/src/missing-class.js'), 'MissingClass2', - isNamedClassDeclaration); - const host = - createHost(bundle, new Esm2015ReflectionHost(new MockLogger(), false, bundle, dts)); - - expect(host.getDtsDeclaration(missingClass)).toBe(null); - }); - - it('should ignore dts files outside of the entrypoint', () => { - loadTestFiles(TYPINGS_SRC_FILES); - loadTestFiles(TYPINGS_DTS_FILES); - const bundle = makeTestBundleProgram( - getRootFiles(TYPINGS_SRC_FILES)[0], false, [_('/ep/src/shadow-class.js')]); - const dts = makeTestBundleProgram( - getRootFiles(TYPINGS_DTS_FILES)[0], false, [_('/ep/typings/shadow-class.d.ts')]); - const shadowClass = getDeclaration( - bundle.program, _('/ep/src/shadow-class.js'), 'ShadowClass', isNamedClassDeclaration); - const host = - createHost(bundle, new Esm2015ReflectionHost(new MockLogger(), false, bundle, dts)); - - const dtsDecl = host.getDtsDeclaration(shadowClass)!; - expect(dtsDecl).not.toBeNull(); - expect(dtsDecl.getSourceFile().fileName).toEqual(_('/ep/typings/shadow-class.d.ts')); - }); - - it('should ignore source files outside of the entrypoint', () => { - const externalLibWithoutTypingsIndex = _('/an_external_lib_without_typings/index.js'); - - class TestEsm2015ReflectionHost extends Esm2015ReflectionHost { - override getExportsOfModule(node: ts.Node) { - if (ts.isSourceFile(node) && (node.fileName === externalLibWithoutTypingsIndex)) { - throw new Error( - `'getExportsOfModule()' called on '${externalLibWithoutTypingsIndex}'.`); - } - return super.getExportsOfModule(node); - } - } - - loadTestFiles(TYPINGS_SRC_FILES); - loadTestFiles(TYPINGS_DTS_FILES); - const bundle = makeTestBundleProgram( - getRootFiles(TYPINGS_SRC_FILES)[0], false, [externalLibWithoutTypingsIndex]); - const dts = makeTestBundleProgram(getRootFiles(TYPINGS_DTS_FILES)[0]); - const missingClass = getDeclaration( - bundle.program, _('/ep/src/missing-class.js'), 'MissingClass2', - isNamedClassDeclaration); - const host = - createHost(bundle, new TestEsm2015ReflectionHost(new MockLogger(), false, bundle, dts)); - - expect(host.getDtsDeclaration(missingClass)).toBeNull(); - }); - - it('should find the dts file that contains a matching class declaration, even if the source files do not match', - () => { - loadTestFiles(TYPINGS_SRC_FILES); - loadTestFiles(TYPINGS_DTS_FILES); - const bundle = makeTestBundleProgram(_('/ep/src/flat-file.js')); - const dts = makeTestBundleProgram(getRootFiles(TYPINGS_DTS_FILES)[0]); - const class1 = getDeclaration( - bundle.program, _('/ep/src/flat-file.js'), 'Class1', isNamedClassDeclaration); - const host = - createHost(bundle, new Esm2015ReflectionHost(new MockLogger(), false, bundle, dts)); - - const dtsDeclaration = host.getDtsDeclaration(class1); - expect(dtsDeclaration!.getSourceFile().fileName).toEqual(_('/ep/typings/class1.d.ts')); - }); - - it('should find aliased exports', () => { - loadTestFiles(TYPINGS_SRC_FILES); - loadTestFiles(TYPINGS_DTS_FILES); - const bundle = makeTestBundleProgram(_('/ep/src/flat-file.js')); - const dts = makeTestBundleProgram(getRootFiles(TYPINGS_DTS_FILES)[0]); - const sourceClass = getDeclaration( - bundle.program, _('/ep/src/flat-file.js'), 'SourceClass', isNamedClassDeclaration); - const host = - createHost(bundle, new Esm2015ReflectionHost(new MockLogger(), false, bundle, dts)); - - const dtsDeclaration = host.getDtsDeclaration(sourceClass); - if (dtsDeclaration === null) { - return fail('Expected dts class to be found'); - } - if (!isNamedClassDeclaration(dtsDeclaration)) { - return fail('Expected a named class to be found.'); - } - expect(dtsDeclaration.name.text).toEqual('TypingsClass'); - expect(_(dtsDeclaration.getSourceFile().fileName)) - .toEqual(_('/ep/typings/typings-class.d.ts')); - }); - - it('should find the dts file that contains a matching class declaration, even if the class is not publicly exported', - () => { - loadTestFiles(TYPINGS_SRC_FILES); - loadTestFiles(TYPINGS_DTS_FILES); - const bundle = makeTestBundleProgram(getRootFiles(TYPINGS_SRC_FILES)[0]); - const dts = makeTestBundleProgram(getRootFiles(TYPINGS_DTS_FILES)[0]); - const internalClass = getDeclaration( - bundle.program, _('/ep/src/internal.js'), 'InternalClass', isNamedClassDeclaration); - const host = - createHost(bundle, new Esm2015ReflectionHost(new MockLogger(), false, bundle, dts)); - - const dtsDeclaration = host.getDtsDeclaration(internalClass); - expect(dtsDeclaration!.getSourceFile().fileName).toEqual(_('/ep/typings/internal.d.ts')); - }); - - it('should match publicly and internal exported classes correctly, even if they have the same name', - () => { - loadTestFiles(TYPINGS_SRC_FILES); - loadTestFiles(TYPINGS_DTS_FILES); - const bundle = makeTestBundleProgram(getRootFiles(TYPINGS_SRC_FILES)[0]); - const dts = makeTestBundleProgram(getRootFiles(TYPINGS_DTS_FILES)[0]); - const host = - createHost(bundle, new Esm2015ReflectionHost(new MockLogger(), false, bundle, dts)); - - const class2 = getDeclaration( - bundle.program, _('/ep/src/class2.js'), 'Class2', isNamedClassDeclaration); - const class2DtsDeclaration = host.getDtsDeclaration(class2); - expect(class2DtsDeclaration!.getSourceFile().fileName) - .toEqual(_('/ep/typings/class2.d.ts')); - - const internalClass2 = getDeclaration( - bundle.program, _('/ep/src/internal.js'), 'Class2', isNamedClassDeclaration); - const internalClass2DtsDeclaration = host.getDtsDeclaration(internalClass2); - expect(internalClass2DtsDeclaration!.getSourceFile().fileName) - .toEqual(_('/ep/typings/internal.d.ts')); - }); - }); - - describe('getInternalNameOfClass()', () => { - it('should return the name of the class (if there is no separate inner class)', () => { - loadTestFiles([SIMPLE_CLASS_FILE]); - const bundle = makeTestBundleProgram(SIMPLE_CLASS_FILE.name); - const host = createHost(bundle, new Esm2015ReflectionHost(new MockLogger(), false, bundle)); - const node = getDeclaration( - bundle.program, SIMPLE_CLASS_FILE.name, 'EmptyClass', isNamedClassDeclaration); - expect(host.getInternalNameOfClass(node).text).toEqual('EmptyClass'); - }); - - it('should return the name of the inner class (if there is an IIFE)', () => { - loadTestFiles([SIMPLE_CLASS_FILE]); - const bundle = makeTestBundleProgram(SIMPLE_CLASS_FILE.name); - const host = createHost(bundle, new Esm2015ReflectionHost(new MockLogger(), false, bundle)); - const node = getDeclaration( - bundle.program, SIMPLE_CLASS_FILE.name, 'SimpleWrappedClass', - isNamedVariableDeclaration); - expect(host.getInternalNameOfClass(node).text).toEqual('SimpleWrappedClassInner'); - }); - - it('should return the name of the inner variable declaration (if there is an aliased class in an IIFE)', - () => { - loadTestFiles([SIMPLE_CLASS_FILE]); - const bundle = makeTestBundleProgram(SIMPLE_CLASS_FILE.name); - const host = - createHost(bundle, new Esm2015ReflectionHost(new MockLogger(), false, bundle)); - const node = getDeclaration( - bundle.program, SIMPLE_CLASS_FILE.name, 'AliasedWrappedClass', - isNamedVariableDeclaration); - expect(host.getInternalNameOfClass(node).text).toEqual('AliasedWrappedClassInner'); - }); - }); - - describe('getAdjacentNameOfClass()', () => { - it('should return the name of the class (if there is no separate inner class)', () => { - loadTestFiles([SIMPLE_CLASS_FILE]); - const bundle = makeTestBundleProgram(SIMPLE_CLASS_FILE.name); - const host = createHost(bundle, new Esm2015ReflectionHost(new MockLogger(), false, bundle)); - const node = getDeclaration( - bundle.program, SIMPLE_CLASS_FILE.name, 'EmptyClass', isNamedClassDeclaration); - expect(host.getAdjacentNameOfClass(node).text).toEqual('EmptyClass'); - }); - - it('should return the name of the inner class (if there is an IIFE)', () => { - loadTestFiles([SIMPLE_CLASS_FILE]); - const bundle = makeTestBundleProgram(SIMPLE_CLASS_FILE.name); - const host = createHost(bundle, new Esm2015ReflectionHost(new MockLogger(), false, bundle)); - const node = getDeclaration( - bundle.program, SIMPLE_CLASS_FILE.name, 'SimpleWrappedClass', - isNamedVariableDeclaration); - expect(host.getAdjacentNameOfClass(node).text).toEqual('SimpleWrappedClassInner'); - }); - - it('should return the name of the inner variable declaration (if there is an aliased class in an IIFE)', - () => { - loadTestFiles([SIMPLE_CLASS_FILE]); - const bundle = makeTestBundleProgram(SIMPLE_CLASS_FILE.name); - const host = - createHost(bundle, new Esm2015ReflectionHost(new MockLogger(), false, bundle)); - const node = getDeclaration( - bundle.program, SIMPLE_CLASS_FILE.name, 'AliasedWrappedClass', - isNamedVariableDeclaration); - expect(host.getAdjacentNameOfClass(node).text).toEqual('AliasedWrappedClassAdjacent'); - }); - }); - - describe('getEndOfClass()', () => { - it('should return the last static property of the class', () => { - const testFile: TestFile = { - name: _('/node_modules/test-package/some/file.js'), - contents: `import {Directive, NgZone, Console} from '@angular/core';\n` + - `export class SomeDirective {\n` + - ` constructor(zone, cons) {}\n` + - ` method() {}\n` + - `}\n` + - `SomeDirective.decorators = [\n` + - ` { type: Directive, args: [{ selector: '[a]' }] },\n` + - ` { type: OtherA }\n` + - `];\n` + - `SomeDirective.ctorParameters = () => [\n` + - ` { type: NgZone },\n` + - ` { type: Console }\n` + - `];\n` + - `callSomeFunction();\n` + - `var value = 100;\n` - }; - loadTestFiles([testFile]); - const bundle = makeTestBundleProgram(testFile.name); - const host = createHost(bundle, new Esm2015ReflectionHost(new MockLogger(), false, bundle)); - const classSymbol = host.findClassSymbols(bundle.program.getSourceFile(testFile.name)!)[0]; - const endOfClass = host.getEndOfClass(classSymbol); - expect(endOfClass.getText()) - .toEqual( - `SomeDirective.ctorParameters = () => [\n` + - ` { type: NgZone },\n` + - ` { type: Console }\n` + - `];`); - }); - - it('should return the class declaration if there are no extra statements', () => { - const testFile: TestFile = { - name: _('/node_modules/test-package/some/file.js'), - contents: `export class SomeDirective {\n` + - ` constructor(zone, cons) {}\n` + - ` method() {}\n` + - `}\n` + - `callSomeFunction();\n` + - `var value = 100;\n` - }; - loadTestFiles([testFile]); - const bundle = makeTestBundleProgram(testFile.name); - const host = createHost(bundle, new Esm2015ReflectionHost(new MockLogger(), false, bundle)); - const classSymbol = host.findClassSymbols(bundle.program.getSourceFile(testFile.name)!)[0]; - const endOfClass = host.getEndOfClass(classSymbol); - expect(endOfClass.getText()) - .toEqual( - `export class SomeDirective {\n` + - ` constructor(zone, cons) {}\n` + - ` method() {}\n` + - `}`); - }); - }); - }); -}); diff --git a/packages/compiler-cli/ngcc/test/host/esm5_host_import_helper_spec.ts b/packages/compiler-cli/ngcc/test/host/esm5_host_import_helper_spec.ts deleted file mode 100644 index 74275caa992..00000000000 --- a/packages/compiler-cli/ngcc/test/host/esm5_host_import_helper_spec.ts +++ /dev/null @@ -1,673 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import ts from 'typescript'; - -import {absoluteFrom, getFileSystem, getSourceFileOrError} from '../../../src/ngtsc/file_system'; -import {runInEachFileSystem, TestFile} from '../../../src/ngtsc/file_system/testing'; -import {MockLogger} from '../../../src/ngtsc/logging/testing'; -import {ClassMemberKind, isNamedFunctionDeclaration, isNamedVariableDeclaration, TypeValueReferenceKind} from '../../../src/ngtsc/reflection'; -import {getDeclaration, loadFakeCore, loadTestFiles, loadTsLib} from '../../../src/ngtsc/testing'; -import {getIifeBody} from '../../src/host/esm2015_host'; -import {Esm5ReflectionHost} from '../../src/host/esm5_host'; -import {convertToDirectTsLibImport, convertToInlineTsLib, makeTestBundleProgram} from '../helpers/utils'; - -import {expectTypeValueReferencesForParameters} from './util'; - -runInEachFileSystem(() => { - describe('Esm5ReflectionHost [import helper style]', () => { - let _: typeof absoluteFrom; - let FILES: {[label: string]: TestFile[]}; - - beforeEach(() => { - _ = absoluteFrom; - const NAMESPACED_IMPORT_FILES = [ - { - name: _('/index.js'), - contents: ` - import * as some_directive from './some_directive'; - import * as some_directive2 from '/node_modules/@angular/core/some_directive'; - import * as ngmodule from './ngmodule'; - ` - }, - { - name: _('/some_directive.js'), - contents: ` - import * as tslib_1 from 'tslib'; - import { Directive, Inject, InjectionToken, Input } from '@angular/core'; - var INJECTED_TOKEN = new InjectionToken('injected'); - var ViewContainerRef = /** @class */ (function () { - function ViewContainerRef() { - } - return ViewContainerRef; - }()); - var TemplateRef = /** @class */ (function () { - function TemplateRef() { - } - return TemplateRef; - }()); - var SomeDirective = /** @class */ (function () { - function SomeDirective(_viewContainer, _template, injected) { - this.instanceProperty = 'instance'; - this.input1 = ''; - this.input2 = 0; - } - SomeDirective.prototype.instanceMethod = function () { }; - SomeDirective.staticMethod = function () { }; - SomeDirective.staticProperty = 'static'; - tslib_1.__decorate([ - Input(), - tslib_1.__metadata("design:type", String) - ], SomeDirective.prototype, "input1", void 0); - tslib_1.__decorate([ - Input(), - tslib_1.__metadata("design:type", Number) - ], SomeDirective.prototype, "input2", void 0); - SomeDirective = tslib_1.__decorate([ - Directive({ selector: '[someDirective]' }), - tslib_1.__param(2, Inject(INJECTED_TOKEN)), - tslib_1.__metadata("design:paramtypes", [ViewContainerRef, - TemplateRef, String]) - ], SomeDirective); - return SomeDirective; - }()); - export { SomeDirective }; - `, - }, - { - name: _('/some_directive_ctor_parameters.js'), - contents: ` - import * as tslib_1 from 'tslib'; - import { Directive, Inject, InjectionToken, Input } from '@angular/core'; - var INJECTED_TOKEN = new InjectionToken('injected'); - var ViewContainerRef = /** @class */ (function () { - function ViewContainerRef() { - } - return ViewContainerRef; - }()); - var TemplateRef = /** @class */ (function () { - function TemplateRef() { - } - return TemplateRef; - }()); - var SomeDirective = /** @class */ (function () { - function SomeDirective(_viewContainer, _template, injected) { - this.input1 = ''; - } - SomeDirective.ctorParameters = function() { return [ - { type: ViewContainerRef, }, - { type: TemplateRef, }, - { type: undefined, decorators: [{ type: Inject, args: [INJECTED_TOKEN,] },] }, - ]; }; - tslib_1.__decorate([ - Input(), - ], SomeDirective.prototype, "input1", void 0); - SomeDirective = tslib_1.__decorate([ - Directive({ selector: '[someDirective]' }), - tslib_1.__param(2, Inject(INJECTED_TOKEN)), - ], SomeDirective); - return SomeDirective; - }()); - export { SomeDirective }; - `, - }, - { - name: _('/node_modules/@angular/core/some_directive.js'), - contents: ` - import * as tslib_1 from 'tslib'; - import { Directive, Input } from './directives'; - var SomeDirective = /** @class */ (function () { - function SomeDirective() { - this.input1 = ''; - } - tslib_1.__decorate([ - Input(), - tslib_1.__metadata("design:type", String) - ], SomeDirective.prototype, "input1", void 0); - SomeDirective = tslib_1.__decorate([ - Directive({ selector: '[someDirective]' }), - ], SomeDirective); - return SomeDirective; -}()); -export { SomeDirective }; -`, - }, - { - name: _('/ngmodule.js'), - contents: ` - import * as tslib_1 from 'tslib'; - import { NgModule } from '@angular/core'; - var HttpClientXsrfModule = /** @class */ (function () { - function HttpClientXsrfModule() { - } - HttpClientXsrfModule_1 = HttpClientXsrfModule; - HttpClientXsrfModule.withOptions = function (options) { - if (options === void 0) { options = {}; } - return { - ngModule: HttpClientXsrfModule_1, - providers: [], - }; - }; - var HttpClientXsrfModule_1; - HttpClientXsrfModule = HttpClientXsrfModule_1 = tslib_1.__decorate([ - NgModule({ - providers: [], - }) - ], HttpClientXsrfModule); - return HttpClientXsrfModule; - }()); - var missingValue; - var nonDecoratedVar; - nonDecoratedVar = 43; - export { HttpClientXsrfModule }; - ` - }, - { - name: _('/some_minified_directive.js'), - contents: ` -import * as tslib_1 from 'tslib'; -import { Directive } from '@angular/core'; -// Note that the IIFE is not in parentheses -var SomeDirective = function () { - function SomeDirective() {} - // Note that the decorator is combined with the return statement - return SomeDirective = tslib_1.__decorate([ - Directive({ selector: '[someDirective]' }), - ], SomeDirective); -}(); -export { SomeDirective }; -`, - }, - { - name: _('/some_aliased_directive.js'), - contents: ` -import * as tslib_1 from 'tslib'; -import { Directive } from '@angular/core'; -var AliasedDirective$1 = /** @class */ (function () { - function AliasedDirective() {} - AliasedDirective = tslib_1.__decorate([ - Directive({ selector: '[someDirective]' }), - ], AliasedDirective); - return AliasedDirective; -}()); -export { AliasedDirective$1 }; -`, - }, - ]; - - const DIRECT_IMPORT_FILES = convertToDirectTsLibImport(NAMESPACED_IMPORT_FILES); - const INLINE_FILES = convertToInlineTsLib(NAMESPACED_IMPORT_FILES); - const INLINE_SUFFIXED_FILES = convertToInlineTsLib(NAMESPACED_IMPORT_FILES, '$2'); - - FILES = { - 'namespaced': NAMESPACED_IMPORT_FILES, - 'direct import': DIRECT_IMPORT_FILES, - 'inline': INLINE_FILES, - 'inline suffixed': INLINE_SUFFIXED_FILES, - }; - }); - - ['namespaced', 'direct import', 'inline', 'inline suffixed'].forEach(label => { - describe(`[${label}]`, () => { - beforeEach(() => { - const fs = getFileSystem(); - loadTsLib(fs); - loadFakeCore(fs); - loadTestFiles(FILES[label]); - }); - - describe('getDecoratorsOfDeclaration()', () => { - it('should find the decorators on a class', () => { - const bundle = makeTestBundleProgram(_('/some_directive.js')); - const host = new Esm5ReflectionHost(new MockLogger(), false, bundle); - const classNode = getDeclaration( - bundle.program, _('/some_directive.js'), 'SomeDirective', - isNamedVariableDeclaration); - const decorators = host.getDecoratorsOfDeclaration(classNode)!; - - expect(decorators).toBeDefined(); - expect(decorators.length).toEqual(1); - - const decorator = decorators[0]; - expect(decorator.name).toEqual('Directive'); - expect(decorator.identifier!.getText()).toEqual('Directive'); - expect(decorator.import).toEqual({name: 'Directive', from: '@angular/core'}); - expect(decorator.args!.map(arg => arg.getText())).toEqual([ - '{ selector: \'[someDirective]\' }', - ]); - }); - - it('should find the decorators on a minified class', () => { - const bundle = makeTestBundleProgram(_('/some_minified_directive.js')); - const host = new Esm5ReflectionHost(new MockLogger(), false, bundle); - const classNode = getDeclaration( - bundle.program, _('/some_minified_directive.js'), 'SomeDirective', - isNamedVariableDeclaration); - const decorators = host.getDecoratorsOfDeclaration(classNode)!; - - expect(decorators).toBeDefined(); - expect(decorators.length).toEqual(1); - - const decorator = decorators[0]; - expect(decorator.name).toEqual('Directive'); - expect(decorator.identifier!.getText()).toEqual('Directive'); - expect(decorator.import).toEqual({name: 'Directive', from: '@angular/core'}); - expect(decorator.args!.map(arg => arg.getText())).toEqual([ - '{ selector: \'[someDirective]\' }', - ]); - }); - - it('should find the decorators on an aliased class', () => { - const bundle = makeTestBundleProgram(_('/some_aliased_directive.js')); - const host = new Esm5ReflectionHost(new MockLogger(), false, bundle); - const classNode = getDeclaration( - bundle.program, _('/some_aliased_directive.js'), 'AliasedDirective$1', - isNamedVariableDeclaration); - const decorators = host.getDecoratorsOfDeclaration(classNode)!; - - expect(decorators).toBeDefined(); - expect(decorators.length).toEqual(1); - - const decorator = decorators[0]; - expect(decorator.name).toEqual('Directive'); - expect(decorator.identifier!.getText()).toEqual('Directive'); - expect(decorator.import).toEqual({name: 'Directive', from: '@angular/core'}); - expect(decorator.args!.map(arg => arg.getText())).toEqual([ - '{ selector: \'[someDirective]\' }', - ]); - }); - - it('should find the decorators on a class when mixing `ctorParameters` and `__decorate`', - () => { - const bundle = makeTestBundleProgram(_('/some_directive_ctor_parameters.js')); - const host = new Esm5ReflectionHost(new MockLogger(), false, bundle); - const classNode = getDeclaration( - bundle.program, _('/some_directive_ctor_parameters.js'), 'SomeDirective', - isNamedVariableDeclaration); - const decorators = host.getDecoratorsOfDeclaration(classNode)!; - - expect(decorators).toBeDefined(); - expect(decorators.length).toEqual(1); - - const decorator = decorators[0]; - expect(decorator.name).toEqual('Directive'); - expect(decorator.identifier!.getText()).toEqual('Directive'); - expect(decorator.import).toEqual({name: 'Directive', from: '@angular/core'}); - expect(decorator.args!.map(arg => arg.getText())).toEqual([ - '{ selector: \'[someDirective]\' }', - ]); - }); - - it('should support decorators being used inside @angular/core', () => { - const bundle = - makeTestBundleProgram(_('/node_modules/@angular/core/some_directive.js')); - const host = new Esm5ReflectionHost(new MockLogger(), true, bundle); - const classNode = getDeclaration( - bundle.program, _('/node_modules/@angular/core/some_directive.js'), 'SomeDirective', - isNamedVariableDeclaration); - const decorators = host.getDecoratorsOfDeclaration(classNode)!; - - expect(decorators).toBeDefined(); - expect(decorators.length).toEqual(1); - - const decorator = decorators[0]; - expect(decorator.name).toEqual('Directive'); - expect(decorator.identifier!.getText()).toEqual('Directive'); - expect(decorator.import).toEqual({name: 'Directive', from: './directives'}); - expect(decorator.args!.map(arg => arg.getText())).toEqual([ - '{ selector: \'[someDirective]\' }', - ]); - }); - }); - - describe('getClassSymbol()', () => { - it('should find a class that has been minified', () => { - const bundle = makeTestBundleProgram(_('/some_minified_directive.js')); - const host = new Esm5ReflectionHost(new MockLogger(), false, bundle); - const classNode = getDeclaration( - bundle.program, _('/some_minified_directive.js'), 'SomeDirective', - isNamedVariableDeclaration); - const innerNode = (getIifeBody(classNode.initializer!) as ts.Block) - .statements.find(isNamedFunctionDeclaration)!; - const classSymbol = host.getClassSymbol(classNode); - - expect(classSymbol).toBeDefined(); - expect(classSymbol!.declaration.valueDeclaration).toBe(classNode); - expect(classSymbol!.implementation.valueDeclaration).toBe(innerNode); - }); - }); - - describe('getMembersOfClass()', () => { - it('should find decorated members on a class', () => { - const bundle = makeTestBundleProgram(_('/some_directive.js')); - const host = new Esm5ReflectionHost(new MockLogger(), false, bundle); - const classNode = getDeclaration( - bundle.program, _('/some_directive.js'), 'SomeDirective', - isNamedVariableDeclaration); - const members = host.getMembersOfClass(classNode); - - const input1 = members.find(member => member.name === 'input1')!; - expect(input1.kind).toEqual(ClassMemberKind.Property); - expect(input1.isStatic).toEqual(false); - expect(input1.decorators!.map(d => d.name)).toEqual(['Input']); - - const input2 = members.find(member => member.name === 'input2')!; - expect(input2.kind).toEqual(ClassMemberKind.Property); - expect(input2.isStatic).toEqual(false); - expect(input1.decorators!.map(d => d.name)).toEqual(['Input']); - }); - - it('should find decorated members on a class when mixing `ctorParameters` and `__decorate`', - () => { - const bundle = makeTestBundleProgram(_('/some_directive_ctor_parameters.js')); - const host = new Esm5ReflectionHost(new MockLogger(), false, bundle); - const classNode = getDeclaration( - bundle.program, _('/some_directive_ctor_parameters.js'), 'SomeDirective', - isNamedVariableDeclaration); - const members = host.getMembersOfClass(classNode); - - const input1 = members.find(member => member.name === 'input1')!; - expect(input1.kind).toEqual(ClassMemberKind.Property); - expect(input1.isStatic).toEqual(false); - expect(input1.decorators!.map(d => d.name)).toEqual(['Input']); - }); - - it('should find non decorated properties on a class', () => { - const bundle = makeTestBundleProgram(_('/some_directive.js')); - const host = new Esm5ReflectionHost(new MockLogger(), false, bundle); - const classNode = getDeclaration( - bundle.program, _('/some_directive.js'), 'SomeDirective', - isNamedVariableDeclaration); - const members = host.getMembersOfClass(classNode); - - const instanceProperty = members.find(member => member.name === 'instanceProperty')!; - expect(instanceProperty.kind).toEqual(ClassMemberKind.Property); - expect(instanceProperty.isStatic).toEqual(false); - expect(ts.isBinaryExpression(instanceProperty.implementation!)).toEqual(true); - expect(instanceProperty.value!.getText()).toEqual(`'instance'`); - }); - - it('should find static methods on a class', () => { - const bundle = makeTestBundleProgram(_('/some_directive.js')); - const host = new Esm5ReflectionHost(new MockLogger(), false, bundle); - const classNode = getDeclaration( - bundle.program, _('/some_directive.js'), 'SomeDirective', - isNamedVariableDeclaration); - const members = host.getMembersOfClass(classNode); - - const staticMethod = members.find(member => member.name === 'staticMethod')!; - expect(staticMethod.kind).toEqual(ClassMemberKind.Method); - expect(staticMethod.isStatic).toEqual(true); - expect(ts.isFunctionExpression(staticMethod.implementation!)).toEqual(true); - }); - - it('should find static properties on a class', () => { - const bundle = makeTestBundleProgram(_('/some_directive.js')); - const host = new Esm5ReflectionHost(new MockLogger(), false, bundle); - const classNode = getDeclaration( - bundle.program, _('/some_directive.js'), 'SomeDirective', - isNamedVariableDeclaration); - const members = host.getMembersOfClass(classNode); - - const staticProperty = members.find(member => member.name === 'staticProperty')!; - expect(staticProperty.kind).toEqual(ClassMemberKind.Property); - expect(staticProperty.isStatic).toEqual(true); - expect(ts.isPropertyAccessExpression(staticProperty.implementation!)).toEqual(true); - expect(staticProperty.value!.getText()).toEqual(`'static'`); - }); - - it('should support decorators being used inside @angular/core', () => { - const bundle = - makeTestBundleProgram(_('/node_modules/@angular/core/some_directive.js')); - const host = new Esm5ReflectionHost(new MockLogger(), true, bundle); - const classNode = getDeclaration( - bundle.program, _('/node_modules/@angular/core/some_directive.js'), 'SomeDirective', - isNamedVariableDeclaration); - const members = host.getMembersOfClass(classNode); - - const input1 = members.find(member => member.name === 'input1')!; - expect(input1.kind).toEqual(ClassMemberKind.Property); - expect(input1.isStatic).toEqual(false); - expect(input1.decorators!.map(d => d.name)).toEqual(['Input']); - }); - }); - describe('getConstructorParameters', () => { - it('should find the decorated constructor parameters', () => { - const bundle = makeTestBundleProgram(_('/some_directive.js')); - const host = new Esm5ReflectionHost(new MockLogger(), false, bundle); - const classNode = getDeclaration( - bundle.program, _('/some_directive.js'), 'SomeDirective', - isNamedVariableDeclaration); - const parameters = host.getConstructorParameters(classNode); - - expect(parameters).toBeDefined(); - expect(parameters!.map(parameter => parameter.name)).toEqual([ - '_viewContainer', '_template', 'injected' - ]); - expectTypeValueReferencesForParameters(parameters!, [ - 'ViewContainerRef', - 'TemplateRef', - 'String', - ]); - }); - - it('should find the decorated constructor parameters when mixing `ctorParameters` and `__decorate`', - () => { - const bundle = makeTestBundleProgram(_('/some_directive_ctor_parameters.js')); - const host = new Esm5ReflectionHost(new MockLogger(), false, bundle); - const classNode = getDeclaration( - bundle.program, _('/some_directive_ctor_parameters.js'), 'SomeDirective', - isNamedVariableDeclaration); - const parameters = host.getConstructorParameters(classNode); - - expect(parameters).toBeDefined(); - expect(parameters!.map(parameter => parameter.name)).toEqual([ - '_viewContainer', '_template', 'injected' - ]); - expectTypeValueReferencesForParameters(parameters!, [ - 'ViewContainerRef', - 'TemplateRef', - null, - ]); - }); - - describe('(returned parameters `decorators`)', () => { - it('should have import information on decorators', () => { - const bundle = makeTestBundleProgram(_('/some_directive.js')); - const host = new Esm5ReflectionHost(new MockLogger(), false, bundle); - const classNode = getDeclaration( - bundle.program, _('/some_directive.js'), 'SomeDirective', - isNamedVariableDeclaration); - const parameters = host.getConstructorParameters(classNode); - const decorators = parameters![2].decorators!; - - expect(decorators.length).toEqual(1); - expect(decorators[0].import).toEqual({name: 'Inject', from: '@angular/core'}); - }); - }); - }); - - describe('findClassSymbols()', () => { - it('should return an array of all classes in the given source file', () => { - const bundle = makeTestBundleProgram(_('/index.js')); - const host = new Esm5ReflectionHost(new MockLogger(), false, bundle); - - const ngModuleFile = getSourceFileOrError(bundle.program, _('/ngmodule.js')); - const ngModuleClasses = host.findClassSymbols(ngModuleFile); - expect(ngModuleClasses.length).toEqual(1); - expect(ngModuleClasses[0].name).toBe('HttpClientXsrfModule'); - - const someDirectiveFile = getSourceFileOrError(bundle.program, _('/some_directive.js')); - const someDirectiveClasses = host.findClassSymbols(someDirectiveFile); - expect(someDirectiveClasses.length).toEqual(3); - expect(someDirectiveClasses[0].name).toBe('ViewContainerRef'); - expect(someDirectiveClasses[1].name).toBe('TemplateRef'); - expect(someDirectiveClasses[2].name).toBe('SomeDirective'); - }); - }); - - describe('getDecoratorsOfSymbol()', () => { - it('should return decorators of class symbol', () => { - const bundle = makeTestBundleProgram(_('/index.js')); - const host = new Esm5ReflectionHost(new MockLogger(), false, bundle); - - const ngModuleFile = getSourceFileOrError(bundle.program, _('/ngmodule.js')); - const ngModuleClasses = host.findClassSymbols(ngModuleFile); - const ngModuleDecorators = ngModuleClasses.map(s => host.getDecoratorsOfSymbol(s)); - - expect(ngModuleClasses.length).toEqual(1); - expect(ngModuleDecorators[0]!.map(d => d.name)).toEqual(['NgModule']); - - const someDirectiveFile = getSourceFileOrError(bundle.program, _('/some_directive.js')); - const someDirectiveClasses = host.findClassSymbols(someDirectiveFile); - const someDirectiveDecorators = - someDirectiveClasses.map(s => host.getDecoratorsOfSymbol(s)); - - expect(someDirectiveDecorators.length).toEqual(3); - expect(someDirectiveDecorators[0]).toBe(null); - expect(someDirectiveDecorators[1]).toBe(null); - expect(someDirectiveDecorators[2]!.map(d => d.name)).toEqual(['Directive']); - }); - }); - - describe('getDeclarationOfIdentifier', () => { - it('should return the declaration of a locally defined identifier', () => { - const bundle = makeTestBundleProgram(_('/some_directive.js')); - const host = new Esm5ReflectionHost(new MockLogger(), false, bundle); - const classNode = getDeclaration( - bundle.program, _('/some_directive.js'), 'SomeDirective', - isNamedVariableDeclaration); - const ctrDecorators = host.getConstructorParameters(classNode)!; - const identifierOfViewContainerRef = (ctrDecorators[0].typeValueReference! as { - kind: TypeValueReferenceKind.LOCAL, - expression: ts.Identifier, - defaultImportStatement: null, - }).expression; - - const expectedDeclarationNode = getDeclaration( - bundle.program, _('/some_directive.js'), 'ViewContainerRef', - isNamedVariableDeclaration); - const actualDeclaration = host.getDeclarationOfIdentifier(identifierOfViewContainerRef); - expect(actualDeclaration).not.toBe(null); - expect(actualDeclaration!.node).toBe(expectedDeclarationNode); - expect(actualDeclaration!.viaModule).toBe(null); - }); - - it('should return the declaration of an externally defined identifier', () => { - const bundle = makeTestBundleProgram(_('/some_directive.js')); - const host = new Esm5ReflectionHost(new MockLogger(), false, bundle); - const classNode = getDeclaration( - bundle.program, _('/some_directive.js'), 'SomeDirective', - isNamedVariableDeclaration); - const classDecorators = host.getDecoratorsOfDeclaration(classNode)!; - const decoratorNode = classDecorators[0].node!; - - const identifierOfDirective = - ts.isCallExpression(decoratorNode) && ts.isIdentifier(decoratorNode.expression) ? - decoratorNode.expression : - null; - - const expectedDeclarationNode = getDeclaration( - bundle.program, _('/node_modules/@angular/core/index.d.ts'), 'Directive', - isNamedVariableDeclaration); - const actualDeclaration = host.getDeclarationOfIdentifier(identifierOfDirective!); - expect(actualDeclaration).not.toBe(null); - expect(actualDeclaration!.node).toBe(expectedDeclarationNode); - expect(actualDeclaration!.viaModule).toBe('@angular/core'); - }); - - it('should find the "actual" declaration of an aliased variable identifier', () => { - const bundle = makeTestBundleProgram(_('/ngmodule.js')); - const host = new Esm5ReflectionHost(new MockLogger(), false, bundle); - const ngModuleRef = findIdentifier( - getSourceFileOrError(bundle.program, _('/ngmodule.js')), 'HttpClientXsrfModule_1', - isNgModulePropertyAssignment); - - const declaration = host.getDeclarationOfIdentifier(ngModuleRef!); - expect(declaration).not.toBe(null); - expect(declaration!.node!.getText()).toContain('function HttpClientXsrfModule()'); - }); - }); - describe('getVariableValue', () => { - it('should find the "actual" declaration of an aliased variable identifier', () => { - const bundle = makeTestBundleProgram(_('/ngmodule.js')); - const host = new Esm5ReflectionHost(new MockLogger(), false, bundle); - const ngModuleRef = findVariableDeclaration( - getSourceFileOrError(bundle.program, _('/ngmodule.js')), 'HttpClientXsrfModule_1'); - - const value = host.getVariableValue(ngModuleRef!); - expect(value).not.toBe(null); - if (!value || !ts.isFunctionDeclaration(value.parent)) { - throw new Error( - `Expected result to be a function declaration: ${value && value.getText()}.`); - } - expect(value.getText()).toBe('HttpClientXsrfModule'); - }); - - it('should return undefined if the variable has no assignment', () => { - const bundle = makeTestBundleProgram(_('/ngmodule.js')); - const host = new Esm5ReflectionHost(new MockLogger(), false, bundle); - const missingValue = findVariableDeclaration( - getSourceFileOrError(bundle.program, _('/ngmodule.js')), 'missingValue'); - const value = host.getVariableValue(missingValue!); - expect(value).toBe(null); - }); - - it('should return null if the variable is not assigned from a call to __decorate', () => { - const bundle = makeTestBundleProgram(_('/ngmodule.js')); - const host = new Esm5ReflectionHost(new MockLogger(), false, bundle); - const nonDecoratedVar = findVariableDeclaration( - getSourceFileOrError(bundle.program, _('/ngmodule.js')), 'nonDecoratedVar'); - const value = host.getVariableValue(nonDecoratedVar!); - expect(value).toBe(null); - }); - }); - - describe('getEndOfClass()', () => { - it('should return the last statement related to the class', () => { - const bundle = makeTestBundleProgram(_('/ngmodule.js')); - const host = new Esm5ReflectionHost(new MockLogger(), false, bundle); - const classSymbol = - host.findClassSymbols(bundle.program.getSourceFile(_('/ngmodule.js'))!)[0]; - const endOfClass = host.getEndOfClass(classSymbol); - expect(endOfClass.getText()) - .toMatch( - /HttpClientXsrfModule = HttpClientXsrfModule_1 = .*__decorate.*\(\[\n\s*NgModule\(\{\n\s*providers: \[],\n\s*}\)\n\s*], HttpClientXsrfModule\);/); - }); - }); - }); - - function findVariableDeclaration( - node: ts.Node|undefined, variableName: string): ts.VariableDeclaration|undefined { - if (!node) { - return; - } - if (isNamedVariableDeclaration(node) && node.name.text === variableName) { - return node; - } - return node.forEachChild(node => findVariableDeclaration(node, variableName)); - } - }); - - function findIdentifier( - node: ts.Node|undefined, identifierName: string, - requireFn: (node: ts.Identifier) => boolean): ts.Identifier|undefined { - if (!node) { - return undefined; - } - if (ts.isIdentifier(node) && node.text === identifierName && requireFn(node)) { - return node; - } - return node.forEachChild(node => findIdentifier(node, identifierName, requireFn)); - } - - function isNgModulePropertyAssignment(identifier: ts.Identifier): boolean { - return ts.isPropertyAssignment(identifier.parent) && - identifier.parent.name.getText() === 'ngModule'; - } - }); -}); diff --git a/packages/compiler-cli/ngcc/test/host/esm5_host_spec.ts b/packages/compiler-cli/ngcc/test/host/esm5_host_spec.ts deleted file mode 100644 index 8a3bb6737f3..00000000000 --- a/packages/compiler-cli/ngcc/test/host/esm5_host_spec.ts +++ /dev/null @@ -1,3157 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ - -import ts from 'typescript'; - -import {absoluteFrom, getFileSystem, getSourceFileOrError} from '../../../src/ngtsc/file_system'; -import {runInEachFileSystem, TestFile} from '../../../src/ngtsc/file_system/testing'; -import {MockLogger} from '../../../src/ngtsc/logging/testing'; -import {ClassMemberKind, ConcreteDeclaration, CtorParameter, DeclarationKind, Decorator, DownleveledEnum, isNamedClassDeclaration, isNamedFunctionDeclaration, isNamedVariableDeclaration, KnownDeclaration, TypeScriptReflectionHost, TypeValueReferenceKind} from '../../../src/ngtsc/reflection'; -import {getDeclaration, loadFakeCore, loadTestFiles} from '../../../src/ngtsc/testing'; -import {DelegatingReflectionHost} from '../../src/host/delegating_host'; -import {Esm2015ReflectionHost, getIifeBody} from '../../src/host/esm2015_host'; -import {Esm5ReflectionHost} from '../../src/host/esm5_host'; -import {NgccReflectionHost} from '../../src/host/ngcc_host'; -import {BundleProgram} from '../../src/packages/bundle_program'; -import {getRootFiles, makeTestBundleProgram, makeTestDtsBundleProgram} from '../helpers/utils'; - -import {expectTypeValueReferencesForParameters} from './util'; - -runInEachFileSystem(() => { - describe('Esm5ReflectionHost', () => { - let _: typeof absoluteFrom; - - let SOME_DIRECTIVE_FILE: TestFile; - let CTOR_DECORATORS_ARRAY_FILE: TestFile; - let ACCESSORS_FILE: TestFile; - let SIMPLE_ES2015_CLASS_FILE: TestFile; - let SIMPLE_CLASS_FILE: TestFile; - let TOPLEVEL_DECORATORS_FILE: TestFile; - let FOO_FUNCTION_FILE: TestFile; - let INVALID_DECORATORS_FILE: TestFile; - let INVALID_DECORATOR_ARGS_FILE: TestFile; - let INVALID_PROP_DECORATORS_FILE: TestFile; - let INVALID_PROP_DECORATOR_ARGS_FILE: TestFile; - let INVALID_CTOR_DECORATORS_FILE: TestFile; - let INVALID_CTOR_DECORATOR_ARGS_FILE: TestFile; - let IMPORTS_FILES: TestFile[]; - let EXPORTS_FILES: TestFile[]; - let FUNCTION_BODY_FILE: TestFile; - let DECORATED_FILES: TestFile[]; - let UNWANTED_PROTOTYPE_EXPORT_FILE: TestFile; - let TYPINGS_SRC_FILES: TestFile[]; - let TYPINGS_DTS_FILES: TestFile[]; - let NAMESPACED_IMPORT_FILE: TestFile; - - // Helpers - const createHost = (bundle: BundleProgram, ngccHost: Esm5ReflectionHost) => { - const tsHost = new TypeScriptReflectionHost(bundle.program.getTypeChecker()); - return new DelegatingReflectionHost(tsHost, ngccHost); - }; - - beforeEach(() => { - _ = absoluteFrom; - SOME_DIRECTIVE_FILE = { - name: _('/some_directive.js'), - contents: ` - import { Directive, Inject, InjectionToken, Input } from '@angular/core'; - - var INJECTED_TOKEN = new InjectionToken('injected'); - var ViewContainerRef = {}; - var TemplateRef = {}; - - var SomeDirective = (function() { - function SomeDirective(_viewContainer, _template, injected) { - this.instanceProperty = 'instance'; - } - SomeDirective.prototype = { - instanceMethod: function() {}, - }; - SomeDirective.staticMethod = function() {}; - SomeDirective.staticProperty = 'static'; - SomeDirective.decorators = [ - { type: Directive, args: [{ selector: '[someDirective]' },] } - ]; - SomeDirective.ctorParameters = function() { return [ - { type: ViewContainerRef, }, - { type: TemplateRef, }, - { type: undefined, decorators: [{ type: Inject, args: [INJECTED_TOKEN,] },] }, - ]; }; - SomeDirective.propDecorators = { - "input1": [{ type: Input },], - "input2": [{ type: Input },], - }; - return SomeDirective; - }()); - `, - }; - - TOPLEVEL_DECORATORS_FILE = { - name: _('/toplevel_decorators.js'), - contents: ` - import { Directive, Inject, InjectionToken, Input } from '@angular/core'; - - var INJECTED_TOKEN = new InjectionToken('injected'); - var ViewContainerRef = {}; - var TemplateRef = {}; - - var SomeDirective = (function() { - function SomeDirective(_viewContainer, _template, injected) {} - return SomeDirective; - }()); - SomeDirective.decorators = [ - { type: Directive, args: [{ selector: '[someDirective]' },] } - ]; - SomeDirective.ctorParameters = function() { return [ - { type: ViewContainerRef, }, - { type: TemplateRef, }, - { type: undefined, decorators: [{ type: Inject, args: [INJECTED_TOKEN,] },] }, - ]; }; - SomeDirective.propDecorators = { - "input1": [{ type: Input },], - "input2": [{ type: Input },], - }; - `, - }; - - CTOR_DECORATORS_ARRAY_FILE = { - name: _('/ctor_decorated_as_array.js'), - contents: ` - var CtorDecoratedAsArray = (function() { - function CtorDecoratedAsArray(arg1) { - } - CtorDecoratedAsArray.ctorParameters = [{ type: ParamType, decorators: [{ type: Inject },] }]; - return CtorDecoratedAsArray; - }()); - `, - }; - - ACCESSORS_FILE = { - name: _('/accessors.js'), - contents: ` - import { Directive, Input, Output } from '@angular/core'; - - var SomeDirective = (function() { - function SomeDirective() { - } - Object.defineProperty(SomeDirective.prototype, "setter", { - set: function (value) { this.value = value; }, - enumerable: true, - configurable: true - }); - Object.defineProperty(SomeDirective.prototype, "getter", { - get: function () { return null; }, - enumerable: true, - configurable: true - }); - Object.defineProperty(SomeDirective.prototype, "setterAndGetter", { - get: function () { return null; }, - set: function (value) { this.value = value; }, - enumerable: true, - configurable: true - }); - Object.defineProperty(SomeDirective, "staticSetter", { - set: function (value) { this.value = value; }, - enumerable: true, - configurable: true - }); - Object.defineProperty(SomeDirective.prototype, "none", { - enumerable: true, - configurable: true - }); - Object.defineProperty(SomeDirective.prototype, "incomplete"); - SomeDirective.decorators = [ - { type: Directive, args: [{ selector: '[someDirective]' },] } - ]; - SomeDirective.propDecorators = { - "setter": [{ type: Input },], - "getter": [{ type: Output },], - "setterAndGetter": [{ type: Input },], - }; - return SomeDirective; - }()); - `, - }; - - SIMPLE_ES2015_CLASS_FILE = { - name: _('/simple_es2015_class.d.ts'), - contents: ` - export class EmptyClass {} - `, - }; - - SIMPLE_CLASS_FILE = { - name: _('/simple_class.js'), - contents: ` - var EmptyClass = (function() { - function EmptyClass() { - } - return EmptyClass; - }()); - var NoParensClass = function() { - function EmptyClass() { - } - return EmptyClass; - }(); - var InnerParensClass = (function() { - function EmptyClass() { - } - return EmptyClass; - })(); - var NoDecoratorConstructorClass = (function() { - function NoDecoratorConstructorClass(foo) { - } - return NoDecoratorConstructorClass; - }()); - var OuterClass1 = (function() { - function InnerClass1() { - } - return InnerClass1; - }()); - var OuterClass2 = (function() { - function InnerClass2() { - } - InnerClass2_1 = InnerClass12 - var InnerClass2_1; - return InnerClass2; - }()); - var SuperClass = (function() { function SuperClass() {} return SuperClass; }()); - var ChildClass = /** @class */ (function (_super) { - __extends(InnerChildClass, _super); - function InnerChildClass() {} - return InnerChildClass; - }(SuperClass); - `, - }; - - FOO_FUNCTION_FILE = { - name: _('/foo_function.js'), - contents: ` - import { Directive } from '@angular/core'; - - function foo() {} - foo.decorators = [ - { type: Directive, args: [{ selector: '[ignored]' },] } - ]; - `, - }; - - INVALID_DECORATORS_FILE = { - name: _('/invalid_decorators.js'), - contents: ` - import { Directive } from '@angular/core'; - var NotArrayLiteral = (function() { - function NotArrayLiteral() { - } - NotArrayLiteral.decorators = () => [ - { type: Directive, args: [{ selector: '[ignored]' },] }, - ]; - return NotArrayLiteral; - }()); - - var NotObjectLiteral = (function() { - function NotObjectLiteral() { - } - NotObjectLiteral.decorators = [ - "This is not an object literal", - { type: Directive }, - ]; - return NotObjectLiteral; - }()); - - var NoTypeProperty = (function() { - function NoTypeProperty() { - } - NoTypeProperty.decorators = [ - { notType: Directive }, - { type: Directive }, - ]; - return NoTypeProperty; - }()); - - var NotIdentifier = (function() { - function NotIdentifier() { - } - NotIdentifier.decorators = [ - { type: 'StringsLiteralsAreNotIdentifiers' }, - { type: Directive }, - ]; - return NotIdentifier; - }()); - `, - }; - - INVALID_DECORATOR_ARGS_FILE = { - name: _('/invalid_decorator_args.js'), - contents: ` - import { Directive } from '@angular/core'; - var NoArgsProperty = (function() { - function NoArgsProperty() { - } - NoArgsProperty.decorators = [ - { type: Directive }, - ]; - return NoArgsProperty; - }()); - - var args = [{ selector: '[ignored]' },]; - var NoPropertyAssignment = (function() { - function NoPropertyAssignment() { - } - NoPropertyAssignment.decorators = [ - { type: Directive, args }, - ]; - return NoPropertyAssignment; - }()); - - var NotArrayLiteral = (function() { - function NotArrayLiteral() { - } - NotArrayLiteral.decorators = [ - { type: Directive, args: () => [{ selector: '[ignored]' },] }, - ]; - return NotArrayLiteral; - }()); - `, - }; - - INVALID_PROP_DECORATORS_FILE = { - name: _('/invalid_prop_decorators.js'), - contents: ` - import { Input } from '@angular/core'; - var NotObjectLiteral = (function() { - function NotObjectLiteral() { - } - NotObjectLiteral.propDecorators = () => ({ - "prop": [{ type: Input },] - }); - return NotObjectLiteral; - }()); - - var NotObjectLiteralProp = (function() { - function NotObjectLiteralProp() { - } - NotObjectLiteralProp.propDecorators = { - "prop": [ - "This is not an object literal", - { type: Input }, - ] - }; - return NotObjectLiteralProp; - }()); - - var NoTypeProperty = (function() { - function NoTypeProperty() { - } - NoTypeProperty.propDecorators = { - "prop": [ - { notType: Input }, - { type: Input }, - ] - }; - return NoTypeProperty; - }()); - - var NotIdentifier = (function() { - function NotIdentifier() { - } - NotIdentifier.propDecorators = { - "prop": [ - { type: 'StringsLiteralsAreNotIdentifiers' }, - { type: Input }, - ] - }; - return NotIdentifier; - }()); - `, - }; - - INVALID_PROP_DECORATOR_ARGS_FILE = { - name: _('/invalid_prop_decorator_args.js'), - contents: ` - import { Input } from '@angular/core'; - var NoArgsProperty = (function() { - function NoArgsProperty() { - } - NoArgsProperty.propDecorators = { - "prop": [{ type: Input },] - }; - return NoArgsProperty; - }()); - - var args = [{ selector: '[ignored]' },]; - var NoPropertyAssignment = (function() { - function NoPropertyAssignment() { - } - NoPropertyAssignment.propDecorators = { - "prop": [{ type: Input, args },] - }; - return NoPropertyAssignment; - }()); - - var NotArrayLiteral = (function() { - function NotArrayLiteral() { - } - NotArrayLiteral.propDecorators = { - "prop": [{ type: Input, args: () => [{ selector: '[ignored]' },] },], - }; - return NotArrayLiteral; - }()); - `, - }; - - INVALID_CTOR_DECORATORS_FILE = { - name: _('/invalid_ctor_decorators.js'), - contents: ` - import { Inject } from '@angular/core'; - var NoParametersDecorator = {}; - var NoParameters = (function() { - function NoParameters() {} - return NoParameters; - }()); - - var NotArrayLiteral = (function() { - function NotArrayLiteral(arg1) { - } - NotArrayLiteral.ctorParameters = function() { return 'StringsAreNotArrayLiterals'; }; - return NotArrayLiteral; - }()); - - var NotObjectLiteral = (function() { - function NotObjectLiteral(arg1, arg2) { - } - NotObjectLiteral.ctorParameters = function() { return [ - "This is not an object literal", - { type: 'ParamType', decorators: [{ type: Inject },] }, - ]; }; - return NotObjectLiteral; - }()); - - var NoTypeProperty = (function() { - function NoTypeProperty(arg1, arg2) { - } - NoTypeProperty.ctorParameters = function() { return [ - { - type: 'ParamType', - decorators: [ - { notType: Inject }, - { type: Inject }, - ] - }, - ]; }; - return NoTypeProperty; - }()); - - var NotIdentifier = (function() { - function NotIdentifier(arg1, arg2) { - } - NotIdentifier.ctorParameters = function() { return [ - { - type: 'ParamType', - decorators: [ - { type: 'StringsLiteralsAreNotIdentifiers' }, - { type: Inject }, - ] - }, - ]; }; - return NotIdentifier; - }()); - `, - }; - - INVALID_CTOR_DECORATOR_ARGS_FILE = { - name: _('/invalid_ctor_decorator_args.js'), - contents: ` - import { Inject } from '@angular/core'; - var NoArgsProperty = (function() { - function NoArgsProperty(arg1) { - } - NoArgsProperty.ctorParameters = function() { return [ - { type: 'ParamType', decorators: [{ type: Inject },] }, - ]; }; - return NoArgsProperty; - }()); - - var args = [{ selector: '[ignored]' },]; - var NoPropertyAssignment = (function() { - function NoPropertyAssignment(arg1) { - } - NoPropertyAssignment.ctorParameters = function() { return [ - { type: 'ParamType', decorators: [{ type: Inject, args },] }, - ]; }; - return NoPropertyAssignment; - }()); - - var NotArrayLiteral = (function() { - function NotArrayLiteral(arg1) { - } - NotArrayLiteral.ctorParameters = function() { return [ - { type: 'ParamType', decorators: [{ type: Inject, args: () => [{ selector: '[ignored]' },] },] }, - ]; }; - return NotArrayLiteral; - }()); - `, - }; - - IMPORTS_FILES = [ - { - name: _('/index.js'), - contents: ` - import * as a from './a'; - import * as b from './b'; - import * as c from './c'; - ` - }, - { - name: _('/a.js'), - contents: ` - export var a = 'a'; - `, - }, - { - name: _('/b.js'), - contents: ` - import {a} from './a.js'; - import {a as foo} from './a.js'; - - var b = a; - var c = foo; - var d = b; - `, - }, - ]; - - EXPORTS_FILES = [ - { - name: _('/index.js'), - contents: ` - import * as a from './a'; - import * as b from './b'; - ` - }, - { - name: _('/a.js'), - contents: ` - export var a = 'a'; - `, - }, - { - name: _('/b.js'), - contents: ` - import {Directive} from '@angular/core'; - import {a} from './a'; - import {a as foo} from './a'; - export {Directive} from '@angular/core'; - export {a} from './a'; - export var b = a; - export var c = foo; - export var d = b; - export var e = 'e'; - export var DirectiveX = Directive; - export var SomeClass = (function() { - function SomeClass() {} - return SomeClass; - }()); - `, - }, - ]; - - FUNCTION_BODY_FILE = { - name: _('/function_body.js'), - contents: ` - function foo(x) { - return x; - } - function bar(x, y) { - if (y === void 0) { y = 42; } - return x + y; - } - function complex() { - var x = 42; - return 42; - } - function baz(x) { - var y; - if (x === void 0) { y = 42; } - return y; - } - var y; - function qux(x) { - if (x === void 0) { y = 42; } - return y; - } - function moo() { - var x; - if (x === void 0) { x = 42; } - return x; - } - var x; - function juu() { - if (x === void 0) { x = 42; } - return x; - } - ` - }; - - DECORATED_FILES = [ - { - name: _('/primary.js'), - contents: ` - import {Directive} from '@angular/core'; - import { D } from './secondary'; - var A = (function() { - function A() {} - A.decorators = [ - { type: Directive, args: [{ selector: '[a]' }] } - ]; - return A; - }()); - var B = (function() { - function B() {} - B.decorators = [ - { type: Directive, args: [{ selector: '[b]' }] } - ]; - return B; - }()); - function x() {} - function y() {} - var C = (function() { - function C() {} - return C; - }); - export { A, x, C }; - ` - }, - { - name: _('/secondary.js'), - contents: ` - import {Directive} from '@angular/core'; - var D = (function() { - function D() {} - D.decorators = [ - { type: Directive, args: [{ selector: '[d]' }] } - ]; - return D; - }()); - export { D }; - ` - } - ]; - - UNWANTED_PROTOTYPE_EXPORT_FILE = { - name: _('/library.d.ts'), - contents: ` - export declare class SomeParam { - someInstanceMethod(): void; - static someStaticProp: any; - }` - }; - - TYPINGS_SRC_FILES = [ - { - name: _('/ep/src/index.js'), - contents: ` - import {InternalClass} from './internal'; - import * as func1 from './func1'; - import * as missing from './missing-class'; - import * as flatFile from './flat-file'; - export * from './class1'; - export * from './class2'; - ` - }, - { - name: _('/ep/src/class1.js'), - contents: ` - var Class1 = (function() { - function Class1() {} - return Class1; - }()); - var MissingClass1 = (function() { - function MissingClass1() {} - return MissingClass1; - }()); - export {Class1, MissingClass1}; - ` - }, - { - name: _('/ep/src/class2.js'), - contents: ` - var Class2 = (function() { - function Class2() {} - return Class2; - }()); - export {Class2}; - ` - }, - {name: _('/ep/src/func1.js'), contents: 'function mooFn() {} export {mooFn}'}, { - name: _('/ep/src/internal.js'), - contents: ` - var InternalClass = (function() { - function InternalClass() {} - return InternalClass; - }()); - var Class2 = (function() { - function Class2() {} - return Class2; - }()); - export {InternalClass, Class2}; - ` - }, - { - name: _('/ep/src/missing-class.js'), - contents: ` - var MissingClass2 = (function() { - function MissingClass2() {} - return MissingClass2; - }()); - export {MissingClass2}; - ` - }, - { - name: _('/ep/src/flat-file.js'), - contents: ` - var Class1 = (function() { - function Class1() {} - return Class1; - }()); - var MissingClass1 = (function() { - function MissingClass1() {} - return MissingClass1; - }()); - var MissingClass2 = (function() { - function MissingClass2() {} - return MissingClass2; - }()); - var SourceClass = (function() { - function SourceClass() {} - return SourceClass; - }()); - export {Class1, SourceClass as AliasedClass, MissingClass1, MissingClass2}; - ` - } - ]; - - TYPINGS_DTS_FILES = [ - { - name: _('/ep/typings/index.d.ts'), - contents: ` - import '../../an_external_lib/index'; - import {InternalClass} from './internal'; - import {mooFn} from './func1'; - export * from './class1'; - export * from './class2'; - ` - }, - { - name: _('/ep/typings/class1.d.ts'), - contents: `export declare class Class1 {}\nexport declare class OtherClass {}` - }, - { - name: _('/ep/typings/class2.d.ts'), - contents: ` - export declare class Class2 {} - export declare interface SomeInterface {} - export {TypingsClass as AliasedClass} from './typings-class'; - ` - }, - {name: _('/ep/typings/func1.d.ts'), contents: 'export declare function mooFn(): void;'}, - { - name: _('/ep/typings/internal.d.ts'), - contents: `export declare class InternalClass {}\nexport declare class Class2 {}` - }, - { - name: _('/ep/typings/typings-class.d.ts'), - contents: `export declare class TypingsClass {}` - }, - {name: _('/ep/typings/shadow-class.d.ts'), contents: `export declare class ShadowClass {}`}, - {name: _('/an_external_lib/index.d.ts'), contents: 'export declare class ShadowClass {}'}, - ]; - - NAMESPACED_IMPORT_FILE = { - name: _('/some_directive.js'), - contents: ` - import * as core from '@angular/core'; - - var SomeDirective = (function() { - function SomeDirective() { - } - SomeDirective.decorators = [ - { type: core.Directive, args: [{ selector: '[someDirective]' },] } - ]; - return SomeDirective; - }()); - ` - }; - }); - - describe('getDecoratorsOfDeclaration()', () => { - it('should find the decorators on a class', () => { - loadTestFiles([SOME_DIRECTIVE_FILE]); - const bundle = makeTestBundleProgram(SOME_DIRECTIVE_FILE.name); - const host = createHost(bundle, new Esm5ReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, SOME_DIRECTIVE_FILE.name, 'SomeDirective', isNamedVariableDeclaration); - const decorators = host.getDecoratorsOfDeclaration(classNode)!; - - expect(decorators).toBeDefined(); - expect(decorators.length).toEqual(1); - - const decorator = decorators[0]; - expect(decorator.name).toEqual('Directive'); - expect(decorator.import).toEqual({name: 'Directive', from: '@angular/core'}); - expect(decorator.args!.map(arg => arg.getText())).toEqual([ - '{ selector: \'[someDirective]\' }', - ]); - }); - - it('should find the decorators on a class at the top level', () => { - loadTestFiles([TOPLEVEL_DECORATORS_FILE]); - const bundle = makeTestBundleProgram(TOPLEVEL_DECORATORS_FILE.name); - const host = createHost(bundle, new Esm5ReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, TOPLEVEL_DECORATORS_FILE.name, 'SomeDirective', - isNamedVariableDeclaration); - const decorators = host.getDecoratorsOfDeclaration(classNode)!; - - expect(decorators).toBeDefined(); - expect(decorators.length).toEqual(1); - - const decorator = decorators[0]; - expect(decorator.name).toEqual('Directive'); - expect(decorator.import).toEqual({name: 'Directive', from: '@angular/core'}); - expect(decorator.args!.map(arg => arg.getText())).toEqual([ - '{ selector: \'[someDirective]\' }', - ]); - }); - - it('should return null if the symbol is not a class', () => { - loadTestFiles([FOO_FUNCTION_FILE]); - const bundle = makeTestBundleProgram(FOO_FUNCTION_FILE.name); - const host = createHost(bundle, new Esm5ReflectionHost(new MockLogger(), false, bundle)); - const functionNode = getDeclaration( - bundle.program, FOO_FUNCTION_FILE.name, 'foo', isNamedFunctionDeclaration); - const decorators = host.getDecoratorsOfDeclaration(functionNode); - expect(decorators).toBe(null); - }); - - it('should return null if there are no decorators', () => { - loadTestFiles([SIMPLE_CLASS_FILE]); - const bundle = makeTestBundleProgram(SIMPLE_CLASS_FILE.name); - const host = createHost(bundle, new Esm5ReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, SIMPLE_CLASS_FILE.name, 'EmptyClass', isNamedVariableDeclaration); - const decorators = host.getDecoratorsOfDeclaration(classNode); - expect(decorators).toBe(null); - }); - - it('should ignore `decorators` if it is not an array literal', () => { - loadTestFiles([INVALID_DECORATORS_FILE]); - const bundle = makeTestBundleProgram(INVALID_DECORATORS_FILE.name); - const host = createHost(bundle, new Esm5ReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, INVALID_DECORATORS_FILE.name, 'NotArrayLiteral', - isNamedVariableDeclaration); - const decorators = host.getDecoratorsOfDeclaration(classNode); - expect(decorators).toEqual([]); - }); - - it('should ignore decorator elements that are not object literals', () => { - loadTestFiles([INVALID_DECORATORS_FILE]); - const bundle = makeTestBundleProgram(INVALID_DECORATORS_FILE.name); - const host = createHost(bundle, new Esm5ReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, INVALID_DECORATORS_FILE.name, 'NotObjectLiteral', - isNamedVariableDeclaration); - const decorators = host.getDecoratorsOfDeclaration(classNode)!; - - expect(decorators.length).toBe(1); - expect(decorators[0]).toEqual(jasmine.objectContaining({name: 'Directive'})); - }); - - it('should ignore decorator elements that have no `type` property', () => { - loadTestFiles([INVALID_DECORATORS_FILE]); - const bundle = makeTestBundleProgram(INVALID_DECORATORS_FILE.name); - const host = createHost(bundle, new Esm5ReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, INVALID_DECORATORS_FILE.name, 'NoTypeProperty', - isNamedVariableDeclaration); - const decorators = host.getDecoratorsOfDeclaration(classNode)!; - - expect(decorators.length).toBe(1); - expect(decorators[0]).toEqual(jasmine.objectContaining({name: 'Directive'})); - }); - - it('should ignore decorator elements whose `type` value is not an identifier', () => { - loadTestFiles([INVALID_DECORATORS_FILE]); - const bundle = makeTestBundleProgram(INVALID_DECORATORS_FILE.name); - const host = createHost(bundle, new Esm5ReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, INVALID_DECORATORS_FILE.name, 'NotIdentifier', - isNamedVariableDeclaration); - const decorators = host.getDecoratorsOfDeclaration(classNode)!; - - expect(decorators.length).toBe(1); - expect(decorators[0]).toEqual(jasmine.objectContaining({name: 'Directive'})); - }); - - it('should have import information on decorators', () => { - loadTestFiles([SOME_DIRECTIVE_FILE]); - const bundle = makeTestBundleProgram(SOME_DIRECTIVE_FILE.name); - const host = createHost(bundle, new Esm5ReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, SOME_DIRECTIVE_FILE.name, 'SomeDirective', isNamedVariableDeclaration); - const decorators = host.getDecoratorsOfDeclaration(classNode)!; - - expect(decorators.length).toEqual(1); - expect(decorators[0].import).toEqual({name: 'Directive', from: '@angular/core'}); - }); - - describe('(returned decorators `args`)', () => { - it('should be an empty array if decorator has no `args` property', () => { - loadTestFiles([INVALID_DECORATOR_ARGS_FILE]); - const bundle = makeTestBundleProgram(INVALID_DECORATOR_ARGS_FILE.name); - const host = createHost(bundle, new Esm5ReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, INVALID_DECORATOR_ARGS_FILE.name, 'NoArgsProperty', - isNamedVariableDeclaration); - const decorators = host.getDecoratorsOfDeclaration(classNode)!; - - expect(decorators.length).toBe(1); - expect(decorators[0].name).toBe('Directive'); - expect(decorators[0].args).toEqual([]); - }); - - it('should be an empty array if decorator\'s `args` has no property assignment', () => { - loadTestFiles([INVALID_DECORATOR_ARGS_FILE]); - const bundle = makeTestBundleProgram(INVALID_DECORATOR_ARGS_FILE.name); - const host = createHost(bundle, new Esm5ReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, INVALID_DECORATOR_ARGS_FILE.name, 'NoPropertyAssignment', - isNamedVariableDeclaration); - const decorators = host.getDecoratorsOfDeclaration(classNode)!; - - expect(decorators.length).toBe(1); - expect(decorators[0].name).toBe('Directive'); - expect(decorators[0].args).toEqual([]); - }); - - it('should be an empty array if `args` property value is not an array literal', () => { - loadTestFiles([INVALID_DECORATOR_ARGS_FILE]); - const bundle = makeTestBundleProgram(INVALID_DECORATOR_ARGS_FILE.name); - const host = createHost(bundle, new Esm5ReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, INVALID_DECORATOR_ARGS_FILE.name, 'NotArrayLiteral', - isNamedVariableDeclaration); - const decorators = host.getDecoratorsOfDeclaration(classNode)!; - - expect(decorators.length).toBe(1); - expect(decorators[0].name).toBe('Directive'); - expect(decorators[0].args).toEqual([]); - }); - }); - }); - - describe('getMembersOfClass()', () => { - it('should find decorated members on a class at the top level', () => { - loadTestFiles([TOPLEVEL_DECORATORS_FILE]); - const bundle = makeTestBundleProgram(TOPLEVEL_DECORATORS_FILE.name); - const host = createHost(bundle, new Esm5ReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, TOPLEVEL_DECORATORS_FILE.name, 'SomeDirective', - isNamedVariableDeclaration); - const members = host.getMembersOfClass(classNode); - - const input1 = members.find(member => member.name === 'input1')!; - expect(input1.kind).toEqual(ClassMemberKind.Property); - expect(input1.isStatic).toEqual(false); - expect(input1.decorators!.map(d => d.name)).toEqual(['Input']); - expect(input1.decorators![0].import).toEqual({name: 'Input', from: '@angular/core'}); - - const input2 = members.find(member => member.name === 'input2')!; - expect(input2.kind).toEqual(ClassMemberKind.Property); - expect(input2.isStatic).toEqual(false); - expect(input2.decorators!.map(d => d.name)).toEqual(['Input']); - expect(input2.decorators![0].import).toEqual({name: 'Input', from: '@angular/core'}); - }); - - it('should find decorated members on a class', () => { - loadTestFiles([SOME_DIRECTIVE_FILE]); - const bundle = makeTestBundleProgram(SOME_DIRECTIVE_FILE.name); - const host = createHost(bundle, new Esm5ReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, SOME_DIRECTIVE_FILE.name, 'SomeDirective', isNamedVariableDeclaration); - const members = host.getMembersOfClass(classNode); - - const input1 = members.find(member => member.name === 'input1')!; - expect(input1.kind).toEqual(ClassMemberKind.Property); - expect(input1.isStatic).toEqual(false); - expect(input1.decorators!.map(d => d.name)).toEqual(['Input']); - - const input2 = members.find(member => member.name === 'input2')!; - expect(input2.kind).toEqual(ClassMemberKind.Property); - expect(input2.isStatic).toEqual(false); - expect(input2.decorators!.map(d => d.name)).toEqual(['Input']); - }); - - it('should find Object.defineProperty members on a class', () => { - loadTestFiles([ACCESSORS_FILE]); - const bundle = makeTestBundleProgram(ACCESSORS_FILE.name); - const host = createHost(bundle, new Esm5ReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, ACCESSORS_FILE.name, 'SomeDirective', isNamedVariableDeclaration); - const members = host.getMembersOfClass(classNode); - - const setter = members.find(member => member.name === 'setter')!; - expect(setter.kind).toEqual(ClassMemberKind.Setter); - expect(setter.isStatic).toEqual(false); - expect(setter.value).toBeNull(); - expect(setter.decorators!.map(d => d.name)).toEqual(['Input']); - expect(ts.isFunctionExpression(setter.implementation!)).toEqual(true); - expect((setter.implementation as ts.FunctionExpression).body.statements[0].getText()) - .toEqual('this.value = value;'); - - const getter = members.find(member => member.name === 'getter')!; - expect(getter.kind).toEqual(ClassMemberKind.Getter); - expect(getter.isStatic).toEqual(false); - expect(getter.value).toBeNull(); - expect(getter.decorators!.map(d => d.name)).toEqual(['Output']); - expect(ts.isFunctionExpression(getter.implementation!)).toEqual(true); - expect((getter.implementation as ts.FunctionExpression).body.statements[0].getText()) - .toEqual('return null;'); - - const [combinedSetter, combinedGetter] = - members.filter(member => member.name === 'setterAndGetter'); - expect(combinedSetter.kind).toEqual(ClassMemberKind.Setter); - expect(combinedSetter.isStatic).toEqual(false); - expect(combinedSetter.decorators!.map(d => d.name)).toEqual(['Input']); - expect(combinedGetter.kind).toEqual(ClassMemberKind.Getter); - expect(combinedGetter.isStatic).toEqual(false); - expect(combinedGetter.decorators!.map(d => d.name)).toEqual([]); - - const staticSetter = members.find(member => member.name === 'staticSetter')!; - expect(staticSetter.kind).toEqual(ClassMemberKind.Setter); - expect(staticSetter.isStatic).toEqual(true); - expect(staticSetter.value).toBeNull(); - expect(staticSetter.decorators!.map(d => d.name)).toEqual([]); - - const none = members.find(member => member.name === 'none'); - expect(none).toBeUndefined(); - - const incomplete = members.find(member => member.name === 'incomplete'); - expect(incomplete).toBeUndefined(); - }); - - it('should find non decorated properties on a class', () => { - loadTestFiles([SOME_DIRECTIVE_FILE]); - const bundle = makeTestBundleProgram(SOME_DIRECTIVE_FILE.name); - const host = createHost(bundle, new Esm5ReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, SOME_DIRECTIVE_FILE.name, 'SomeDirective', isNamedVariableDeclaration); - const members = host.getMembersOfClass(classNode); - - const instanceProperty = members.find(member => member.name === 'instanceProperty')!; - expect(instanceProperty.kind).toEqual(ClassMemberKind.Property); - expect(instanceProperty.isStatic).toEqual(false); - expect(ts.isBinaryExpression(instanceProperty.implementation!)).toEqual(true); - expect(instanceProperty.value!.getText()).toEqual(`'instance'`); - }); - - it('should find static methods on a class', () => { - loadTestFiles([SOME_DIRECTIVE_FILE]); - const bundle = makeTestBundleProgram(SOME_DIRECTIVE_FILE.name); - const host = createHost(bundle, new Esm5ReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, SOME_DIRECTIVE_FILE.name, 'SomeDirective', isNamedVariableDeclaration); - const members = host.getMembersOfClass(classNode); - - const staticMethod = members.find(member => member.name === 'staticMethod')!; - expect(staticMethod.kind).toEqual(ClassMemberKind.Method); - expect(staticMethod.isStatic).toEqual(true); - expect(staticMethod.value).toBeNull(); - expect(ts.isFunctionExpression(staticMethod.implementation!)).toEqual(true); - }); - - it('should find static properties on a class', () => { - loadTestFiles([SOME_DIRECTIVE_FILE]); - const bundle = makeTestBundleProgram(SOME_DIRECTIVE_FILE.name); - const host = createHost(bundle, new Esm5ReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, SOME_DIRECTIVE_FILE.name, 'SomeDirective', isNamedVariableDeclaration); - const members = host.getMembersOfClass(classNode); - - const staticProperty = members.find(member => member.name === 'staticProperty')!; - expect(staticProperty.kind).toEqual(ClassMemberKind.Property); - expect(staticProperty.isStatic).toEqual(true); - expect(ts.isPropertyAccessExpression(staticProperty.implementation!)).toEqual(true); - expect(staticProperty.value!.getText()).toEqual(`'static'`); - }); - - it('should accept `ctorParameters` as an array', () => { - loadTestFiles([CTOR_DECORATORS_ARRAY_FILE]); - const bundle = makeTestBundleProgram(CTOR_DECORATORS_ARRAY_FILE.name); - const host = createHost(bundle, new Esm5ReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, CTOR_DECORATORS_ARRAY_FILE.name, 'CtorDecoratedAsArray', - isNamedVariableDeclaration); - const parameters = host.getConstructorParameters(classNode)!; - - expect(parameters).toBeDefined(); - expect(parameters.map(parameter => parameter.name)).toEqual(['arg1']); - expectTypeValueReferencesForParameters(parameters, ['ParamType']); - }); - - it('should throw if the symbol is not a class', () => { - loadTestFiles([FOO_FUNCTION_FILE]); - const bundle = makeTestBundleProgram(FOO_FUNCTION_FILE.name); - const host = createHost(bundle, new Esm5ReflectionHost(new MockLogger(), false, bundle)); - const functionNode = getDeclaration( - bundle.program, FOO_FUNCTION_FILE.name, 'foo', isNamedFunctionDeclaration); - expect(() => { - host.getMembersOfClass(functionNode); - }).toThrowError(`Attempted to get members of a non-class: "function foo() {}"`); - }); - - it('should return an empty array if there are no prop decorators', () => { - loadTestFiles([SIMPLE_CLASS_FILE]); - const bundle = makeTestBundleProgram(SIMPLE_CLASS_FILE.name); - const host = createHost(bundle, new Esm5ReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, SIMPLE_CLASS_FILE.name, 'EmptyClass', isNamedVariableDeclaration); - const members = host.getMembersOfClass(classNode); - - expect(members).toEqual([]); - }); - - it('should not process decorated properties in `propDecorators` if it is not an object literal', - () => { - loadTestFiles([INVALID_PROP_DECORATORS_FILE]); - const bundle = makeTestBundleProgram(INVALID_PROP_DECORATORS_FILE.name); - const host = createHost(bundle, new Esm5ReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, INVALID_PROP_DECORATORS_FILE.name, 'NotObjectLiteral', - isNamedVariableDeclaration); - const members = host.getMembersOfClass(classNode); - - expect(members.map(member => member.name)).not.toContain('prop'); - }); - - it('should ignore prop decorator elements that are not object literals', () => { - loadTestFiles([INVALID_PROP_DECORATORS_FILE]); - const bundle = makeTestBundleProgram(INVALID_PROP_DECORATORS_FILE.name); - const host = createHost(bundle, new Esm5ReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, INVALID_PROP_DECORATORS_FILE.name, 'NotObjectLiteralProp', - isNamedVariableDeclaration); - const members = host.getMembersOfClass(classNode); - const prop = members.find(m => m.name === 'prop')!; - const decorators = prop.decorators!; - - expect(decorators.length).toBe(1); - expect(decorators[0]).toEqual(jasmine.objectContaining({name: 'Input'})); - }); - - it('should ignore prop decorator elements that have no `type` property', () => { - loadTestFiles([INVALID_PROP_DECORATORS_FILE]); - const bundle = makeTestBundleProgram(INVALID_PROP_DECORATORS_FILE.name); - const host = createHost(bundle, new Esm5ReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, INVALID_PROP_DECORATORS_FILE.name, 'NoTypeProperty', - isNamedVariableDeclaration); - const members = host.getMembersOfClass(classNode); - const prop = members.find(m => m.name === 'prop')!; - const decorators = prop.decorators!; - - expect(decorators.length).toBe(1); - expect(decorators[0]).toEqual(jasmine.objectContaining({name: 'Input'})); - }); - - it('should ignore prop decorator elements whose `type` value is not an identifier', () => { - loadTestFiles([INVALID_PROP_DECORATORS_FILE]); - const bundle = makeTestBundleProgram(INVALID_PROP_DECORATORS_FILE.name); - const host = createHost(bundle, new Esm5ReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, INVALID_PROP_DECORATORS_FILE.name, 'NotIdentifier', - isNamedVariableDeclaration); - const members = host.getMembersOfClass(classNode); - const prop = members.find(m => m.name === 'prop')!; - const decorators = prop.decorators!; - - expect(decorators.length).toBe(1); - expect(decorators[0]).toEqual(jasmine.objectContaining({name: 'Input'})); - }); - - describe('(returned prop decorators `args`)', () => { - it('should be an empty array if prop decorator has no `args` property', () => { - loadTestFiles([INVALID_PROP_DECORATOR_ARGS_FILE]); - const bundle = makeTestBundleProgram(INVALID_PROP_DECORATOR_ARGS_FILE.name); - const host = createHost(bundle, new Esm5ReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, INVALID_PROP_DECORATOR_ARGS_FILE.name, 'NoArgsProperty', - isNamedVariableDeclaration); - const members = host.getMembersOfClass(classNode); - const prop = members.find(m => m.name === 'prop')!; - const decorators = prop.decorators!; - - expect(decorators.length).toBe(1); - expect(decorators[0].name).toBe('Input'); - expect(decorators[0].args).toEqual([]); - }); - - it('should be an empty array if prop decorator\'s `args` has no property assignment', - () => { - loadTestFiles([INVALID_PROP_DECORATOR_ARGS_FILE]); - const bundle = makeTestBundleProgram(INVALID_PROP_DECORATOR_ARGS_FILE.name); - const host = - createHost(bundle, new Esm5ReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, INVALID_PROP_DECORATOR_ARGS_FILE.name, 'NoPropertyAssignment', - isNamedVariableDeclaration); - const members = host.getMembersOfClass(classNode); - const prop = members.find(m => m.name === 'prop')!; - const decorators = prop.decorators!; - - expect(decorators.length).toBe(1); - expect(decorators[0].name).toBe('Input'); - expect(decorators[0].args).toEqual([]); - }); - - it('should be an empty array if `args` property value is not an array literal', () => { - loadTestFiles([INVALID_PROP_DECORATOR_ARGS_FILE]); - const bundle = makeTestBundleProgram(INVALID_PROP_DECORATOR_ARGS_FILE.name); - const host = createHost(bundle, new Esm5ReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, INVALID_PROP_DECORATOR_ARGS_FILE.name, 'NotArrayLiteral', - isNamedVariableDeclaration); - const members = host.getMembersOfClass(classNode); - const prop = members.find(m => m.name === 'prop')!; - const decorators = prop.decorators!; - - expect(decorators.length).toBe(1); - expect(decorators[0].name).toBe('Input'); - expect(decorators[0].args).toEqual([]); - }); - }); - - it('should ignore the prototype pseudo-static property on class imported from typings files', - () => { - loadTestFiles([UNWANTED_PROTOTYPE_EXPORT_FILE]); - const bundle = makeTestBundleProgram(UNWANTED_PROTOTYPE_EXPORT_FILE.name); - const host = createHost(bundle, new Esm5ReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, UNWANTED_PROTOTYPE_EXPORT_FILE.name, 'SomeParam', - isNamedClassDeclaration); - const members = host.getMembersOfClass(classNode); - expect(members.find(m => m.name === 'prototype')).toBeUndefined(); - }); - }); - - describe('getConstructorParameters()', () => { - it('should retain imported name for type value references for decorated constructor parameter types', - () => { - const files = [ - { - name: _('/node_modules/shared-lib/foo.d.ts'), - contents: ` - declare class Foo {} - export {Foo as Bar}; - `, - }, - { - name: _('/node_modules/shared-lib/index.d.ts'), - contents: ` - export {Bar as Baz} from './foo'; - `, - }, - { - name: _('/local.js'), - contents: ` - var Internal = (function() { - function Internal() { - } - return Internal; - }()); - export {Internal as External}; - ` - }, - { - name: _('/main.js'), - contents: ` - import {Baz} from 'shared-lib'; - import {External} from './local'; - var SameFile = (function() { - function SameFile() { - } - return SameFile; - }()); - export SameFile; - - var SomeClass = (function() { - function SomeClass(arg1, arg2, arg3) {} - return SomeClass; - }()); - SomeClass.ctorParameters = function() { return [{ type: Baz }, { type: External }, { type: SameFile }]; }; - export SomeClass; - `, - }, - ]; - - loadTestFiles(files); - const bundle = makeTestBundleProgram(_('/main.js')); - const host = createHost(bundle, new Esm5ReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, _('/main.js'), 'SomeClass', isNamedVariableDeclaration); - - const parameters = host.getConstructorParameters(classNode)!; - - expect(parameters.map(p => p.name)).toEqual(['arg1', 'arg2', 'arg3']); - expectTypeValueReferencesForParameters( - parameters, ['Baz', 'External', 'SameFile'], ['shared-lib', './local', null]); - }); - - it('should find the decorated constructor parameters', () => { - loadTestFiles([SOME_DIRECTIVE_FILE]); - const bundle = makeTestBundleProgram(SOME_DIRECTIVE_FILE.name); - const host = createHost(bundle, new Esm5ReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, SOME_DIRECTIVE_FILE.name, 'SomeDirective', isNamedVariableDeclaration); - const parameters = host.getConstructorParameters(classNode); - - expect(parameters).toBeDefined(); - expect(parameters!.map(parameter => parameter.name)).toEqual([ - '_viewContainer', '_template', 'injected' - ]); - expectTypeValueReferencesForParameters(parameters!, [ - 'ViewContainerRef', - 'TemplateRef', - null, - ]); - }); - - it('should find the decorated constructor parameters at the top level', () => { - loadTestFiles([TOPLEVEL_DECORATORS_FILE]); - const bundle = makeTestBundleProgram(TOPLEVEL_DECORATORS_FILE.name); - const host = createHost(bundle, new Esm5ReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, TOPLEVEL_DECORATORS_FILE.name, 'SomeDirective', - isNamedVariableDeclaration); - const parameters = host.getConstructorParameters(classNode); - - expect(parameters).toBeDefined(); - expect(parameters!.map(parameter => parameter.name)).toEqual([ - '_viewContainer', '_template', 'injected' - ]); - expectTypeValueReferencesForParameters(parameters!, [ - 'ViewContainerRef', - 'TemplateRef', - null, - ]); - }); - - it('should throw if the symbol is not a class', () => { - loadTestFiles([FOO_FUNCTION_FILE]); - const bundle = makeTestBundleProgram(FOO_FUNCTION_FILE.name); - const host = createHost(bundle, new Esm5ReflectionHost(new MockLogger(), false, bundle)); - const functionNode = getDeclaration( - bundle.program, FOO_FUNCTION_FILE.name, 'foo', isNamedFunctionDeclaration); - expect(() => { - host.getConstructorParameters(functionNode); - }) - .toThrowError( - 'Attempted to get constructor parameters of a non-class: "function foo() {}"'); - }); - - // In ES5 there is no such thing as a constructor-less class - // it('should return `null` if there is no constructor', () => { }); - - it('should return an array even if there are no decorators', () => { - loadTestFiles([SIMPLE_CLASS_FILE]); - const bundle = makeTestBundleProgram(SIMPLE_CLASS_FILE.name); - const host = createHost(bundle, new Esm5ReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, SIMPLE_CLASS_FILE.name, 'NoDecoratorConstructorClass', - isNamedVariableDeclaration); - const parameters = host.getConstructorParameters(classNode); - - expect(parameters).toEqual(jasmine.any(Array)); - expect(parameters!.length).toEqual(1); - expect(parameters![0].name).toEqual('foo'); - expect(parameters![0].decorators).toBe(null); - }); - - it('should return an empty array if there are no constructor parameters', () => { - loadTestFiles([INVALID_CTOR_DECORATORS_FILE]); - const bundle = makeTestBundleProgram(INVALID_CTOR_DECORATORS_FILE.name); - const host = createHost(bundle, new Esm5ReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, INVALID_CTOR_DECORATORS_FILE.name, 'NoParameters', - isNamedVariableDeclaration); - const parameters = host.getConstructorParameters(classNode); - - expect(parameters).toEqual([]); - }); - - // In ES5 there are no arrow functions - // it('should ignore `ctorParameters` if it is an arrow function', () => { }); - - it('should ignore `ctorParameters` if it does not return an array literal', () => { - loadTestFiles([INVALID_CTOR_DECORATORS_FILE]); - const bundle = makeTestBundleProgram(INVALID_CTOR_DECORATORS_FILE.name); - const host = createHost(bundle, new Esm5ReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, INVALID_CTOR_DECORATORS_FILE.name, 'NotArrayLiteral', - isNamedVariableDeclaration); - const parameters = host.getConstructorParameters(classNode); - - expect(parameters!.length).toBe(1); - expect(parameters![0]).toEqual(jasmine.objectContaining({ - name: 'arg1', - decorators: null, - })); - }); - - describe('(returned parameters `decorators`)', () => { - it('should ignore param decorator elements that are not object literals', () => { - loadTestFiles([INVALID_CTOR_DECORATORS_FILE]); - const bundle = makeTestBundleProgram(INVALID_CTOR_DECORATORS_FILE.name); - const host = createHost(bundle, new Esm5ReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, INVALID_CTOR_DECORATORS_FILE.name, 'NotObjectLiteral', - isNamedVariableDeclaration); - const parameters = host.getConstructorParameters(classNode); - - expect(parameters!.length).toBe(2); - expect(parameters![0]).toEqual(jasmine.objectContaining({ - name: 'arg1', - decorators: null, - })); - expect(parameters![1]).toEqual(jasmine.objectContaining({ - name: 'arg2', - decorators: jasmine.any(Array) as any - })); - }); - - it('should ignore param decorator elements that have no `type` property', () => { - loadTestFiles([INVALID_CTOR_DECORATORS_FILE]); - const bundle = makeTestBundleProgram(INVALID_CTOR_DECORATORS_FILE.name); - const host = createHost(bundle, new Esm5ReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, INVALID_CTOR_DECORATORS_FILE.name, 'NoTypeProperty', - isNamedVariableDeclaration); - const parameters = host.getConstructorParameters(classNode); - const decorators = parameters![0].decorators!; - - expect(decorators.length).toBe(1); - expect(decorators[0]).toEqual(jasmine.objectContaining({name: 'Inject'})); - }); - - it('should ignore param decorator elements whose `type` value is not an identifier', () => { - loadTestFiles([INVALID_CTOR_DECORATORS_FILE]); - const bundle = makeTestBundleProgram(INVALID_CTOR_DECORATORS_FILE.name); - const host = createHost(bundle, new Esm5ReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, INVALID_CTOR_DECORATORS_FILE.name, 'NotIdentifier', - isNamedVariableDeclaration); - const parameters = host.getConstructorParameters(classNode); - const decorators = parameters![0].decorators!; - - expect(decorators.length).toBe(1); - expect(decorators[0]).toEqual(jasmine.objectContaining({name: 'Inject'})); - }); - - it('should have import information on decorators', () => { - loadTestFiles([SOME_DIRECTIVE_FILE]); - const bundle = makeTestBundleProgram(SOME_DIRECTIVE_FILE.name); - const host = createHost(bundle, new Esm5ReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, SOME_DIRECTIVE_FILE.name, 'SomeDirective', - isNamedVariableDeclaration); - const parameters = host.getConstructorParameters(classNode); - const decorators = parameters![2].decorators!; - - expect(decorators.length).toEqual(1); - expect(decorators[0].import).toEqual({name: 'Inject', from: '@angular/core'}); - }); - }); - - function getConstructorParameters( - constructor: string, - mode?: 'inlined'|'inlined_with_suffix'|'imported'|'imported_namespace') { - let fileHeader = ''; - - switch (mode) { - case 'imported': - fileHeader = `import {__spread, __spreadArray, __read} from 'tslib';`; - break; - case 'imported_namespace': - fileHeader = `import * as tslib from 'tslib';`; - break; - case 'inlined': - fileHeader = - `var __spread = (this && this.__spread) || function (...args) { /* ... */ };\n` + - `var __spreadArray = (this && this.__spreadArray) || function (...args) { /* ... */ };\n` + - `var __read = (this && this.__read) || function (...args) { /* ... */ };\n`; - break; - case 'inlined_with_suffix': - fileHeader = - `var __spread$1 = (this && this.__spread$1) || function (...args) { /* ... */ };\n` + - `var __spreadArray$1 = (this && this.__spreadArray$1) || function (...args) { /* ... */ };\n` + - `var __read$2 = (this && this.__read$2) || function (...args) { /* ... */ };\n`; - break; - } - - const file = { - name: _('/synthesized_constructors.js'), - contents: ` - ${fileHeader} - var TestClass = /** @class */ (function (_super) { - __extends(TestClass, _super); - ${constructor} - return TestClass; - }(null)); - `, - }; - - loadTestFiles([file]); - const bundle = makeTestBundleProgram(file.name); - const host = createHost(bundle, new Esm5ReflectionHost(new MockLogger(), false, bundle)); - const classNode = - getDeclaration(bundle.program, file.name, 'TestClass', isNamedVariableDeclaration); - return host.getConstructorParameters(classNode); - } - - describe('TS -> ES5: synthesized constructors', () => { - it('recognizes _this assignment from super call', () => { - const parameters = getConstructorParameters(` - function TestClass() { - var _this = _super !== null && _super.apply(this, arguments) || this; - _this.synthesizedProperty = null; - return _this; - } - `); - - expect(parameters).toBeNull(); - }); - - it('recognizes super call as return statement', () => { - const parameters = getConstructorParameters(` - function TestClass() { - return _super !== null && _super.apply(this, arguments) || this; - } - `); - - expect(parameters).toBeNull(); - }); - - it('handles the case where a unique name was generated for _super or _this', () => { - const parameters = getConstructorParameters(` - function TestClass() { - var _this_1 = _super_1 !== null && _super_1.apply(this, arguments) || this; - _this_1._this = null; - _this_1._super = null; - return _this_1; - } - `); - - expect(parameters).toBeNull(); - }); - - it('does not consider constructors with parameters as synthesized', () => { - const parameters = getConstructorParameters(` - function TestClass(arg) { - return _super !== null && _super.apply(this, arguments) || this; - } - `); - - expect(parameters!.length).toBe(1); - }); - - it('does not consider manual super calls as synthesized', () => { - const parameters = getConstructorParameters(` - function TestClass() { - return _super.call(this) || this; - } - `); - - expect(parameters!.length).toBe(0); - }); - - it('does not consider empty constructors as synthesized', () => { - const parameters = getConstructorParameters(`function TestClass() {}`); - expect(parameters!.length).toBe(0); - }); - }); - - // See: https://github.com/angular/angular/issues/38453. - describe('ES2015 -> ES5: synthesized constructors through TSC downleveling', () => { - it('recognizes delegate super call using inline spread helper', () => { - const parameters = getConstructorParameters( - ` - function TestClass() { - return _super.apply(this, __spread(arguments)) || this; - }`, - 'inlined'); - - expect(parameters).toBeNull(); - }); - - it('recognizes delegate super call using inline spreadArray helper', () => { - const parameters = getConstructorParameters( - ` - function TestClass() { - return _super.apply(this, __spreadArray([], __read(arguments))) || this; - }`, - 'inlined'); - - expect(parameters).toBeNull(); - }); - - it('recognizes delegate super call using inline spread helper with suffix', () => { - const parameters = getConstructorParameters( - ` - function TestClass() { - return _super.apply(this, __spread$1(arguments)) || this; - }`, - 'inlined_with_suffix'); - - expect(parameters).toBeNull(); - }); - - it('recognizes delegate super call using inline spreadArray helper with suffix', () => { - const parameters = getConstructorParameters( - ` - function TestClass() { - return _super.apply(this, __spreadArray$1([], __read$2(arguments))) || this; - }`, - 'inlined_with_suffix'); - - expect(parameters).toBeNull(); - }); - - it('recognizes delegate super call using imported spread helper', () => { - const parameters = getConstructorParameters( - ` - function TestClass() { - return _super.apply(this, __spread(arguments)) || this; - }`, - 'imported'); - - expect(parameters).toBeNull(); - }); - - it('recognizes delegate super call using imported spreadArray helper', () => { - const parameters = getConstructorParameters( - ` - function TestClass() { - return _super.apply(this, __spreadArray([], __read(arguments))) || this; - }`, - 'imported'); - - expect(parameters).toBeNull(); - }); - - it('recognizes delegate super call using namespace imported spread helper', () => { - const parameters = getConstructorParameters( - ` - function TestClass() { - return _super.apply(this, tslib.__spread(arguments)) || this; - }`, - 'imported_namespace'); - - expect(parameters).toBeNull(); - }); - - it('recognizes delegate super call using namespace imported spreadArray helper', () => { - const parameters = getConstructorParameters( - ` - function TestClass() { - return _super.apply(this, tslib.__spreadArray([], tslib.__read(arguments))) || this; - }`, - 'imported_namespace'); - - expect(parameters).toBeNull(); - }); - - describe('with class member assignment', () => { - it('recognizes delegate super call using inline spread helper', () => { - const parameters = getConstructorParameters( - ` - function TestClass() { - var _this = _super.apply(this, __spread(arguments)) || this; - _this.synthesizedProperty = null; - return _this; - }`, - 'inlined'); - - expect(parameters).toBeNull(); - }); - - it('recognizes delegate super call using inline spreadArray helper', () => { - const parameters = getConstructorParameters( - ` - function TestClass() { - var _this = _super.apply(this, __spreadArray([], __read(arguments))) || this; - _this.synthesizedProperty = null; - return _this; - }`, - 'inlined'); - - expect(parameters).toBeNull(); - }); - - it('recognizes delegate super call using inline spread helper with suffix', () => { - const parameters = getConstructorParameters( - ` - function TestClass() { - var _this = _super.apply(this, __spread$1(arguments)) || this; - _this.synthesizedProperty = null; - return _this; - }`, - 'inlined_with_suffix'); - - expect(parameters).toBeNull(); - }); - - it('recognizes delegate super call using inline spreadArray helper with suffix', () => { - const parameters = getConstructorParameters( - ` - function TestClass() { - var _this = _super.apply(this, __spreadArray$1([], __read$2(arguments))) || this; - _this.synthesizedProperty = null; - return _this; - }`, - 'inlined_with_suffix'); - - expect(parameters).toBeNull(); - }); - - it('recognizes delegate super call using imported spread helper', () => { - const parameters = getConstructorParameters( - ` - function TestClass() { - var _this = _super.apply(this, __spread(arguments)) || this; - _this.synthesizedProperty = null; - return _this; - }`, - 'imported'); - - expect(parameters).toBeNull(); - }); - - it('recognizes delegate super call using imported spreadArray helper', () => { - const parameters = getConstructorParameters( - ` - function TestClass() { - var _this = _super.apply(this, __spreadArray([], __read(arguments))) || this; - _this.synthesizedProperty = null; - return _this; - }`, - 'imported'); - - expect(parameters).toBeNull(); - }); - - it('recognizes delegate super call using namespace imported spread helper', () => { - const parameters = getConstructorParameters( - ` - function TestClass() { - var _this = _super.apply(this, tslib.__spread(arguments)) || this; - _this.synthesizedProperty = null; - return _this; - }`, - 'imported_namespace'); - - expect(parameters).toBeNull(); - }); - - it('recognizes delegate super call using namespace imported spreadArray helper', () => { - const parameters = getConstructorParameters( - ` - function TestClass() { - var _this = _super.apply(this, tslib.__spreadArray([], tslib.__read(arguments))) || this; - _this.synthesizedProperty = null; - return _this; - }`, - 'imported_namespace'); - - expect(parameters).toBeNull(); - }); - }); - - it('handles the case where a unique name was generated for _super or _this', () => { - const parameters = getConstructorParameters( - ` - function TestClass() { - var _this_1 = _super_1.apply(this, __spread(arguments)) || this; - _this_1._this = null; - _this_1._super = null; - return _this_1; - }`, - 'inlined'); - - expect(parameters).toBeNull(); - }); - - it('does not consider constructors with parameters as synthesized', () => { - const parameters = getConstructorParameters( - ` - function TestClass(arg) { - return _super.apply(this, __spread(arguments)) || this; - }`, - 'inlined'); - - expect(parameters!.length).toBe(1); - }); - }); - - describe('(returned parameters `decorators.args`)', () => { - it('should be an empty array if param decorator has no `args` property', () => { - loadTestFiles([INVALID_CTOR_DECORATOR_ARGS_FILE]); - const bundle = makeTestBundleProgram(INVALID_CTOR_DECORATOR_ARGS_FILE.name); - const host = createHost(bundle, new Esm5ReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, INVALID_CTOR_DECORATOR_ARGS_FILE.name, 'NoArgsProperty', - isNamedVariableDeclaration); - const parameters = host.getConstructorParameters(classNode); - expect(parameters!.length).toBe(1); - const decorators = parameters![0].decorators!; - - expect(decorators.length).toBe(1); - expect(decorators[0].name).toBe('Inject'); - expect(decorators[0].args).toEqual([]); - }); - - it('should be an empty array if param decorator\'s `args` has no property assignment', - () => { - loadTestFiles([INVALID_CTOR_DECORATOR_ARGS_FILE]); - const bundle = makeTestBundleProgram(INVALID_CTOR_DECORATOR_ARGS_FILE.name); - const host = - createHost(bundle, new Esm5ReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, INVALID_CTOR_DECORATOR_ARGS_FILE.name, 'NoPropertyAssignment', - isNamedVariableDeclaration); - const parameters = host.getConstructorParameters(classNode); - const decorators = parameters![0].decorators!; - - expect(decorators.length).toBe(1); - expect(decorators[0].name).toBe('Inject'); - expect(decorators[0].args).toEqual([]); - }); - - it('should be an empty array if `args` property value is not an array literal', () => { - loadTestFiles([INVALID_CTOR_DECORATOR_ARGS_FILE]); - const bundle = makeTestBundleProgram(INVALID_CTOR_DECORATOR_ARGS_FILE.name); - const host = createHost(bundle, new Esm5ReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, INVALID_CTOR_DECORATOR_ARGS_FILE.name, 'NotArrayLiteral', - isNamedVariableDeclaration); - const parameters = host.getConstructorParameters(classNode); - const decorators = parameters![0].decorators!; - - expect(decorators.length).toBe(1); - expect(decorators[0].name).toBe('Inject'); - expect(decorators[0].args).toEqual([]); - }); - }); - }); - - describe('getDefinitionOfFunction()', () => { - it('should return an object describing the function declaration passed as an argument', - () => { - loadTestFiles([FUNCTION_BODY_FILE]); - const bundle = makeTestBundleProgram(FUNCTION_BODY_FILE.name); - const host = createHost(bundle, new Esm5ReflectionHost(new MockLogger(), false, bundle)); - - const fooNode = getDeclaration( - bundle.program, FUNCTION_BODY_FILE.name, 'foo', isNamedFunctionDeclaration)!; - const fooDef = host.getDefinitionOfFunction(fooNode)!; - expect(fooDef.node).toBe(fooNode); - expect(fooDef.body!.length).toEqual(1); - expect(fooDef.body![0].getText()).toEqual(`return x;`); - expect(fooDef.parameters.length).toEqual(1); - expect(fooDef.parameters[0].name).toEqual('x'); - expect(fooDef.parameters[0].initializer).toBe(null); - - const barNode = getDeclaration( - bundle.program, FUNCTION_BODY_FILE.name, 'bar', isNamedFunctionDeclaration)!; - const barDef = host.getDefinitionOfFunction(barNode)!; - expect(barDef.node).toBe(barNode); - expect(barDef.body!.length).toEqual(1); - expect(ts.isReturnStatement(barDef.body![0])).toBeTruthy(); - expect(barDef.body![0].getText()).toEqual(`return x + y;`); - expect(barDef.parameters.length).toEqual(2); - expect(barDef.parameters[0].name).toEqual('x'); - expect(fooDef.parameters[0].initializer).toBe(null); - expect(barDef.parameters[1].name).toEqual('y'); - expect(barDef.parameters[1].initializer!.getText()).toEqual('42'); - - const bazNode = getDeclaration( - bundle.program, FUNCTION_BODY_FILE.name, 'baz', isNamedFunctionDeclaration)!; - const bazDef = host.getDefinitionOfFunction(bazNode)!; - expect(bazDef.node).toBe(bazNode); - expect(bazDef.body!.length).toEqual(3); - expect(bazDef.parameters.length).toEqual(1); - expect(bazDef.parameters[0].name).toEqual('x'); - expect(bazDef.parameters[0].initializer).toBe(null); - - const quxNode = getDeclaration( - bundle.program, FUNCTION_BODY_FILE.name, 'qux', isNamedFunctionDeclaration)!; - const quxDef = host.getDefinitionOfFunction(quxNode)!; - expect(quxDef.node).toBe(quxNode); - expect(quxDef.body!.length).toEqual(2); - expect(quxDef.parameters.length).toEqual(1); - expect(quxDef.parameters[0].name).toEqual('x'); - expect(quxDef.parameters[0].initializer).toBe(null); - }); - }); - - describe('getImportOfIdentifier()', () => { - it('should find the import of an identifier', () => { - loadTestFiles(IMPORTS_FILES); - const bundle = makeTestBundleProgram(_('/index.js')); - const host = createHost(bundle, new Esm5ReflectionHost(new MockLogger(), false, bundle)); - const variableNode = - getDeclaration(bundle.program, _('/b.js'), 'b', isNamedVariableDeclaration); - const importOfIdent = host.getImportOfIdentifier(variableNode.initializer as ts.Identifier); - - expect(importOfIdent).toEqual({name: 'a', from: './a.js'}); - }); - - it('should find the name by which the identifier was exported, not imported', () => { - loadTestFiles(IMPORTS_FILES); - const bundle = makeTestBundleProgram(_('/index.js')); - const host = createHost(bundle, new Esm5ReflectionHost(new MockLogger(), false, bundle)); - const variableNode = - getDeclaration(bundle.program, _('/b.js'), 'c', isNamedVariableDeclaration); - const importOfIdent = host.getImportOfIdentifier(variableNode.initializer as ts.Identifier); - - expect(importOfIdent).toEqual({name: 'a', from: './a.js'}); - }); - - it('should return null if the identifier was not imported', () => { - loadTestFiles(IMPORTS_FILES); - const bundle = makeTestBundleProgram(_('/index.js')); - const host = createHost(bundle, new Esm5ReflectionHost(new MockLogger(), false, bundle)); - const variableNode = - getDeclaration(bundle.program, _('/b.js'), 'd', isNamedVariableDeclaration); - const importOfIdent = host.getImportOfIdentifier(variableNode.initializer as ts.Identifier); - - expect(importOfIdent).toBeNull(); - }); - }); - - describe('getDeclarationOfIdentifier()', () => { - // Helpers - const createTestForTsHelper = - (program: ts.Program, host: NgccReflectionHost, srcFile: TestFile, - getHelperDeclaration: (name: string) => ts.Declaration) => - (varName: string, helperName: string, knownAs: KnownDeclaration, - viaModule: string|null = null) => { - const node = - getDeclaration(program, srcFile.name, varName, ts.isVariableDeclaration); - const helperIdentifier = getIdentifierFromCallExpression(node); - const helperDeclaration = host.getDeclarationOfIdentifier(helperIdentifier); - - expect(helperDeclaration).toEqual({ - kind: DeclarationKind.Concrete, - known: knownAs, - node: getHelperDeclaration(helperName), - viaModule, - identity: null, - }); - }; - - const getIdentifierFromCallExpression = (decl: ts.VariableDeclaration) => { - if (decl.initializer !== undefined && ts.isCallExpression(decl.initializer)) { - const expr = decl.initializer.expression; - if (ts.isIdentifier(expr)) return expr; - if (ts.isPropertyAccessExpression(expr) && ts.isIdentifier(expr.name)) return expr.name; - } - throw new Error(`Unable to extract identifier from declaration '${decl.getText()}'.`); - }; - - it('should return the declaration of a locally defined identifier', () => { - loadTestFiles([SOME_DIRECTIVE_FILE]); - const bundle = makeTestBundleProgram(SOME_DIRECTIVE_FILE.name); - const host = createHost(bundle, new Esm5ReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, SOME_DIRECTIVE_FILE.name, 'SomeDirective', isNamedVariableDeclaration); - const ctrDecorators = host.getConstructorParameters(classNode)!; - const identifierOfViewContainerRef = (ctrDecorators[0].typeValueReference! as { - kind: TypeValueReferenceKind.LOCAL, - expression: ts.Identifier, - defaultImportStatement: null, - }).expression; - - const expectedDeclarationNode = getDeclaration( - bundle.program, SOME_DIRECTIVE_FILE.name, 'ViewContainerRef', - isNamedVariableDeclaration); - const actualDeclaration = host.getDeclarationOfIdentifier(identifierOfViewContainerRef); - expect(actualDeclaration).not.toBe(null); - expect(actualDeclaration!.node).toBe(expectedDeclarationNode); - expect(actualDeclaration!.viaModule).toBe(null); - expect((actualDeclaration as ConcreteDeclaration).identity).toBe(null); - }); - - it('should return the declaration of an externally defined identifier', () => { - loadFakeCore(getFileSystem()); - loadTestFiles([SOME_DIRECTIVE_FILE]); - const bundle = makeTestBundleProgram(SOME_DIRECTIVE_FILE.name); - const host = createHost(bundle, new Esm5ReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, SOME_DIRECTIVE_FILE.name, 'SomeDirective', isNamedVariableDeclaration); - const classDecorators = host.getDecoratorsOfDeclaration(classNode)!; - const identifierOfDirective = - ((classDecorators[0].node as ts.ObjectLiteralExpression).properties[0] as - ts.PropertyAssignment) - .initializer as ts.Identifier; - - const expectedDeclarationNode = getDeclaration( - bundle.program, _('/node_modules/@angular/core/index.d.ts'), 'Directive', - isNamedVariableDeclaration); - const actualDeclaration = host.getDeclarationOfIdentifier(identifierOfDirective); - expect(actualDeclaration).not.toBe(null); - expect(actualDeclaration!.node).toBe(expectedDeclarationNode); - expect(actualDeclaration!.viaModule).toBe('@angular/core'); - }); - - it('should return the source-file of an import namespace', () => { - loadFakeCore(getFileSystem()); - loadTestFiles([NAMESPACED_IMPORT_FILE]); - const bundle = makeTestBundleProgram(NAMESPACED_IMPORT_FILE.name); - const host = createHost(bundle, new Esm5ReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, NAMESPACED_IMPORT_FILE.name, 'SomeDirective', - isNamedVariableDeclaration); - const classDecorators = host.getDecoratorsOfDeclaration(classNode)!; - const identifier = - (((classDecorators[0].node as ts.ObjectLiteralExpression).properties[0] as - ts.PropertyAssignment) - .initializer as ts.PropertyAccessExpression) - .expression as ts.Identifier; - - const expectedDeclarationNode = - getSourceFileOrError(bundle.program, _('/node_modules/@angular/core/index.d.ts')); - const actualDeclaration = host.getDeclarationOfIdentifier(identifier); - expect(actualDeclaration).not.toBe(null); - expect(actualDeclaration!.node).toBe(expectedDeclarationNode); - expect(actualDeclaration!.viaModule).toBe('@angular/core'); - }); - - it('should return the correct declaration for an inner function identifier inside an ES5 IIFE', - () => { - const superGetDeclarationOfIdentifierSpy = - spyOn(Esm2015ReflectionHost.prototype, 'getDeclarationOfIdentifier') - .and.callThrough(); - loadTestFiles([SIMPLE_CLASS_FILE]); - const bundle = makeTestBundleProgram(SIMPLE_CLASS_FILE.name); - const host = createHost(bundle, new Esm5ReflectionHost(new MockLogger(), false, bundle)); - - const outerDeclaration = getDeclaration( - bundle.program, SIMPLE_CLASS_FILE.name, 'EmptyClass', isNamedVariableDeclaration); - const innerDeclaration = - (((outerDeclaration.initializer as ts.ParenthesizedExpression).expression as - ts.CallExpression) - .expression as ts.FunctionExpression) - .body.statements[0] as ts.FunctionDeclaration; - - const outerIdentifier = outerDeclaration.name as ts.Identifier; - const innerIdentifier = innerDeclaration.name as ts.Identifier; - - expect(host.getDeclarationOfIdentifier(outerIdentifier)!.node).toBe(outerDeclaration); - expect(superGetDeclarationOfIdentifierSpy).toHaveBeenCalledWith(outerIdentifier); - expect(superGetDeclarationOfIdentifierSpy).toHaveBeenCalledTimes(1); - - superGetDeclarationOfIdentifierSpy.calls.reset(); - - expect(host.getDeclarationOfIdentifier(innerIdentifier)!.node).toBe(outerDeclaration); - expect(superGetDeclarationOfIdentifierSpy).toHaveBeenCalledWith(innerIdentifier); - expect(superGetDeclarationOfIdentifierSpy).toHaveBeenCalledWith(outerIdentifier); - expect(superGetDeclarationOfIdentifierSpy).toHaveBeenCalledTimes(2); - }); - - it('should return the correct declaration for an outer alias identifier', () => { - const PROGRAM_FILE: TestFile = { - name: _('/test.js'), - contents: ` - var AliasedClass = AliasedClass_1 = (function () { - function InnerClass() { - } - return InnerClass; - }()); - var AliasedClass_1; - `, - }; - - loadTestFiles([PROGRAM_FILE]); - const bundle = makeTestBundleProgram(PROGRAM_FILE.name); - const host = createHost(bundle, new Esm5ReflectionHost(new MockLogger(), false, bundle)); - - const expectedDeclaration = getDeclaration( - bundle.program, PROGRAM_FILE.name, 'AliasedClass', isNamedVariableDeclaration); - // Grab the `AliasedClass_1` identifier (which is an alias for `AliasedClass`). - const aliasIdentifier = - (expectedDeclaration.initializer as ts.BinaryExpression).left as ts.Identifier; - const actualDeclaration = host.getDeclarationOfIdentifier(aliasIdentifier)!; - - expect(aliasIdentifier.getText()).toBe('AliasedClass_1'); - expect(actualDeclaration.node!.getText()).toBe(expectedDeclaration.getText()); - }); - - it('should return the correct outer declaration for an aliased inner class declaration inside an ES5 IIFE', - () => { - // Note that the inner class declaration `function FroalaEditorModule() {}` is aliased - // internally to `FroalaEditorModule_1`, which is used in the object returned from - // `forRoot()`. - const PROGRAM_FILE: TestFile = { - name: _('/test.js'), - contents: ` - var FroalaEditorModule = /** @class */ (function () { - function FroalaEditorModule() { - } - FroalaEditorModule_1 = FroalaEditorModule; - FroalaEditorModule.forRoot = function () { - return { ngModule: FroalaEditorModule_1, providers: [] }; - }; - var FroalaEditorModule_1; - FroalaEditorModule = FroalaEditorModule_1 = __decorate([ - NgModule({ - declarations: [FroalaEditorDirective], - exports: [FroalaEditorDirective] - }) - ], FroalaEditorModule); - return FroalaEditorModule; - }()); - export { FroalaEditorModule }; - ` - }; - - loadTestFiles([PROGRAM_FILE]); - const bundle = makeTestBundleProgram(PROGRAM_FILE.name); - const host = createHost(bundle, new Esm5ReflectionHost(new MockLogger(), false, bundle)); - - const expectedDeclaration = getDeclaration( - bundle.program, PROGRAM_FILE.name, 'FroalaEditorModule', isNamedVariableDeclaration); - // Grab the `FroalaEditorModule_1` identifier returned from the `forRoot()` method - const forRootMethod = - ((((expectedDeclaration.initializer as ts.ParenthesizedExpression).expression as - ts.CallExpression) - .expression as ts.FunctionExpression) - .body.statements[2] as ts.ExpressionStatement); - const identifier = - (((((forRootMethod.expression as ts.BinaryExpression).right as ts.FunctionExpression) - .body.statements[0] as ts.ReturnStatement) - .expression as ts.ObjectLiteralExpression) - .properties[0] as ts.PropertyAssignment) - .initializer as ts.Identifier; - const actualDeclaration = host.getDeclarationOfIdentifier(identifier)!; - expect(actualDeclaration.node!.getText()).toBe(expectedDeclaration.getText()); - }); - - it('should recognize TypeScript helpers (as function declarations)', () => { - const file: TestFile = { - name: _('/test.js'), - contents: ` - function __assign(t, ...sources) { /* ... */ } - function __spread(...args) { /* ... */ } - function __spreadArrays(...args) { /* ... */ } - function __spreadArray(to, from) { /* ... */ } - function __read(o) { /* ... */ } - - var a = __assign({foo: 'bar'}, {baz: 'qux'}); - var b = __spread(['foo', 'bar'], ['baz', 'qux']); - var c = __spreadArrays(['foo', 'bar'], ['baz', 'qux']); - var d = __spreadArray(['foo', 'bar'], ['baz', 'qux']); - var e = __read(['foo', 'bar']); - `, - }; - loadTestFiles([file]); - const bundle = makeTestBundleProgram(file.name); - const host = createHost(bundle, new Esm5ReflectionHost(new MockLogger(), false, bundle)); - - const testForHelper = createTestForTsHelper( - bundle.program, host, file, - helperName => - getDeclaration(bundle.program, file.name, helperName, ts.isFunctionDeclaration)); - - testForHelper('a', '__assign', KnownDeclaration.TsHelperAssign); - testForHelper('b', '__spread', KnownDeclaration.TsHelperSpread); - testForHelper('c', '__spreadArrays', KnownDeclaration.TsHelperSpreadArrays); - testForHelper('d', '__spreadArray', KnownDeclaration.TsHelperSpreadArray); - testForHelper('e', '__read', KnownDeclaration.TsHelperRead); - }); - - it('should recognize suffixed TypeScript helpers (as function declarations)', () => { - const file: TestFile = { - name: _('/test.js'), - contents: ` - function __assign$1(t, ...sources) { /* ... */ } - function __spread$2(...args) { /* ... */ } - function __spreadArrays$3(...args) { /* ... */ } - function __spreadArray$3(to, from) { /* ... */ } - function __read$3(o) { /* ... */ } - - var a = __assign$1({foo: 'bar'}, {baz: 'qux'}); - var b = __spread$2(['foo', 'bar'], ['baz', 'qux']); - var c = __spreadArrays$3(['foo', 'bar'], ['baz', 'qux']); - var d = __spreadArray$3(['foo', 'bar'], ['baz', 'qux']); - var e = __read$3(['foo', 'bar']); - `, - }; - loadTestFiles([file]); - const bundle = makeTestBundleProgram(file.name); - const host = createHost(bundle, new Esm5ReflectionHost(new MockLogger(), false, bundle)); - - const testForHelper = createTestForTsHelper( - bundle.program, host, file, - helperName => - getDeclaration(bundle.program, file.name, helperName, ts.isFunctionDeclaration)); - - testForHelper('a', '__assign$1', KnownDeclaration.TsHelperAssign); - testForHelper('b', '__spread$2', KnownDeclaration.TsHelperSpread); - testForHelper('c', '__spreadArrays$3', KnownDeclaration.TsHelperSpreadArrays); - testForHelper('d', '__spreadArray$3', KnownDeclaration.TsHelperSpreadArray); - testForHelper('e', '__read$3', KnownDeclaration.TsHelperRead); - }); - - it('should recognize TypeScript helpers (as variable declarations)', () => { - const file: TestFile = { - name: _('/test.js'), - contents: ` - var __assign = (this && this.__assign) || function (t, ...sources) { /* ... */ } - var __spread = (this && this.__spread) || function (...args) { /* ... */ } - var __spreadArrays = (this && this.__spreadArrays) || function (...args) { /* ... */ } - var __spreadArray = (this && this.__spreadArray) || function (to, from) { /* ... */ } - var __read = (this && this._read) || function (o) { /* ... */ } - - var a = __assign({foo: 'bar'}, {baz: 'qux'}); - var b = __spread(['foo', 'bar'], ['baz', 'qux']); - var c = __spreadArrays(['foo', 'bar'], ['baz', 'qux']); - var d = __spreadArray(['foo', 'bar'], ['baz', 'qux']); - var e = __read(['foo', 'bar']); - `, - }; - loadTestFiles([file]); - const bundle = makeTestBundleProgram(file.name); - const host = createHost(bundle, new Esm5ReflectionHost(new MockLogger(), false, bundle)); - - const testForHelper = createTestForTsHelper( - bundle.program, host, file, - helperName => - getDeclaration(bundle.program, file.name, helperName, ts.isVariableDeclaration)); - - testForHelper('a', '__assign', KnownDeclaration.TsHelperAssign); - testForHelper('b', '__spread', KnownDeclaration.TsHelperSpread); - testForHelper('c', '__spreadArrays', KnownDeclaration.TsHelperSpreadArrays); - testForHelper('d', '__spreadArray', KnownDeclaration.TsHelperSpreadArray); - testForHelper('e', '__read', KnownDeclaration.TsHelperRead); - }); - - it('should recognize suffixed TypeScript helpers (as variable declarations)', () => { - const file: TestFile = { - name: _('/test.js'), - contents: ` - var __assign$1 = (this && this.__assign$1) || function (t, ...sources) { /* ... */ } - var __spread$2 = (this && this.__spread$2) || function (...args) { /* ... */ } - var __spreadArrays$3 = (this && this.__spreadArrays$3) || function (...args) { /* ... */ } - var __spreadArray$3 = (this && this.__spreadArray$3) || function (to, from) { /* ... */ } - var __read$3 = (this && this.__read$3) || function (o) { /* ... */ } - - var a = __assign$1({foo: 'bar'}, {baz: 'qux'}); - var b = __spread$2(['foo', 'bar'], ['baz', 'qux']); - var c = __spreadArrays$3(['foo', 'bar'], ['baz', 'qux']); - var d = __spreadArray$3(['foo', 'bar'], ['baz', 'qux']); - var e = __read$3(['foo', 'bar']); - `, - }; - loadTestFiles([file]); - const bundle = makeTestBundleProgram(file.name); - const host = createHost(bundle, new Esm5ReflectionHost(new MockLogger(), false, bundle)); - - const testForHelper = createTestForTsHelper( - bundle.program, host, file, - helperName => - getDeclaration(bundle.program, file.name, helperName, ts.isVariableDeclaration)); - - testForHelper('a', '__assign$1', KnownDeclaration.TsHelperAssign); - testForHelper('b', '__spread$2', KnownDeclaration.TsHelperSpread); - testForHelper('c', '__spreadArrays$3', KnownDeclaration.TsHelperSpreadArrays); - testForHelper('d', '__spreadArray$3', KnownDeclaration.TsHelperSpreadArray); - testForHelper('e', '__read$3', KnownDeclaration.TsHelperRead); - }); - - it('should recognize imported TypeScript helpers (named imports)', () => { - const files: TestFile[] = [ - { - name: _('/test.js'), - contents: ` - import {__assign, __spread, __spreadArrays, __spreadArray, __read} from 'tslib'; - - var a = __assign({foo: 'bar'}, {baz: 'qux'}); - var b = __spread(['foo', 'bar'], ['baz', 'qux']); - var c = __spreadArrays(['foo', 'bar'], ['baz', 'qux']); - var d = __spreadArray(['foo', 'bar'], ['baz', 'qux']); - var e = __read(['foo', 'bar']); - `, - }, - { - name: _('/node_modules/tslib/index.d.ts'), - contents: ` - export declare function __assign(t: any, ...sources: any[]): any; - export declare function __spread(...args: any[][]): any[]; - export declare function __spreadArrays(...args: any[][]): any[]; - export declare function __spreadArray(to: any[], from: any[]): any[]; - export declare function __read(o: any, n?: number): any[]; - `, - }, - ]; - loadTestFiles(files); - - const [testFile, tslibFile] = files; - const bundle = makeTestBundleProgram(testFile.name); - const host = createHost(bundle, new Esm5ReflectionHost(new MockLogger(), false, bundle)); - - const testForHelper = createTestForTsHelper( - bundle.program, host, testFile, - helperName => getDeclaration( - bundle.program, tslibFile.name, helperName, ts.isFunctionDeclaration)); - - testForHelper('a', '__assign', KnownDeclaration.TsHelperAssign, 'tslib'); - testForHelper('b', '__spread', KnownDeclaration.TsHelperSpread, 'tslib'); - testForHelper('c', '__spreadArrays', KnownDeclaration.TsHelperSpreadArrays, 'tslib'); - testForHelper('d', '__spreadArray', KnownDeclaration.TsHelperSpreadArray, 'tslib'); - testForHelper('e', '__read', KnownDeclaration.TsHelperRead, 'tslib'); - }); - - it('should recognize imported TypeScript helpers (star import)', () => { - const files: TestFile[] = [ - { - name: _('/test.js'), - contents: ` - import * as tslib_1 from 'tslib'; - - var a = tslib_1.__assign({foo: 'bar'}, {baz: 'qux'}); - var b = tslib_1.__spread(['foo', 'bar'], ['baz', 'qux']); - var c = tslib_1.__spreadArrays(['foo', 'bar'], ['baz', 'qux']); - var d = tslib_1.__spreadArray(['foo', 'bar'], ['baz', 'qux']); - var e = tslib_1.__read(['foo', 'bar']); - `, - }, - { - name: _('/node_modules/tslib/index.d.ts'), - contents: ` - export declare function __assign(t: any, ...sources: any[]): any; - export declare function __spread(...args: any[][]): any[]; - export declare function __spreadArrays(...args: any[][]): any[]; - export declare function __spreadArray(to: any[], from: any[]): any[]; - export declare function __read(o: any, n?: number): any[]; - `, - }, - ]; - loadTestFiles(files); - - const [testFile, tslibFile] = files; - const bundle = makeTestBundleProgram(testFile.name); - const host = createHost(bundle, new Esm5ReflectionHost(new MockLogger(), false, bundle)); - - const testForHelper = createTestForTsHelper( - bundle.program, host, testFile, - helperName => getDeclaration( - bundle.program, tslibFile.name, helperName, ts.isFunctionDeclaration)); - - testForHelper('a', '__assign', KnownDeclaration.TsHelperAssign, 'tslib'); - testForHelper('b', '__spread', KnownDeclaration.TsHelperSpread, 'tslib'); - testForHelper('c', '__spreadArrays', KnownDeclaration.TsHelperSpreadArrays, 'tslib'); - testForHelper('d', '__spreadArray', KnownDeclaration.TsHelperSpreadArray, 'tslib'); - testForHelper('e', '__read', KnownDeclaration.TsHelperRead, 'tslib'); - }); - - it('should recognize undeclared, unimported TypeScript helpers (by name)', () => { - const file: TestFile = { - name: _('/test.js'), - contents: ` - var a = __assign({foo: 'bar'}, {baz: 'qux'}); - var b = __spread(['foo', 'bar'], ['baz', 'qux']); - var c = __spreadArrays(['foo', 'bar'], ['baz', 'qux']); - var d = __spreadArray(['foo', 'bar'], ['baz', 'qux']); - var e = __read(['foo', 'bar']); - `, - }; - loadTestFiles([file]); - const bundle = makeTestBundleProgram(file.name); - const host = createHost(bundle, new Esm5ReflectionHost(new MockLogger(), false, bundle)); - - const testForHelper = (varName: string, helperName: string, knownAs: KnownDeclaration) => { - const node = getDeclaration(bundle.program, file.name, varName, ts.isVariableDeclaration); - const helperIdentifier = getIdentifierFromCallExpression(node); - const helperDeclaration = host.getDeclarationOfIdentifier(helperIdentifier); - - expect(helperDeclaration).toEqual({ - kind: DeclarationKind.Inline, - known: knownAs, - node: helperIdentifier, - viaModule: null, - }); - }; - - testForHelper('a', '__assign', KnownDeclaration.TsHelperAssign); - testForHelper('b', '__spread', KnownDeclaration.TsHelperSpread); - testForHelper('c', '__spreadArrays', KnownDeclaration.TsHelperSpreadArrays); - testForHelper('d', '__spreadArray', KnownDeclaration.TsHelperSpreadArray); - testForHelper('e', '__read', KnownDeclaration.TsHelperRead); - }); - - it('should recognize suffixed, undeclared, unimported TypeScript helpers (by name)', () => { - const file: TestFile = { - name: _('/test.js'), - contents: ` - var a = __assign$1({foo: 'bar'}, {baz: 'qux'}); - var b = __spread$2(['foo', 'bar'], ['baz', 'qux']); - var c = __spreadArrays$3(['foo', 'bar'], ['baz', 'qux']); - var d = __spreadArray$3(['foo', 'bar'], ['baz', 'qux']); - var e = __read$3(['foo', 'bar']); - `, - }; - loadTestFiles([file]); - const bundle = makeTestBundleProgram(file.name); - const host = createHost(bundle, new Esm5ReflectionHost(new MockLogger(), false, bundle)); - - const testForHelper = (varName: string, helperName: string, knownAs: KnownDeclaration) => { - const node = getDeclaration(bundle.program, file.name, varName, ts.isVariableDeclaration); - const helperIdentifier = getIdentifierFromCallExpression(node); - const helperDeclaration = host.getDeclarationOfIdentifier(helperIdentifier); - - expect(helperDeclaration).toEqual({ - kind: DeclarationKind.Inline, - known: knownAs, - node: helperIdentifier, - viaModule: null, - }); - }; - - testForHelper('a', '__assign$1', KnownDeclaration.TsHelperAssign); - testForHelper('b', '__spread$2', KnownDeclaration.TsHelperSpread); - testForHelper('c', '__spreadArrays$3', KnownDeclaration.TsHelperSpreadArrays); - testForHelper('d', '__spreadArray$3', KnownDeclaration.TsHelperSpreadArray); - testForHelper('e', '__read', KnownDeclaration.TsHelperRead); - }); - - it('should recognize enum declarations with string values', () => { - const testFile: TestFile = { - name: _('/node_modules/test-package/some/file.js'), - contents: ` - export var Enum; - (function (Enum) { - Enum["ValueA"] = "1"; - Enum["ValueB"] = "2"; - })(Enum || (Enum = {})); - - var value = Enum;` - }; - loadTestFiles([testFile]); - const bundle = makeTestBundleProgram(testFile.name); - const host = createHost(bundle, new Esm5ReflectionHost(new MockLogger(), false, bundle)); - const valueDecl = getDeclaration( - bundle.program, _('/node_modules/test-package/some/file.js'), 'value', - ts.isVariableDeclaration); - const declaration = host.getDeclarationOfIdentifier( - valueDecl.initializer as ts.Identifier) as ConcreteDeclaration; - - const enumMembers = (declaration.identity as DownleveledEnum).enumMembers; - expect(declaration.node.parent.parent.getText()).toBe('export var Enum;'); - expect(enumMembers!.length).toBe(2); - expect(enumMembers![0].name.getText()).toBe('"ValueA"'); - expect(enumMembers![0].initializer!.getText()).toBe('"1"'); - expect(enumMembers![1].name.getText()).toBe('"ValueB"'); - expect(enumMembers![1].initializer!.getText()).toBe('"2"'); - }); - - it('should recognize enum declarations with numeric values', () => { - const testFile: TestFile = { - name: _('/node_modules/test-package/some/file.js'), - contents: ` - export var Enum; - (function (Enum) { - Enum[Enum["ValueA"] = "1"] = "ValueA"; - Enum[Enum["ValueB"] = "2"] = "ValueB"; - })(Enum || (Enum = {})); - - var value = Enum;` - }; - loadTestFiles([testFile]); - const bundle = makeTestBundleProgram(testFile.name); - const host = createHost(bundle, new Esm5ReflectionHost(new MockLogger(), false, bundle)); - const valueDecl = getDeclaration( - bundle.program, _('/node_modules/test-package/some/file.js'), 'value', - ts.isVariableDeclaration); - const declaration = host.getDeclarationOfIdentifier( - valueDecl.initializer as ts.Identifier) as ConcreteDeclaration; - - const enumMembers = (declaration.identity as DownleveledEnum).enumMembers; - expect(declaration.node.parent.parent.getText()).toBe('export var Enum;'); - expect(enumMembers!.length).toBe(2); - expect(enumMembers![0].name.getText()).toBe('"ValueA"'); - expect(enumMembers![0].initializer!.getText()).toBe('"1"'); - expect(enumMembers![1].name.getText()).toBe('"ValueB"'); - expect(enumMembers![1].initializer!.getText()).toBe('"2"'); - }); - - it('should not consider IIFEs that do no assign members to the parameter as an enum declaration', - () => { - const testFile: TestFile = { - name: _('/node_modules/test-package/some/file.js'), - contents: ` - export var Enum; - (function (E) { - Enum["ValueA"] = "1"; - Enum["ValueB"] = "2"; - })(Enum || (Enum = {})); - - var value = Enum;` - }; - loadTestFiles([testFile]); - const bundle = makeTestBundleProgram(testFile.name); - const host = createHost(bundle, new Esm5ReflectionHost(new MockLogger(), false, bundle)); - const valueDecl = getDeclaration( - bundle.program, _('/node_modules/test-package/some/file.js'), 'value', - ts.isVariableDeclaration); - const declaration = host.getDeclarationOfIdentifier( - valueDecl.initializer as ts.Identifier) as ConcreteDeclaration; - - expect(declaration.node.parent.parent.getText()).toBe('export var Enum;'); - expect(declaration.identity).toBe(null); - }); - - it('should not consider IIFEs without call argument as an enum declaration', () => { - const testFile: TestFile = { - name: _('/node_modules/test-package/some/file.js'), - contents: ` - export var Enum; - (function (Enum) { - Enum["ValueA"] = "1"; - Enum["ValueB"] = "2"; - })(); - - var value = Enum;` - }; - loadTestFiles([testFile]); - const bundle = makeTestBundleProgram(testFile.name); - const host = createHost(bundle, new Esm5ReflectionHost(new MockLogger(), false, bundle)); - const valueDecl = getDeclaration( - bundle.program, _('/node_modules/test-package/some/file.js'), 'value', - ts.isVariableDeclaration); - const declaration = host.getDeclarationOfIdentifier( - valueDecl.initializer as ts.Identifier) as ConcreteDeclaration; - - expect(declaration.node.parent.parent.getText()).toBe('export var Enum;'); - expect(declaration.identity).toBe(null); - }); - }); - - describe('getExportsOfModule()', () => { - it('should return a map of all the exports from a given module', () => { - loadFakeCore(getFileSystem()); - loadTestFiles(EXPORTS_FILES); - const bundle = makeTestBundleProgram(_('/index.js')); - const host = createHost(bundle, new Esm5ReflectionHost(new MockLogger(), false, bundle)); - const file = getSourceFileOrError(bundle.program, _('/b.js')); - const exportDeclarations = host.getExportsOfModule(file); - expect(exportDeclarations).not.toBe(null); - expect(Array.from(exportDeclarations!.keys())).toEqual([ - 'Directive', - 'a', - 'b', - 'c', - 'd', - 'e', - 'DirectiveX', - 'SomeClass', - ]); - - const values = - Array.from(exportDeclarations!.values()) - .map(declaration => [declaration.node!.getText(), declaration.viaModule]); - expect(values).toEqual([ - [`Directive: FnWithArg<(clazz: any) => any>`, null], - [`a = 'a'`, null], - [`b = a`, null], - [`c = foo`, null], - [`d = b`, null], - [`e = 'e'`, null], - [`DirectiveX = Directive`, null], - [ - `SomeClass = (function() { - function SomeClass() {} - return SomeClass; - }())`, - null - ], - ]); - }); - - it('should recognize declarations of known TypeScript helpers', () => { - const tslib = { - name: _('/tslib.d.ts'), - contents: ` - export declare function __assign(t: any, ...sources: any[]): any; - export declare function __spread(...args: any[][]): any[]; - export declare function __spreadArrays(...args: any[][]): any[]; - export declare function __spreadArray(to: any[], from: any[]): any[]; - export declare function __read(o: any, n?: number): any[]; - export declare function __unknownHelper(...args: any[]): any; - `, - }; - loadTestFiles([tslib]); - const bundle = makeTestBundleProgram(tslib.name); - const host = createHost(bundle, new Esm5ReflectionHost(new MockLogger(), false, bundle)); - const sf = getSourceFileOrError(bundle.program, tslib.name); - const exportDeclarations = host.getExportsOfModule(sf)!; - - expect([...exportDeclarations].map(([exportName, {known}]) => [exportName, known])) - .toEqual([ - ['__assign', KnownDeclaration.TsHelperAssign], - ['__spread', KnownDeclaration.TsHelperSpread], - ['__spreadArrays', KnownDeclaration.TsHelperSpreadArrays], - ['__spreadArray', KnownDeclaration.TsHelperSpreadArray], - ['__read', KnownDeclaration.TsHelperRead], - ['__unknownHelper', null], - ]); - }); - }); - - describe('getClassSymbol()', () => { - it('should return the class symbol for an ES2015 class', () => { - loadTestFiles([SIMPLE_ES2015_CLASS_FILE]); - const bundle = makeTestBundleProgram(SIMPLE_ES2015_CLASS_FILE.name); - const host = createHost(bundle, new Esm5ReflectionHost(new MockLogger(), false, bundle)); - const node = getDeclaration( - bundle.program, SIMPLE_ES2015_CLASS_FILE.name, 'EmptyClass', isNamedClassDeclaration); - const classSymbol = host.getClassSymbol(node); - - expect(classSymbol).toBeDefined(); - expect(classSymbol!.declaration.valueDeclaration).toBe(node); - expect(classSymbol!.implementation.valueDeclaration).toBe(node); - }); - - it('should return the class symbol for an ES5 class (outer variable declaration)', () => { - loadTestFiles([SIMPLE_CLASS_FILE]); - const bundle = makeTestBundleProgram(SIMPLE_CLASS_FILE.name); - const host = createHost(bundle, new Esm5ReflectionHost(new MockLogger(), false, bundle)); - const outerNode = getDeclaration( - bundle.program, SIMPLE_CLASS_FILE.name, 'EmptyClass', isNamedVariableDeclaration); - const innerNode = (getIifeBody(outerNode.initializer!) as ts.Block) - .statements.find(isNamedFunctionDeclaration)!; - const classSymbol = host.getClassSymbol(outerNode); - - expect(classSymbol).toBeDefined(); - expect(classSymbol!.declaration.valueDeclaration).toBe(outerNode); - expect(classSymbol!.implementation.valueDeclaration).toBe(innerNode); - }); - - it('should return the class symbol for an ES5 class (inner function declaration)', () => { - loadTestFiles([SIMPLE_CLASS_FILE]); - const bundle = makeTestBundleProgram(SIMPLE_CLASS_FILE.name); - const host = createHost(bundle, new Esm5ReflectionHost(new MockLogger(), false, bundle)); - const outerNode = getDeclaration( - bundle.program, SIMPLE_CLASS_FILE.name, 'EmptyClass', isNamedVariableDeclaration); - const innerNode = (getIifeBody(outerNode.initializer!) as ts.Block) - .statements.find(isNamedFunctionDeclaration)!; - const classSymbol = host.getClassSymbol(innerNode); - - expect(classSymbol).toBeDefined(); - expect(classSymbol!.declaration.valueDeclaration).toBe(outerNode); - expect(classSymbol!.implementation.valueDeclaration).toBe(innerNode); - }); - - it('should return the same class symbol (of the outer declaration) for outer and inner declarations', - () => { - loadTestFiles([SIMPLE_CLASS_FILE]); - const bundle = makeTestBundleProgram(SIMPLE_CLASS_FILE.name); - const host = createHost(bundle, new Esm5ReflectionHost(new MockLogger(), false, bundle)); - const outerNode = getDeclaration( - bundle.program, SIMPLE_CLASS_FILE.name, 'EmptyClass', isNamedVariableDeclaration); - const innerNode = (getIifeBody(outerNode.initializer!) as ts.Block) - .statements.find(isNamedFunctionDeclaration)!; - - const innerSymbol = host.getClassSymbol(innerNode)!; - const outerSymbol = host.getClassSymbol(outerNode)!; - expect(innerSymbol.declaration).toBe(outerSymbol.declaration); - expect(innerSymbol.implementation).toBe(outerSymbol.implementation); - }); - - it('should return the class symbol for an ES5 class whose IIFE is not wrapped in parens', - () => { - loadTestFiles([SIMPLE_CLASS_FILE]); - const bundle = makeTestBundleProgram(SIMPLE_CLASS_FILE.name); - const host = createHost(bundle, new Esm5ReflectionHost(new MockLogger(), false, bundle)); - const outerNode = getDeclaration( - bundle.program, SIMPLE_CLASS_FILE.name, 'NoParensClass', isNamedVariableDeclaration); - const innerNode = (getIifeBody(outerNode.initializer!) as ts.Block) - .statements.find(isNamedFunctionDeclaration)!; - const classSymbol = host.getClassSymbol(outerNode); - - expect(classSymbol).toBeDefined(); - expect(classSymbol!.declaration.valueDeclaration).toBe(outerNode); - expect(classSymbol!.implementation.valueDeclaration).toBe(innerNode); - }); - - it('should return the class symbol for an ES5 class whose IIFE is not wrapped with inner parens', - () => { - loadTestFiles([SIMPLE_CLASS_FILE]); - const bundle = makeTestBundleProgram(SIMPLE_CLASS_FILE.name); - const host = createHost(bundle, new Esm5ReflectionHost(new MockLogger(), false, bundle)); - const outerNode = getDeclaration( - bundle.program, SIMPLE_CLASS_FILE.name, 'InnerParensClass', - isNamedVariableDeclaration); - const innerNode = (getIifeBody(outerNode.initializer!) as ts.Block) - .statements.find(isNamedFunctionDeclaration)!; - const classSymbol = host.getClassSymbol(outerNode); - - expect(classSymbol).toBeDefined(); - expect(classSymbol!.declaration.valueDeclaration).toBe(outerNode); - expect(classSymbol!.implementation.valueDeclaration).toBe(innerNode); - }); - - it('should return undefined if node is not an ES5 class', () => { - loadTestFiles([FOO_FUNCTION_FILE]); - const bundle = makeTestBundleProgram(FOO_FUNCTION_FILE.name); - const host = createHost(bundle, new Esm5ReflectionHost(new MockLogger(), false, bundle)); - const node = getDeclaration( - bundle.program, FOO_FUNCTION_FILE.name, 'foo', isNamedFunctionDeclaration); - const classSymbol = host.getClassSymbol(node); - - expect(classSymbol).toBeUndefined(); - }); - - it('should return undefined if variable declaration is not initialized using an IIFE', () => { - const testFile = { - name: _('/test.js'), - contents: `var MyClass = null;`, - }; - loadTestFiles([testFile]); - const bundle = makeTestBundleProgram(testFile.name); - const host = createHost(bundle, new Esm5ReflectionHost(new MockLogger(), false, bundle)); - const node = - getDeclaration(bundle.program, testFile.name, 'MyClass', isNamedVariableDeclaration); - const classSymbol = host.getClassSymbol(node); - - expect(classSymbol).toBeUndefined(); - }); - }); - - describe('isClass()', () => { - it('should return true if a given node is a TS class declaration', () => { - loadTestFiles([SIMPLE_ES2015_CLASS_FILE]); - const bundle = makeTestBundleProgram(SIMPLE_ES2015_CLASS_FILE.name); - const host = createHost(bundle, new Esm5ReflectionHost(new MockLogger(), false, bundle)); - const node = getDeclaration( - bundle.program, SIMPLE_ES2015_CLASS_FILE.name, 'EmptyClass', isNamedClassDeclaration); - expect(host.isClass(node)).toBe(true); - }); - - it('should return true if a given node is the outer variable declaration of a class', () => { - loadTestFiles([SIMPLE_CLASS_FILE]); - const bundle = makeTestBundleProgram(SIMPLE_CLASS_FILE.name); - const host = createHost(bundle, new Esm5ReflectionHost(new MockLogger(), false, bundle)); - const node = getDeclaration( - bundle.program, SIMPLE_CLASS_FILE.name, 'EmptyClass', ts.isVariableDeclaration); - expect(host.isClass(node)).toBe(true); - }); - - it('should return true if a given node is the inner variable declaration of a class', () => { - loadTestFiles([SIMPLE_CLASS_FILE]); - const bundle = makeTestBundleProgram(SIMPLE_CLASS_FILE.name); - const host = createHost(bundle, new Esm5ReflectionHost(new MockLogger(), false, bundle)); - const outerNode = getDeclaration( - bundle.program, SIMPLE_CLASS_FILE.name, 'EmptyClass', ts.isVariableDeclaration); - const innerNode = (getIifeBody(outerNode.initializer!) as ts.Block) - .statements.find(isNamedFunctionDeclaration)!; - expect(host.isClass(innerNode)).toBe(true); - }); - - it('should return false if a given node is a function declaration', () => { - loadTestFiles([FOO_FUNCTION_FILE]); - const bundle = makeTestBundleProgram(FOO_FUNCTION_FILE.name); - const host = createHost(bundle, new Esm5ReflectionHost(new MockLogger(), false, bundle)); - const node = getDeclaration( - bundle.program, FOO_FUNCTION_FILE.name, 'foo', isNamedFunctionDeclaration); - expect(host.isClass(node)).toBe(false); - }); - }); - - describe('hasBaseClass()', () => { - function hasBaseClass(source: string) { - const file = { - name: _('/synthesized_constructors.js'), - contents: source, - }; - - loadTestFiles([file]); - const bundle = makeTestBundleProgram(file.name); - const host = createHost(bundle, new Esm5ReflectionHost(new MockLogger(), false, bundle)); - const classNode = - getDeclaration(bundle.program, file.name, 'TestClass', isNamedVariableDeclaration); - return host.hasBaseClass(classNode); - } - - it('should consider an IIFE with _super parameter as having a base class', () => { - const result = hasBaseClass(` - var TestClass = /** @class */ (function (_super) { - __extends(TestClass, _super); - function TestClass() {} - return TestClass; - }(null));`); - expect(result).toBe(true); - }); - - it('should consider an IIFE with a unique name generated for the _super parameter as having a base class', - () => { - const result = hasBaseClass(` - var TestClass = /** @class */ (function (_super_1) { - __extends(TestClass, _super_1); - function TestClass() {} - return TestClass; - }(null));`); - expect(result).toBe(true); - }); - - it('should not consider an IIFE without parameter as having a base class', () => { - const result = hasBaseClass(` - var TestClass = /** @class */ (function () { - __extends(TestClass, _super); - function TestClass() {} - return TestClass; - }(null));`); - expect(result).toBe(false); - }); - }); - - describe('getBaseClassExpression()', () => { - function getBaseClassIdentifier(source: string): ts.Identifier|null { - const file = { - name: _('/synthesized_constructors.js'), - contents: source, - }; - - loadTestFiles([file]); - const bundle = makeTestBundleProgram(file.name); - const host = createHost(bundle, new Esm5ReflectionHost(new MockLogger(), false, bundle)); - const classNode = - getDeclaration(bundle.program, file.name, 'TestClass', isNamedVariableDeclaration); - const expression = host.getBaseClassExpression(classNode); - if (expression !== null && !ts.isIdentifier(expression)) { - throw new Error( - 'Expected class to inherit via an identifier but got: ' + expression.getText()); - } - return expression; - } - - it('should find the base class of an IIFE with _super parameter', () => { - const identifier = getBaseClassIdentifier(` - var BaseClass = /** @class */ (function () { - function BaseClass() {} - return BaseClass; - }()); - var TestClass = /** @class */ (function (_super) { - __extends(TestClass, _super); - function TestClass() {} - return TestClass; - }(BaseClass));`); - expect(identifier!.text).toBe('BaseClass'); - }); - - it('should find the base class of an IIFE with a unique name generated for the _super parameter', - () => { - const identifier = getBaseClassIdentifier(` - var BaseClass = /** @class */ (function () { - function BaseClass() {} - return BaseClass; - }()); - var TestClass = /** @class */ (function (_super_1) { - __extends(TestClass, _super_1); - function TestClass() {} - return TestClass; - }(BaseClass));`); - expect(identifier!.text).toBe('BaseClass'); - }); - - it('should not find a base class for an IIFE without parameter', () => { - const identifier = getBaseClassIdentifier(` - var BaseClass = /** @class */ (function () { - function BaseClass() {} - return BaseClass; - }()); - var TestClass = /** @class */ (function () { - __extends(TestClass, _super); - function TestClass() {} - return TestClass; - }(BaseClass));`); - expect(identifier).toBe(null); - }); - - it('should find a dynamic base class expression of an IIFE', () => { - const file = { - name: _('/synthesized_constructors.js'), - contents: ` - var BaseClass = /** @class */ (function () { - function BaseClass() {} - return BaseClass; - }()); - function foo() { return BaseClass; } - var TestClass = /** @class */ (function (_super) { - __extends(TestClass, _super); - function TestClass() {} - return TestClass; - }(foo()));`, - }; - - loadTestFiles([file]); - const bundle = makeTestBundleProgram(file.name); - const host = createHost(bundle, new Esm5ReflectionHost(new MockLogger(), false, bundle)); - const classNode = - getDeclaration(bundle.program, file.name, 'TestClass', isNamedVariableDeclaration); - const expression = host.getBaseClassExpression(classNode)!; - expect(expression.getText()).toBe('foo()'); - }); - }); - - describe('findClassSymbols()', () => { - it('should return an array of all classes in the given source file', () => { - loadTestFiles(DECORATED_FILES); - const bundle = makeTestBundleProgram(getRootFiles(DECORATED_FILES)[0]); - const host = createHost(bundle, new Esm5ReflectionHost(new MockLogger(), false, bundle)); - const primaryFile = getSourceFileOrError(bundle.program, DECORATED_FILES[0].name); - const secondaryFile = getSourceFileOrError(bundle.program, DECORATED_FILES[1].name); - - const classSymbolsPrimary = host.findClassSymbols(primaryFile); - expect(classSymbolsPrimary.length).toEqual(2); - expect(classSymbolsPrimary.map(c => c.name)).toEqual(['A', 'B']); - - const classSymbolsSecondary = host.findClassSymbols(secondaryFile); - expect(classSymbolsSecondary.length).toEqual(1); - expect(classSymbolsSecondary.map(c => c.name)).toEqual(['D']); - }); - }); - - describe('getDecoratorsOfSymbol()', () => { - it('should return decorators of class symbol', () => { - loadTestFiles(DECORATED_FILES); - const bundle = makeTestBundleProgram(getRootFiles(DECORATED_FILES)[0]); - const host = createHost(bundle, new Esm5ReflectionHost(new MockLogger(), false, bundle)); - const primaryFile = getSourceFileOrError(bundle.program, DECORATED_FILES[0].name); - const secondaryFile = getSourceFileOrError(bundle.program, DECORATED_FILES[1].name); - - const classSymbolsPrimary = host.findClassSymbols(primaryFile); - const classDecoratorsPrimary = classSymbolsPrimary.map(s => host.getDecoratorsOfSymbol(s)); - expect(classDecoratorsPrimary.length).toEqual(2); - expect(classDecoratorsPrimary[0]!.map(d => d.name)).toEqual(['Directive']); - expect(classDecoratorsPrimary[1]!.map(d => d.name)).toEqual(['Directive']); - - const classSymbolsSecondary = host.findClassSymbols(secondaryFile); - const classDecoratorsSecondary = - classSymbolsSecondary.map(s => host.getDecoratorsOfSymbol(s)); - expect(classDecoratorsSecondary.length).toEqual(1); - expect(classDecoratorsSecondary[0]!.map(d => d.name)).toEqual(['Directive']); - }); - }); - - describe('getDtsDeclaration()', () => { - it('should find the dts declaration that has the same relative path to the source file', - () => { - loadTestFiles(TYPINGS_SRC_FILES); - loadTestFiles(TYPINGS_DTS_FILES); - const bundle = makeTestBundleProgram(getRootFiles(TYPINGS_SRC_FILES)[0]); - const dts = makeTestBundleProgram(getRootFiles(TYPINGS_DTS_FILES)[0]); - const class1 = getDeclaration( - bundle.program, _('/ep/src/class1.js'), 'Class1', ts.isVariableDeclaration); - const host = - createHost(bundle, new Esm5ReflectionHost(new MockLogger(), false, bundle, dts)); - - const dtsDeclaration = host.getDtsDeclaration(class1); - expect(dtsDeclaration!.getSourceFile().fileName).toEqual(_('/ep/typings/class1.d.ts')); - }); - - it('should find the dts declaration for exported functions', () => { - loadTestFiles(TYPINGS_SRC_FILES); - loadTestFiles(TYPINGS_DTS_FILES); - const bundle = makeTestBundleProgram(_('/ep/src/func1.js')); - const dts = makeTestDtsBundleProgram(_('/ep/typings/func1.d.ts'), _('/')); - const mooFn = getDeclaration( - bundle.program, _('/ep/src/func1.js'), 'mooFn', ts.isFunctionDeclaration); - const host = - createHost(bundle, new Esm5ReflectionHost(new MockLogger(), false, bundle, dts)); - - const dtsDeclaration = host.getDtsDeclaration(mooFn); - expect(dtsDeclaration!.getSourceFile().fileName).toEqual(_('/ep/typings/func1.d.ts')); - }); - - it('should return null if there is no matching class in the matching dts file', () => { - loadTestFiles(TYPINGS_SRC_FILES); - loadTestFiles(TYPINGS_DTS_FILES); - const bundle = makeTestBundleProgram(getRootFiles(TYPINGS_SRC_FILES)[0]); - const dts = makeTestBundleProgram(getRootFiles(TYPINGS_DTS_FILES)[0]); - const missingClass = getDeclaration( - bundle.program, _('/ep/src/class1.js'), 'MissingClass1', ts.isVariableDeclaration); - const host = - createHost(bundle, new Esm5ReflectionHost(new MockLogger(), false, bundle, dts)); - - expect(host.getDtsDeclaration(missingClass)).toBe(null); - }); - - it('should return null if there is no matching dts file', () => { - loadTestFiles(TYPINGS_SRC_FILES); - loadTestFiles(TYPINGS_DTS_FILES); - const bundle = makeTestBundleProgram(getRootFiles(TYPINGS_SRC_FILES)[0]); - const dts = makeTestBundleProgram(getRootFiles(TYPINGS_DTS_FILES)[0]); - const missingClass = getDeclaration( - bundle.program, _('/ep/src/missing-class.js'), 'MissingClass2', - ts.isVariableDeclaration); - const host = - createHost(bundle, new Esm5ReflectionHost(new MockLogger(), false, bundle, dts)); - - expect(host.getDtsDeclaration(missingClass)).toBe(null); - }); - - it('should find the dts file that contains a matching class declaration, even if the source files do not match', - () => { - loadTestFiles(TYPINGS_SRC_FILES); - loadTestFiles(TYPINGS_DTS_FILES); - const bundle = makeTestBundleProgram(_('/ep/src/flat-file.js')); - const dts = makeTestBundleProgram(getRootFiles(TYPINGS_DTS_FILES)[0]); - const class1 = getDeclaration( - bundle.program, _('/ep/src/flat-file.js'), 'Class1', ts.isVariableDeclaration); - const host = - createHost(bundle, new Esm5ReflectionHost(new MockLogger(), false, bundle, dts)); - - const dtsDeclaration = host.getDtsDeclaration(class1); - expect(dtsDeclaration!.getSourceFile().fileName).toEqual(_('/ep/typings/class1.d.ts')); - }); - - it('should find aliased exports', () => { - loadTestFiles(TYPINGS_SRC_FILES); - loadTestFiles(TYPINGS_DTS_FILES); - const bundle = makeTestBundleProgram(_('/ep/src/flat-file.js')); - const dts = makeTestBundleProgram(getRootFiles(TYPINGS_DTS_FILES)[0]); - const sourceClass = getDeclaration( - bundle.program, _('/ep/src/flat-file.js'), 'SourceClass', ts.isVariableDeclaration); - const host = - createHost(bundle, new Esm5ReflectionHost(new MockLogger(), false, bundle, dts)); - - const dtsDeclaration = host.getDtsDeclaration(sourceClass); - if (dtsDeclaration === null) { - return fail('Expected dts class to be found'); - } - if (!isNamedClassDeclaration(dtsDeclaration)) { - return fail('Expected a named class to be found.'); - } - expect(dtsDeclaration.name.text).toEqual('TypingsClass'); - expect(_(dtsDeclaration.getSourceFile().fileName)) - .toEqual(_('/ep/typings/typings-class.d.ts')); - }); - - it('should find the dts file that contains a matching class declaration, even if the class is not publicly exported', - () => { - loadTestFiles(TYPINGS_SRC_FILES); - loadTestFiles(TYPINGS_DTS_FILES); - const bundle = makeTestBundleProgram(getRootFiles(TYPINGS_SRC_FILES)[0]); - const dts = makeTestBundleProgram(getRootFiles(TYPINGS_DTS_FILES)[0]); - const internalClass = getDeclaration( - bundle.program, _('/ep/src/internal.js'), 'InternalClass', ts.isVariableDeclaration); - const host = - createHost(bundle, new Esm5ReflectionHost(new MockLogger(), false, bundle, dts)); - - const dtsDeclaration = host.getDtsDeclaration(internalClass); - expect(dtsDeclaration!.getSourceFile().fileName).toEqual(_('/ep/typings/internal.d.ts')); - }); - - it('should match publicly and internal exported classes correctly, even if they have the same name', - () => { - loadTestFiles(TYPINGS_SRC_FILES); - loadTestFiles(TYPINGS_DTS_FILES); - const bundle = makeTestBundleProgram(getRootFiles(TYPINGS_SRC_FILES)[0]); - const dts = makeTestBundleProgram(getRootFiles(TYPINGS_DTS_FILES)[0]); - const host = - createHost(bundle, new Esm5ReflectionHost(new MockLogger(), false, bundle, dts)); - - const class2 = getDeclaration( - bundle.program, _('/ep/src/class2.js'), 'Class2', isNamedVariableDeclaration); - const class2DtsDeclaration = host.getDtsDeclaration(class2); - expect(class2DtsDeclaration!.getSourceFile().fileName) - .toEqual(_('/ep/typings/class2.d.ts')); - - const internalClass2 = getDeclaration( - bundle.program, _('/ep/src/internal.js'), 'Class2', isNamedVariableDeclaration); - const internalClass2DtsDeclaration = host.getDtsDeclaration(internalClass2); - expect(internalClass2DtsDeclaration!.getSourceFile().fileName) - .toEqual(_('/ep/typings/internal.d.ts')); - }); - }); - - describe('getInternalNameOfClass()', () => { - it('should return the name of the inner class declaration', () => { - loadTestFiles([SIMPLE_CLASS_FILE]); - const bundle = makeTestBundleProgram(SIMPLE_CLASS_FILE.name); - const host = createHost(bundle, new Esm5ReflectionHost(new MockLogger(), false, bundle)); - - const emptyClass = getDeclaration( - bundle.program, SIMPLE_CLASS_FILE.name, 'EmptyClass', isNamedVariableDeclaration); - expect(host.getInternalNameOfClass(emptyClass).text).toEqual('EmptyClass'); - - const class1 = getDeclaration( - bundle.program, SIMPLE_CLASS_FILE.name, 'OuterClass1', isNamedVariableDeclaration); - expect(host.getInternalNameOfClass(class1).text).toEqual('InnerClass1'); - - const class2 = getDeclaration( - bundle.program, SIMPLE_CLASS_FILE.name, 'OuterClass2', isNamedVariableDeclaration); - expect(host.getInternalNameOfClass(class2).text).toEqual('InnerClass2'); - - const childClass = getDeclaration( - bundle.program, SIMPLE_CLASS_FILE.name, 'ChildClass', isNamedVariableDeclaration); - expect(host.getInternalNameOfClass(childClass).text).toEqual('InnerChildClass'); - }); - }); - - describe('getAdjacentNameOfClass()', () => { - it('should return the name of the inner class declaration', () => { - loadTestFiles([SIMPLE_CLASS_FILE]); - const bundle = makeTestBundleProgram(SIMPLE_CLASS_FILE.name); - const host = createHost(bundle, new Esm5ReflectionHost(new MockLogger(), false, bundle)); - - const emptyClass = getDeclaration( - bundle.program, SIMPLE_CLASS_FILE.name, 'EmptyClass', isNamedVariableDeclaration); - expect(host.getAdjacentNameOfClass(emptyClass).text).toEqual('EmptyClass'); - - const class1 = getDeclaration( - bundle.program, SIMPLE_CLASS_FILE.name, 'OuterClass1', isNamedVariableDeclaration); - expect(host.getAdjacentNameOfClass(class1).text).toEqual('InnerClass1'); - - const class2 = getDeclaration( - bundle.program, SIMPLE_CLASS_FILE.name, 'OuterClass2', isNamedVariableDeclaration); - expect(host.getAdjacentNameOfClass(class2).text).toEqual('InnerClass2'); - - const childClass = getDeclaration( - bundle.program, SIMPLE_CLASS_FILE.name, 'ChildClass', isNamedVariableDeclaration); - expect(host.getAdjacentNameOfClass(childClass).text).toEqual('InnerChildClass'); - }); - }); - - describe('getEndOfClass()', () => { - it('should return the last static property of the class', () => { - loadTestFiles([SOME_DIRECTIVE_FILE]); - const bundle = makeTestBundleProgram(SOME_DIRECTIVE_FILE.name); - const host = createHost(bundle, new Esm5ReflectionHost(new MockLogger(), false, bundle)); - const classSymbol = - host.findClassSymbols(bundle.program.getSourceFile(SOME_DIRECTIVE_FILE.name)!)[0]; - const endOfClass = host.getEndOfClass(classSymbol); - expect(endOfClass.getText()).toEqual(`SomeDirective.propDecorators = { - "input1": [{ type: Input },], - "input2": [{ type: Input },], - };`); - }); - }); - }); -}); diff --git a/packages/compiler-cli/ngcc/test/host/umd_host_import_helper_spec.ts b/packages/compiler-cli/ngcc/test/host/umd_host_import_helper_spec.ts deleted file mode 100644 index 88b113b817b..00000000000 --- a/packages/compiler-cli/ngcc/test/host/umd_host_import_helper_spec.ts +++ /dev/null @@ -1,162 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ - -import {absoluteFrom} from '../../../src/ngtsc/file_system'; -import {runInEachFileSystem, TestFile} from '../../../src/ngtsc/file_system/testing'; -import {MockLogger} from '../../../src/ngtsc/logging/testing'; -import {ClassMemberKind, isNamedVariableDeclaration} from '../../../src/ngtsc/reflection'; -import {getDeclaration, loadTestFiles} from '../../../src/ngtsc/testing'; -import {UmdReflectionHost} from '../../src/host/umd_host'; -import {makeTestBundleProgram} from '../helpers/utils'; - -import {expectTypeValueReferencesForParameters} from './util'; - -runInEachFileSystem(() => { - describe('UmdReflectionHost [import helper style]', () => { - let _: typeof absoluteFrom; - - let SOME_DIRECTIVE_FILE: TestFile; - - beforeEach(() => { - _ = absoluteFrom; - - SOME_DIRECTIVE_FILE = { - name: _('/some_directive.umd.js'), - contents: ` -(function (global, factory) { - typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('@angular/core')) : - typeof define === 'function' && define.amd ? define('some_directive', ['exports', '@angular/core'], factory) : - (factory(global.some_directive,global.ng.core)); -}(this, (function (exports,core) { 'use strict'; - - var __decorate = null; - var __metadata = null; - var __param = null; - - var INJECTED_TOKEN = new InjectionToken('injected'); - var ViewContainerRef = {}; - var TemplateRef = {}; - - var SomeDirective = (function() { - function SomeDirective(_viewContainer, _template, injected) {} - __decorate([ - core.Input(), - __metadata("design:type", String) - ], SomeDirective.prototype, "input1", void 0); - __decorate([ - core.Input(), - __metadata("design:type", Number) - ], SomeDirective.prototype, "input2", void 0); - SomeDirective = __decorate([ - core.Directive({ selector: '[someDirective]' }), - __param(2, core.Inject(INJECTED_TOKEN)), - __metadata("design:paramtypes", [ViewContainerRef, TemplateRef, String]) - ], SomeDirective); - return SomeDirective; - }()); - exports.SomeDirective = SomeDirective; - - var AliasedDirective$1 = /** @class */ (function () { - function AliasedDirective() {} - AliasedDirective = __decorate([ - core.Directive({ selector: '[someDirective]' }), - ], AliasedDirective); - return AliasedDirective; - }()); - exports.AliasedDirective$1 = AliasedDirective$1; -})));`, - }; - }); - - describe('getDecoratorsOfDeclaration()', () => { - it('should find the decorators on a class', () => { - loadTestFiles([SOME_DIRECTIVE_FILE]); - const bundle = makeTestBundleProgram(SOME_DIRECTIVE_FILE.name); - const host = new UmdReflectionHost(new MockLogger(), false, bundle); - const classNode = getDeclaration( - bundle.program, SOME_DIRECTIVE_FILE.name, 'SomeDirective', isNamedVariableDeclaration); - const decorators = host.getDecoratorsOfDeclaration(classNode)!; - - expect(decorators).toBeDefined(); - expect(decorators.length).toEqual(1); - - const decorator = decorators[0]; - expect(decorator.name).toEqual('Directive'); - expect(decorator.identifier!.getText()).toEqual('core.Directive'); - expect(decorator.import).toEqual({name: 'Directive', from: '@angular/core'}); - expect(decorator.args!.map(arg => arg.getText())).toEqual([ - '{ selector: \'[someDirective]\' }', - ]); - }); - - it('should find the decorators on an aliased class', () => { - loadTestFiles([SOME_DIRECTIVE_FILE]); - const bundle = makeTestBundleProgram(SOME_DIRECTIVE_FILE.name); - const host = new UmdReflectionHost(new MockLogger(), false, bundle); - const classNode = getDeclaration( - bundle.program, SOME_DIRECTIVE_FILE.name, 'AliasedDirective$1', - isNamedVariableDeclaration); - const decorators = host.getDecoratorsOfDeclaration(classNode)!; - - expect(decorators).toBeDefined(); - expect(decorators.length).toEqual(1); - - const decorator = decorators[0]; - expect(decorator.name).toEqual('Directive'); - expect(decorator.identifier!.getText()).toEqual('core.Directive'); - expect(decorator.import).toEqual({name: 'Directive', from: '@angular/core'}); - expect(decorator.args!.map(arg => arg.getText())).toEqual([ - '{ selector: \'[someDirective]\' }', - ]); - }); - }); - - describe('getMembersOfClass()', () => { - it('should find decorated members on a class', () => { - loadTestFiles([SOME_DIRECTIVE_FILE]); - const bundle = makeTestBundleProgram(SOME_DIRECTIVE_FILE.name); - const host = new UmdReflectionHost(new MockLogger(), false, bundle); - const classNode = getDeclaration( - bundle.program, SOME_DIRECTIVE_FILE.name, 'SomeDirective', isNamedVariableDeclaration); - const members = host.getMembersOfClass(classNode); - - const input1 = members.find(member => member.name === 'input1')!; - expect(input1.kind).toEqual(ClassMemberKind.Property); - expect(input1.isStatic).toEqual(false); - expect(input1.decorators!.map(d => d.name)).toEqual(['Input']); - - const input2 = members.find(member => member.name === 'input2')!; - expect(input2.kind).toEqual(ClassMemberKind.Property); - expect(input2.isStatic).toEqual(false); - expect(input1.decorators!.map(d => d.name)).toEqual(['Input']); - }); - - describe('getConstructorParameters', () => { - it('should find the decorated constructor parameters', () => { - loadTestFiles([SOME_DIRECTIVE_FILE]); - const bundle = makeTestBundleProgram(SOME_DIRECTIVE_FILE.name); - const host = new UmdReflectionHost(new MockLogger(), false, bundle); - const classNode = getDeclaration( - bundle.program, SOME_DIRECTIVE_FILE.name, 'SomeDirective', - isNamedVariableDeclaration); - const parameters = host.getConstructorParameters(classNode); - - expect(parameters).toBeDefined(); - expect(parameters!.map(parameter => parameter.name)).toEqual([ - '_viewContainer', '_template', 'injected' - ]); - expectTypeValueReferencesForParameters(parameters!, [ - 'ViewContainerRef', - 'TemplateRef', - null, - ]); - }); - }); - }); - }); -}); diff --git a/packages/compiler-cli/ngcc/test/host/umd_host_spec.ts b/packages/compiler-cli/ngcc/test/host/umd_host_spec.ts deleted file mode 100644 index eef012a7778..00000000000 --- a/packages/compiler-cli/ngcc/test/host/umd_host_spec.ts +++ /dev/null @@ -1,3570 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ - -import ts from 'typescript'; - -import {absoluteFrom, getFileSystem, getSourceFileOrError} from '../../../src/ngtsc/file_system'; -import {runInEachFileSystem, TestFile} from '../../../src/ngtsc/file_system/testing'; -import {MockLogger} from '../../../src/ngtsc/logging/testing'; -import {ClassMemberKind, ConcreteDeclaration, CtorParameter, DeclarationKind, DownleveledEnum, Import, InlineDeclaration, isNamedClassDeclaration, isNamedFunctionDeclaration, isNamedVariableDeclaration, KnownDeclaration, TypeScriptReflectionHost, TypeValueReferenceKind} from '../../../src/ngtsc/reflection'; -import {getDeclaration, loadFakeCore, loadTestFiles} from '../../../src/ngtsc/testing'; -import {isExportsDeclaration, isExportsStatement} from '../../src/host/commonjs_umd_utils'; -import {DelegatingReflectionHost} from '../../src/host/delegating_host'; -import {NgccReflectionHost} from '../../src/host/ngcc_host'; -import {parseStatementForUmdModule, UmdReflectionHost} from '../../src/host/umd_host'; -import {BundleProgram} from '../../src/packages/bundle_program'; -import {hasNameIdentifier} from '../../src/utils'; -import {testForEachUmdFormat} from '../helpers/umd_utils'; -import {getRootFiles, makeTestBundleProgram} from '../helpers/utils'; - -import {expectTypeValueReferencesForParameters} from './util'; - -runInEachFileSystem(() => { - for (const mode of ['inline exports', 'exported vars']) { - describe( - `UmdReflectionHost [with ${mode}]`, testForEachUmdFormat(({createUmdModule}) => { - let _: typeof absoluteFrom; - - let SOME_DIRECTIVE_FILE: TestFile; - let TOPLEVEL_DECORATORS_FILE: TestFile; - let CTOR_DECORATORS_ARRAY_FILE: TestFile; - let SIMPLE_ES2015_CLASS_FILE: TestFile; - let SIMPLE_CLASS_FILE: TestFile; - let FOO_FUNCTION_FILE: TestFile; - let INLINE_EXPORT_FILE: TestFile; - let EXPORTS_IDENTIFIERS_FILE: TestFile; - let INVALID_DECORATORS_FILE: TestFile; - let INVALID_DECORATOR_ARGS_FILE: TestFile; - let INVALID_PROP_DECORATORS_FILE: TestFile; - let INVALID_PROP_DECORATOR_ARGS_FILE: TestFile; - let INVALID_CTOR_DECORATORS_FILE: TestFile; - let INVALID_CTOR_DECORATOR_ARGS_FILE: TestFile; - let IMPORTS_FILES: TestFile[]; - let EXPORTS_FILES: TestFile[]; - let FUNCTION_BODY_FILE: TestFile; - let DECORATED_FILES: TestFile[]; - let TYPINGS_SRC_FILES: TestFile[]; - let TYPINGS_DTS_FILES: TestFile[]; - - // Helpers - const createHost = (bundle: BundleProgram, ngccHost: UmdReflectionHost) => { - const tsHost = new TypeScriptReflectionHost(bundle.program.getTypeChecker()); - return new DelegatingReflectionHost(tsHost, ngccHost); - }; - - // There are two different forms of UMD export declaration. - // - // - "exported vars" where there is a variable declaration that is then assigned to the - // `exports` object. For example: - // ``` - // var MyClass = <...>; - // exports.MyClass = MyClass; - // ``` - // - // - "inline exports" where there is no intemediate variable declaration. For example: - // ``` - // exports.MyClass = <...>; - // ``` - - // The following helpers allow us to setup code examples to use these two different - // approaches. - - /** - * Create an export declaration: e.g. - * - * ``` - * exports. = ; - * ``` - * - * or - * - * ``` - * var = ; - * ``` - */ - const expDecl = (name: string, noVar = false) => - mode === 'exported vars' ? `${noVar ? '' : 'var '}${name}` : `exports.${name}`; - - /** - * Export a variable that references a declaration (only for when the export was defined - * as a variable). For example: - * - * ``` - * exports. = ; - * ``` - */ - const varExp = (name: string) => - mode === 'exported vars' ? `exports.${name} = ${name};` : ''; - - /** - * Select a predicate for matching an exported declaration based on whether the code - * contains "exported vars" or "inline exports". - */ - const isDesiredDeclaration: typeof hasNameIdentifier = - mode === 'exported vars' ? isNamedVariableDeclaration : isExportsDeclaration; - - beforeEach(() => { - _ = absoluteFrom; - - SOME_DIRECTIVE_FILE = { - name: _('/some_directive.umd.js'), - contents: createUmdModule('some_directive', ['@angular/core'], ` -var INJECTED_TOKEN = new InjectionToken('injected'); -var ViewContainerRef = {}; -var TemplateRef = {}; - -${expDecl('SomeDirective')} = (function() { - function SomeDirective(_viewContainer, _template, injected) { - this.instanceProperty = 'instance'; - } - SomeDirective.prototype = { - instanceMethod: function() {}, - }; - SomeDirective.staticMethod = function() {}; - SomeDirective.staticProperty = 'static'; - SomeDirective.decorators = [ - { type: core.Directive, args: [{ selector: '[someDirective]' },] } - ]; - SomeDirective.ctorParameters = function() { return [ - { type: ViewContainerRef, }, - { type: TemplateRef, }, - { type: undefined, decorators: [{ type: core.Inject, args: [INJECTED_TOKEN,] },] }, - ]; }; - SomeDirective.propDecorators = { - "input1": [{ type: core.Input },], - "input2": [{ type: core.Input },], - }; - return SomeDirective; -}()); -${varExp('SomeDirective')} -`), - }; - - TOPLEVEL_DECORATORS_FILE = { - name: _('/toplevel_decorators.umd.js'), - contents: createUmdModule('toplevel_decorators', ['@angular/core'], ` -var INJECTED_TOKEN = new core.InjectionToken('injected'); -var ViewContainerRef = {}; -var TemplateRef = {}; - -${expDecl('SomeDirective')} = (function() { - function SomeDirective(_viewContainer, _template, injected) {} - return SomeDirective; -}()); -${expDecl('SomeDirective', true)}.decorators = [ - { type: core.Directive, args: [{ selector: '[someDirective]' },] } -]; -${expDecl('SomeDirective', true)}.ctorParameters = function() { return [ - { type: ViewContainerRef, }, - { type: TemplateRef, }, - { type: undefined, decorators: [{ type: core.Inject, args: [INJECTED_TOKEN,] },] }, -]; }; -${expDecl('SomeDirective', true)}.propDecorators = { - "input1": [{ type: core.Input },], - "input2": [{ type: core.Input },], -}; -${varExp('SomeDirective')} -`), - }; - - CTOR_DECORATORS_ARRAY_FILE = { - name: _('/ctor_decorated_as_array.js'), - contents: createUmdModule('ctor_decorated_as_array', ['@angular/core'], ` - ${expDecl('CtorDecoratedAsArray')} = (function() { - function CtorDecoratedAsArray(arg1) { - } - CtorDecoratedAsArray.ctorParameters = [{ type: ParamType, decorators: [{ type: Inject },] }]; - return CtorDecoratedAsArray; - }()); - ${varExp('CtorDecoratedAsArray')} - `), - }; - - SIMPLE_ES2015_CLASS_FILE = { - name: _('/simple_es2015_class.d.ts'), - contents: ` - export class EmptyClass {} -`, - }; - - SIMPLE_CLASS_FILE = { - name: _('/simple_class.js'), - contents: createUmdModule('simple_class', [], ` -${expDecl('EmptyClass')} = (function() { - function EmptyClass() { - } - return EmptyClass; -}()); -${expDecl('NoParensClass')} = function() { - function NoParensClass() { - } - return NoParensClass; -}(); -${expDecl('InnerParensClass')} = (function() { - function InnerParensClass() { - } - return InnerParensClass; -})(); -${expDecl('NoDecoratorConstructorClass')} = (function() { - function NoDecoratorConstructorClass(foo) { - } - return NoDecoratorConstructorClass; -}()); -${expDecl('OuterClass1')} = (function() { - function InnerClass1() { - } - return InnerClass1; -}()); -${expDecl('OuterClass2')} = (function() { - function InnerClass2() { - } - InnerClass2_1 = InnerClass12 - var InnerClass2_1; - return InnerClass2; -}()); -${expDecl('SuperClass')} = (function() { function SuperClass() {} return SuperClass; }()); -${expDecl('ChildClass')} = /** @class */ (function (_super) { -__extends(InnerChildClass, _super); - function InnerChildClass() {} - return InnerChildClass; -}(SuperClass); -${varExp('EmptyClass')} -${varExp('NoDecoratorConstructorClass')} -`), - }; - - FOO_FUNCTION_FILE = { - name: _('/foo_function.js'), - contents: createUmdModule('foo_function', ['@angular/core'], ` -function foo() {} -foo.decorators = [ - { type: core.Directive, args: [{ selector: '[ignored]' },] } -]; -${varExp('foo')} -`), - }; - - INLINE_EXPORT_FILE = { - name: _('/inline_export.js'), - contents: createUmdModule('inline_export', ['@angular/core'], ` -function foo() {} -foo.decorators = [ - { type: core.Directive, args: [{ selector: '[ignored]' },] } -]; -exports.directives = [foo]; -exports.Inline = (function() { function Inline() {} return Inline; })(); -`), - }; - - EXPORTS_IDENTIFIERS_FILE = { - name: _('/exports_identifiers.js'), - contents: createUmdModule('inline_identifiers', ['@angular/core'], ` -var x = exports; -exports.foo = 42; - -function simpleFn() { - exports.foo = 42; -} - -function localVar() { - var exports = {}; - exports.foo = 42; -} - -function exportsArg(exports) { - exports.foo = 42; -} -`), - }; - - INVALID_DECORATORS_FILE = { - name: _('/invalid_decorators.js'), - contents: createUmdModule('invalid_decorators', ['@angular/core'], ` -${expDecl('NotArrayLiteral')} = (function() { - function NotArrayLiteral() { - } - NotArrayLiteral.decorators = () => [ - { type: core.Directive, args: [{ selector: '[ignored]' },] }, - ]; - return NotArrayLiteral; -}()); - -${expDecl('NotObjectLiteral')} = (function() { - function NotObjectLiteral() { - } - NotObjectLiteral.decorators = [ - "This is not an object literal", - { type: core.Directive }, - ]; - return NotObjectLiteral; -}()); - -${expDecl('NoTypeProperty')} = (function() { - function NoTypeProperty() { - } - NoTypeProperty.decorators = [ - { notType: core.Directive }, - { type: core.Directive }, - ]; - return NoTypeProperty; -}()); - -${expDecl('NotIdentifier')} = (function() { - function NotIdentifier() { - } - NotIdentifier.decorators = [ - { type: 'StringsLiteralsAreNotIdentifiers' }, - { type: core.Directive }, - ]; - return NotIdentifier; -}()); -`), - }; - - INVALID_DECORATOR_ARGS_FILE = { - name: _('/invalid_decorator_args.js'), - contents: createUmdModule('invalid_decorator_args', ['@angular/core'], ` -${expDecl('NoArgsProperty')} = (function() { - function NoArgsProperty() { - } - NoArgsProperty.decorators = [ - { type: core.Directive }, - ]; - return NoArgsProperty; -}()); - -var args = [{ selector: '[ignored]' },]; -${expDecl('NoPropertyAssignment')} = (function() { - function NoPropertyAssignment() { - } - NoPropertyAssignment.decorators = [ - { type: core.Directive, args }, - ]; - return NoPropertyAssignment; -}()); - -${expDecl('NotArrayLiteral')} = (function() { - function NotArrayLiteral() { - } - NotArrayLiteral.decorators = [ - { type: core.Directive, args: () => [{ selector: '[ignored]' },] }, - ]; - return NotArrayLiteral; -}()); -`), - }; - - INVALID_PROP_DECORATORS_FILE = { - name: _('/invalid_prop_decorators.js'), - contents: createUmdModule('invalid_prop_decorators', ['@angular/core'], ` -${expDecl('NotObjectLiteral')} = (function() { - function NotObjectLiteral() { - } - NotObjectLiteral.propDecorators = () => ({ - "prop": [{ type: core.Directive },] - }); - return NotObjectLiteral; -}()); - -${expDecl('NotObjectLiteralProp')} = (function() { - function NotObjectLiteralProp() { - } - NotObjectLiteralProp.propDecorators = { - "prop": [ - "This is not an object literal", - { type: core.Directive }, - ] - }; - return NotObjectLiteralProp; -}()); - -${expDecl('NoTypeProperty')} = (function() { - function NoTypeProperty() { - } - NoTypeProperty.propDecorators = { - "prop": [ - { notType: core.Directive }, - { type: core.Directive }, - ] - }; - return NoTypeProperty; -}()); - -${expDecl('NotIdentifier')} = (function() { - function NotIdentifier() { - } - NotIdentifier.propDecorators = { - "prop": [ - { type: 'StringsLiteralsAreNotIdentifiers' }, - { type: core.Directive }, - ] - }; - return NotIdentifier; -}()); -`), - }; - - INVALID_PROP_DECORATOR_ARGS_FILE = { - name: _('/invalid_prop_decorator_args.js'), - contents: createUmdModule('invalid_prop_decorator_args', ['@angular/core'], ` -${expDecl('NoArgsProperty')} = (function() { - function NoArgsProperty() { - } - NoArgsProperty.propDecorators = { - "prop": [{ type: core.Input },] - }; - return NoArgsProperty; -}()); - -var args = [{ selector: '[ignored]' },]; -${expDecl('NoPropertyAssignment')} = (function() { - function NoPropertyAssignment() { - } - NoPropertyAssignment.propDecorators = { - "prop": [{ type: core.Input, args },] - }; - return NoPropertyAssignment; -}()); - -${expDecl('NotArrayLiteral')} = (function() { - function NotArrayLiteral() { - } - NotArrayLiteral.propDecorators = { - "prop": [{ type: core.Input, args: () => [{ selector: '[ignored]' },] },], - }; - return NotArrayLiteral; -}()); -`), - }; - - INVALID_CTOR_DECORATORS_FILE = { - name: _('/invalid_ctor_decorators.js'), - contents: createUmdModule('invalid_ctor_decorators', ['@angular/core'], ` -${expDecl('NoParameters')} = (function() { - function NoParameters() {} - return NoParameters; -}()); - -${expDecl('NotArrayLiteral')} = (function() { - function NotArrayLiteral(arg1) { - } - NotArrayLiteral.ctorParameters = function() { return 'StringsAreNotArrayLiterals'; }; - return NotArrayLiteral; -}()); - -${expDecl('NotObjectLiteral')} = (function() { - function NotObjectLiteral(arg1, arg2) { - } - NotObjectLiteral.ctorParameters = function() { return [ - "This is not an object literal", - { type: 'ParamType', decorators: [{ type: core.Inject },] }, - ]; }; - return NotObjectLiteral; -}()); - -${expDecl('NoTypeProperty')} = (function() { - function NoTypeProperty(arg1, arg2) { - } - NoTypeProperty.ctorParameters = function() { return [ - { - type: 'ParamType', - decorators: [ - { notType: core.Inject }, - { type: core.Inject }, - ] - }, - ]; }; - return NoTypeProperty; -}()); - -${expDecl('NotIdentifier')} = (function() { - function NotIdentifier(arg1, arg2) { - } - NotIdentifier.ctorParameters = function() { return [ - { - type: 'ParamType', - decorators: [ - { type: 'StringsLiteralsAreNotIdentifiers' }, - { type: core.Inject }, - ] - }, - ]; }; - return NotIdentifier; -}()); -`), - }; - - INVALID_CTOR_DECORATOR_ARGS_FILE = { - name: _('/invalid_ctor_decorator_args.js'), - contents: createUmdModule('invalid_ctor_decorator_args', ['@angular/core'], ` -${expDecl('NoArgsProperty')} = (function() { - function NoArgsProperty(arg1) { - } - NoArgsProperty.ctorParameters = function() { return [ - { type: 'ParamType', decorators: [{ type: core.Inject },] }, - ]; }; - return NoArgsProperty; -}()); - -var args = [{ selector: '[ignored]' },]; -${expDecl('NoPropertyAssignment')} = (function() { - function NoPropertyAssignment(arg1) { - } - NoPropertyAssignment.ctorParameters = function() { return [ - { type: 'ParamType', decorators: [{ type: core.Inject, args },] }, - ]; }; - return NoPropertyAssignment; -}()); - -${expDecl('NotArrayLiteral')} = (function() { - function NotArrayLiteral(arg1) { - } - NotArrayLiteral.ctorParameters = function() { return [ - { type: 'ParamType', decorators: [{ type: core.Inject, args: () => [{ selector: '[ignored]' },] },] }, - ]; }; - return NotArrayLiteral; -}()); -`), - }; - - IMPORTS_FILES = [ - { - name: _('/index.js'), - contents: createUmdModule('index', ['./file_a', './file_b', './file_c'], ''), - }, - { - name: _('/file_a.js'), - contents: createUmdModule('file_a', [], ` -var a = 'a'; -${varExp('a')} -`), - }, - { - name: _('/file_b.js'), - contents: createUmdModule('file_b', ['./file_a'], ` -var b = file_a.a; -var c = 'c'; -var d = c; -`), - }, - { - name: _('/file_c.js'), - contents: createUmdModule('file_c', ['./file_a'], 'var c = file_a.a;'), - }, - ]; - - EXPORTS_FILES = [ - { - name: _('/index.js'), - contents: createUmdModule( - 'index', - [ - './a_module', './b_module', './wildcard_reexports', - './wildcard_reexports_imported_helpers', './wildcard_reexports_with_require', - './define_property_reexports' - ], - ''), - }, - { - name: _('/a_module.js'), - contents: createUmdModule( - 'a_module', [], - ` var a = 'a';\n` + - ` exports.a = a;`), - }, - { - name: _('/b_module.js'), - contents: createUmdModule( - 'b_module', ['@angular/core', './a_module'], - ` var b = a_module.a;\n` + - ` var e = 'e';\n` + - ` var SomeClass = (function() {\n` + - ` function SomeClass() {}\n` + - ` return SomeClass;\n` + - ` }());\n` + - `\n` + - ` exports.Directive = core.Directive;\n` + - ` exports.a = a_module.a;\n` + - ` exports.b = b;\n` + - ` exports.c = a_module.a;\n` + - ` exports.d = b;\n` + - ` exports.e = e;\n` + - ` exports.DirectiveX = core.Directive;\n` + - ` var SomeClass_1;\n` + - ` exports.SomeClass = SomeClass_1 = SomeClass;`), - }, - { - name: _('/xtra_module.js'), - contents: createUmdModule( - 'xtra_module', [], - ` var xtra1 = 'xtra1';\n` + - ` var xtra2 = 'xtra2';\n` + - ` exports.xtra1 = xtra1;\n` + - ` exports.xtra2 = xtra2;`), - }, - { - name: _('/wildcard_reexports.js'), - contents: createUmdModule( - 'wildcard_reexports', ['./b_module', './xtra_module'], - ` function __export(m) {\n` + - ` for (var p in m) if (!exports.hasOwnProperty(p)) exports[p] = m[p];\n` + - ` }\n` + - ` __export(b_module);\n` + - ` __export(xtra_module);`), - }, - { - name: _('/wildcard_reexports_imported_helpers.js'), - contents: createUmdModule( - 'wildcard_reexports', ['tslib', './b_module', './xtra_module'], - ` tslib.__exportStar(b_module, exports);\n` + - ` tslib.__exportStar(xtra_module, exports);`), - }, - { - name: _('/wildcard_reexports_with_require.js'), - contents: `(function (global, factory) {\n` + - ` typeof exports === 'object' && typeof module !== 'undefined' ? factory(require, exports) :\n` + - ` typeof define === 'function' && define.amd ? define('wildcard_reexports_with_require', ['require', 'exports'], factory) :\n` + - ` (factory(global.require, global.wildcard_reexports_with_require));\n` + - `}(this, (function (require, exports) { 'use strict';\n` + - ` function __export(m) {\n` + - ` for (var p in m) if (!exports.hasOwnProperty(p)) exports[p] = m[p];\n` + - ` }\n` + - ` var b_module = require('./b_module');\n` + - ` __export(b_module);\n` + - ` __export(require('./xtra_module'));\n` + - `})));\n`, - }, - { - name: _('/define_property_reexports.js'), - contents: `(function (global, factory) {\n` + - ` typeof exports === 'object' && typeof module !== 'undefined' ? factory(require, exports) :\n` + - ` typeof define === 'function' && define.amd ? define('define_property_reexports', ['require', 'exports'], factory) :\n` + - ` (factory(global.require, global.define_property_reexports));\n` + - `}(this, (function (require, exports) { 'use strict';\n` + - `var moduleA = require("./a_module");\n` + - `Object.defineProperty(exports, "newA", { enumerable: true, get: function () { return moduleA.a; } });\n` + - `})));`, - }, - { - name: _('/decorated_class.js'), - contents: createUmdModule( - 'decorated_class', [], - `var DecoratedClass_1;\n` + - `\n` + - `exports.DecoratedClass = DecoratedClass_1 = (function() {\n` + - ` function DecoratedClass() {\n` + - ` }\n` + - ` return DecoratedClass;\n` + - `}());\n` + - `\n` + - `exports.DecoratedClass = DecoratedClass_1 = __decorate([\n` + - ` SomeDecorator([ej2AngularBase.ComponentBase, ej2AngularBase.FormBase]),\n` + - ` __metadata("design:paramtypes", [core.ElementRef,\n` + - ` core.Renderer2,\n` + - ` core.ViewContainerRef,\n` + - ` core.Injector])\n` + - `], exports.DecoratedClass);`), - } - ]; - - FUNCTION_BODY_FILE = { - name: _('/function_body.js'), - contents: createUmdModule('function_body', [], ` -function foo(x) { - return x; -} -function bar(x, y) { - if (y === void 0) { y = 42; } - return x + y; -} -function complex() { - var x = 42; - return 42; -} -function baz(x) { - var y; - if (x === void 0) { y = 42; } - return y; -} -var y; -function qux(x) { - if (x === void 0) { y = 42; } - return y; -} -function moo() { - var x; - if (x === void 0) { x = 42; } - return x; -} -var x; -function juu() { - if (x === void 0) { x = 42; } - return x; -} -`), - }; - - DECORATED_FILES = [ - { - name: _('/primary.js'), - contents: createUmdModule('primary', ['@angular/core', './secondary'], ` - ${expDecl('A')} = (function() { - function A() {} - A.decorators = [ - { type: core.Directive, args: [{ selector: '[a]' }] } - ]; - return A; - }()); - var B = (function() { - function B() {} - B.decorators = [ - { type: core.Directive, args: [{ selector: '[b]' }] } - ]; - return B; - }()); - function x() {} - function y() {} - ${expDecl('C')} = (function() { - function C() {} - return C; - }); - ${varExp('A')} - exports.x = x; - ${varExp('C')} -`), - }, - { - name: _('/secondary.js'), - contents: createUmdModule('primary', ['@angular/core'], ` - ${expDecl('D')} = (function() { - function D() {} - D.decorators = [ - { type: core.Directive, args: [{ selector: '[d]' }] } - ]; - return D; - }()); - ${varExp('D')} -`), - } - ]; - - TYPINGS_SRC_FILES = [ - { - name: _('/ep/src/index.js'), - contents: createUmdModule( - 'index', - [ - './internal', './class1', './class2', './missing-class', './flat-file', - './func1' - ], - ` - function __export(m) { - for (var p in m) if (!exports.hasOwnProperty(p)) exports[p] = m[p]; - } - var InternalClass = internal.InternalClass; - __export(class1); - __export(class2); - `), - }, - { - name: _('/ep/src/class1.js'), - contents: createUmdModule('class1', [], ` - ${expDecl('Class1')} = (function() { - function Class1() {} - return Class1; - }()); - ${expDecl('MissingClass1')} = (function() { - function MissingClass1() {} - return MissingClass1; - }()); - ${varExp('Class1')} - ${varExp('MissingClass1')} - `), - }, - { - name: _('/ep/src/class2.js'), - contents: createUmdModule('class2', [], ` - ${expDecl('Class2')} = (function() { - function Class2() {} - return Class2; - }()); - ${varExp('Class2')} - `), - }, - { - name: _('/ep/src/func1.js'), - contents: createUmdModule('internal', [], ` - function mooFn() {} - exports.mooFn = mooFn; - `), - }, - { - name: _('/ep/src/internal.js'), - contents: createUmdModule('internal', [], ` - ${expDecl('InternalClass')} = (function() { - function InternalClass() {} - return InternalClass; - }()); - ${expDecl('Class2')} = (function() { - function Class2() {} - return Class2; - }()); - ${varExp('InternalClass')} - ${varExp('Class2')} - `), - }, - { - name: _('/ep/src/missing-class.js'), - contents: createUmdModule('missingClass', [], ` - ${expDecl('MissingClass2')} = (function() { - function MissingClass2() {} - return MissingClass2; - }()); - exports. MissingClass2 = MissingClass2; - `), - }, - { - name: _('/ep/src/flat-file.js'), - contents: createUmdModule('missingClass', [], ` - ${expDecl('Class1')} = (function() { - function Class1() {} - return Class1; - }()); - ${expDecl('MissingClass1')} = (function() { - function MissingClass1() {} - return MissingClass1; - }()); - ${expDecl('MissingClass2')} = (function() { - function MissingClass2() {} - return MissingClass2; - }()); - var SourceClass = (function() { - function SourceClass() {} - return SourceClass; - }()); - ${varExp('Class1')} - exports.AliasedClass = SourceClass; - ${varExp('MissingClass1')} - ${varExp('MissingClass2')} - `), - } - ]; - - TYPINGS_DTS_FILES = [ - { - name: _('/ep/typings/index.d.ts'), - contents: ` - import '../../an_external_lib/index'; - import {InternalClass} from './internal'; - import {mooFn} from './func1'; - export * from './class1'; - export * from './class2'; - ` - }, - { - name: _('/ep/typings/class1.d.ts'), - contents: `export declare class Class1 {}\nexport declare class OtherClass {}` - }, - { - name: _('/ep/typings/class2.d.ts'), - contents: ` - export declare class Class2 {} - export declare interface SomeInterface {} - export {TypingsClass as AliasedClass} from './typings-class'; - ` - }, - { - name: _('/ep/typings/func1.d.ts'), - contents: 'export declare function mooFn(): void;' - }, - { - name: _('/ep/typings/internal.d.ts'), - contents: `export declare class InternalClass {}\nexport declare class Class2 {}` - }, - { - name: _('/ep/typings/typings-class.d.ts'), - contents: `export declare class TypingsClass {}` - }, - { - name: _('/ep/typings/shadow-class.d.ts'), - contents: `export declare class ShadowClass {}` - }, - { - name: _('/an_external_lib/index.d.ts'), - contents: 'export declare class ShadowClass {}' - }, - ]; - }); - - describe('getDecoratorsOfDeclaration()', () => { - it('should find the decorators on a class', () => { - loadTestFiles([SOME_DIRECTIVE_FILE]); - const bundle = makeTestBundleProgram(SOME_DIRECTIVE_FILE.name); - const host = - createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, SOME_DIRECTIVE_FILE.name, 'SomeDirective', isDesiredDeclaration); - const decorators = host.getDecoratorsOfDeclaration(classNode)!; - - expect(decorators).toBeDefined(); - expect(decorators.length).toEqual(1); - - const decorator = decorators[0]; - expect(decorator.name).toEqual('Directive'); - expect(decorator.import).toEqual({name: 'Directive', from: '@angular/core'}); - expect(decorator.args!.map(arg => arg.getText())).toEqual([ - '{ selector: \'[someDirective]\' }', - ]); - }); - - it('should find the decorators on a class at the top level', () => { - loadTestFiles([TOPLEVEL_DECORATORS_FILE]); - const bundle = makeTestBundleProgram(TOPLEVEL_DECORATORS_FILE.name); - const host = - createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, TOPLEVEL_DECORATORS_FILE.name, 'SomeDirective', - isDesiredDeclaration); - const decorators = host.getDecoratorsOfDeclaration(classNode)!; - - expect(decorators).toBeDefined(); - expect(decorators.length).toEqual(1); - - const decorator = decorators[0]; - expect(decorator.name).toEqual('Directive'); - expect(decorator.import).toEqual({name: 'Directive', from: '@angular/core'}); - expect(decorator.args!.map(arg => arg.getText())).toEqual([ - '{ selector: \'[someDirective]\' }', - ]); - }); - - it('should return null if the symbol is not a class', () => { - loadTestFiles([FOO_FUNCTION_FILE]); - const bundle = makeTestBundleProgram(FOO_FUNCTION_FILE.name); - const host = - createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); - const functionNode = getDeclaration( - bundle.program, FOO_FUNCTION_FILE.name, 'foo', isNamedFunctionDeclaration); - const decorators = host.getDecoratorsOfDeclaration(functionNode); - expect(decorators).toBe(null); - }); - - it('should return null if there are no decorators', () => { - loadTestFiles([SIMPLE_CLASS_FILE]); - const bundle = makeTestBundleProgram(SIMPLE_CLASS_FILE.name); - const host = - createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, SIMPLE_CLASS_FILE.name, 'EmptyClass', isDesiredDeclaration); - const decorators = host.getDecoratorsOfDeclaration(classNode); - expect(decorators).toBe(null); - }); - - it('should ignore `decorators` if it is not an array literal', () => { - loadTestFiles([INVALID_DECORATORS_FILE]); - const bundle = makeTestBundleProgram(INVALID_DECORATORS_FILE.name); - const host = - createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, INVALID_DECORATORS_FILE.name, 'NotArrayLiteral', - isDesiredDeclaration); - const decorators = host.getDecoratorsOfDeclaration(classNode); - expect(decorators).toEqual([]); - }); - - it('should ignore decorator elements that are not object literals', () => { - loadTestFiles([INVALID_DECORATORS_FILE]); - const bundle = makeTestBundleProgram(INVALID_DECORATORS_FILE.name); - const host = - createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, INVALID_DECORATORS_FILE.name, 'NotObjectLiteral', - isDesiredDeclaration); - const decorators = host.getDecoratorsOfDeclaration(classNode)!; - - expect(decorators.length).toBe(1); - expect(decorators[0]).toEqual(jasmine.objectContaining({name: 'Directive'})); - }); - - it('should ignore decorator elements that have no `type` property', () => { - loadTestFiles([INVALID_DECORATORS_FILE]); - const bundle = makeTestBundleProgram(INVALID_DECORATORS_FILE.name); - const host = - createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, INVALID_DECORATORS_FILE.name, 'NoTypeProperty', - isDesiredDeclaration); - const decorators = host.getDecoratorsOfDeclaration(classNode)!; - - expect(decorators.length).toBe(1); - expect(decorators[0]).toEqual(jasmine.objectContaining({name: 'Directive'})); - }); - - it('should ignore decorator elements whose `type` value is not an identifier', () => { - loadTestFiles([INVALID_DECORATORS_FILE]); - const bundle = makeTestBundleProgram(INVALID_DECORATORS_FILE.name); - const host = - createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, INVALID_DECORATORS_FILE.name, 'NotIdentifier', - isDesiredDeclaration); - const decorators = host.getDecoratorsOfDeclaration(classNode)!; - - expect(decorators.length).toBe(1); - expect(decorators[0]).toEqual(jasmine.objectContaining({name: 'Directive'})); - }); - - it('should have import information on decorators', () => { - loadTestFiles([SOME_DIRECTIVE_FILE]); - const bundle = makeTestBundleProgram(SOME_DIRECTIVE_FILE.name); - const host = - createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); - - const classNode = getDeclaration( - bundle.program, SOME_DIRECTIVE_FILE.name, 'SomeDirective', isDesiredDeclaration); - const decorators = host.getDecoratorsOfDeclaration(classNode)!; - - expect(decorators.length).toEqual(1); - expect(decorators[0].import).toEqual({name: 'Directive', from: '@angular/core'}); - }); - - it('should find decorated members on a class at the top level', () => { - loadTestFiles([TOPLEVEL_DECORATORS_FILE]); - const bundle = makeTestBundleProgram(TOPLEVEL_DECORATORS_FILE.name); - const host = - createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, TOPLEVEL_DECORATORS_FILE.name, 'SomeDirective', - isDesiredDeclaration); - const members = host.getMembersOfClass(classNode); - - const input1 = members.find(member => member.name === 'input1')!; - expect(input1.kind).toEqual(ClassMemberKind.Property); - expect(input1.isStatic).toEqual(false); - expect(input1.decorators!.map(d => d.name)).toEqual(['Input']); - - const input2 = members.find(member => member.name === 'input2')!; - expect(input2.kind).toEqual(ClassMemberKind.Property); - expect(input2.isStatic).toEqual(false); - expect(input1.decorators!.map(d => d.name)).toEqual(['Input']); - }); - - describe('(returned decorators `args`)', () => { - it('should be an empty array if decorator has no `args` property', () => { - loadTestFiles([INVALID_DECORATOR_ARGS_FILE]); - const bundle = makeTestBundleProgram(INVALID_DECORATOR_ARGS_FILE.name); - const host = - createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, INVALID_DECORATOR_ARGS_FILE.name, 'NoArgsProperty', - isDesiredDeclaration); - const decorators = host.getDecoratorsOfDeclaration(classNode)!; - - expect(decorators.length).toBe(1); - expect(decorators[0].name).toBe('Directive'); - expect(decorators[0].args).toEqual([]); - }); - - it('should be an empty array if decorator\'s `args` has no property assignment', - () => { - loadTestFiles([INVALID_DECORATOR_ARGS_FILE]); - const bundle = makeTestBundleProgram(INVALID_DECORATOR_ARGS_FILE.name); - const host = - createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, INVALID_DECORATOR_ARGS_FILE.name, 'NoPropertyAssignment', - isDesiredDeclaration); - const decorators = host.getDecoratorsOfDeclaration(classNode)!; - - expect(decorators.length).toBe(1); - expect(decorators[0].name).toBe('Directive'); - expect(decorators[0].args).toEqual([]); - }); - - it('should be an empty array if `args` property value is not an array literal', - () => { - loadTestFiles([INVALID_DECORATOR_ARGS_FILE]); - const bundle = makeTestBundleProgram(INVALID_DECORATOR_ARGS_FILE.name); - const host = - createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, INVALID_DECORATOR_ARGS_FILE.name, 'NotArrayLiteral', - isDesiredDeclaration); - const decorators = host.getDecoratorsOfDeclaration(classNode)!; - - expect(decorators.length).toBe(1); - expect(decorators[0].name).toBe('Directive'); - expect(decorators[0].args).toEqual([]); - }); - }); - }); - - describe('getMembersOfClass()', () => { - it('should find decorated members on a class', () => { - loadTestFiles([SOME_DIRECTIVE_FILE]); - const bundle = makeTestBundleProgram(SOME_DIRECTIVE_FILE.name); - const host = - createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, SOME_DIRECTIVE_FILE.name, 'SomeDirective', isDesiredDeclaration); - const members = host.getMembersOfClass(classNode); - - const input1 = members.find(member => member.name === 'input1')!; - expect(input1.kind).toEqual(ClassMemberKind.Property); - expect(input1.isStatic).toEqual(false); - expect(input1.decorators!.map(d => d.name)).toEqual(['Input']); - - const input2 = members.find(member => member.name === 'input2')!; - expect(input2.kind).toEqual(ClassMemberKind.Property); - expect(input2.isStatic).toEqual(false); - expect(input1.decorators!.map(d => d.name)).toEqual(['Input']); - }); - - it('should find non decorated properties on a class', () => { - loadTestFiles([SOME_DIRECTIVE_FILE]); - const bundle = makeTestBundleProgram(SOME_DIRECTIVE_FILE.name); - const host = - createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, SOME_DIRECTIVE_FILE.name, 'SomeDirective', isDesiredDeclaration); - const members = host.getMembersOfClass(classNode); - - const instanceProperty = members.find(member => member.name === 'instanceProperty')!; - expect(instanceProperty.kind).toEqual(ClassMemberKind.Property); - expect(instanceProperty.isStatic).toEqual(false); - expect(ts.isBinaryExpression(instanceProperty.implementation!)).toEqual(true); - expect(instanceProperty.value!.getText()).toEqual(`'instance'`); - }); - - it('should find static methods on a class', () => { - loadTestFiles([SOME_DIRECTIVE_FILE]); - const bundle = makeTestBundleProgram(SOME_DIRECTIVE_FILE.name); - const host = - createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, SOME_DIRECTIVE_FILE.name, 'SomeDirective', isDesiredDeclaration); - const members = host.getMembersOfClass(classNode); - - const staticMethod = members.find(member => member.name === 'staticMethod')!; - expect(staticMethod.kind).toEqual(ClassMemberKind.Method); - expect(staticMethod.isStatic).toEqual(true); - expect(ts.isFunctionExpression(staticMethod.implementation!)).toEqual(true); - }); - - it('should find static properties on a class', () => { - loadTestFiles([SOME_DIRECTIVE_FILE]); - const bundle = makeTestBundleProgram(SOME_DIRECTIVE_FILE.name); - const host = - createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, SOME_DIRECTIVE_FILE.name, 'SomeDirective', isDesiredDeclaration); - const members = host.getMembersOfClass(classNode); - - const staticProperty = members.find(member => member.name === 'staticProperty')!; - expect(staticProperty.kind).toEqual(ClassMemberKind.Property); - expect(staticProperty.isStatic).toEqual(true); - expect(ts.isPropertyAccessExpression(staticProperty.implementation!)).toEqual(true); - expect(staticProperty.value!.getText()).toEqual(`'static'`); - }); - - it('should throw if the symbol is not a class', () => { - loadTestFiles([FOO_FUNCTION_FILE]); - const bundle = makeTestBundleProgram(FOO_FUNCTION_FILE.name); - const host = - createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); - const functionNode = getDeclaration( - bundle.program, FOO_FUNCTION_FILE.name, 'foo', isNamedFunctionDeclaration); - expect(() => { - host.getMembersOfClass(functionNode); - }).toThrowError(`Attempted to get members of a non-class: "function foo() {}"`); - }); - - it('should return an empty array if there are no prop decorators', () => { - loadTestFiles([SIMPLE_CLASS_FILE]); - const bundle = makeTestBundleProgram(SIMPLE_CLASS_FILE.name); - const host = - createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, SIMPLE_CLASS_FILE.name, 'EmptyClass', isDesiredDeclaration); - const members = host.getMembersOfClass(classNode); - - expect(members).toEqual([]); - }); - - it('should not process decorated properties in `propDecorators` if it is not an object literal', - () => { - loadTestFiles([INVALID_PROP_DECORATORS_FILE]); - const bundle = makeTestBundleProgram(INVALID_PROP_DECORATORS_FILE.name); - const host = - createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, INVALID_PROP_DECORATORS_FILE.name, 'NotObjectLiteral', - isDesiredDeclaration); - const members = host.getMembersOfClass(classNode); - - expect(members.map(member => member.name)).not.toContain('prop'); - }); - - it('should ignore prop decorator elements that are not object literals', () => { - loadTestFiles([INVALID_PROP_DECORATORS_FILE]); - const bundle = makeTestBundleProgram(INVALID_PROP_DECORATORS_FILE.name); - const host = - createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, INVALID_PROP_DECORATORS_FILE.name, 'NotObjectLiteralProp', - isDesiredDeclaration); - const members = host.getMembersOfClass(classNode); - const prop = members.find(m => m.name === 'prop')!; - const decorators = prop.decorators!; - - expect(decorators.length).toBe(1); - expect(decorators[0]).toEqual(jasmine.objectContaining({name: 'Directive'})); - }); - - it('should ignore prop decorator elements that have no `type` property', () => { - loadTestFiles([INVALID_PROP_DECORATORS_FILE]); - const bundle = makeTestBundleProgram(INVALID_PROP_DECORATORS_FILE.name); - const host = - createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, INVALID_PROP_DECORATORS_FILE.name, 'NoTypeProperty', - isDesiredDeclaration); - const members = host.getMembersOfClass(classNode); - const prop = members.find(m => m.name === 'prop')!; - const decorators = prop.decorators!; - - expect(decorators.length).toBe(1); - expect(decorators[0]).toEqual(jasmine.objectContaining({name: 'Directive'})); - }); - }); - - it('should ignore prop decorator elements whose `type` value is not an identifier', - () => { - loadTestFiles([INVALID_PROP_DECORATORS_FILE]); - const bundle = makeTestBundleProgram(INVALID_PROP_DECORATORS_FILE.name); - const host = - createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, INVALID_PROP_DECORATORS_FILE.name, 'NotIdentifier', - isDesiredDeclaration); - const members = host.getMembersOfClass(classNode); - const prop = members.find(m => m.name === 'prop')!; - const decorators = prop.decorators!; - - expect(decorators.length).toBe(1); - expect(decorators[0]).toEqual(jasmine.objectContaining({name: 'Directive'})); - }); - - it('should have import information on decorators', () => { - loadTestFiles([SOME_DIRECTIVE_FILE]); - const bundle = makeTestBundleProgram(SOME_DIRECTIVE_FILE.name); - const host = createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); - - const classNode = getDeclaration( - bundle.program, SOME_DIRECTIVE_FILE.name, 'SomeDirective', isDesiredDeclaration); - const decorators = host.getDecoratorsOfDeclaration(classNode)!; - - expect(decorators.length).toEqual(1); - expect(decorators[0].import).toEqual({name: 'Directive', from: '@angular/core'}); - }); - - describe('(returned prop decorators `args`)', () => { - it('should be an empty array if prop decorator has no `args` property', () => { - loadTestFiles([INVALID_PROP_DECORATOR_ARGS_FILE]); - const bundle = makeTestBundleProgram(INVALID_PROP_DECORATOR_ARGS_FILE.name); - const host = - createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, INVALID_PROP_DECORATOR_ARGS_FILE.name, 'NoArgsProperty', - isDesiredDeclaration); - const members = host.getMembersOfClass(classNode); - const prop = members.find(m => m.name === 'prop')!; - const decorators = prop.decorators!; - - expect(decorators.length).toBe(1); - expect(decorators[0].name).toBe('Input'); - expect(decorators[0].args).toEqual([]); - }); - - it('should be an empty array if prop decorator\'s `args` has no property assignment', - () => { - loadTestFiles([INVALID_PROP_DECORATOR_ARGS_FILE]); - const bundle = makeTestBundleProgram(INVALID_PROP_DECORATOR_ARGS_FILE.name); - const host = - createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, INVALID_PROP_DECORATOR_ARGS_FILE.name, 'NoPropertyAssignment', - isDesiredDeclaration); - const members = host.getMembersOfClass(classNode); - const prop = members.find(m => m.name === 'prop')!; - const decorators = prop.decorators!; - - expect(decorators.length).toBe(1); - expect(decorators[0].name).toBe('Input'); - expect(decorators[0].args).toEqual([]); - }); - - it('should be an empty array if `args` property value is not an array literal', () => { - loadTestFiles([INVALID_PROP_DECORATOR_ARGS_FILE]); - const bundle = makeTestBundleProgram(INVALID_PROP_DECORATOR_ARGS_FILE.name); - const host = - createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, INVALID_PROP_DECORATOR_ARGS_FILE.name, 'NotArrayLiteral', - isDesiredDeclaration); - const members = host.getMembersOfClass(classNode); - const prop = members.find(m => m.name === 'prop')!; - const decorators = prop.decorators!; - - expect(decorators.length).toBe(1); - expect(decorators[0].name).toBe('Input'); - expect(decorators[0].args).toEqual([]); - }); - }); - - describe('getConstructorParameters', () => { - it('should retain imported name for type value references for decorated constructor parameter types', - () => { - const files = [ - { - name: _('/node_modules/shared-lib/foo.d.ts'), - contents: ` - declare class Foo {} - export {Foo as Bar}; - `, - }, - { - name: _('/node_modules/shared-lib/index.d.ts'), - contents: ` - export {Bar as Baz} from './foo'; - `, - }, - { - name: _('/local.js'), - contents: ` - (function (global, factory) { - typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : - typeof define === 'function' && define.amd ? define('local', ['exports'], factory) : - (factory(global.local)); - }(this, (function (exports) { 'use strict'; - var Internal = (function() { - function Internal() { - } - return Internal; - }()); - exports.External = Internal; - }))); - ` - }, - { - name: _('/main.js'), - contents: ` - (function (global, factory) { - typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('shared-lib'), require('./local')) : - typeof define === 'function' && define.amd ? define('main', ['exports', 'shared-lib', './local'], factory) : - (factory(global.main, global.shared, global.local)); - }(this, (function (exports, shared, local) { 'use strict'; - ${expDecl('SameFile')} = (function() { - function SameFile() { - } - return SameFile; - }()); - ${varExp('SameFile')} - - ${expDecl('SomeClass')} = (function() { - function SomeClass(arg1, arg2, arg3) {} - return SomeClass; - }()); - ${expDecl('SomeClass', true)}.ctorParameters = function() { return [{ type: shared.Baz }, { type: local.External }, { type: SameFile }]; }; - ${varExp('SomeClass')} - }))); - `, - }, - ]; - - loadTestFiles(files); - const bundle = makeTestBundleProgram(_('/main.js')); - const host = - createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, _('/main.js'), 'SomeClass', isNamedFunctionDeclaration); - - const parameters = host.getConstructorParameters(classNode)!; - - expect(parameters.map(p => p.name)).toEqual(['arg1', 'arg2', 'arg3']); - expectTypeValueReferencesForParameters( - parameters, ['Baz', 'External', 'SameFile'], ['shared-lib', './local', null]); - }); - - it('should find the decorated constructor parameters', () => { - loadTestFiles([SOME_DIRECTIVE_FILE]); - const bundle = makeTestBundleProgram(SOME_DIRECTIVE_FILE.name); - const host = - createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, SOME_DIRECTIVE_FILE.name, 'SomeDirective', isDesiredDeclaration); - const parameters = host.getConstructorParameters(classNode); - - expect(parameters).toBeDefined(); - expect(parameters!.map(parameter => parameter.name)).toEqual([ - '_viewContainer', '_template', 'injected' - ]); - expectTypeValueReferencesForParameters(parameters!, [ - 'ViewContainerRef', - 'TemplateRef', - null, - ]); - }); - - it('should find the decorated constructor parameters at the top level', () => { - loadTestFiles([TOPLEVEL_DECORATORS_FILE]); - const bundle = makeTestBundleProgram(TOPLEVEL_DECORATORS_FILE.name); - const host = - createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, TOPLEVEL_DECORATORS_FILE.name, 'SomeDirective', - isDesiredDeclaration); - const parameters = host.getConstructorParameters(classNode); - - expect(parameters).toBeDefined(); - expect(parameters!.map(parameter => parameter.name)).toEqual([ - '_viewContainer', '_template', 'injected' - ]); - expectTypeValueReferencesForParameters(parameters!, [ - 'ViewContainerRef', - 'TemplateRef', - null, - ]); - }); - - it('should accept `ctorParameters` as an array', () => { - loadTestFiles([CTOR_DECORATORS_ARRAY_FILE]); - const bundle = makeTestBundleProgram(CTOR_DECORATORS_ARRAY_FILE.name); - const host = - createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, CTOR_DECORATORS_ARRAY_FILE.name, 'CtorDecoratedAsArray', - isDesiredDeclaration); - const parameters = host.getConstructorParameters(classNode)!; - - expect(parameters).toBeDefined(); - expect(parameters.map(parameter => parameter.name)).toEqual(['arg1']); - expectTypeValueReferencesForParameters(parameters, ['ParamType']); - }); - - it('should throw if the symbol is not a class', () => { - loadTestFiles([FOO_FUNCTION_FILE]); - const bundle = makeTestBundleProgram(FOO_FUNCTION_FILE.name); - const host = - createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); - const functionNode = getDeclaration( - bundle.program, FOO_FUNCTION_FILE.name, 'foo', isNamedFunctionDeclaration); - expect(() => { - host.getConstructorParameters(functionNode); - }) - .toThrowError( - 'Attempted to get constructor parameters of a non-class: "function foo() {}"'); - }); - - // In ES5 there is no such thing as a constructor-less class - // it('should return `null` if there is no constructor', () => { }); - - it('should return an array even if there are no decorators', () => { - loadTestFiles([SIMPLE_CLASS_FILE]); - const bundle = makeTestBundleProgram(SIMPLE_CLASS_FILE.name); - const host = - createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, SIMPLE_CLASS_FILE.name, 'NoDecoratorConstructorClass', - isDesiredDeclaration); - const parameters = host.getConstructorParameters(classNode); - - expect(parameters).toEqual(jasmine.any(Array)); - expect(parameters!.length).toEqual(1); - expect(parameters![0].name).toEqual('foo'); - expect(parameters![0].decorators).toBe(null); - }); - - it('should return an empty array if there are no constructor parameters', () => { - loadTestFiles([INVALID_CTOR_DECORATORS_FILE]); - const bundle = makeTestBundleProgram(INVALID_CTOR_DECORATORS_FILE.name); - const host = - createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, INVALID_CTOR_DECORATORS_FILE.name, 'NoParameters', - isDesiredDeclaration); - const parameters = host.getConstructorParameters(classNode); - - expect(parameters).toEqual([]); - }); - - // In ES5 there are no arrow functions - // it('should ignore `ctorParameters` if it is an arrow function', () => { }); - - it('should ignore `ctorParameters` if it does not return an array literal', () => { - loadTestFiles([INVALID_CTOR_DECORATORS_FILE]); - const bundle = makeTestBundleProgram(INVALID_CTOR_DECORATORS_FILE.name); - const host = - createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, INVALID_CTOR_DECORATORS_FILE.name, 'NotArrayLiteral', - isDesiredDeclaration); - const parameters = host.getConstructorParameters(classNode); - - expect(parameters!.length).toBe(1); - expect(parameters![0]).toEqual(jasmine.objectContaining({ - name: 'arg1', - decorators: null, - })); - }); - - describe('(returned parameters `decorators`)', () => { - it('should ignore param decorator elements that are not object literals', () => { - loadTestFiles([INVALID_CTOR_DECORATORS_FILE]); - const bundle = makeTestBundleProgram(INVALID_CTOR_DECORATORS_FILE.name); - const host = - createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, INVALID_CTOR_DECORATORS_FILE.name, 'NotObjectLiteral', - isDesiredDeclaration); - const parameters = host.getConstructorParameters(classNode); - - expect(parameters!.length).toBe(2); - expect(parameters![0]).toEqual(jasmine.objectContaining({ - name: 'arg1', - decorators: null, - })); - expect(parameters![1]).toEqual(jasmine.objectContaining({ - name: 'arg2', - decorators: jasmine.any(Array) as any - })); - }); - - it('should ignore param decorator elements that have no `type` property', () => { - loadTestFiles([INVALID_CTOR_DECORATORS_FILE]); - const bundle = makeTestBundleProgram(INVALID_CTOR_DECORATORS_FILE.name); - const host = - createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, INVALID_CTOR_DECORATORS_FILE.name, 'NoTypeProperty', - isDesiredDeclaration); - const parameters = host.getConstructorParameters(classNode); - const decorators = parameters![0].decorators!; - - expect(decorators.length).toBe(1); - expect(decorators[0]).toEqual(jasmine.objectContaining({name: 'Inject'})); - }); - - it('should ignore param decorator elements whose `type` value is not an identifier', - () => { - loadTestFiles([INVALID_CTOR_DECORATORS_FILE]); - const bundle = makeTestBundleProgram(INVALID_CTOR_DECORATORS_FILE.name); - const host = - createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, INVALID_CTOR_DECORATORS_FILE.name, 'NotIdentifier', - isDesiredDeclaration); - const parameters = host.getConstructorParameters(classNode); - const decorators = parameters![0].decorators!; - - expect(decorators.length).toBe(1); - expect(decorators[0]).toEqual(jasmine.objectContaining({name: 'Inject'})); - }); - - it('should use `getImportOfIdentifier()` to retrieve import info', () => { - loadTestFiles([SOME_DIRECTIVE_FILE]); - const bundle = makeTestBundleProgram(SOME_DIRECTIVE_FILE.name); - const host = - createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, SOME_DIRECTIVE_FILE.name, 'SomeDirective', - isDesiredDeclaration); - const mockImportInfo: Import = {from: '@angular/core', name: 'Directive'}; - const spy = spyOn(UmdReflectionHost.prototype, 'getImportOfIdentifier') - .and.returnValue(mockImportInfo); - - const parameters = host.getConstructorParameters(classNode); - const decorators = parameters![2].decorators!; - - expect(decorators.length).toEqual(1); - expect(decorators[0].import).toBe(mockImportInfo); - }); - }); - - describe('(returned parameters `decorators.args`)', () => { - it('should be an empty array if param decorator has no `args` property', () => { - loadTestFiles([INVALID_CTOR_DECORATOR_ARGS_FILE]); - const bundle = makeTestBundleProgram(INVALID_CTOR_DECORATOR_ARGS_FILE.name); - const host = - createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, INVALID_CTOR_DECORATOR_ARGS_FILE.name, 'NoArgsProperty', - isDesiredDeclaration); - const parameters = host.getConstructorParameters(classNode); - expect(parameters!.length).toBe(1); - const decorators = parameters![0].decorators!; - - expect(decorators.length).toBe(1); - expect(decorators[0].name).toBe('Inject'); - expect(decorators[0].args).toEqual([]); - }); - - it('should be an empty array if param decorator\'s `args` has no property assignment', - () => { - loadTestFiles([INVALID_CTOR_DECORATOR_ARGS_FILE]); - const bundle = makeTestBundleProgram(INVALID_CTOR_DECORATOR_ARGS_FILE.name); - const host = - createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, INVALID_CTOR_DECORATOR_ARGS_FILE.name, - 'NoPropertyAssignment', isDesiredDeclaration); - const parameters = host.getConstructorParameters(classNode); - const decorators = parameters![0].decorators!; - - expect(decorators.length).toBe(1); - expect(decorators[0].name).toBe('Inject'); - expect(decorators[0].args).toEqual([]); - }); - - it('should be an empty array if `args` property value is not an array literal', - () => { - loadTestFiles([INVALID_CTOR_DECORATOR_ARGS_FILE]); - const bundle = makeTestBundleProgram(INVALID_CTOR_DECORATOR_ARGS_FILE.name); - const host = - createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, INVALID_CTOR_DECORATOR_ARGS_FILE.name, 'NotArrayLiteral', - isDesiredDeclaration); - const parameters = host.getConstructorParameters(classNode); - const decorators = parameters![0].decorators!; - - expect(decorators.length).toBe(1); - expect(decorators[0].name).toBe('Inject'); - expect(decorators[0].args).toEqual([]); - }); - }); - - function getConstructorParameters( - constructor: string, - mode: 'inlined'|'inlined_with_suffix'|'imported' = 'imported') { - let fileHeaderWithUmd = ''; - - switch (mode) { - case 'imported': - fileHeaderWithUmd = ` - (function (global, factory) { - typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('tslib')) : - typeof define === 'function' && define.amd ? define('test', ['exports', 'tslib'], factory) : - (factory(global.test, global.tslib)); - }(this, (function (exports, tslib) { 'use strict'; - `; - break; - case 'inlined': - fileHeaderWithUmd = ` - (function (global, factory) { - typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : - typeof define === 'function' && define.amd ? define('test', ['exports'], factory) : - (factory(global.test)); - }(this, (function (exports) { 'use strict'; - - var __spread = (this && this.__spread) || function (...args) { /* ... */ } - var __spreadArray = (this && this.__spreadArray) || function (...args) { /* ... */ } - var __read = (this && this.__read) || function (...args) { /* ... */ } - `; - break; - case 'inlined_with_suffix': - fileHeaderWithUmd = ` - (function (global, factory) { - typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : - typeof define === 'function' && define.amd ? define('test', ['exports'], factory) : - (factory(global.test)); - }(this, (function (exports) { 'use strict'; - - var __spread$1 = (this && this.__spread$1) || function (...args) { /* ... */ } - var __spreadArray$1 = (this && this.__spreadArray$1) || function (...args) { /* ... */ } - var __read$2 = (this && this.__read$2) || function (...args) { /* ... */ } - `; - break; - } - - const file = { - name: _('/synthesized_constructors.js'), - contents: ` - ${fileHeaderWithUmd} - var TestClass = /** @class */ (function (_super) { - __extends(TestClass, _super); - ${constructor} - return TestClass; - }(null)); - - exports.TestClass = TestClass; - }))); - `, - }; - - loadTestFiles([file]); - const bundle = makeTestBundleProgram(file.name); - const host = - createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, file.name, 'TestClass', isNamedVariableDeclaration); - return host.getConstructorParameters(classNode); - } - - describe('TS -> ES5: synthesized constructors', () => { - it('recognizes _this assignment from super call', () => { - const parameters = getConstructorParameters(` - function TestClass() { - var _this = _super !== null && _super.apply(this, arguments) || this; - _this.synthesizedProperty = null; - return _this; - } - `); - - expect(parameters).toBeNull(); - }); - - it('recognizes super call as return statement', () => { - const parameters = getConstructorParameters(` - function TestClass() { - return _super !== null && _super.apply(this, arguments) || this; - } - `); - - expect(parameters).toBeNull(); - }); - - it('handles the case where a unique name was generated for _super or _this', () => { - const parameters = getConstructorParameters(` - function TestClass() { - var _this_1 = _super_1 !== null && _super_1.apply(this, arguments) || this; - _this_1._this = null; - _this_1._super = null; - return _this_1; - } - `); - - expect(parameters).toBeNull(); - }); - - it('does not consider constructors with parameters as synthesized', () => { - const parameters = getConstructorParameters(` - function TestClass(arg) { - return _super !== null && _super.apply(this, arguments) || this; - } - `); - - expect(parameters!.length).toBe(1); - }); - - it('does not consider manual super calls as synthesized', () => { - const parameters = getConstructorParameters(` - function TestClass() { - return _super.call(this) || this; - } - `); - - expect(parameters!.length).toBe(0); - }); - - it('does not consider empty constructors as synthesized', () => { - const parameters = getConstructorParameters(`function TestClass() {}`); - expect(parameters!.length).toBe(0); - }); - }); - - // See: https://github.com/angular/angular/issues/38453. - describe('ES2015 -> ES5: synthesized constructors through TSC downleveling', () => { - it('recognizes delegate super call using inline spread helper', () => { - const parameters = getConstructorParameters( - ` - function TestClass() { - return _super.apply(this, __spread(arguments)) || this; - }`, - 'inlined'); - - expect(parameters).toBeNull(); - }); - - it('recognizes delegate super call using inline spreadArray helper', () => { - const parameters = getConstructorParameters( - ` - function TestClass() { - return _super.apply(this, __spreadArray([], __read(arguments))) || this; - }`, - 'inlined'); - - expect(parameters).toBeNull(); - }); - - it('recognizes delegate super call using inline spread helper with suffix', () => { - const parameters = getConstructorParameters( - ` - function TestClass() { - return _super.apply(this, __spread$1(arguments)) || this; - }`, - 'inlined_with_suffix'); - - expect(parameters).toBeNull(); - }); - - it('recognizes delegate super call using inline spreadArray helper with suffix', - () => { - const parameters = getConstructorParameters( - ` - function TestClass() { - return _super.apply(this, __spreadArray$1([], __read$2(arguments))) || this; - }`, - 'inlined_with_suffix'); - - expect(parameters).toBeNull(); - }); - - it('recognizes delegate super call using imported spread helper', () => { - const parameters = getConstructorParameters( - ` - function TestClass() { - return _super.apply(this, tslib_1.__spread(arguments)) || this; - }`, - 'imported'); - - expect(parameters).toBeNull(); - }); - - it('recognizes delegate super call using imported spreadArray helper', () => { - const parameters = getConstructorParameters( - ` - function TestClass() { - return _super.apply(this, tslib_1.__spreadArray([], tslib.__read(arguments))) || this; - }`, - 'imported'); - - expect(parameters).toBeNull(); - }); - - describe('with class member assignment', () => { - it('recognizes delegate super call using inline spread helper', () => { - const parameters = getConstructorParameters( - ` - function TestClass() { - var _this = _super.apply(this, __spread(arguments)) || this; - _this.synthesizedProperty = null; - return _this; - }`, - 'inlined'); - - expect(parameters).toBeNull(); - }); - - it('recognizes delegate super call using inline spreadArray helper', () => { - const parameters = getConstructorParameters( - ` - function TestClass() { - var _this = _super.apply(this, __spreadArray([], __read(arguments))) || this; - _this.synthesizedProperty = null; - return _this; - }`, - 'inlined'); - - expect(parameters).toBeNull(); - }); - - it('recognizes delegate super call using inline spread helper with suffix', () => { - const parameters = getConstructorParameters( - ` - function TestClass() { - var _this = _super.apply(this, __spread$1(arguments)) || this; - _this.synthesizedProperty = null; - return _this; - }`, - 'inlined_with_suffix'); - - expect(parameters).toBeNull(); - }); - - it('recognizes delegate super call using inline spreadArray helper with suffix', - () => { - const parameters = getConstructorParameters( - ` - function TestClass() { - var _this = _super.apply(this, __spreadArray$1([], __read$2(arguments))) || this; - _this.synthesizedProperty = null; - return _this; - }`, - 'inlined_with_suffix'); - - expect(parameters).toBeNull(); - }); - - it('recognizes delegate super call using imported spread helper', () => { - const parameters = getConstructorParameters( - ` - function TestClass() { - var _this = _super.apply(this, tslib_1.__spread(arguments)) || this; - _this.synthesizedProperty = null; - return _this; - }`, - 'imported'); - - expect(parameters).toBeNull(); - }); - - it('recognizes delegate super call using imported spreadArray helper', () => { - const parameters = getConstructorParameters( - ` - function TestClass() { - var _this = _super.apply(this, tslib_1.__spreadArray([], tslib.__read(arguments))) || this; - _this.synthesizedProperty = null; - return _this; - }`, - 'imported'); - - expect(parameters).toBeNull(); - }); - }); - - it('handles the case where a unique name was generated for _super or _this', () => { - const parameters = getConstructorParameters( - ` - function TestClass() { - var _this_1 = _super_1.apply(this, __spread(arguments)) || this; - _this_1._this = null; - _this_1._super = null; - return _this_1; - }`, - 'inlined'); - - expect(parameters).toBeNull(); - }); - - it('does not consider constructors with parameters as synthesized', () => { - const parameters = getConstructorParameters( - ` - function TestClass(arg) { - return _super.apply(this, __spread(arguments)) || this; - }`, - 'inlined'); - - expect(parameters!.length).toBe(1); - }); - }); - }); - - describe('getDefinitionOfFunction()', () => { - it('should return an object describing the function declaration passed as an argument', - () => { - loadTestFiles([FUNCTION_BODY_FILE]); - const bundle = makeTestBundleProgram(FUNCTION_BODY_FILE.name); - const host = - createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); - - const fooNode = getDeclaration( - bundle.program, FUNCTION_BODY_FILE.name, 'foo', isNamedFunctionDeclaration)!; - const fooDef = host.getDefinitionOfFunction(fooNode)!; - expect(fooDef.node).toBe(fooNode); - expect(fooDef.body!.length).toEqual(1); - expect(fooDef.body![0].getText()).toEqual(`return x;`); - expect(fooDef.parameters.length).toEqual(1); - expect(fooDef.parameters[0].name).toEqual('x'); - expect(fooDef.parameters[0].initializer).toBe(null); - - const barNode = getDeclaration( - bundle.program, FUNCTION_BODY_FILE.name, 'bar', isNamedFunctionDeclaration)!; - const barDef = host.getDefinitionOfFunction(barNode)!; - expect(barDef.node).toBe(barNode); - expect(barDef.body!.length).toEqual(1); - expect(ts.isReturnStatement(barDef.body![0])).toBeTruthy(); - expect(barDef.body![0].getText()).toEqual(`return x + y;`); - expect(barDef.parameters.length).toEqual(2); - expect(barDef.parameters[0].name).toEqual('x'); - expect(fooDef.parameters[0].initializer).toBe(null); - expect(barDef.parameters[1].name).toEqual('y'); - expect(barDef.parameters[1].initializer!.getText()).toEqual('42'); - - const bazNode = getDeclaration( - bundle.program, FUNCTION_BODY_FILE.name, 'baz', isNamedFunctionDeclaration)!; - const bazDef = host.getDefinitionOfFunction(bazNode)!; - expect(bazDef.node).toBe(bazNode); - expect(bazDef.body!.length).toEqual(3); - expect(bazDef.parameters.length).toEqual(1); - expect(bazDef.parameters[0].name).toEqual('x'); - expect(bazDef.parameters[0].initializer).toBe(null); - - const quxNode = getDeclaration( - bundle.program, FUNCTION_BODY_FILE.name, 'qux', isNamedFunctionDeclaration)!; - const quxDef = host.getDefinitionOfFunction(quxNode)!; - expect(quxDef.node).toBe(quxNode); - expect(quxDef.body!.length).toEqual(2); - expect(quxDef.parameters.length).toEqual(1); - expect(quxDef.parameters[0].name).toEqual('x'); - expect(quxDef.parameters[0].initializer).toBe(null); - }); - }); - - describe('getImportOfIdentifier', () => { - it('should find the import of an identifier', () => { - loadTestFiles(IMPORTS_FILES); - const bundle = makeTestBundleProgram(_('/index.js')); - const host = - createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); - const variableNode = - getDeclaration(bundle.program, _('/file_b.js'), 'b', isNamedVariableDeclaration); - const identifier = (variableNode.initializer && - ts.isPropertyAccessExpression(variableNode.initializer) && - ts.isIdentifier(variableNode.initializer.name)) ? - variableNode.initializer.name : - null; - - expect(identifier).not.toBe(null); - const importOfIdent = host.getImportOfIdentifier(identifier!); - expect(importOfIdent).toEqual({name: 'a', from: './file_a'}); - }); - - it('should return null if the identifier was not imported', () => { - loadTestFiles(IMPORTS_FILES); - const bundle = makeTestBundleProgram(_('/index.js')); - const host = - createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); - const variableNode = - getDeclaration(bundle.program, _('/file_b.js'), 'd', isNamedVariableDeclaration); - const importOfIdent = - host.getImportOfIdentifier(variableNode.initializer as ts.Identifier); - - expect(importOfIdent).toBeNull(); - }); - - it('should handle factory functions not wrapped in parentheses', () => { - loadTestFiles(IMPORTS_FILES); - const bundle = makeTestBundleProgram(_('/index.js')); - const host = - createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); - const variableNode = - getDeclaration(bundle.program, _('/file_c.js'), 'c', isNamedVariableDeclaration); - const identifier = (variableNode.initializer && - ts.isPropertyAccessExpression(variableNode.initializer) && - ts.isIdentifier(variableNode.initializer.name)) ? - variableNode.initializer.name : - null; - - expect(identifier).not.toBe(null); - const importOfIdent = host.getImportOfIdentifier(identifier!); - expect(importOfIdent).toEqual({name: 'a', from: './file_a'}); - }); - }); - - describe('getDeclarationOfIdentifier', () => { - // Helpers - const createTestForTsHelper = - (host: NgccReflectionHost, factoryFn: ts.FunctionExpression, - getHelperDeclaration: (factoryFn: ts.FunctionExpression, name: string) => - ts.Declaration) => - (varName: string, helperName: string, knownAs: KnownDeclaration, - viaModule: string|null = null) => { - const node = getVariableDeclaration(factoryFn, varName); - const helperIdentifier = getIdentifierFromCallExpression(node); - const helperDeclaration = host.getDeclarationOfIdentifier(helperIdentifier); - - expect(helperDeclaration).toEqual({ - kind: DeclarationKind.Concrete, - known: knownAs, - node: getHelperDeclaration(factoryFn, helperName), - viaModule, - identity: null, - }); - }; - - const getFunctionDeclaration = (factoryFn: ts.FunctionExpression, name: string) => - factoryFn.body.statements.filter(ts.isFunctionDeclaration) - .find(decl => (decl.name !== undefined) && (decl.name.text === name))!; - - const getIdentifierFromCallExpression = (decl: ts.VariableDeclaration) => { - if (decl.initializer !== undefined && ts.isCallExpression(decl.initializer)) { - const expr = decl.initializer.expression; - if (ts.isIdentifier(expr)) return expr; - if (ts.isPropertyAccessExpression(expr) && ts.isIdentifier(expr.name)) - return expr.name; - } - throw new Error(`Unable to extract identifier from declaration '${decl.getText()}'.`); - }; - - const getVariableDeclaration = (factoryFn: ts.FunctionExpression, name: string) => - factoryFn.body.statements.filter(ts.isVariableStatement) - .map(stmt => stmt.declarationList.declarations[0]) - .find(decl => ts.isIdentifier(decl.name) && (decl.name.text === name))!; - - it('should return the declaration of a locally defined identifier', () => { - loadTestFiles([SOME_DIRECTIVE_FILE]); - const bundle = makeTestBundleProgram(SOME_DIRECTIVE_FILE.name); - const host = - createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, SOME_DIRECTIVE_FILE.name, 'SomeDirective', isDesiredDeclaration); - const ctrDecorators = host.getConstructorParameters(classNode)!; - const identifierOfViewContainerRef = (ctrDecorators[0].typeValueReference! as { - kind: TypeValueReferenceKind.LOCAL, - expression: ts.Identifier, - defaultImportStatement: null, - }).expression; - - const expectedDeclarationNode = getDeclaration( - bundle.program, SOME_DIRECTIVE_FILE.name, 'ViewContainerRef', - isNamedVariableDeclaration); - const actualDeclaration = - host.getDeclarationOfIdentifier(identifierOfViewContainerRef); - expect(actualDeclaration).not.toBe(null); - expect(actualDeclaration!.node).toBe(expectedDeclarationNode); - expect(actualDeclaration!.viaModule).toBe(null); - expect((actualDeclaration as ConcreteDeclaration).identity).toBe(null); - }); - - it('should return the correct declaration for an outer alias identifier', () => { - const PROGRAM_FILE: TestFile = { - name: _('/test.js'), - contents: ` - (function (global, factory) { - typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : - typeof define === 'function' && define.amd ? define('test', ['exports'], factory) : - (factory(global.test)); - }(this, (function (exports,module) { 'use strict'; - ${expDecl('AliasedClass')} = AliasedClass_1 = (function () { - function InnerClass() { - } - return InnerClass; - }()); - var AliasedClass_1; - }))); - `, - }; - - loadTestFiles([PROGRAM_FILE]); - const bundle = makeTestBundleProgram(PROGRAM_FILE.name); - const host = - createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); - - const expectedDeclaration = getDeclaration( - bundle.program, PROGRAM_FILE.name, 'AliasedClass', isDesiredDeclaration); - // Grab the `AliasedClass_1` identifier (which is an alias for `AliasedClass`). - const aliasIdentifier = getDeclaration( - bundle.program, PROGRAM_FILE.name, 'AliasedClass_1', isNamedVariableDeclaration); - const actualDeclaration = host.getDeclarationOfIdentifier(aliasIdentifier.name); - - expect(aliasIdentifier.getText()).toBe('AliasedClass_1'); - expect(actualDeclaration).not.toBe(null); - expect(actualDeclaration!.node).toBe(expectedDeclaration); - }); - - it('should return the source-file of an import namespace', () => { - loadFakeCore(getFileSystem()); - loadTestFiles([SOME_DIRECTIVE_FILE]); - const bundle = makeTestBundleProgram(SOME_DIRECTIVE_FILE.name); - const host = - createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, SOME_DIRECTIVE_FILE.name, 'SomeDirective', isDesiredDeclaration); - const classDecorators = host.getDecoratorsOfDeclaration(classNode)!; - const identifierOfDirective = - (((classDecorators[0].node as ts.ObjectLiteralExpression).properties[0] as - ts.PropertyAssignment) - .initializer as ts.PropertyAccessExpression) - .expression as ts.Identifier; - - const expectedDeclarationNode = - getSourceFileOrError(bundle.program, _('/node_modules/@angular/core/index.d.ts')); - const actualDeclaration = host.getDeclarationOfIdentifier(identifierOfDirective); - expect(actualDeclaration).not.toBe(null); - expect(actualDeclaration!.node).toBe(expectedDeclarationNode); - expect(actualDeclaration!.viaModule).toBe('@angular/core'); - }); - - it('should return the source file as the declaration of a standalone "exports" identifier', - () => { - loadTestFiles([EXPORTS_IDENTIFIERS_FILE]); - const bundle = makeTestBundleProgram(EXPORTS_IDENTIFIERS_FILE.name); - const host = - createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); - const xVar = getDeclaration( - bundle.program, EXPORTS_IDENTIFIERS_FILE.name, 'x', - isNamedVariableDeclaration); - const exportsIdentifier = xVar.initializer; - if (exportsIdentifier === undefined || !ts.isIdentifier(exportsIdentifier)) { - throw new Error('Bad test - unable to find `var x = exports;` statement'); - } - const exportsDeclaration = host.getDeclarationOfIdentifier(exportsIdentifier); - expect(exportsDeclaration).not.toBeNull(); - expect(exportsDeclaration!.node).toBe(bundle.file); - }); - - it('should return the source file as the declaration of "exports" in the LHS of a property access', - () => { - loadTestFiles([EXPORTS_IDENTIFIERS_FILE]); - const bundle = makeTestBundleProgram(EXPORTS_IDENTIFIERS_FILE.name); - const host = - createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); - const exportsStatement = walkForNode(bundle.file, isExportsStatement); - if (exportsStatement === undefined) { - throw new Error('Bad test - unable to find `exports.foo = 42;` statement'); - } - const exportsIdentifier = exportsStatement.expression.left.expression; - const exportsDeclaration = host.getDeclarationOfIdentifier(exportsIdentifier); - expect(exportsDeclaration).not.toBeNull(); - expect(exportsDeclaration!.node).toBe(bundle.file); - }); - - it('should return the source file as the declaration of "exports" in the LHS of a property access inside a local function', - () => { - loadTestFiles([EXPORTS_IDENTIFIERS_FILE]); - const bundle = makeTestBundleProgram(EXPORTS_IDENTIFIERS_FILE.name); - const host = - createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); - const simpleFn = getDeclaration( - bundle.program, EXPORTS_IDENTIFIERS_FILE.name, 'simpleFn', - ts.isFunctionDeclaration); - const exportsStatement = walkForNode(simpleFn, isExportsStatement); - if (exportsStatement === undefined) { - throw new Error('Bad test - unable to find `exports.foo = 42;` statement'); - } - const exportsIdentifier = exportsStatement.expression.left.expression; - const exportDeclaration = host.getDeclarationOfIdentifier(exportsIdentifier); - expect(exportDeclaration).not.toBeNull(); - expect(exportDeclaration!.node).toBe(bundle.file); - }); - - it('should return the variable declaration if a standalone "exports" is declared locally', - () => { - loadTestFiles([EXPORTS_IDENTIFIERS_FILE]); - const bundle = makeTestBundleProgram(EXPORTS_IDENTIFIERS_FILE.name); - const host = - createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); - const localVarFn = getDeclaration( - bundle.program, EXPORTS_IDENTIFIERS_FILE.name, 'localVar', - ts.isFunctionDeclaration); - const exportsVar = walkForNode(localVarFn, isNamedVariableDeclaration); - if (exportsVar === undefined) { - throw new Error('Bad test - unable to find `var exports = {}` statement'); - } - const exportsIdentifier = exportsVar.name; - if (exportsIdentifier === undefined || !ts.isIdentifier(exportsIdentifier)) { - throw new Error('Bad test - unable to find `var exports = {};` statement'); - } - const exportsDeclaration = host.getDeclarationOfIdentifier(exportsIdentifier); - expect(exportsDeclaration).not.toBeNull(); - expect(exportsDeclaration!.node).toBe(exportsVar); - }); - - it('should return the variable declaration of "exports" in the LHS of a property access, if it is declared locally', - () => { - loadTestFiles([EXPORTS_IDENTIFIERS_FILE]); - const bundle = makeTestBundleProgram(EXPORTS_IDENTIFIERS_FILE.name); - const host = - createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); - const localVarFn = getDeclaration( - bundle.program, EXPORTS_IDENTIFIERS_FILE.name, 'localVar', - ts.isFunctionDeclaration); - const exportsVar = walkForNode(localVarFn, isNamedVariableDeclaration); - const exportsStatement = walkForNode(localVarFn, isExportsStatement); - if (exportsStatement === undefined) { - throw new Error('Bad test - unable to find `exports.foo = 42;` statement'); - } - const exportsIdentifier = exportsStatement.expression.left.expression; - const exportDeclaration = host.getDeclarationOfIdentifier(exportsIdentifier); - expect(exportDeclaration).not.toBeNull(); - expect(exportDeclaration?.node).toBe(exportsVar); - }); - - it('should return the variable declaration of "exports" in the LHS of a property access, if it is a local function parameter', - () => { - loadTestFiles([EXPORTS_IDENTIFIERS_FILE]); - const bundle = makeTestBundleProgram(EXPORTS_IDENTIFIERS_FILE.name); - const host = - createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); - const exportsArgFn = getDeclaration( - bundle.program, EXPORTS_IDENTIFIERS_FILE.name, 'exportsArg', - ts.isFunctionDeclaration); - const exportsVar = exportsArgFn.parameters[0]; - const exportsStatement = walkForNode(exportsArgFn, isExportsStatement); - if (exportsStatement === undefined) { - throw new Error('Bad test - unable to find `exports.foo = 42;` statement'); - } - const exportsIdentifier = exportsStatement.expression.left.expression; - const exportDeclaration = host.getDeclarationOfIdentifier(exportsIdentifier); - expect(exportDeclaration).not.toBeNull(); - expect(exportDeclaration?.node).toBe(exportsVar); - }); - - it('should recognize TypeScript helpers (as function declarations)', () => { - const file: TestFile = { - name: _('/test.js'), - contents: ` - (function (global, factory) { - typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : - typeof define === 'function' && define.amd ? define('test', ['exports'], factory) : - (factory(global.test)); - }(this, (function (exports) { 'use strict'; - function __assign(t, ...sources) { /* ... */ } - function __spread(...args) { /* ... */ } - function __spreadArrays(...args) { /* ... */ } - function __spreadArray(to, from) { /* ... */ } - function __read(o) { /* ... */ } - - var a = __assign({foo: 'bar'}, {baz: 'qux'}); - var b = __spread(['foo', 'bar'], ['baz', 'qux']); - var c = __spreadArrays(['foo', 'bar'], ['baz', 'qux']); - var d = __spreadArray(['foo', 'bar'], ['baz', 'qux']); - var e = __read(['foo', 'bar']); - }))); - `, - }; - loadTestFiles([file]); - const bundle = makeTestBundleProgram(file.name); - const host = - createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); - const {factoryFn} = parseStatementForUmdModule( - getSourceFileOrError(bundle.program, file.name).statements[0])!; - - const testForHelper = createTestForTsHelper(host, factoryFn, getFunctionDeclaration); - - testForHelper('a', '__assign', KnownDeclaration.TsHelperAssign); - testForHelper('b', '__spread', KnownDeclaration.TsHelperSpread); - testForHelper('c', '__spreadArrays', KnownDeclaration.TsHelperSpreadArrays); - testForHelper('d', '__spreadArray', KnownDeclaration.TsHelperSpreadArray); - testForHelper('e', '__read', KnownDeclaration.TsHelperRead); - }); - - it('should recognize suffixed TypeScript helpers (as function declarations)', () => { - const file: TestFile = { - name: _('/test.js'), - contents: ` - (function (global, factory) { - typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : - typeof define === 'function' && define.amd ? define('test', ['exports'], factory) : - (factory(global.test)); - }(this, (function (exports) { 'use strict'; - function __assign$1(t, ...sources) { /* ... */ } - function __spread$2(...args) { /* ... */ } - function __spreadArrays$3(...args) { /* ... */ } - function __spreadArray$3(to, from) { /* ... */ } - function __read$3(o) { /* ... */ } - - var a = __assign$1({foo: 'bar'}, {baz: 'qux'}); - var b = __spread$2(['foo', 'bar'], ['baz', 'qux']); - var c = __spreadArrays$3(['foo', 'bar'], ['baz', 'qux']); - var d = __spreadArray$3(['foo', 'bar'], ['baz', 'qux']); - var e = __read$3(['foo', 'bar']); - }))); - `, - }; - loadTestFiles([file]); - const bundle = makeTestBundleProgram(file.name); - const host = - createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); - const {factoryFn} = parseStatementForUmdModule( - getSourceFileOrError(bundle.program, file.name).statements[0])!; - - const testForHelper = createTestForTsHelper(host, factoryFn, getFunctionDeclaration); - - testForHelper('a', '__assign$1', KnownDeclaration.TsHelperAssign); - testForHelper('b', '__spread$2', KnownDeclaration.TsHelperSpread); - testForHelper('c', '__spreadArrays$3', KnownDeclaration.TsHelperSpreadArrays); - testForHelper('d', '__spreadArray$3', KnownDeclaration.TsHelperSpreadArray); - testForHelper('e', '__read$3', KnownDeclaration.TsHelperRead); - }); - - it('should recognize TypeScript helpers (as variable declarations)', () => { - const file: TestFile = { - name: _('/test.js'), - contents: ` - (function (global, factory) { - typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : - typeof define === 'function' && define.amd ? define('test', ['exports'], factory) : - (factory(global.test)); - }(this, (function (exports) { 'use strict'; - var __assign = (this && this.__assign) || function (t, ...sources) { /* ... */ } - var __spread = (this && this.__spread) || function (...args) { /* ... */ } - var __spreadArrays = (this && this.__spreadArrays) || function (...args) { /* ... */ } - var __spreadArray = (this && this.__spreadArray) || function (to, from) { /* ... */ } - var __read = (this && this.__read) || function (o) { /* ... */ } - - var a = __assign({foo: 'bar'}, {baz: 'qux'}); - var b = __spread(['foo', 'bar'], ['baz', 'qux']); - var c = __spreadArrays(['foo', 'bar'], ['baz', 'qux']); - var d = __spreadArray(['foo', 'bar'], ['baz', 'qux']); - var e = __read(['foo', 'bar']); - }))); - `, - }; - loadTestFiles([file]); - const bundle = makeTestBundleProgram(file.name); - const host = - createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); - const {factoryFn} = parseStatementForUmdModule( - getSourceFileOrError(bundle.program, file.name).statements[0])!; - - const testForHelper = createTestForTsHelper(host, factoryFn, getVariableDeclaration); - - testForHelper('a', '__assign', KnownDeclaration.TsHelperAssign); - testForHelper('b', '__spread', KnownDeclaration.TsHelperSpread); - testForHelper('c', '__spreadArrays', KnownDeclaration.TsHelperSpreadArrays); - testForHelper('d', '__spreadArray', KnownDeclaration.TsHelperSpreadArray); - testForHelper('e', '__read', KnownDeclaration.TsHelperRead); - }); - - it('should recognize suffixed TypeScript helpers (as variable declarations)', () => { - const file: TestFile = { - name: _('/test.js'), - contents: ` - (function (global, factory) { - typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : - typeof define === 'function' && define.amd ? define('test', ['exports'], factory) : - (factory(global.test)); - }(this, (function (exports) { 'use strict'; - var __assign$1 = (this && this.__assign$1) || function (t, ...sources) { /* ... */ } - var __spread$2 = (this && this.__spread$2) || function (...args) { /* ... */ } - var __spreadArrays$3 = (this && this.__spreadArrays$3) || function (...args) { /* ... */ } - var __spreadArray$3 = (this && this.__spreadArray$3) || function (to, from) { /* ... */ } - var __read$3 = (this && this.__read$3) || function (o) { /* ... */ } - - var a = __assign$1({foo: 'bar'}, {baz: 'qux'}); - var b = __spread$2(['foo', 'bar'], ['baz', 'qux']); - var c = __spreadArrays$3(['foo', 'bar'], ['baz', 'qux']); - var d = __spreadArray$3(['foo', 'bar'], ['baz', 'qux']); - var e = __read$3(['foo', 'bar']); - }))); - `, - }; - loadTestFiles([file]); - const bundle = makeTestBundleProgram(file.name); - const host = - createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); - const {factoryFn} = parseStatementForUmdModule( - getSourceFileOrError(bundle.program, file.name).statements[0])!; - - const testForHelper = createTestForTsHelper(host, factoryFn, getVariableDeclaration); - - testForHelper('a', '__assign$1', KnownDeclaration.TsHelperAssign); - testForHelper('b', '__spread$2', KnownDeclaration.TsHelperSpread); - testForHelper('c', '__spreadArrays$3', KnownDeclaration.TsHelperSpreadArrays); - testForHelper('d', '__spreadArray$3', KnownDeclaration.TsHelperSpreadArray); - testForHelper('e', '__read$3', KnownDeclaration.TsHelperRead); - }); - - it('should recognize imported TypeScript helpers', () => { - const files: TestFile[] = [ - { - name: _('/test.js'), - contents: ` - (function (global, factory) { - typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('tslib')) : - typeof define === 'function' && define.amd ? define('test', ['exports', 'tslib'], factory) : - (factory(global.test, global.tslib)); - }(this, (function (exports, tslib_1) { 'use strict'; - var a = tslib_1.__assign({foo: 'bar'}, {baz: 'qux'}); - var b = tslib_1.__spread(['foo', 'bar'], ['baz', 'qux']); - var c = tslib_1.__spreadArrays(['foo', 'bar'], ['baz', 'qux']); - var d = tslib_1.__spreadArray(['foo', 'bar'], ['baz', 'qux']); - var e = tslib_1.__read(['foo', 'bar']); - }))); - `, - }, - { - name: _('/node_modules/tslib/index.d.ts'), - contents: ` - export declare function __assign(t: any, ...sources: any[]): any; - export declare function __spread(...args: any[][]): any[]; - export declare function __spreadArrays(...args: any[][]): any[]; - export declare function __spreadArray(to: any[], from: any[]): any[]; - export declare function __read(o: any, n?: number): any[]; - `, - }, - ]; - loadTestFiles(files); - - const [testFile, tslibFile] = files; - const bundle = makeTestBundleProgram(testFile.name); - const host = - createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); - const {factoryFn} = parseStatementForUmdModule( - getSourceFileOrError(bundle.program, testFile.name).statements[0])!; - const testForHelper = createTestForTsHelper( - host, factoryFn, - (_fn, helperName) => getDeclaration( - bundle.program, tslibFile.name, helperName, ts.isFunctionDeclaration)); - - testForHelper('a', '__assign', KnownDeclaration.TsHelperAssign, 'tslib'); - testForHelper('b', '__spread', KnownDeclaration.TsHelperSpread, 'tslib'); - testForHelper('c', '__spreadArrays', KnownDeclaration.TsHelperSpreadArrays, 'tslib'); - testForHelper('d', '__spreadArray', KnownDeclaration.TsHelperSpreadArray, 'tslib'); - testForHelper('e', '__read', KnownDeclaration.TsHelperRead, 'tslib'); - }); - - it('should recognize undeclared, unimported TypeScript helpers (by name)', () => { - const file: TestFile = { - name: _('/test.js'), - contents: ` - (function (global, factory) { - typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : - typeof define === 'function' && define.amd ? define('test', ['exports'], factory) : - (factory(global.test)); - }(this, (function (exports) { 'use strict'; - var a = __assign({foo: 'bar'}, {baz: 'qux'}); - var b = __spread(['foo', 'bar'], ['baz', 'qux']); - var c = __spreadArrays(['foo', 'bar'], ['baz', 'qux']); - var d = __spreadArray(['foo', 'bar'], ['baz', 'qux']); - var e = __read(['foo', 'bar']); - }))); - `, - }; - loadTestFiles([file]); - const bundle = makeTestBundleProgram(file.name); - const host = - createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); - const {factoryFn} = parseStatementForUmdModule( - getSourceFileOrError(bundle.program, file.name).statements[0])!; - - const testForHelper = - (varName: string, helperName: string, knownAs: KnownDeclaration) => { - const node = getVariableDeclaration(factoryFn, varName); - const helperIdentifier = getIdentifierFromCallExpression(node); - const helperDeclaration = host.getDeclarationOfIdentifier(helperIdentifier); - - expect(helperDeclaration).toEqual({ - kind: DeclarationKind.Inline, - known: knownAs, - node: helperIdentifier, - viaModule: null, - }); - }; - - testForHelper('a', '__assign', KnownDeclaration.TsHelperAssign); - testForHelper('b', '__spread', KnownDeclaration.TsHelperSpread); - testForHelper('c', '__spreadArrays', KnownDeclaration.TsHelperSpreadArrays); - testForHelper('d', '__spreadArray', KnownDeclaration.TsHelperSpreadArray); - testForHelper('e', '__read', KnownDeclaration.TsHelperRead); - }); - - it('should recognize suffixed, undeclared, unimported TypeScript helpers (by name)', - () => { - const file: TestFile = { - name: _('/test.js'), - contents: ` - (function (global, factory) { - typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : - typeof define === 'function' && define.amd ? define('test', ['exports'], factory) : - (factory(global.test)); - }(this, (function (exports) { 'use strict'; - var a = __assign$1({foo: 'bar'}, {baz: 'qux'}); - var b = __spread$2(['foo', 'bar'], ['baz', 'qux']); - var c = __spreadArrays$3(['foo', 'bar'], ['baz', 'qux']); - var d = __spreadArray$3(['foo', 'bar'], ['baz', 'qux']); - var e = __read$3(['foo', 'bar']); - }))); - `, - }; - loadTestFiles([file]); - const bundle = makeTestBundleProgram(file.name); - const host = - createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); - const {factoryFn} = parseStatementForUmdModule( - getSourceFileOrError(bundle.program, file.name).statements[0])!; - - const testForHelper = - (varName: string, helperName: string, knownAs: KnownDeclaration) => { - const node = getVariableDeclaration(factoryFn, varName); - const helperIdentifier = getIdentifierFromCallExpression(node); - const helperDeclaration = host.getDeclarationOfIdentifier(helperIdentifier); - - expect(helperDeclaration).toEqual({ - kind: DeclarationKind.Inline, - known: knownAs, - node: helperIdentifier, - viaModule: null, - }); - }; - - testForHelper('a', '__assign$1', KnownDeclaration.TsHelperAssign); - testForHelper('b', '__spread$2', KnownDeclaration.TsHelperSpread); - testForHelper('c', '__spreadArrays$3', KnownDeclaration.TsHelperSpreadArrays); - testForHelper('d', '__spreadArray$3', KnownDeclaration.TsHelperSpreadArray); - testForHelper('e', '__read$3', KnownDeclaration.TsHelperRead); - }); - - it('should recognize enum declarations with string values', () => { - const testFile: TestFile = { - name: _('/node_modules/test-package/some/file.js'), - contents: ` - (function (global, factory) { - typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('@angular/core')) : - typeof define === 'function' && define.amd ? define('some_directive', ['exports', '@angular/core'], factory) : - (factory(global.some_directive,global.ng.core)); - }(this, (function (exports,core) { 'use strict'; - var Enum; - (function (Enum) { - Enum["ValueA"] = "1"; - Enum["ValueB"] = "2"; - })(exports.Enum || (exports.Enum = {})); - - var value = Enum; - }))); - ` - }; - loadTestFiles([testFile]); - const bundle = makeTestBundleProgram(testFile.name); - const host = - createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); - const {factoryFn} = parseStatementForUmdModule( - getSourceFileOrError(bundle.program, _('/node_modules/test-package/some/file.js')) - .statements[0])!; - const valueDecl = getVariableDeclaration(factoryFn, 'value'); - const declaration = - host.getDeclarationOfIdentifier(valueDecl.initializer as ts.Identifier) as - ConcreteDeclaration; - - const enumMembers = (declaration.identity as DownleveledEnum).enumMembers; - expect(declaration.node.parent.parent.getText()).toBe('var Enum;'); - expect(enumMembers!.length).toBe(2); - expect(enumMembers![0].name.getText()).toBe('"ValueA"'); - expect(enumMembers![0].initializer!.getText()).toBe('"1"'); - expect(enumMembers![1].name.getText()).toBe('"ValueB"'); - expect(enumMembers![1].initializer!.getText()).toBe('"2"'); - }); - - it('should recognize enum declarations with numeric values', () => { - const testFile: TestFile = { - name: _('/node_modules/test-package/some/file.js'), - contents: ` - (function (global, factory) { - typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('@angular/core')) : - typeof define === 'function' && define.amd ? define('some_directive', ['exports', '@angular/core'], factory) : - (factory(global.some_directive,global.ng.core)); - }(this, (function (exports,core) { 'use strict'; - var Enum; - (function (Enum) { - Enum[Enum["ValueA"] = "1"] = "ValueA"; - Enum[Enum["ValueB"] = "2"] = "ValueB"; - })(exports.Enum || (exports.Enum = {})); - - var value = Enum; - }))); - ` - }; - loadTestFiles([testFile]); - const bundle = makeTestBundleProgram(testFile.name); - const host = - createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); - const {factoryFn} = parseStatementForUmdModule( - getSourceFileOrError(bundle.program, _('/node_modules/test-package/some/file.js')) - .statements[0])!; - const valueDecl = getVariableDeclaration(factoryFn, 'value'); - const declaration = - host.getDeclarationOfIdentifier(valueDecl.initializer as ts.Identifier) as - ConcreteDeclaration; - - const enumMembers = (declaration.identity as DownleveledEnum).enumMembers; - expect(declaration.node.parent.parent.getText()).toBe('var Enum;'); - expect(enumMembers!.length).toBe(2); - expect(enumMembers![0].name.getText()).toBe('"ValueA"'); - expect(enumMembers![0].initializer!.getText()).toBe('"1"'); - expect(enumMembers![1].name.getText()).toBe('"ValueB"'); - expect(enumMembers![1].initializer!.getText()).toBe('"2"'); - }); - - it('should not consider IIFEs that do no assign members to the parameter as an enum declaration', - () => { - const testFile: TestFile = { - name: _('/node_modules/test-package/some/file.js'), - contents: ` - (function (global, factory) { - typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('@angular/core')) : - typeof define === 'function' && define.amd ? define('some_directive', ['exports', '@angular/core'], factory) : - (factory(global.some_directive,global.ng.core)); - }(this, (function (exports,core) { 'use strict'; - var Enum; - (function (E) { - Enum["ValueA"] = "1"]; - Enum["ValueB"] = "2"]; - })(exports.Enum || (exports.Enum = {})); - - var value = Enum; - }))); - ` - }; - loadTestFiles([testFile]); - const bundle = makeTestBundleProgram(testFile.name); - const host = - createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); - const valueDecl = getDeclaration( - bundle.program, _('/node_modules/test-package/some/file.js'), 'value', - isNamedVariableDeclaration); - const declaration = - host.getDeclarationOfIdentifier(valueDecl.initializer as ts.Identifier) as - ConcreteDeclaration; - - expect(declaration.node.parent.parent.getText()).toBe('var Enum;'); - expect(declaration.identity).toBe(null); - }); - - it('should not consider IIFEs without call argument as an enum declaration', () => { - const testFile: TestFile = { - name: _('/node_modules/test-package/some/file.js'), - contents: ` - (function (global, factory) { - typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('@angular/core')) : - typeof define === 'function' && define.amd ? define('some_directive', ['exports', '@angular/core'], factory) : - (factory(global.some_directive,global.ng.core)); - }(this, (function (exports,core) { 'use strict'; - var Enum; - (function (Enum) { - Enum["ValueA"] = "1"]; - Enum["ValueB"] = "2"]; - })(); - - var value = Enum; - }))); - ` - }; - loadTestFiles([testFile]); - const bundle = makeTestBundleProgram(testFile.name); - const host = - createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); - const valueDecl = getDeclaration( - bundle.program, _('/node_modules/test-package/some/file.js'), 'value', - isNamedVariableDeclaration); - const declaration = - host.getDeclarationOfIdentifier(valueDecl.initializer as ts.Identifier) as - ConcreteDeclaration; - - expect(declaration.node.parent.parent.getText()).toBe('var Enum;'); - expect(declaration.identity).toBe(null); - }); - }); - - describe('getExportsOfModule()', () => { - it('should return a map of all the exports from a given module', () => { - loadFakeCore(getFileSystem()); - loadTestFiles(EXPORTS_FILES); - const bundle = makeTestBundleProgram(_('/index.js')); - const host = - createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); - const file = getSourceFileOrError(bundle.program, _('/b_module.js')); - const exportDeclarations = host.getExportsOfModule(file); - expect(exportDeclarations).not.toBe(null); - expect(Array.from(exportDeclarations!.entries()) - .map(entry => [entry[0], entry[1].node!.getText(), entry[1].viaModule])) - .toEqual([ - ['Directive', `Directive: FnWithArg<(clazz: any) => any>`, '@angular/core'], - ['a', `a = 'a'`, null], - ['b', `b = a_module.a`, null], - ['c', `a = 'a'`, null], - ['d', `b = a_module.a`, null], - ['e', `e = 'e'`, null], - ['DirectiveX', `Directive: FnWithArg<(clazz: any) => any>`, '@angular/core'], - [ - 'SomeClass', - 'SomeClass = (function() {\n function SomeClass() {}\n return SomeClass;\n }())', - null - ], - ]); - }); - - it('should return the correct declaration for a decorated class', () => { - loadFakeCore(getFileSystem()); - loadTestFiles(EXPORTS_FILES); - const bundle = makeTestBundleProgram(_('/decorated_class.js')); - const host = - createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); - const file = getSourceFileOrError(bundle.program, _('/decorated_class.js')); - const exportDeclarations = host.getExportsOfModule(file); - expect(exportDeclarations).not.toBe(null); - expect(exportDeclarations!.size).toEqual(1); - const classDecl = exportDeclarations!.get('DecoratedClass') as InlineDeclaration; - expect(classDecl).toBeDefined(); - expect(classDecl.kind).toEqual(DeclarationKind.Inline); - expect(classDecl.known).toBe(null); - expect(classDecl.viaModule).toBe(null); - expect(classDecl.node.getText()).toEqual('exports.DecoratedClass'); - expect(classDecl.implementation!.getText()).toContain('function DecoratedClass() {'); - }); - - it('should handle wildcard re-exports of other modules (with emitted helpers)', () => { - loadFakeCore(getFileSystem()); - loadTestFiles(EXPORTS_FILES); - const bundle = makeTestBundleProgram(_('/index.js')); - const host = - createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); - const file = getSourceFileOrError(bundle.program, _('/wildcard_reexports.js')); - const exportDeclarations = host.getExportsOfModule(file); - expect(exportDeclarations).not.toBe(null); - expect(Array.from(exportDeclarations!.entries()) - .map(entry => [entry[0], entry[1].node!.getText(), entry[1].viaModule])) - .toEqual([ - ['Directive', `Directive: FnWithArg<(clazz: any) => any>`, _('/b_module')], - ['a', `a = 'a'`, _('/b_module')], - ['b', `b = a_module.a`, _('/b_module')], - ['c', `a = 'a'`, _('/b_module')], - ['d', `b = a_module.a`, _('/b_module')], - ['e', `e = 'e'`, _('/b_module')], - ['DirectiveX', `Directive: FnWithArg<(clazz: any) => any>`, _('/b_module')], - [ - 'SomeClass', - `SomeClass = (function() {\n function SomeClass() {}\n return SomeClass;\n }())`, - _('/b_module') - ], - ['xtra1', `xtra1 = 'xtra1'`, _('/xtra_module')], - ['xtra2', `xtra2 = 'xtra2'`, _('/xtra_module')], - ]); - }); - - it('should handle wildcard re-exports of other modules (with imported helpers)', () => { - loadFakeCore(getFileSystem()); - loadTestFiles(EXPORTS_FILES); - const bundle = makeTestBundleProgram(_('/index.js')); - const host = - createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); - const file = getSourceFileOrError( - bundle.program, _('/wildcard_reexports_imported_helpers.js')); - const exportDeclarations = host.getExportsOfModule(file); - expect(exportDeclarations).not.toBe(null); - expect(Array.from(exportDeclarations!.entries()) - .map(entry => [entry[0], entry[1].node!.getText(), entry[1].viaModule])) - .toEqual([ - ['Directive', `Directive: FnWithArg<(clazz: any) => any>`, _('/b_module')], - ['a', `a = 'a'`, _('/b_module')], - ['b', `b = a_module.a`, _('/b_module')], - ['c', `a = 'a'`, _('/b_module')], - ['d', `b = a_module.a`, _('/b_module')], - ['e', `e = 'e'`, _('/b_module')], - ['DirectiveX', `Directive: FnWithArg<(clazz: any) => any>`, _('/b_module')], - [ - 'SomeClass', - `SomeClass = (function() {\n function SomeClass() {}\n return SomeClass;\n }())`, - _('/b_module') - ], - ['xtra1', `xtra1 = 'xtra1'`, _('/xtra_module')], - ['xtra2', `xtra2 = 'xtra2'`, _('/xtra_module')], - ]); - }); - - it('should handle inline exports', () => { - loadFakeCore(getFileSystem()); - loadTestFiles([INLINE_EXPORT_FILE]); - const bundle = makeTestBundleProgram(INLINE_EXPORT_FILE.name); - const host = - createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); - const file = getSourceFileOrError(bundle.program, INLINE_EXPORT_FILE.name); - const exportDeclarations = host.getExportsOfModule(file); - expect(exportDeclarations).not.toBe(null); - const entries: [string, InlineDeclaration][] = - Array.from(exportDeclarations!.entries()) as any; - expect(entries.map( - ([name, decl]) => - [name, decl.node!.getText(), decl.implementation!.getText(), - decl.viaModule])) - .toEqual([ - ['directives', 'exports.directives', '[foo]', null], - ['Inline', 'exports.Inline', 'function Inline() {}', null], - ]); - }); - - it('should recognize declarations of known TypeScript helpers', () => { - const tslib = { - name: _('/tslib.d.ts'), - contents: ` - export declare function __assign(t: any, ...sources: any[]): any; - export declare function __spread(...args: any[][]): any[]; - export declare function __spreadArrays(...args: any[][]): any[]; - export declare function __spreadArray(to: any[], from: any[]): any[]; - export declare function __read(o: any, n?: number): any[]; - export declare function __unknownHelper(...args: any[]): any; - `, - }; - loadTestFiles([tslib]); - const bundle = makeTestBundleProgram(tslib.name); - const host = - createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); - const sf = getSourceFileOrError(bundle.program, tslib.name); - const exportDeclarations = host.getExportsOfModule(sf)!; - - expect([...exportDeclarations].map(([exportName, {known}]) => [exportName, known])) - .toEqual([ - ['__assign', KnownDeclaration.TsHelperAssign], - ['__spread', KnownDeclaration.TsHelperSpread], - ['__spreadArrays', KnownDeclaration.TsHelperSpreadArrays], - ['__spreadArray', KnownDeclaration.TsHelperSpreadArray], - ['__read', KnownDeclaration.TsHelperRead], - ['__unknownHelper', null], - ]); - }); - - it('should define property exports from a module', () => { - loadFakeCore(getFileSystem()); - loadTestFiles(EXPORTS_FILES); - const bundle = makeTestBundleProgram(_('/index.js')); - const host = - createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); - const file = getSourceFileOrError(bundle.program, _('/define_property_reexports.js')); - const exportDeclarations = host.getExportsOfModule(file); - expect(exportDeclarations).not.toBe(null); - expect(Array.from(exportDeclarations!.entries()) - .map(entry => [entry[0], entry[1].node!.getText(), entry[1].viaModule])) - .toEqual([ - ['newA', `a = 'a'`, null], - ]); - }); - }); - - describe('getClassSymbol()', () => { - it('should return the class symbol for an ES2015 class', () => { - loadTestFiles([SIMPLE_ES2015_CLASS_FILE]); - const bundle = makeTestBundleProgram(SIMPLE_ES2015_CLASS_FILE.name); - const host = - createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); - const node = getDeclaration( - bundle.program, SIMPLE_ES2015_CLASS_FILE.name, 'EmptyClass', - isNamedClassDeclaration); - const classSymbol = host.getClassSymbol(node); - - expect(classSymbol).toBeDefined(); - expect(classSymbol!.declaration.valueDeclaration).toBe(node); - expect(classSymbol!.implementation.valueDeclaration).toBe(node); - }); - - it('should handle wildcard re-exports of other modules (with emitted helpers)', () => { - loadFakeCore(getFileSystem()); - loadTestFiles(EXPORTS_FILES); - const bundle = makeTestBundleProgram(_('/index.js')); - const host = - createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); - const file = getSourceFileOrError(bundle.program, _('/wildcard_reexports.js')); - const exportDeclarations = host.getExportsOfModule(file); - expect(exportDeclarations).not.toBe(null); - expect(Array.from(exportDeclarations!.entries()) - .map(entry => [entry[0], entry[1].node!.getText(), entry[1].viaModule])) - .toEqual([ - ['Directive', `Directive: FnWithArg<(clazz: any) => any>`, _('/b_module')], - ['a', `a = 'a'`, _('/b_module')], - ['b', `b = a_module.a`, _('/b_module')], - ['c', `a = 'a'`, _('/b_module')], - ['d', `b = a_module.a`, _('/b_module')], - ['e', `e = 'e'`, _('/b_module')], - ['DirectiveX', `Directive: FnWithArg<(clazz: any) => any>`, _('/b_module')], - [ - 'SomeClass', - `SomeClass = (function() {\n function SomeClass() {}\n return SomeClass;\n }())`, - _('/b_module') - ], - ['xtra1', `xtra1 = 'xtra1'`, _('/xtra_module')], - ['xtra2', `xtra2 = 'xtra2'`, _('/xtra_module')], - ]); - }); - - it('should handle wildcard re-exports of other modules (with imported helpers)', () => { - loadFakeCore(getFileSystem()); - loadTestFiles(EXPORTS_FILES); - const bundle = makeTestBundleProgram(_('/index.js')); - const host = - createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); - const file = getSourceFileOrError( - bundle.program, _('/wildcard_reexports_imported_helpers.js')); - const exportDeclarations = host.getExportsOfModule(file); - expect(exportDeclarations).not.toBe(null); - expect(Array.from(exportDeclarations!.entries()) - .map(entry => [entry[0], entry[1].node!.getText(), entry[1].viaModule])) - .toEqual([ - ['Directive', `Directive: FnWithArg<(clazz: any) => any>`, _('/b_module')], - ['a', `a = 'a'`, _('/b_module')], - ['b', `b = a_module.a`, _('/b_module')], - ['c', `a = 'a'`, _('/b_module')], - ['d', `b = a_module.a`, _('/b_module')], - ['e', `e = 'e'`, _('/b_module')], - ['DirectiveX', `Directive: FnWithArg<(clazz: any) => any>`, _('/b_module')], - [ - 'SomeClass', - `SomeClass = (function() {\n function SomeClass() {}\n return SomeClass;\n }())`, - _('/b_module') - ], - ['xtra1', `xtra1 = 'xtra1'`, _('/xtra_module')], - ['xtra2', `xtra2 = 'xtra2'`, _('/xtra_module')], - ]); - }); - - it('should handle wildcard re-exports of other modules using `require()` calls', () => { - loadFakeCore(getFileSystem()); - loadTestFiles(EXPORTS_FILES); - const bundle = makeTestBundleProgram(_('/index.js')); - const host = - createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); - const file = - getSourceFileOrError(bundle.program, _('/wildcard_reexports_with_require.js')); - const exportDeclarations = host.getExportsOfModule(file); - expect(exportDeclarations).not.toBe(null); - expect(Array.from(exportDeclarations!.entries()) - .map(entry => [entry[0], entry[1].node!.getText(), entry[1].viaModule])) - .toEqual([ - ['Directive', `Directive: FnWithArg<(clazz: any) => any>`, _('/b_module')], - ['a', `a = 'a'`, _('/b_module')], - ['b', `b = a_module.a`, _('/b_module')], - ['c', `a = 'a'`, _('/b_module')], - ['d', `b = a_module.a`, _('/b_module')], - ['e', `e = 'e'`, _('/b_module')], - ['DirectiveX', `Directive: FnWithArg<(clazz: any) => any>`, _('/b_module')], - [ - 'SomeClass', - `SomeClass = (function() {\n function SomeClass() {}\n return SomeClass;\n }())`, - _('/b_module') - ], - ['xtra1', `xtra1 = 'xtra1'`, _('/xtra_module')], - ['xtra2', `xtra2 = 'xtra2'`, _('/xtra_module')], - ]); - }); - - it('should return the class symbol for an ES5 class (outer variable declaration)', - () => { - loadTestFiles([SIMPLE_CLASS_FILE]); - const bundle = makeTestBundleProgram(SIMPLE_CLASS_FILE.name); - const host = - createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); - const outerNode = getDeclaration( - bundle.program, SIMPLE_CLASS_FILE.name, 'EmptyClass', isDesiredDeclaration); - const innerNode = getDeclaration( - bundle.program, SIMPLE_CLASS_FILE.name, 'EmptyClass', - isNamedFunctionDeclaration); - const classSymbol = host.getClassSymbol(outerNode); - - expect(classSymbol).toBeDefined(); - expect(classSymbol!.declaration.valueDeclaration).toBe(outerNode as any); - expect(classSymbol!.implementation.valueDeclaration).toBe(innerNode); - }); - - it('should return the class symbol for an ES5 class (inner function declaration)', - () => { - loadTestFiles([SIMPLE_CLASS_FILE]); - const bundle = makeTestBundleProgram(SIMPLE_CLASS_FILE.name); - const host = - createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); - const outerNode = getDeclaration( - bundle.program, SIMPLE_CLASS_FILE.name, 'EmptyClass', isDesiredDeclaration); - const innerNode = getDeclaration( - bundle.program, SIMPLE_CLASS_FILE.name, 'EmptyClass', - isNamedFunctionDeclaration); - const classSymbol = host.getClassSymbol(innerNode); - - expect(classSymbol).toBeDefined(); - expect(classSymbol!.declaration.valueDeclaration).toBe(outerNode as any); - expect(classSymbol!.implementation.valueDeclaration).toBe(innerNode); - }); - - it('should return the same class symbol (of the outer declaration) for outer and inner declarations', - () => { - loadTestFiles([SIMPLE_CLASS_FILE]); - const bundle = makeTestBundleProgram(SIMPLE_CLASS_FILE.name); - const host = - createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); - const outerNode = getDeclaration( - bundle.program, SIMPLE_CLASS_FILE.name, 'EmptyClass', isDesiredDeclaration); - const innerNode = getDeclaration( - bundle.program, SIMPLE_CLASS_FILE.name, 'EmptyClass', - isNamedFunctionDeclaration); - - const innerSymbol = host.getClassSymbol(innerNode)!; - const outerSymbol = host.getClassSymbol(outerNode)!; - expect(innerSymbol.declaration).toBe(outerSymbol.declaration); - expect(innerSymbol.implementation).toBe(outerSymbol.implementation); - }); - - it('should return the class symbol for an ES5 class whose IIFE is not wrapped in parens', - () => { - loadTestFiles([SIMPLE_CLASS_FILE]); - const bundle = makeTestBundleProgram(SIMPLE_CLASS_FILE.name); - const host = - createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); - const outerNode = getDeclaration( - bundle.program, SIMPLE_CLASS_FILE.name, 'NoParensClass', isDesiredDeclaration); - const innerNode = getDeclaration( - bundle.program, SIMPLE_CLASS_FILE.name, 'NoParensClass', - isNamedFunctionDeclaration); - const classSymbol = host.getClassSymbol(outerNode); - - expect(classSymbol).toBeDefined(); - expect(classSymbol!.declaration.valueDeclaration).toBe(outerNode as any); - expect(classSymbol!.implementation.valueDeclaration).toBe(innerNode); - }); - - it('should return the class symbol for an ES5 class whose IIFE is not wrapped with inner parens', - () => { - loadTestFiles([SIMPLE_CLASS_FILE]); - const bundle = makeTestBundleProgram(SIMPLE_CLASS_FILE.name); - const host = - createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); - const outerNode = getDeclaration( - bundle.program, SIMPLE_CLASS_FILE.name, 'InnerParensClass', - isDesiredDeclaration); - const innerNode = getDeclaration( - bundle.program, SIMPLE_CLASS_FILE.name, 'InnerParensClass', - isNamedFunctionDeclaration); - const classSymbol = host.getClassSymbol(outerNode); - - expect(classSymbol).toBeDefined(); - expect(classSymbol!.declaration.valueDeclaration).toBe(outerNode as any); - expect(classSymbol!.implementation.valueDeclaration).toBe(innerNode); - }); - - it('should return undefined if node is not an ES5 class', () => { - loadTestFiles([FOO_FUNCTION_FILE]); - const bundle = makeTestBundleProgram(FOO_FUNCTION_FILE.name); - const host = - createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); - const node = getDeclaration( - bundle.program, FOO_FUNCTION_FILE.name, 'foo', isNamedFunctionDeclaration); - const classSymbol = host.getClassSymbol(node); - - expect(classSymbol).toBeUndefined(); - }); - - it('should return undefined if variable declaration is not initialized using an IIFE', - () => { - const testFile = { - name: _('/test.js'), - contents: `var MyClass = null;`, - }; - loadTestFiles([testFile]); - const bundle = makeTestBundleProgram(testFile.name); - const host = - createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); - const node = getDeclaration( - bundle.program, testFile.name, 'MyClass', isNamedVariableDeclaration); - const classSymbol = host.getClassSymbol(node); - - expect(classSymbol).toBeUndefined(); - }); - }); - - describe('isClass()', () => { - it('should return true if a given node is a TS class declaration', () => { - loadTestFiles([SIMPLE_ES2015_CLASS_FILE]); - const bundle = makeTestBundleProgram(SIMPLE_ES2015_CLASS_FILE.name); - const host = - createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); - const node = getDeclaration( - bundle.program, SIMPLE_ES2015_CLASS_FILE.name, 'EmptyClass', - isNamedClassDeclaration); - expect(host.isClass(node)).toBe(true); - }); - - it('should return true if a given node is the outer variable declaration of a class', - () => { - loadTestFiles([SIMPLE_CLASS_FILE]); - const bundle = makeTestBundleProgram(SIMPLE_CLASS_FILE.name); - const host = - createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); - const node = getDeclaration( - bundle.program, SIMPLE_CLASS_FILE.name, 'EmptyClass', isDesiredDeclaration); - expect(host.isClass(node)).toBe(true); - }); - - it('should return true if a given node is the inner variable declaration of a class', - () => { - loadTestFiles([SIMPLE_CLASS_FILE]); - const bundle = makeTestBundleProgram(SIMPLE_CLASS_FILE.name); - const host = - createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); - const node = getDeclaration( - bundle.program, SIMPLE_CLASS_FILE.name, 'EmptyClass', - isNamedFunctionDeclaration); - expect(host.isClass(node)).toBe(true); - }); - - it('should return false if a given node is a function declaration', () => { - loadTestFiles([FOO_FUNCTION_FILE]); - const bundle = makeTestBundleProgram(FOO_FUNCTION_FILE.name); - const host = - createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); - const node = getDeclaration( - bundle.program, FOO_FUNCTION_FILE.name, 'foo', isNamedFunctionDeclaration); - expect(host.isClass(node)).toBe(false); - }); - }); - - describe('hasBaseClass()', () => { - function hasBaseClass(source: string) { - const file = { - name: _('/synthesized_constructors.js'), - contents: source, - }; - - loadTestFiles([file]); - const bundle = makeTestBundleProgram(file.name); - const host = - createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); - const classNode = - getDeclaration(bundle.program, file.name, 'TestClass', isDesiredDeclaration); - return host.hasBaseClass(classNode); - } - - it('should consider an IIFE with _super parameter as having a base class', () => { - const result = hasBaseClass(` - ${expDecl('TestClass')} = /** @class */ (function (_super) { - __extends(TestClass, _super); - function TestClass() {} - return TestClass; - }(null));`); - expect(result).toBe(true); - }); - - it('should consider an IIFE with a unique name generated for the _super parameter as having a base class', - () => { - const result = hasBaseClass(` - ${expDecl('TestClass')} = /** @class */ (function (_super_1) { - __extends(TestClass, _super_1); - function TestClass() {} - return TestClass; - }(null));`); - expect(result).toBe(true); - }); - - it('should not consider an IIFE without parameter as having a base class', () => { - const result = hasBaseClass(` - ${expDecl('TestClass')} = /** @class */ (function () { - __extends(TestClass, _super); - function TestClass() {} - return TestClass; - }(null));`); - expect(result).toBe(false); - }); - }); - - describe('getBaseClassExpression()', () => { - function getBaseClassIdentifier(source: string): ts.Identifier|null { - const file = { - name: _('/synthesized_constructors.js'), - contents: source, - }; - - loadTestFiles([file]); - const bundle = makeTestBundleProgram(file.name); - const host = - createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); - const classNode = - getDeclaration(bundle.program, file.name, 'TestClass', isDesiredDeclaration); - const expression = host.getBaseClassExpression(classNode); - if (expression !== null && !ts.isIdentifier(expression)) { - throw new Error( - 'Expected class to inherit via an identifier but got: ' + expression.getText()); - } - return expression; - } - - it('should find the base class of an IIFE with _super parameter', () => { - const identifier = getBaseClassIdentifier(` - ${expDecl('BaseClass')} = /** @class */ (function () { - function BaseClass() {} - return BaseClass; - }()); - ${expDecl('TestClass')} = /** @class */ (function (_super) { - __extends(TestClass, _super); - function TestClass() {} - return TestClass; - }(BaseClass));`); - expect(identifier!.text).toBe('BaseClass'); - }); - - it('should find the base class of an IIFE with a unique name generated for the _super parameter', - () => { - const identifier = getBaseClassIdentifier(` - ${expDecl('BaseClass')} = /** @class */ (function () { - function BaseClass() {} - return BaseClass; - }()); - ${expDecl('TestClass')} = /** @class */ (function (_super_1) { - __extends(TestClass, _super_1); - function TestClass() {} - return TestClass; - }(BaseClass));`); - expect(identifier!.text).toBe('BaseClass'); - }); - - it('should not find a base class for an IIFE without parameter', () => { - const identifier = getBaseClassIdentifier(` - ${expDecl('BaseClass')} = /** @class */ (function () { - function BaseClass() {} - return BaseClass; - }()); - ${expDecl('TestClass')} = /** @class */ (function () { - __extends(TestClass, _super); - function TestClass() {} - return TestClass; - }(BaseClass));`); - expect(identifier).toBe(null); - }); - - it('should find a dynamic base class expression of an IIFE', () => { - const file = { - name: _('/synthesized_constructors.js'), - contents: ` - ${expDecl('BaseClass')} = /** @class */ (function () { - function BaseClass() {} - return BaseClass; - }()); - function foo() { return BaseClass; } - ${expDecl('TestClass')} = /** @class */ (function (_super) { - __extends(TestClass, _super); - function TestClass() {} - return TestClass; - }(foo()));`, - }; - - loadTestFiles([file]); - const bundle = makeTestBundleProgram(file.name); - const host = - createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); - const classNode = - getDeclaration(bundle.program, file.name, 'TestClass', isDesiredDeclaration); - const expression = host.getBaseClassExpression(classNode)!; - expect(expression.getText()).toBe('foo()'); - }); - }); - - describe('findClassSymbols()', () => { - it('should return an array of all classes in the given source file', () => { - loadTestFiles(DECORATED_FILES); - const bundle = makeTestBundleProgram(DECORATED_FILES[0].name); - const host = - createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); - const primaryFile = getSourceFileOrError(bundle.program, DECORATED_FILES[0].name); - const secondaryFile = getSourceFileOrError(bundle.program, DECORATED_FILES[1].name); - - const classSymbolsPrimary = host.findClassSymbols(primaryFile); - expect(classSymbolsPrimary.length).toEqual(2); - expect(classSymbolsPrimary.map(c => c.name)).toEqual(['A', 'B']); - - const classSymbolsSecondary = host.findClassSymbols(secondaryFile); - expect(classSymbolsSecondary.length).toEqual(1); - expect(classSymbolsSecondary.map(c => c.name)).toEqual(['D']); - }); - }); - - describe('getDecoratorsOfSymbol()', () => { - it('should return decorators of class symbol', () => { - loadTestFiles(DECORATED_FILES); - const bundle = makeTestBundleProgram(DECORATED_FILES[0].name); - const host = - createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); - const primaryFile = getSourceFileOrError(bundle.program, DECORATED_FILES[0].name); - const secondaryFile = getSourceFileOrError(bundle.program, DECORATED_FILES[1].name); - - const classSymbolsPrimary = host.findClassSymbols(primaryFile); - const classDecoratorsPrimary = - classSymbolsPrimary.map(s => host.getDecoratorsOfSymbol(s)); - expect(classDecoratorsPrimary.length).toEqual(2); - expect(classDecoratorsPrimary[0]!.map(d => d.name)).toEqual(['Directive']); - expect(classDecoratorsPrimary[1]!.map(d => d.name)).toEqual(['Directive']); - - const classSymbolsSecondary = host.findClassSymbols(secondaryFile); - const classDecoratorsSecondary = - classSymbolsSecondary.map(s => host.getDecoratorsOfSymbol(s)); - expect(classDecoratorsSecondary.length).toEqual(1); - expect(classDecoratorsSecondary[0]!.map(d => d.name)).toEqual(['Directive']); - }); - }); - - describe('getDtsDeclaration()', () => { - it('should find the dts declaration that has the same relative path to the source file', - () => { - loadTestFiles(TYPINGS_SRC_FILES); - loadTestFiles(TYPINGS_DTS_FILES); - const bundle = makeTestBundleProgram(getRootFiles(TYPINGS_SRC_FILES)[0]); - const dts = makeTestBundleProgram(getRootFiles(TYPINGS_DTS_FILES)[0]); - const host = createHost( - bundle, new UmdReflectionHost(new MockLogger(), false, bundle, dts)); - const class1 = getDeclaration( - bundle.program, _('/ep/src/class1.js'), 'Class1', isDesiredDeclaration); - - const dtsDeclaration = host.getDtsDeclaration(class1); - expect(dtsDeclaration!.getSourceFile().fileName) - .toEqual(_('/ep/typings/class1.d.ts')); - }); - - it('should find the dts declaration for exported functions', () => { - loadTestFiles(TYPINGS_SRC_FILES); - loadTestFiles(TYPINGS_DTS_FILES); - const bundle = makeTestBundleProgram(_('/ep/src/func1.js')); - const dts = makeTestBundleProgram(_('/ep/typings/func1.d.ts')); - const mooFn = getDeclaration( - bundle.program, _('/ep/src/func1.js'), 'mooFn', isExportsDeclaration); - const host = - createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle, dts)); - - const dtsDeclaration = host.getDtsDeclaration(mooFn); - expect(dtsDeclaration!.getSourceFile().fileName).toEqual(_('/ep/typings/func1.d.ts')); - }); - - it('should return null if there is no matching class in the matching dts file', () => { - loadTestFiles(TYPINGS_SRC_FILES); - loadTestFiles(TYPINGS_DTS_FILES); - const bundle = makeTestBundleProgram(getRootFiles(TYPINGS_SRC_FILES)[0]); - const dts = makeTestBundleProgram(getRootFiles(TYPINGS_DTS_FILES)[0]); - const missingClass = getDeclaration( - bundle.program, _('/ep/src/class1.js'), 'MissingClass1', isDesiredDeclaration); - const host = - createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle, dts)); - expect(host.getDtsDeclaration(missingClass)).toBe(null); - }); - - it('should return null if there is no matching dts file', () => { - loadTestFiles(TYPINGS_SRC_FILES); - loadTestFiles(TYPINGS_DTS_FILES); - const bundle = makeTestBundleProgram(getRootFiles(TYPINGS_SRC_FILES)[0]); - const dts = makeTestBundleProgram(getRootFiles(TYPINGS_DTS_FILES)[0]); - const missingClass = getDeclaration( - bundle.program, _('/ep/src/missing-class.js'), 'MissingClass2', - isDesiredDeclaration); - const host = - createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle, dts)); - - expect(host.getDtsDeclaration(missingClass)).toBe(null); - }); - - - - it('should find the dts file that contains a matching class declaration, even if the source files do not match', - () => { - loadTestFiles(TYPINGS_SRC_FILES); - loadTestFiles(TYPINGS_DTS_FILES); - const bundle = makeTestBundleProgram(_('/ep/src/flat-file.js')); - const dts = makeTestBundleProgram(getRootFiles(TYPINGS_DTS_FILES)[0]); - const class1 = getDeclaration( - bundle.program, _('/ep/src/flat-file.js'), 'Class1', isDesiredDeclaration); - const host = createHost( - bundle, new UmdReflectionHost(new MockLogger(), false, bundle, dts)); - - const dtsDeclaration = host.getDtsDeclaration(class1); - expect(dtsDeclaration!.getSourceFile().fileName) - .toEqual(_('/ep/typings/class1.d.ts')); - }); - - it('should find aliased exports', () => { - loadTestFiles(TYPINGS_SRC_FILES); - loadTestFiles(TYPINGS_DTS_FILES); - const bundle = makeTestBundleProgram(_('/ep/src/flat-file.js')); - const dts = makeTestBundleProgram(getRootFiles(TYPINGS_DTS_FILES)[0]); - const sourceClass = getDeclaration( - bundle.program, _('/ep/src/flat-file.js'), 'AliasedClass', isExportsDeclaration); - const host = - createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle, dts)); - - const dtsDeclaration = host.getDtsDeclaration(sourceClass); - if (dtsDeclaration === null) { - return fail('Expected dts class to be found'); - } - if (!isNamedClassDeclaration(dtsDeclaration)) { - return fail('Expected a named class to be found.'); - } - expect(dtsDeclaration.name.text).toEqual('TypingsClass'); - expect(_(dtsDeclaration.getSourceFile().fileName)) - .toEqual(_('/ep/typings/typings-class.d.ts')); - }); - - it('should match publicly and internal exported classes correctly, even if they have the same name', - () => { - loadTestFiles(TYPINGS_SRC_FILES); - loadTestFiles(TYPINGS_DTS_FILES); - const bundle = makeTestBundleProgram(getRootFiles(TYPINGS_SRC_FILES)[0]); - const dts = makeTestBundleProgram(getRootFiles(TYPINGS_DTS_FILES)[0]); - const host = createHost( - bundle, new UmdReflectionHost(new MockLogger(), false, bundle, dts)); - const class2 = getDeclaration( - bundle.program, _('/ep/src/class2.js'), 'Class2', isDesiredDeclaration); - const class2DtsDeclaration = host.getDtsDeclaration(class2); - expect(class2DtsDeclaration!.getSourceFile().fileName) - .toEqual(_('/ep/typings/class2.d.ts')); - - const internalClass2 = getDeclaration( - bundle.program, _('/ep/src/internal.js'), 'Class2', isDesiredDeclaration); - const internalClass2DtsDeclaration = host.getDtsDeclaration(internalClass2); - expect(internalClass2DtsDeclaration!.getSourceFile().fileName) - .toEqual(_('/ep/typings/internal.d.ts')); - }); - - it('should prefer the publicly exported class if there are multiple classes with the same name', - () => { - loadTestFiles(TYPINGS_SRC_FILES); - loadTestFiles(TYPINGS_DTS_FILES); - const bundle = makeTestBundleProgram(_('/ep/src/index.js')); - const dts = makeTestBundleProgram(_('/ep/typings/index.d.ts')); - const class2 = getDeclaration( - bundle.program, _('/ep/src/class2.js'), 'Class2', isDesiredDeclaration); - const internalClass2 = getDeclaration( - bundle.program, _('/ep/src/internal.js'), 'Class2', isDesiredDeclaration); - const host = createHost( - bundle, new UmdReflectionHost(new MockLogger(), false, bundle, dts)); - - const class2DtsDeclaration = host.getDtsDeclaration(class2); - expect(class2DtsDeclaration!.getSourceFile().fileName) - .toEqual(_('/ep/typings/class2.d.ts')); - - const internalClass2DtsDeclaration = host.getDtsDeclaration(internalClass2); - expect(internalClass2DtsDeclaration!.getSourceFile().fileName) - .toEqual(_('/ep/typings/internal.d.ts')); - }); - }); - - describe('getInternalNameOfClass()', () => { - it('should return the name of the inner class declaration', () => { - loadTestFiles([SIMPLE_CLASS_FILE]); - const bundle = makeTestBundleProgram(SIMPLE_CLASS_FILE.name); - const host = - createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); - - const emptyClass = getDeclaration( - bundle.program, SIMPLE_CLASS_FILE.name, 'EmptyClass', isDesiredDeclaration); - expect(host.getInternalNameOfClass(emptyClass).text).toEqual('EmptyClass'); - - const class1 = getDeclaration( - bundle.program, SIMPLE_CLASS_FILE.name, 'OuterClass1', isDesiredDeclaration); - expect(host.getInternalNameOfClass(class1).text).toEqual('InnerClass1'); - - const class2 = getDeclaration( - bundle.program, SIMPLE_CLASS_FILE.name, 'OuterClass2', isDesiredDeclaration); - expect(host.getInternalNameOfClass(class2).text).toEqual('InnerClass2'); - - const childClass = getDeclaration( - bundle.program, SIMPLE_CLASS_FILE.name, 'ChildClass', isDesiredDeclaration); - expect(host.getInternalNameOfClass(childClass).text).toEqual('InnerChildClass'); - }); - }); - - describe('getAdjacentNameOfClass()', () => { - it('should return the name of the inner class declaration', () => { - loadTestFiles([SIMPLE_CLASS_FILE]); - const bundle = makeTestBundleProgram(SIMPLE_CLASS_FILE.name); - const host = - createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); - - const emptyClass = getDeclaration( - bundle.program, SIMPLE_CLASS_FILE.name, 'EmptyClass', isDesiredDeclaration); - expect(host.getAdjacentNameOfClass(emptyClass).text).toEqual('EmptyClass'); - - const class1 = getDeclaration( - bundle.program, SIMPLE_CLASS_FILE.name, 'OuterClass1', isDesiredDeclaration); - expect(host.getAdjacentNameOfClass(class1).text).toEqual('InnerClass1'); - - const class2 = getDeclaration( - bundle.program, SIMPLE_CLASS_FILE.name, 'OuterClass2', isDesiredDeclaration); - expect(host.getAdjacentNameOfClass(class2).text).toEqual('InnerClass2'); - - const childClass = getDeclaration( - bundle.program, SIMPLE_CLASS_FILE.name, 'ChildClass', isDesiredDeclaration); - expect(host.getAdjacentNameOfClass(childClass).text).toEqual('InnerChildClass'); - }); - }); - })); - } -}); - -type WalkerPredicate = (node: ts.Node) => node is T; -function walkForNode(node: ts.Node, predicate: WalkerPredicate): T|undefined { - return node.forEachChild(child => predicate(child) ? child : walkForNode(child, predicate)); -} diff --git a/packages/compiler-cli/ngcc/test/host/util.ts b/packages/compiler-cli/ngcc/test/host/util.ts deleted file mode 100644 index f8b56b2be12..00000000000 --- a/packages/compiler-cli/ngcc/test/host/util.ts +++ /dev/null @@ -1,68 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import {Trait, TraitState} from '@angular/compiler-cli/src/ngtsc/transform'; -import ts from 'typescript'; - -import {SemanticSymbol} from '../../../src/ngtsc/incremental/semantic_graph'; -import {CtorParameter, TypeValueReferenceKind} from '../../../src/ngtsc/reflection'; - -/** - * Check that a given list of `CtorParameter`s has `typeValueReference`s of specific `ts.Identifier` - * names. - */ -export function expectTypeValueReferencesForParameters( - parameters: CtorParameter[], expectedParams: (string|null)[], - fromModule: (string|null)[] = []) { - parameters!.forEach((param, idx) => { - const expected = expectedParams[idx]; - if (expected !== null) { - if (param.typeValueReference.kind === TypeValueReferenceKind.UNAVAILABLE) { - fail(`Incorrect typeValueReference generated for ${param.name}, expected "${ - expected}" because "${param.typeValueReference.reason}"`); - } else if ( - param.typeValueReference.kind === TypeValueReferenceKind.LOCAL && - fromModule[idx] != null) { - fail(`Incorrect typeValueReference generated for ${param.name}, expected non-LOCAL (from ${ - fromModule[idx]}) but was marked LOCAL`); - } else if ( - param.typeValueReference.kind !== TypeValueReferenceKind.LOCAL && - fromModule[idx] == null) { - fail(`Incorrect typeValueReference generated for ${ - param.name}, expected LOCAL but was imported from ${ - param.typeValueReference.moduleName}`); - } else if (param.typeValueReference.kind === TypeValueReferenceKind.LOCAL) { - if (!ts.isIdentifier(param.typeValueReference.expression) && - !ts.isPropertyAccessExpression(param.typeValueReference.expression)) { - fail(`Incorrect typeValueReference generated for ${ - param.name}, expected an identifier but got "${ - param.typeValueReference.expression.getText()}"`); - } else { - expect(param.typeValueReference.expression.getText()).toEqual(expected); - } - } else if (param.typeValueReference.kind === TypeValueReferenceKind.IMPORTED) { - expect(param.typeValueReference.moduleName).toBe(fromModule[idx]!); - expect(param.typeValueReference.importedName).toBe(expected); - } - } - }); -} - -export function getTraitDiagnostics(trait: Trait): - ts.Diagnostic[]|null { - if (trait.state === TraitState.Analyzed) { - return trait.analysisDiagnostics; - } else if (trait.state === TraitState.Resolved) { - const diags = [ - ...(trait.analysisDiagnostics ?? []), - ...(trait.resolveDiagnostics ?? []), - ]; - return diags.length > 0 ? diags : null; - } else { - return null; - } -} diff --git a/packages/compiler-cli/ngcc/test/integration/ngcc_spec.ts b/packages/compiler-cli/ngcc/test/integration/ngcc_spec.ts deleted file mode 100644 index 56d974e9c13..00000000000 --- a/packages/compiler-cli/ngcc/test/integration/ngcc_spec.ts +++ /dev/null @@ -1,4076 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ - -/// -import {runfiles} from '@bazel/runfiles'; -import realFs from 'fs'; -import os from 'os'; - -import {absoluteFrom, AbsoluteFsPath, FileSystem, getFileSystem} from '../../../src/ngtsc/file_system'; -import {Folder, MockFileSystem, runInEachFileSystem, TestFile} from '../../../src/ngtsc/file_system/testing'; -import {MockLogger} from '../../../src/ngtsc/logging/testing'; -import {loadTestFiles} from '../../../src/ngtsc/testing'; -import {LockFilePathResolver} from '../../src/locking/lock_file'; -import {mainNgcc} from '../../src/main'; -import {clearTsConfigCache} from '../../src/ngcc_options'; -import {hasBeenProcessed, markAsProcessed} from '../../src/packages/build_marker'; -import {EntryPointJsonProperty, EntryPointPackageJson, SUPPORTED_FORMAT_PROPERTIES} from '../../src/packages/entry_point'; -import {EntryPointManifestFile} from '../../src/packages/entry_point_manifest'; -import {Transformer} from '../../src/packages/transformer'; -import {DirectPackageJsonUpdater, PackageJsonUpdater} from '../../src/writing/package_json_updater'; -import {mockRequireResolveForLockfile, mockRequireResolveForLockFileUnlockerScript, mockRequireResolveForWorkerScript} from '../helpers/utils'; - -import {loadNgccIntegrationTestFiles} from './util'; - -const ANGULAR_CORE_IMPORT_REGEX = /import \* as ɵngcc\d+ from '@angular\/core';/; -const testFiles = loadNgccIntegrationTestFiles(); - -runInEachFileSystem(() => { - describe('ngcc main()', () => { - let _: typeof absoluteFrom; - let fs: FileSystem; - let pkgJsonUpdater: PackageJsonUpdater; - const STANDARD_MARKERS = { - main: '0.0.0-PLACEHOLDER', - module: '0.0.0-PLACEHOLDER', - es2015: '0.0.0-PLACEHOLDER', - esm2015: '0.0.0-PLACEHOLDER', - fesm2015: '0.0.0-PLACEHOLDER', - typings: '0.0.0-PLACEHOLDER', - }; - - beforeEach(() => { - _ = absoluteFrom; - fs = getFileSystem(); - pkgJsonUpdater = new DirectPackageJsonUpdater(fs); - - mockRequireResolveForLockfile(); - mockRequireResolveForWorkerScript(); - mockRequireResolveForLockFileUnlockerScript(); - - initMockFileSystem(fs, testFiles); - - // Force single-process execution in unit tests by mocking available CPUs to 1. - spyOn(os, 'cpus').and.returnValue([{model: 'Mock CPU'} as any]); - }); - - afterEach(() => { - clearTsConfigCache(); - }); - - /** - * Sets up the esm5 format in the Angular core package. By default, package output - * no longer contains esm5 output, so we process the fesm2015 file into ES5 and - * link it as if its the ESM5 output. - */ - function setupAngularCoreEsm5() { - const pkgPath = _('/node_modules/@angular/core'); - const pkgJsonPath = fs.join(pkgPath, 'package.json'); - const pkgJson = JSON.parse(fs.readFile(pkgJsonPath)) as EntryPointPackageJson; - - fs.ensureDir(fs.join(pkgPath, 'fesm5')); - fs.writeFile( - fs.join(pkgPath, 'fesm5/core.js'), - realFs.readFileSync(runfiles.resolvePackageRelative('./fesm5_angular_core.js'), 'utf8')); - - pkgJson.esm5 = './fesm5/core.js'; - pkgJson.fesm5 = './fesm5/core.js'; - - fs.writeFile(pkgJsonPath, JSON.stringify(pkgJson, null, 2)); - } - - it('should run ngcc without errors for esm2015', () => { - expect(() => mainNgcc({basePath: '/node_modules', propertiesToConsider: ['esm2015']})) - .not.toThrow(); - }); - - it('should run ngcc without errors for esm5', () => { - setupAngularCoreEsm5(); - - expect(() => mainNgcc({ - basePath: '/node_modules', - propertiesToConsider: ['esm5'], - targetEntryPointPath: '@angular/core', - logger: new MockLogger(), - })) - .not.toThrow(); - - expect(loadPackage('@angular/core').__processed_by_ivy_ngcc__).toBeDefined(); - }); - - it('should run ngcc without errors when "main" property is not present', () => { - mainNgcc({ - basePath: '/dist', - propertiesToConsider: ['main', 'es2015'], - logger: new MockLogger(), - }); - - expect(loadPackage('local-package', _('/dist')).__processed_by_ivy_ngcc__).toEqual({ - es2015: '0.0.0-PLACEHOLDER', - typings: '0.0.0-PLACEHOLDER', - }); - }); - - it('should throw, if some of the entry-points are unprocessable', () => { - const createEntryPoint = (name: string, prop: EntryPointJsonProperty): TestFile[] => { - return [ - { - name: _(`/dist/${name}/package.json`), - contents: `{"name": "${name}", "typings": "./index.d.ts", "${prop}": "./index.js"}`, - }, - {name: _(`/dist/${name}/index.js`), contents: 'var DUMMY_DATA = true;'}, - {name: _(`/dist/${name}/index.d.ts`), contents: 'export type DummyData = boolean;'}, - {name: _(`/dist/${name}/index.metadata.json`), contents: 'DUMMY DATA'}, - ]; - }; - - loadTestFiles([ - ...createEntryPoint('processable-1', 'es2015'), - ...createEntryPoint('unprocessable-2', 'main'), - ...createEntryPoint('unprocessable-3', 'main'), - ]); - - expect(() => mainNgcc({ - basePath: '/dist', - propertiesToConsider: ['es2015', 'fesm5', 'module'], - logger: new MockLogger(), - })) - .toThrowError( - 'Unable to process any formats for the following entry-points (tried es2015, fesm5, module): \n' + - ` - ${_('/dist/unprocessable-2')}\n` + - ` - ${_('/dist/unprocessable-3')}`); - }); - - it('should throw, if an error happens during processing', () => { - spyOn(Transformer.prototype, 'transform').and.throwError('Test error.'); - - expect(() => mainNgcc({ - basePath: '/dist', - targetEntryPointPath: 'local-package', - propertiesToConsider: ['main', 'es2015'], - logger: new MockLogger(), - })) - .toThrowError(`Test error.`); - - expect(loadPackage('@angular/core').__processed_by_ivy_ngcc__).toBeUndefined(); - expect(loadPackage('local-package', _('/dist')).__processed_by_ivy_ngcc__).toBeUndefined(); - }); - - it('should report an error, if one of the format-paths is missing or empty', () => { - loadTestFiles([ - // A package with a format-path (main) that points to a missing file. - { - name: _(`/dist/pkg-with-missing-main/package.json`), - contents: ` - { - "name": "pkg-with-missing-main", - "typings": "./index.d.ts", - "es2015": "./index-es2015.js", - "fesm5": "./index-es5.js", - "main": "./index-missing.js" - } - `, - }, - { - name: _('/dist/pkg-with-missing-main/index.d.ts'), - contents: 'export type DummyData = boolean;' - }, - { - name: _('/dist/pkg-with-missing-main/index-es2015.js'), - contents: 'var DUMMY_DATA = true;' - }, - {name: _('/dist/pkg-with-missing-main/index-es5.js'), contents: 'var DUMMY_DATA = true;'}, - {name: _('/dist/pkg-with-missing-main/index.metadata.json'), contents: 'DUMMY DATA'}, - - // A package with a format-path (main) that points to an empty file. - { - name: _(`/dist/pkg-with-empty-main/package.json`), - contents: ` - { - "name": "pkg-with-empty-main", - "typings": "./index.d.ts", - "es2015": "./index-es2015.js", - "fesm5": "./index-es5.js", - "main": "./index-empty.js" - } - `, - }, - { - name: _('/dist/pkg-with-empty-main/index.d.ts'), - contents: 'export type DummyData = boolean;' - }, - {name: _('/dist/pkg-with-empty-main/index-empty.js'), contents: ''}, - {name: _('/dist/pkg-with-empty-main/index-es2015.js'), contents: 'var DUMMY_DATA = true;'}, - {name: _('/dist/pkg-with-empty-main/index-es5.js'), contents: 'var DUMMY_DATA = true;'}, - {name: _('/dist/pkg-with-empty-main/index.metadata.json'), contents: 'DUMMY DATA'}, - ]); - - const logger = new MockLogger(); - mainNgcc({ - basePath: '/dist', - propertiesToConsider: ['es2015', 'main', 'fesm5'], - logger, - }); - - expect(loadPackage('pkg-with-missing-main', _('/dist')).__processed_by_ivy_ngcc__).toEqual({ - es2015: jasmine.any(String), - fesm5: jasmine.any(String), - typings: jasmine.any(String), - }); - expect(loadPackage('pkg-with-empty-main', _('/dist')).__processed_by_ivy_ngcc__).toEqual({ - es2015: jasmine.any(String), - fesm5: jasmine.any(String), - typings: jasmine.any(String), - }); - - expect(logger.logs.error).toEqual([ - [ - 'Failed to compile entry-point pkg-with-missing-main (`main` as unknown format) due to ' + - 'property `main` pointing to a missing or empty file: ./index-missing.js', - ], - [ - 'Failed to compile entry-point pkg-with-empty-main (`main` as unknown format) due to ' + - 'property `main` pointing to a missing or empty file: ./index-empty.js', - ], - ]); - }); - - it('should generate correct metadata for decorated getter/setter properties', () => { - setupAngularCoreEsm5(); - loadTestFiles([ - { - name: _('/node_modules/test-package/index.d.ts'), - contents: ` - export declare class FooDirective { - get bar(): string; - set bar(value: string); - } - export declare class FooModule { - }`, - }, - { - name: _('/node_modules/test-package/index.js'), - contents: ` - var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { - var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; - if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); - else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; - return c > 3 && r && Object.defineProperty(target, key, r), r; - }; - var __metadata = (this && this.__metadata) || function (k, v) { - if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); - }; - import { Directive, Input, NgModule } from '@angular/core'; - var FooDirective = /** @class */ (function () { - function FooDirective() { - } - Object.defineProperty(FooDirective.prototype, "bar", { - get: function () { return 'bar'; }, - set: function (value) { }, - enumerable: false, - configurable: true - }); - __decorate([ - Input(), - __metadata("design:type", String), - __metadata("design:paramtypes", [String]) - ], FooDirective.prototype, "bar", null); - FooDirective = __decorate([ - Directive({ selector: '[foo]' }) - ], FooDirective); - return FooDirective; - }()); - export { FooDirective }; - var FooModule = /** @class */ (function () { - function FooModule() { - } - FooModule = __decorate([ - NgModule({ - declarations: [FooDirective], - }) - ], FooModule); - return FooModule; - }()); - export { FooModule };`, - }, - { - name: _('/node_modules/test-package/index.metadata.json'), - contents: `{}`, - }, - { - name: _('/node_modules/test-package/package.json'), - contents: ` - { - "name": "test-package", - "version": "0.0.1", - "esm5": "./index.js", - "typings": "./index.d.ts" - }`, - }, - ]); - - mainNgcc({ - basePath: '/node_modules', - targetEntryPointPath: 'test-package', - propertiesToConsider: ['esm5'], - }); - - const jsContents = fs.readFile(_(`/node_modules/test-package/index.js`)).replace(/\s+/g, ' '); - expect(jsContents) - .toContain( - '(function () { (typeof ngDevMode === "undefined" || ngDevMode) && ' + - 'ɵngcc0.ɵsetClassMetadata(FooDirective, ' + - '[{ type: Directive, args: [{ selector: \'[foo]\' }] }], ' + - 'function () { return []; }, ' + - '{ bar: [{ type: Input }] }); })();'); - }); - - ['esm5', 'esm2015'].forEach(target => { - it(`should be able to process spread operator inside objects for ${ - target} format (imported helpers)`, - () => { - setupAngularCoreEsm5(); - loadTestFiles([ - { - name: _('/node_modules/test-package/src/index.d.ts'), - contents: ` - export declare class FooDirective { - } - export declare class FooModule { - }`, - }, - { - name: _('/node_modules/test-package/esm2015/src/index.js'), - contents: ` - import { __decorate } from "tslib"; - import { Directive, NgModule } from '@angular/core'; - const a = { '[class.a]': 'true' }; - const b = { '[class.b]': 'true' }; - let FooDirective = class FooDirective { - }; - FooDirective = __decorate([ - Directive({ - selector: '[foo]', - host: Object.assign(Object.assign(Object.assign({}, a), b), { '[class.c]': 'false' }) - }) - ], FooDirective); - export { FooDirective }; - let FooModule = class FooModule { - }; - FooModule = __decorate([ - NgModule({ - declarations: [FooDirective], - }) - ], FooModule); - export { FooModule };`, - }, - { - name: _('/node_modules/test-package/esm2015/index.js'), - contents: `export * from './src/index';`, - }, - { - name: _('/node_modules/test-package/esm5/src/index.js'), - contents: ` - import { __assign, __decorate } from "tslib"; - import { Directive, NgModule } from '@angular/core'; - var a = { '[class.a]': 'true' }; - var b = { '[class.b]': 'true' }; - var FooDirective = /** @class */ (function () { - function FooDirective() { - } - FooDirective = __decorate([ - Directive({ - selector: '[foo]', - host: __assign(__assign(__assign({}, a), b), { '[class.c]': 'false' }) - }) - ], FooDirective); - return FooDirective; - }()); - export { FooDirective }; - var FooModule = /** @class */ (function () { - function FooModule() { - } - FooModule = __decorate([ - NgModule({ - declarations: [FooDirective], - }) - ], FooModule); - return FooModule; - }()); - export { FooModule };`, - }, - { - name: _('/node_modules/test-package/esm5/index.js'), - contents: `export * from './src/index';`, - }, - { - name: _('/node_modules/test-package/index.d.ts'), - contents: `export * from './src/index';`, - }, - { - name: _('/node_modules/test-package/index.metadata.json'), - contents: `{}`, - }, - { - name: _('/node_modules/test-package/package.json'), - contents: ` - { - "name": "test-package", - "version": "0.0.1", - "esm5": "./esm5/index.js", - "esm2015": "./esm2015/index.js", - "module": "./esm2015/index.js", - "typings": "./index.d.ts" - }`, - }, - ]); - - fs.writeFile( - _('/node_modules/tslib/index.d.ts'), - `export declare function __assign(...args: object[]): object;`); - - mainNgcc({ - basePath: '/node_modules', - targetEntryPointPath: 'test-package', - propertiesToConsider: [target], - }); - - const jsContents = fs.readFile(_(`/node_modules/test-package/${target}/src/index.js`)) - .replace(/\s+/g, ' '); - expect(jsContents).toContain('ngcc0.ɵɵclassProp("a", true)("b", true)("c", false)'); - }); - - it(`should be able to process emitted spread operator inside objects for ${ - target} format (emitted helpers)`, - () => { - setupAngularCoreEsm5(); - loadTestFiles([ - { - name: _('/node_modules/test-package/src/index.d.ts'), - contents: ` - export declare class FooDirective { - } - export declare class FooModule { - }`, - }, - { - name: _('/node_modules/test-package/esm2015/src/index.js'), - contents: ` - var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { - var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; - if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); - else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; - return c > 3 && r && Object.defineProperty(target, key, r), r; - }; - import { Directive, NgModule } from '@angular/core'; - const a = { '[class.a]': 'true' }; - const b = { '[class.b]': 'true' }; - let FooDirective = class FooDirective { - }; - FooDirective = __decorate([ - Directive({ - selector: '[foo]', - host: Object.assign(Object.assign(Object.assign({}, a), b), { '[class.c]': 'false' }) - }) - ], FooDirective); - export { FooDirective }; - let FooModule = class FooModule { - }; - FooModule = __decorate([ - NgModule({ - declarations: [FooDirective], - }) - ], FooModule); - export { FooModule };`, - }, - { - name: _('/node_modules/test-package/esm2015/index.js'), - contents: `export * from './src/index';`, - }, - { - name: _('/node_modules/test-package/esm5/src/index.js'), - contents: ` - var __assign = (this && this.__assign) || function () { - __assign = Object.assign || function(t) { - for (var s, i = 1, n = arguments.length; i < n; i++) { - s = arguments[i]; - for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) - t[p] = s[p]; - } - return t; - }; - return __assign.apply(this, arguments); - }; - var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { - var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; - if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); - else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; - return c > 3 && r && Object.defineProperty(target, key, r), r; - }; - import { Directive, NgModule } from '@angular/core'; - var a = { '[class.a]': 'true' }; - var b = { '[class.b]': 'true' }; - var FooDirective = /** @class */ (function () { - function FooDirective() { - } - FooDirective = __decorate([ - Directive({ - selector: '[foo]', - host: __assign(__assign(__assign({}, a), b), { '[class.c]': 'false' }) - }) - ], FooDirective); - return FooDirective; - }()); - export { FooDirective }; - var FooModule = /** @class */ (function () { - function FooModule() { - } - FooModule = __decorate([ - NgModule({ - declarations: [FooDirective], - }) - ], FooModule); - return FooModule; - }()); - export { FooModule };`, - }, - { - name: _('/node_modules/test-package/esm5/index.js'), - contents: `export * from './src/index';`, - }, - { - name: _('/node_modules/test-package/index.d.ts'), - contents: `export * from './src/index';`, - }, - { - name: _('/node_modules/test-package/index.metadata.json'), - contents: `{}`, - }, - { - name: _('/node_modules/test-package/package.json'), - contents: ` - { - "name": "test-package", - "version": "0.0.1", - "esm5": "./esm5/index.js", - "esm2015": "./esm2015/index.js", - "module": "./esm2015/index.js", - "typings": "./index.d.ts" - }`, - }, - ]); - - mainNgcc({ - basePath: '/node_modules', - targetEntryPointPath: 'test-package', - propertiesToConsider: [target], - }); - - const jsContents = fs.readFile(_(`/node_modules/test-package/${target}/src/index.js`)) - .replace(/\s+/g, ' '); - expect(jsContents).toContain('ngcc0.ɵɵclassProp("a", true)("b", true)("c", false)'); - }); - }); - - it(`should be able to detect synthesized constructors in ES5 with downlevelIteration enabled (imported helpers)`, - () => { - setupAngularCoreEsm5(); - loadTestFiles([ - { - name: _('/node_modules/test-package/src/index.d.ts'), - contents: ` - export declare class Base { - } - export declare class SubClass extends Base { - constructor(); - }`, - }, - { - name: _('/node_modules/test-package/esm2015/src/index.js'), - contents: ` - import { __decorate, __metadata } from "tslib"; - import { Injectable } from '@angular/core'; - let Base = class Base { - }; - Base = __decorate([ - Injectable() - ], Base); - export { Base }; - let SubClass = class SubClass extends Base { - constructor() { - // Note: mimic the situation where TS is first emitted into ES2015, resulting - // in the spread super call below, and then downleveled into ES5 using the - // "downlevelIteration" option. - super(...arguments); - this.foo = 'bar'; - } - }; - SubClass = __decorate([ - Injectable(), - __metadata("design:paramtypes", []) - ], SubClass); - export { SubClass };`, - }, - { - name: _('/node_modules/test-package/esm2015/index.js'), - contents: `export * from './src/index';`, - }, - { - name: _('/node_modules/test-package/esm5/src/index.js'), - contents: ` - import { __decorate, __extends, __metadata, __read, __spreadArray } from "tslib"; - import { Injectable } from '@angular/core'; - var Base = /** @class */ (function () { - function Base() { - } - Base = __decorate([ - Injectable() - ], Base); - return Base; - }()); - export { Base }; - var SubClass = /** @class */ (function (_super) { - __extends(SubClass, _super); - function SubClass() { - var _this = _super.apply(this, __spreadArray([], __read(arguments))) || this; - _this.foo = 'bar'; - return _this; - } - SubClass = __decorate([ - Injectable(), - __metadata("design:paramtypes", []) - ], SubClass); - return SubClass; - }(Base)); - export { SubClass };`, - }, - { - name: _('/node_modules/test-package/esm5/index.js'), - contents: `export * from './src/index';`, - }, - { - name: _('/node_modules/test-package/index.d.ts'), - contents: `export * from './src/index';`, - }, - { - name: _('/node_modules/test-package/index.metadata.json'), - contents: `{}`, - }, - { - name: _('/node_modules/test-package/package.json'), - contents: ` - { - "name": "test-package", - "version": "0.0.1", - "esm5": "./esm5/index.js", - "esm2015": "./esm2015/index.js", - "module": "./esm2015/index.js", - "typings": "./index.d.ts" - }`, - }, - ]); - - mainNgcc({ - basePath: '/node_modules', - targetEntryPointPath: 'test-package', - propertiesToConsider: ['esm5'], - }); - - const jsContents = fs.readFile(_(`/node_modules/test-package/esm5/src/index.js`)); - expect(jsContents) - .toContain( - 'var ɵSubClass_BaseFactory; return function SubClass_Factory(t) { return (ɵSubClass_BaseFactory || (ɵSubClass_BaseFactory = ɵngcc0.ɵɵgetInheritedFactory(SubClass)))(t || SubClass); };'); - }); - - it(`should be able to detect synthesized constructors in ES5 with downlevelIteration enabled (emitted helpers)`, - () => { - setupAngularCoreEsm5(); - loadTestFiles([ - { - name: _('/node_modules/test-package/src/index.d.ts'), - contents: ` - export declare class Base { - } - export declare class SubClass extends Base { - constructor(); - }`, - }, - { - name: _('/node_modules/test-package/esm2015/src/index.js'), - contents: ` - var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { - var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; - if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); - else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; - return c > 3 && r && Object.defineProperty(target, key, r), r; - }; - var __metadata = (this && this.__metadata) || function (k, v) { - if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); - }; - import { Injectable } from '@angular/core'; - let Base = class Base { - }; - Base = __decorate([ - Injectable() - ], Base); - export { Base }; - let SubClass = class SubClass extends Base { - constructor() { - // Note: mimic the situation where TS is first emitted into ES2015, resulting - // in the spread super call below, and then downleveled into ES5 using the - // "downlevelIteration" option. - super(...arguments); - this.foo = 'bar'; - } - }; - SubClass = __decorate([ - Injectable(), - __metadata("design:paramtypes", []) - ], SubClass); - export { SubClass };`, - }, - { - name: _('/node_modules/test-package/esm2015/index.js'), - contents: `export * from './src/index';`, - }, - { - name: _('/node_modules/test-package/esm5/src/index.js'), - contents: ` - var __extends = (this && this.__extends) || (function () { - var extendStatics = function (d, b) { - extendStatics = Object.setPrototypeOf || - ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || - function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; }; - return extendStatics(d, b); - }; - return function (d, b) { - if (typeof b !== "function" && b !== null) - throw new TypeError("Class extends value " + String(b) + " is not a constructor or null"); - extendStatics(d, b); - function __() { this.constructor = d; } - d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); - }; - })(); - var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { - var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; - if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); - else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; - return c > 3 && r && Object.defineProperty(target, key, r), r; - }; - var __metadata = (this && this.__metadata) || function (k, v) { - if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); - }; - var __read = (this && this.__read) || function (o, n) { - var m = typeof Symbol === "function" && o[Symbol.iterator]; - if (!m) return o; - var i = m.call(o), r, ar = [], e; - try { - while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value); - } - catch (error) { e = { error: error }; } - finally { - try { - if (r && !r.done && (m = i["return"])) m.call(i); - } - finally { if (e) throw e.error; } - } - return ar; - }; - var __spreadArray = (this && this.__spreadArray) || function (to, from) { - for (var i = 0, il = from.length, j = to.length; i < il; i++, j++) - to[j] = from[i]; - return to; - }; - import { Injectable } from '@angular/core'; - var Base = /** @class */ (function () { - function Base() { - } - Base = __decorate([ - Injectable() - ], Base); - return Base; - }()); - export { Base }; - var SubClass = /** @class */ (function (_super) { - __extends(SubClass, _super); - function SubClass() { - var _this = _super.apply(this, __spreadArray([], __read(arguments))) || this; - _this.foo = 'bar'; - return _this; - } - SubClass = __decorate([ - Injectable(), - __metadata("design:paramtypes", []) - ], SubClass); - return SubClass; - }(Base)); - export { SubClass };`, - }, - { - name: _('/node_modules/test-package/esm5/index.js'), - contents: `export * from './src/index';`, - }, - { - name: _('/node_modules/test-package/index.d.ts'), - contents: `export * from './src/index';`, - }, - { - name: _('/node_modules/test-package/index.metadata.json'), - contents: `{}`, - }, - { - name: _('/node_modules/test-package/package.json'), - contents: ` - { - "name": "test-package", - "version": "0.0.1", - "esm5": "./esm5/index.js", - "esm2015": "./esm2015/index.js", - "module": "./esm2015/index.js", - "typings": "./index.d.ts" - }`, - }, - ]); - - mainNgcc({ - basePath: '/node_modules', - targetEntryPointPath: 'test-package', - propertiesToConsider: ['esm5'], - }); - - const jsContents = fs.readFile(_(`/node_modules/test-package/esm5/src/index.js`)); - expect(jsContents) - .toContain( - 'var ɵSubClass_BaseFactory; return function SubClass_Factory(t) { return (ɵSubClass_BaseFactory || (ɵSubClass_BaseFactory = ɵngcc0.ɵɵgetInheritedFactory(SubClass)))(t || SubClass); };'); - }); - - it('should not add `const` in ES5 generated code', () => { - setupAngularCoreEsm5(); - loadTestFiles([ - { - name: _('/node_modules/test-package/index.d.ts'), - contents: ` - export declare class FooDirective { - } - export declare class FooModule { - }`, - }, - { - name: _('/node_modules/test-package/index.js'), - contents: ` - var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { - var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; - if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); - else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; - return c > 3 && r && Object.defineProperty(target, key, r), r; - }; - import { Directive, NgModule } from '@angular/core'; - var FooDirective = /** @class */ (function () { - function FooDirective() { - } - FooDirective = __decorate([ - Directive({ - selector: '[foo]', - host: { bar: '' }, - }) - ], FooDirective); - return FooDirective; - }()); - export { FooDirective }; - var FooModule = /** @class */ (function () { - function FooModule() { - } - FooModule = __decorate([ - NgModule({ - declarations: [FooDirective], - }) - ], FooModule); - return FooModule; - }()); - export { FooModule };`, - }, - { - name: _('/node_modules/test-package/index.metadata.json'), - contents: `{}`, - }, - { - name: _('/node_modules/test-package/package.json'), - contents: ` - { - "name": "test-package", - "version": "0.0.1", - "esm5": "./index.js", - "typings": "./index.d.ts" - }`, - }, - ]); - - mainNgcc({ - basePath: '/node_modules', - targetEntryPointPath: 'test-package', - propertiesToConsider: ['esm5'], - }); - - const jsContents = fs.readFile(_(`/node_modules/test-package/index.js`)); - expect(jsContents).not.toMatch(/\bconst \w+\s*=/); - }); - - it('should be able to reflect into external libraries', () => { - loadTestFiles([ - { - name: _('/node_modules/lib/src/index.d.ts'), - contents: ` - export * from './constants'; - export * from './module';`, - }, - { - name: _('/node_modules/lib/esm2015/src/index.js'), - contents: ` - export * from './constants'; - export * from './module';`, - }, - { - name: _('/node_modules/lib/src/constants.d.ts'), - contents: ` - export declare const selectorA = "[selector-a]"; - export declare class Selectors { - static readonly B = "[selector-b]"; - }`, - }, - { - name: _('/node_modules/lib/esm2015/src/constants.js'), - contents: ` - export const selectorA = '[selector-a]'; - export class Selectors { - } - Selectors.B = '[selector-b]';`, - }, - { - name: _('/node_modules/lib/src/module.d.ts'), - contents: ` - import { ModuleWithProviders } from '@angular/core'; - export declare class MyOtherModule { - } - export declare class MyModule { - static forRoot(): ModuleWithProviders; - }`, - }, - { - name: _('/node_modules/lib/esm2015/src/module.js'), - contents: ` - var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { - var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; - if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); - else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; - return c > 3 && r && Object.defineProperty(target, key, r), r; - }; - import { NgModule } from '@angular/core'; - let MyOtherModule = class MyOtherModule { - }; - MyOtherModule = __decorate([ - NgModule() - ], MyOtherModule); - export { MyOtherModule }; - export class MyModule { - static forRoot() { - return { ngModule: MyOtherModule }; - } - }`, - }, - { - name: _('/node_modules/lib/esm2015/index.js'), - contents: `export * from './src/index';`, - }, - { - name: _('/node_modules/lib/esm5/src/index.js'), - contents: ` - export * from './constants'; - export * from './module';`, - }, - { - name: _('/node_modules/lib/esm5/src/constants.js'), - contents: ` - export var selectorA = '[selector-a]'; - var Selectors = /** @class */ (function () { - function Selectors() { - } - Selectors.B = '[selector-b]'; - return Selectors; - }()); - export { Selectors };`, - }, - { - name: _('/node_modules/lib/esm5/src/module.js'), - contents: ` - var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { - var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; - if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); - else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; - return c > 3 && r && Object.defineProperty(target, key, r), r; - }; - import { NgModule } from '@angular/core'; - var MyOtherModule = /** @class */ (function () { - function MyOtherModule() { - } - MyOtherModule = __decorate([ - NgModule() - ], MyOtherModule); - return MyOtherModule; - }()); - export { MyOtherModule }; - var MyModule = /** @class */ (function () { - function MyModule() { - } - MyModule.forRoot = function () { - return { ngModule: MyOtherModule }; - }; - return MyModule; - }()); - export { MyModule };`, - }, - { - name: _('/node_modules/lib/esm5/index.js'), - contents: `export * from './src/index';`, - }, - { - name: _('/node_modules/lib/index.d.ts'), - contents: `export * from './src/index';`, - }, - { - name: _('/node_modules/lib/index.metadata.json'), - contents: `{}`, - }, - { - name: _('/node_modules/lib/package.json'), - contents: ` - { - "name": "lib", - "version": "0.0.1", - "esm5": "./esm5/index.js", - "esm2015": "./esm2015/index.js", - "module": "./esm2015/index.js", - "typings": "./index.d.ts" - }`, - }, - ]); - - loadTestFiles([ - { - name: _('/node_modules/test-package/index.d.ts'), - contents: ` - export declare class DirectiveA { - } - export declare class DirectiveB { - } - export declare class FooModule { - }`, - }, - { - name: _('/node_modules/test-package/index.js'), - contents: ` - var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { - var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; - if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); - else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; - return c > 3 && r && Object.defineProperty(target, key, r), r; - }; - import { Directive, NgModule } from '@angular/core'; - import * as lib from 'lib'; - let DirectiveA = class DirectiveA { - }; - DirectiveA = __decorate([ - Directive({ - selector: lib.selectorA, - }) - ], DirectiveA); - export { DirectiveA }; - let DirectiveB = class DirectiveB { - }; - DirectiveB = __decorate([ - Directive({ - selector: lib.Selectors.B, - }) - ], DirectiveB); - export { DirectiveB }; - let FooModule = class FooModule { - }; - FooModule = __decorate([ - NgModule({ - imports: [lib.MyModule.forRoot()], - declarations: [DirectiveA, DirectiveB], - }) - ], FooModule); - export { FooModule };`, - }, - { - name: _('/node_modules/test-package/index.metadata.json'), - contents: `{}`, - }, - { - name: _('/node_modules/test-package/package.json'), - contents: ` - { - "name": "test-package", - "version": "0.0.1", - "esm2015": "./index.js", - "typings": "./index.d.ts" - }`, - }, - ]); - - mainNgcc({ - basePath: '/node_modules', - targetEntryPointPath: 'test-package', - propertiesToConsider: ['esm2015'], - }); - - const jsContents = fs.readFile(_(`/node_modules/test-package/index.js`)); - expect(jsContents).toContain('"selector-a"'); - expect(jsContents).toContain('"selector-b"'); - expect(jsContents).toContain('imports: [ɵngcc1.MyOtherModule]'); - }); - - it('should be able to resolve enum values', () => { - loadTestFiles([ - { - name: _('/node_modules/test-package/src/index.d.ts'), - contents: ` - export declare enum StringEnum { - ValueA = "a", - ValueB = "b" - } - export declare enum NumericEnum { - Value3 = 3, - Value4 = 4 - } - export declare class FooCmp { - } - export declare class FooModule { - }`, - }, - { - name: _('/node_modules/test-package/esm2015/src/index.js'), - contents: ` - var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { - var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; - if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); - else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; - return c > 3 && r && Object.defineProperty(target, key, r), r; - }; - import { Component, NgModule } from '@angular/core'; - export var StringEnum; - (function (StringEnum) { - StringEnum["ValueA"] = "a"; - StringEnum["ValueB"] = "b"; - })(StringEnum || (StringEnum = {})); - export var NumericEnum; - (function (NumericEnum) { - NumericEnum[NumericEnum["Value3"] = 3] = "Value3"; - NumericEnum[NumericEnum["Value4"] = 4] = "Value4"; - })(NumericEnum || (NumericEnum = {})); - let FooCmp = class FooCmp { - }; - FooCmp = __decorate([ - Component({ - template: \`\${StringEnum.ValueA} - \${StringEnum.ValueB} - \${NumericEnum.Value3} - \${NumericEnum.Value4}\`, - }) - ], FooCmp); - export { FooCmp }; - let FooModule = class FooModule { - }; - FooModule = __decorate([ - NgModule({ - declarations: [FooCmp], - }) - ], FooModule); - export { FooModule };`, - }, - { - name: _('/node_modules/test-package/esm2015/index.js'), - contents: `export * from './src/index';`, - }, - { - name: _('/node_modules/test-package/esm5/src/index.js'), - contents: ` - var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { - var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; - if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); - else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; - return c > 3 && r && Object.defineProperty(target, key, r), r; - }; - import { Component, NgModule } from '@angular/core'; - export var StringEnum; - (function (StringEnum) { - StringEnum["ValueA"] = "a"; - StringEnum["ValueB"] = "b"; - })(StringEnum || (StringEnum = {})); - export var NumericEnum; - (function (NumericEnum) { - NumericEnum[NumericEnum["Value3"] = 3] = "Value3"; - NumericEnum[NumericEnum["Value4"] = 4] = "Value4"; - })(NumericEnum || (NumericEnum = {})); - var FooCmp = /** @class */ (function () { - function FooCmp() { - } - FooCmp = __decorate([ - Component({ - template: StringEnum.ValueA + " - " + StringEnum.ValueB + " - " + NumericEnum.Value3 + " - " + NumericEnum.Value4, - }) - ], FooCmp); - return FooCmp; - }()); - export { FooCmp }; - var FooModule = /** @class */ (function () { - function FooModule() { - } - FooModule = __decorate([ - NgModule({ - declarations: [FooCmp], - }) - ], FooModule); - return FooModule; - }()); - export { FooModule };`, - }, - { - name: _('/node_modules/test-package/esm5/index.js'), - contents: `export * from './src/index';`, - }, - { - name: _('/node_modules/test-package/index.d.ts'), - contents: `export * from './src/index';`, - }, - { - name: _('/node_modules/test-package/index.metadata.json'), - contents: `{}`, - }, - { - name: _('/node_modules/test-package/package.json'), - contents: ` - { - "name": "test-package", - "version": "0.0.1", - "esm5": "./esm5/index.js", - "esm2015": "./esm2015/index.js", - "module": "./esm2015/index.js", - "typings": "./index.d.ts" - }`, - }, - ]); - - mainNgcc({ - basePath: '/node_modules', - targetEntryPointPath: 'test-package', - propertiesToConsider: ['esm2015', 'esm5'], - }); - - const es2015Contents = fs.readFile(_(`/node_modules/test-package/esm2015/src/index.js`)); - expect(es2015Contents).toContain('ɵngcc0.ɵɵtext(0, "a - b - 3 - 4")'); - - const es5Contents = fs.readFile(_(`/node_modules/test-package/esm5/src/index.js`)); - expect(es5Contents).toContain('ɵngcc0.ɵɵtext(0, "a - b - 3 - 4")'); - }); - - it('should not crash when scanning for ModuleWithProviders needs to evaluate code from an external package', - () => { - // Regression test for https://github.com/angular/angular/issues/37508 - // During `ModuleWithProviders` analysis, return statements in methods are evaluated using - // the partial evaluator to identify whether they correspond with a `ModuleWithProviders` - // function. If an arbitrary method has a return statement that calls into an external - // module which doesn't have declaration files, ngcc would attempt to reflect on said - // module using the reflection host of the entry-point. This would crash in the case where - // e.g. the entry-point is UMD and the external module would be CommonJS, as the UMD - // reflection host would throw because it is unable to deal with CommonJS. - - // Setup a non-TS package with CommonJS module format - loadTestFiles([ - { - name: _(`/node_modules/identity/package.json`), - contents: `{"name": "identity", "main": "./index.js"}`, - }, - { - name: _(`/node_modules/identity/index.js`), - contents: ` - function identity(x) { return x; }; - exports.identity = identity; - module.exports = identity; - `, - }, - ]); - - // Setup an Angular entry-point with UMD module format that references an export of the - // CommonJS package. - loadTestFiles([ - { - name: _('/node_modules/test-package/package.json'), - contents: '{"name": "test-package", "main": "./index.js", "typings": "./index.d.ts"}' - }, - { - name: _('/node_modules/test-package/index.js'), - contents: ` - (function (global, factory) { - typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('identity')) : - typeof define === 'function' && define.amd ? define('test', ['exports', 'identity'], factory) : - (factory(global.test, global.identity)); - }(this, (function (exports, identity) { 'use strict'; - function Foo(x) { - // The below statement is analyzed for 'ModuleWithProviders', so is evaluated - // by ngcc. The reference into the non-TS CommonJS package used to crash ngcc. - return identity.identity(x); - } - exports.Foo = Foo; - }))); - ` - }, - { - name: _('/node_modules/test-package/index.d.ts'), - contents: 'export declare class Foo { static doSomething(x: any): any; }' - }, - {name: _('/node_modules/test-package/index.metadata.json'), contents: 'DUMMY DATA'}, - ]); - - expect(() => mainNgcc({ - basePath: '/node_modules', - targetEntryPointPath: 'test-package', - propertiesToConsider: ['main'], - })) - .not.toThrow(); - }); - - it('should support inline UMD/CommonJS exports declarations', () => { - // Setup an Angular entry-point in UMD module format that has an inline exports declaration - // referenced by an NgModule. - loadTestFiles([ - { - name: _('/node_modules/test-package/package.json'), - contents: '{"name": "test-package", "main": "./index.js", "typings": "./index.d.ts"}' - }, - { - name: _('/node_modules/test-package/index.js'), - contents: ` - (function (global, factory) { - typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('@angular/core')) : - typeof define === 'function' && define.amd ? define('test', ['exports', 'core'], factory) : - (factory(global.test, global.core)); - }(this, (function (exports, core) { 'use strict'; - exports.FooModule = /** @class */ (function () { - function FooModule() {} - FooModule = __decorate([ - core.NgModule({declarations: exports.declarations}) - ], FooModule); - return FooModule; - }()); - - exports.declarations = [exports.FooDirective]; - - exports.FooDirective = /** @class */ (function () { - function FooDirective() {} - FooDirective = __decorate([ - core.Directive({selector: '[foo]'}) - ], FooDirective); - return FooDirective; - }()); - }))); - ` - }, - { - name: _('/node_modules/test-package/index.d.ts'), - contents: ` - export declare class FooModule { } - export declare class FooDirective { } - ` - }, - {name: _('/node_modules/test-package/index.metadata.json'), contents: 'DUMMY DATA'}, - ]); - - expect(() => mainNgcc({ - basePath: '/node_modules', - targetEntryPointPath: 'test-package', - propertiesToConsider: ['main'], - })) - .not.toThrow(); - - const processedFile = fs.readFile(_('/node_modules/test-package/index.js')); - expect(processedFile) - .toContain( - 'FooModule.ɵmod = /*@__PURE__*/ ɵngcc0.ɵɵdefineNgModule({ type: FooModule });'); - expect(processedFile) - .toContain( - 'ɵngcc0.ɵɵsetNgModuleScope(FooModule, { declarations: function () { return [exports.FooDirective]; } });'); - }); - - it('should support inline UMD/CommonJS exports declarations using an element access as export', - () => { - // Setup an Angular entry-point in UMD module format that uses element access syntax for - // export declarations, as a bundler like Rollup may have generated. - loadTestFiles([ - { - name: _('/node_modules/test-package/package.json'), - contents: '{"name": "test-package", "main": "./index.js", "typings": "./index.d.ts"}' - }, - { - name: _('/node_modules/test-package/index.js'), - contents: ` - (function (global, factory) { - typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('@angular/core')) : - typeof define === 'function' && define.amd ? define('test', ['exports', 'core'], factory) : - (factory(global.test, global.core)); - }(this, (function (exports, core) { 'use strict'; - exports['FooModule'] = /** @class */ (function () { - function FooModule() {} - FooModule = __decorate([ - core.NgModule({declarations: exports.declarations}) - ], FooModule); - return FooModule; - }()); - - exports['declarations'] = [exports['FooDirective']]; - - exports['FooDirective'] = /** @class */ (function () { - function FooDirective() {} - FooDirective = __decorate([ - core.Directive({selector: '[foo]'}) - ], FooDirective); - return FooDirective; - }()); - }))); - ` - }, - { - name: _('/node_modules/test-package/index.d.ts'), - contents: ` - export declare class FooModule { } - export declare class FooDirective { } - ` - }, - {name: _('/node_modules/test-package/index.metadata.json'), contents: 'DUMMY DATA'}, - ]); - - expect(() => mainNgcc({ - basePath: '/node_modules', - targetEntryPointPath: 'test-package', - propertiesToConsider: ['main'], - })) - .not.toThrow(); - - const processedFile = fs.readFile(_('/node_modules/test-package/index.js')); - expect(processedFile) - .toContain( - 'FooModule.ɵmod = /*@__PURE__*/ ɵngcc0.ɵɵdefineNgModule({ type: FooModule });'); - expect(processedFile) - .toContain( - 'ɵngcc0.ɵɵsetNgModuleScope(FooModule, { declarations: function () { return [exports.FooDirective]; } });'); - }); - - it('should not be able to evaluate code in external packages when no .d.ts files are present', - () => { - loadTestFiles([ - { - name: _(`/node_modules/external/package.json`), - contents: `{"name": "external", "main": "./index.js"}`, - }, - { - name: _(`/node_modules/external/index.js`), - contents: ` - export const selector = 'my-selector'; - `, - }, - ]); - - loadTestFiles([ - { - name: _('/node_modules/test-package/src/index.d.ts'), - contents: ` - export declare class FooComponent { - } - export declare class FooModule { - }`, - }, - { - name: _('/node_modules/test-package/esm2015/src/index.js'), - contents: ` - var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { - var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; - if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); - else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; - return c > 3 && r && Object.defineProperty(target, key, r), r; - }; - import { NgModule, Component } from '@angular/core'; - import { selector } from 'external'; - let FooComponent = class FooComponent { - }; - FooComponent = __decorate([ - Component({ - selector, - template: '' - }) - ], FooComponent); - export { FooComponent }; - let FooModule = class FooModule { - }; - FooModule = __decorate([ - NgModule({ - declarations: [FooComponent], - }) - ], FooModule); - export { FooModule };`, - }, - { - name: _('/node_modules/test-package/esm2015/index.js'), - contents: `export * from './src/index';`, - }, - { - name: _('/node_modules/test-package/esm5/src/index.js'), - contents: ` - var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { - var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; - if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); - else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; - return c > 3 && r && Object.defineProperty(target, key, r), r; - }; - import { NgModule, Component } from '@angular/core'; - import { selector } from 'external'; - var FooComponent = /** @class */ (function () { - function FooComponent() { - } - FooComponent = __decorate([ - Component({ - selector: selector, - template: '' - }) - ], FooComponent); - return FooComponent; - }()); - export { FooComponent }; - var FooModule = /** @class */ (function () { - function FooModule() { - } - FooModule = __decorate([ - NgModule({ - declarations: [FooComponent], - }) - ], FooModule); - return FooModule; - }()); - export { FooModule };`, - }, - { - name: _('/node_modules/test-package/esm5/index.js'), - contents: `export * from './src/index';`, - }, - { - name: _('/node_modules/test-package/index.d.ts'), - contents: `export * from './src/index';`, - }, - { - name: _('/node_modules/test-package/index.metadata.json'), - contents: `{}`, - }, - { - name: _('/node_modules/test-package/package.json'), - contents: ` - { - "name": "test-package", - "version": "0.0.1", - "esm5": "./esm5/index.js", - "esm2015": "./esm2015/index.js", - "module": "./esm2015/index.js", - "typings": "./index.d.ts" - }`, - }, - ]); - - try { - mainNgcc({ - basePath: '/node_modules', - targetEntryPointPath: 'test-package', - propertiesToConsider: ['esm2015', 'esm5'], - }); - fail('should have thrown'); - } catch (e: any) { - expect(e.message).toContain( - 'Failed to compile entry-point test-package (`esm2015` as esm2015) due to compilation errors:'); - expect(e.message).toContain('NG1010'); - expect(e.message).toContain('selector must be a string'); - } - }); - - it('should add ɵfac but not duplicate ɵprov properties on injectables', () => { - loadTestFiles([ - { - name: _('/node_modules/test-package/index.d.ts'), - contents: ` - export declare const TestClassToken = "TestClassToken"; - export declare class TestClass { - static ɵprov: unknown; - }`, - }, - { - name: _('/node_modules/test-package/index.js'), - contents: ` - var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { - var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; - if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); - else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; - return c > 3 && r && Object.defineProperty(target, key, r), r; - }; - import { Injectable, ɵɵdefineInjectable } from '@angular/core'; - export const TestClassToken = 'TestClassToken'; - let TestClass = class TestClass { - }; - TestClass.ɵprov = ɵɵdefineInjectable({ factory: () => { }, token: TestClassToken, providedIn: "module" }); - TestClass = __decorate([ - Injectable({ providedIn: 'module' }) - ], TestClass); - export { TestClass };`, - }, - { - name: _('/node_modules/test-package/index.metadata.json'), - contents: `{}`, - }, - { - name: _('/node_modules/test-package/package.json'), - contents: ` - { - "name": "test-package", - "version": "0.0.1", - "esm2015": "./index.js", - "typings": "./index.d.ts" - }`, - }, - ]); - - const before = fs.readFile(_(`/node_modules/test-package/index.js`)); - const originalProp = /ɵprov[^;]+/.exec(before)![0]; - mainNgcc({ - basePath: '/node_modules', - targetEntryPointPath: 'test-package', - propertiesToConsider: ['esm2015'], - }); - const after = fs.readFile(_(`/node_modules/test-package/index.js`)); - - expect(before).toContain(originalProp); - expect(countOccurrences(before, 'ɵprov')).toEqual(1); - expect(countOccurrences(before, 'ɵfac')).toEqual(0); - - expect(after).toContain(originalProp); - expect(countOccurrences(after, 'ɵprov')).toEqual(1); - expect(countOccurrences(after, 'ɵfac')).toEqual(1); - }); - - // This is necessary to ensure XPipeDef.fac is defined when delegated from injectable def - it('should always generate factory def (fac) before injectable def (prov)', () => { - loadTestFiles([ - { - name: _('/node_modules/test-package/index.d.ts'), - contents: ` - import { PipeTransform } from '@angular/core'; - export declare class TestClass implements PipeTransform { - transform(value: any): any; - }`, - }, - { - name: _('/node_modules/test-package/index.js'), - contents: ` - var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { - var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; - if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); - else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; - return c > 3 && r && Object.defineProperty(target, key, r), r; - }; - import { Injectable, Pipe } from '@angular/core'; - let TestClass = class TestClass { - transform(value) { return value; } - }; - TestClass = __decorate([ - Injectable(), - Pipe({ - name: 'myTestPipe' - }) - ], TestClass); - export { TestClass };`, - }, - { - name: _('/node_modules/test-package/index.metadata.json'), - contents: `{}`, - }, - { - name: _('/node_modules/test-package/package.json'), - contents: ` - { - "name": "test-package", - "version": "0.0.1", - "esm2015": "./index.js", - "typings": "./index.d.ts" - }`, - }, - ]); - - mainNgcc({ - basePath: '/node_modules', - targetEntryPointPath: 'test-package', - propertiesToConsider: ['esm2015'], - }); - - const jsContents = fs.readFile(_(`/node_modules/test-package/index.js`)); - expect(jsContents) - .toContain( - `TestClass.ɵfac = function TestClass_Factory(t) { return new (t || TestClass)(); };\n` + - `TestClass.ɵpipe = /*@__PURE__*/ ɵngcc0.ɵɵdefinePipe({ name: "myTestPipe", type: TestClass, pure: true });\n` + - `TestClass.ɵprov = /*@__PURE__*/ ɵngcc0.ɵɵdefineInjectable({`); - }); - - // https://github.com/angular/angular/issues/38883 - it('should recognize ChangeDetectorRef as special symbol for pipes', () => { - loadTestFiles([ - { - name: _('/node_modules/test-package/index.d.ts'), - contents: ` - import { ChangeDetectorRef, PipeTransform } from '@angular/core'; - export declare class TestClass implements PipeTransform { - constructor(cdr: ChangeDetectorRef); - transform(value: any): any; - }`, - }, - { - name: _('/node_modules/test-package/index.js'), - contents: ` - var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { - var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; - if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); - else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; - return c > 3 && r && Object.defineProperty(target, key, r), r; - }; - var __metadata = (this && this.__metadata) || function (k, v) { - if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); - }; - import { ChangeDetectorRef, Pipe } from '@angular/core'; - let TestClass = class TestClass { - constructor(cdr) { } - transform(value) { return value; } - }; - TestClass = __decorate([ - Pipe({ - name: 'myTestPipe' - }), - __metadata("design:paramtypes", [ChangeDetectorRef]) - ], TestClass); - export { TestClass };`, - }, - { - name: _('/node_modules/test-package/index.metadata.json'), - contents: `{}`, - }, - { - name: _('/node_modules/test-package/package.json'), - contents: ` - { - "name": "test-package", - "version": "0.0.1", - "esm2015": "./index.js", - "typings": "./index.d.ts" - }`, - }, - ]); - - mainNgcc({ - basePath: '/node_modules', - targetEntryPointPath: 'test-package', - propertiesToConsider: ['esm2015'], - }); - - const jsContents = fs.readFile(_(`/node_modules/test-package/index.js`)); - expect(jsContents) - .toContain( - `TestClass.ɵfac = function TestClass_Factory(t) { ` + - `return new (t || TestClass)(ɵngcc0.ɵɵdirectiveInject(ɵngcc0.ChangeDetectorRef, 16)); };`); - }); - - it('should use the correct type name in typings files when an export has a different name in source files', - () => { - // We need to make sure that changes to the typings files use the correct name - // static ɵprov: ɵngcc0.ɵɵInjectableDeclaration<ɵangular_packages_common_common_a>; - mainNgcc({ - basePath: '/node_modules', - targetEntryPointPath: '@angular/common', - propertiesToConsider: ['esm2015'] - }); - - // In `@angular/common` the `BrowserPlatformLocation` class gets exported as something like - // `ɵangular_packages_common_common_a`. - const jsContents = fs.readFile(_(`/node_modules/@angular/common/fesm2015/common.js`)); - const exportedNameMatch = - jsContents.match(/export.* BrowserPlatformLocation as ([^ ,}]+)/); - if (exportedNameMatch === null) { - return fail( - 'Expected `/node_modules/@angular/common/fesm2015/common.js` to export `BrowserPlatformLocation` via an alias'); - } - const exportedName = exportedNameMatch[1]; - - // We need to make sure that the flat typings file exports this directly - const dtsContents = fs.readFile(_('/node_modules/@angular/common/common.d.ts')); - expect(dtsContents) - .toContain(`export declare class ${exportedName} extends PlatformLocation`); - // And that ngcc's modifications to that class use the correct (exported) name - expect(dtsContents) - .toContain(`static ɵfac: ɵngcc0.ɵɵFactoryDeclaration<${exportedName}, never>`); - }); - - it('should include constructor metadata in factory definitions', () => { - mainNgcc({ - basePath: '/node_modules', - targetEntryPointPath: '@angular/common', - propertiesToConsider: ['esm2015'] - }); - - const dtsContents = fs.readFile(_('/node_modules/@angular/common/common.d.ts')); - expect(dtsContents) - .toContain( - `static ɵfac: ɵngcc0.ɵɵFactoryDeclaration`); - }); - - it('should add generic type for ModuleWithProviders and generate exports for private modules', - () => { - loadTestFiles([ - { - name: _('/node_modules/test-package/src/index.d.ts'), - contents: ` - import { ModuleWithProviders } from '@angular/core'; - export declare class FooModule { - static forRoot(): ModuleWithProviders; - }`, - }, - { - name: _('/node_modules/test-package/esm2015/src/index.js'), - contents: ` - import { InternalFooModule } from './internal'; - export class FooModule { - static forRoot() { - return { - ngModule: InternalFooModule, - }; - } - }`, - }, - { - name: _('/node_modules/test-package/src/internal.d.ts'), - contents: ` - export declare class InternalFooModule { - }`, - }, - { - name: _('/node_modules/test-package/esm2015/src/internal.js'), - contents: ` - var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { - var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; - if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); - else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; - return c > 3 && r && Object.defineProperty(target, key, r), r; - }; - import { NgModule } from '@angular/core'; - let InternalFooModule = class InternalFooModule { - }; - InternalFooModule = __decorate([ - NgModule() - ], InternalFooModule); - export { InternalFooModule };`, - }, - { - name: _('/node_modules/test-package/esm2015/index.js'), - contents: `export * from './src/index';`, - }, - { - name: _('/node_modules/test-package/esm5/src/index.js'), - contents: ` - import { InternalFooModule } from './internal'; - var FooModule = /** @class */ (function () { - function FooModule() { - } - FooModule.forRoot = function () { - return { - ngModule: InternalFooModule, - }; - }; - return FooModule; - }()); - export { FooModule };`, - }, - { - name: _('/node_modules/test-package/esm5/src/internal.js'), - contents: ` - var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { - var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; - if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); - else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; - return c > 3 && r && Object.defineProperty(target, key, r), r; - }; - import { NgModule } from '@angular/core'; - var InternalFooModule = /** @class */ (function () { - function InternalFooModule() { - } - InternalFooModule = __decorate([ - NgModule() - ], InternalFooModule); - return InternalFooModule; - }()); - export { InternalFooModule };`, - }, - { - name: _('/node_modules/test-package/esm5/index.js'), - contents: `export * from './src/index';`, - }, - { - name: _('/node_modules/test-package/index.d.ts'), - contents: `export * from './src/index';`, - }, - { - name: _('/node_modules/test-package/index.metadata.json'), - contents: `{}`, - }, - { - name: _('/node_modules/test-package/package.json'), - contents: ` - { - "name": "test-package", - "version": "0.0.1", - "esm5": "./esm5/index.js", - "esm2015": "./esm2015/index.js", - "module": "./esm2015/index.js", - "typings": "./index.d.ts" - }`, - }, - ]); - - mainNgcc({ - basePath: '/node_modules', - targetEntryPointPath: 'test-package', - propertiesToConsider: ['esm2015', 'esm5', 'module'], - }); - - // The .d.ts where FooModule is declared should have a generic type added - const dtsContents = fs.readFile(_(`/node_modules/test-package/src/index.d.ts`)); - expect(dtsContents).toContain(`import * as ɵngcc0 from './internal';`); - expect(dtsContents) - .toContain(`static forRoot(): ModuleWithProviders<ɵngcc0.InternalFooModule>`); - - // The public facing .d.ts should export the InternalFooModule - const entryDtsContents = fs.readFile(_(`/node_modules/test-package/index.d.ts`)); - expect(entryDtsContents).toContain(`export {InternalFooModule} from './src/internal';`); - - // The esm2015 index source should export the InternalFooModule - const esm2015Contents = fs.readFile(_(`/node_modules/test-package/esm2015/index.js`)); - expect(esm2015Contents).toContain(`export {InternalFooModule} from './src/internal';`); - - // The esm5 index source should also export the InternalFooModule - const esm5Contents = fs.readFile(_(`/node_modules/test-package/esm5/index.js`)); - expect(esm5Contents).toContain(`export {InternalFooModule} from './src/internal';`); - }); - - it('should use `$localize` calls rather than tagged templates in ES5 generated code', () => { - setupAngularCoreEsm5(); - loadTestFiles([ - { - name: _('/node_modules/test-package/index.d.ts'), - contents: ` - export declare class FooComponent { - } - export declare class FooModule { - }`, - }, - { - name: _('/node_modules/test-package/index.js'), - contents: ` - var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { - var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; - if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); - else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; - return c > 3 && r && Object.defineProperty(target, key, r), r; - }; - import { Component, NgModule } from '@angular/core'; - var FooComponent = /** @class */ (function () { - function FooComponent() { - } - FooComponent = __decorate([ - Component({ - selector: '[foo]', - template: '
A message
' - }) - ], FooComponent); - return FooComponent; - }()); - export { FooComponent }; - var FooModule = /** @class */ (function () { - function FooModule() { - } - FooModule = __decorate([ - NgModule({ - declarations: [FooComponent], - }) - ], FooModule); - return FooModule; - }()); - export { FooModule };`, - }, - { - name: _('/node_modules/test-package/index.metadata.json'), - contents: `{}`, - }, - { - name: _('/node_modules/test-package/package.json'), - contents: ` - { - "name": "test-package", - "version": "0.0.1", - "esm5": "./index.js", - "typings": "./index.d.ts" - }`, - }, - ]); - - mainNgcc({ - basePath: '/node_modules', - targetEntryPointPath: 'test-package', - propertiesToConsider: ['esm5'], - }); - - const jsContents = fs.readFile(_(`/node_modules/test-package/index.js`)); - expect(jsContents).not.toMatch(/\$localize\s*`/); - expect(jsContents) - .toMatch( - /\$localize\(ɵngcc\d+\.__makeTemplateObject\(\[":some:`description`\\u241Fefc92f285b3c24b083a8a594f62c7fccf3118766\\u241F3806630072763809030:A message"], \[":some\\\\:\\\\`description\\\\`\\u241Fefc92f285b3c24b083a8a594f62c7fccf3118766\\u241F3806630072763809030:A message"]\)\);/); - }); - - describe('in async mode', () => { - it('should run ngcc without errors for fesm2015', async () => { - const promise = mainNgcc({ - basePath: '/node_modules', - propertiesToConsider: ['fesm2015'], - async: true, - }); - - expect(promise).toEqual(jasmine.any(Promise)); - await promise; - }); - - it('should reject, if some of the entry-points are unprocessable', async () => { - const createEntryPoint = (name: string, prop: EntryPointJsonProperty): TestFile[] => { - return [ - { - name: _(`/dist/${name}/package.json`), - contents: `{"name": "${name}", "typings": "./index.d.ts", "${prop}": "./index.js"}`, - }, - {name: _(`/dist/${name}/index.js`), contents: 'var DUMMY_DATA = true;'}, - {name: _(`/dist/${name}/index.d.ts`), contents: 'export type DummyData = boolean;'}, - {name: _(`/dist/${name}/index.metadata.json`), contents: 'DUMMY DATA'}, - ]; - }; - - loadTestFiles([ - ...createEntryPoint('processable-1', 'es2015'), - ...createEntryPoint('unprocessable-2', 'main'), - ...createEntryPoint('unprocessable-3', 'main'), - ]); - - const promise = mainNgcc({ - basePath: '/dist', - propertiesToConsider: ['es2015', 'fesm5', 'module'], - logger: new MockLogger(), - async: true, - }); - - await promise.then( - () => Promise.reject('Expected promise to be rejected.'), - err => expect(err).toEqual(new Error( - 'Unable to process any formats for the following entry-points (tried es2015, fesm5, module): \n' + - ` - ${_('/dist/unprocessable-2')}\n` + - ` - ${_('/dist/unprocessable-3')}`))); - }); - - it('should reject, if an error happens during processing', async () => { - spyOn(Transformer.prototype, 'transform').and.throwError('Test error.'); - - const promise = mainNgcc({ - basePath: '/dist', - targetEntryPointPath: 'local-package', - propertiesToConsider: ['main', 'es2015'], - logger: new MockLogger(), - async: true, - }); - - await promise.then( - () => Promise.reject('Expected promise to be rejected.'), - err => expect(err).toEqual(new Error('Test error.'))); - - expect(loadPackage('@angular/core').__processed_by_ivy_ngcc__).toBeUndefined(); - expect(loadPackage('local-package', _('/dist')).__processed_by_ivy_ngcc__).toBeUndefined(); - }); - }); - - describe('with targetEntryPointPath', () => { - it('should only compile the given package entry-point (and its dependencies).', () => { - mainNgcc({basePath: '/node_modules', targetEntryPointPath: '@angular/common/http/testing'}); - expect(loadPackage('@angular/common/http/testing').__processed_by_ivy_ngcc__) - .toEqual(STANDARD_MARKERS); - // * `common/http` is a dependency of `common/http/testing`, so is compiled. - expect(loadPackage('@angular/common/http').__processed_by_ivy_ngcc__) - .toEqual(STANDARD_MARKERS); - // * `core` is a dependency of `common/http`, so is compiled. - expect(loadPackage('@angular/core').__processed_by_ivy_ngcc__).toEqual(STANDARD_MARKERS); - // * `common` is a private (only in .js not .d.ts) dependency so is compiled. - expect(loadPackage('@angular/common').__processed_by_ivy_ngcc__).toEqual(STANDARD_MARKERS); - // * `common/testing` is not a dependency so is not compiled. - expect(loadPackage('@angular/common/testing').__processed_by_ivy_ngcc__).toBeUndefined(); - }); - - it('should not mark a non-Angular package as processed if it is the target', () => { - mainNgcc({basePath: '/node_modules', targetEntryPointPath: 'test-package'}); - - // * `test-package` has no Angular and is not marked as processed. - expect(loadPackage('test-package').__processed_by_ivy_ngcc__).toBeUndefined(); - - // * `core` is a dependency of `test-package`, but it is also not processed, since - // `test-package` was not processed. - expect(loadPackage('@angular/core').__processed_by_ivy_ngcc__).toBeUndefined(); - }); - - it('should not mark a non-Angular package as processed if it is a dependency', () => { - // `test-package-user` is a valid Angular package that depends upon `test-package`. - loadTestFiles([ - { - name: _('/node_modules/test-package-user/package.json'), - contents: - '{"name": "test-package-user", "es2015": "./index.js", "typings": "./index.d.ts"}' - }, - { - name: _('/node_modules/test-package-user/index.js'), - contents: 'import * as x from \'test-package\';' - }, - { - name: _('/node_modules/test-package-user/index.d.ts'), - contents: 'import * as x from \'test-package\';' - }, - {name: _('/node_modules/test-package-user/index.metadata.json'), contents: 'DUMMY DATA'}, - ]); - - mainNgcc({basePath: '/node_modules', targetEntryPointPath: 'test-package-user'}); - - // * `test-package-user` is processed because it is compiled by Angular - expect(loadPackage('test-package-user').__processed_by_ivy_ngcc__).toEqual({ - es2015: '0.0.0-PLACEHOLDER', - typings: '0.0.0-PLACEHOLDER', - }); - - // * `test-package` is a dependency of `test-package-user` but has not been compiled by - // Angular, and so is not marked as processed - expect(loadPackage('test-package').__processed_by_ivy_ngcc__).toBeUndefined(); - - // * `core` is a dependency of `test-package`, but it is not processed, because - // `test-package` was not processed. - expect(loadPackage('@angular/core').__processed_by_ivy_ngcc__).toBeUndefined(); - }); - - it('should report an error if a dependency of the target does not exist', () => { - expect(() => { - mainNgcc({basePath: '/node_modules', targetEntryPointPath: 'invalid-package'}); - }) - .toThrowError( - 'The target entry-point "invalid-package" has missing dependencies:\n - @angular/missing\n'); - }); - }); - - describe('early skipping of target entry-point', () => { - describe('[compileAllFormats === true]', () => { - it('should skip all processing if all the properties are marked as processed', () => { - const logger = new MockLogger(); - markPropertiesAsProcessed('@angular/common/http/testing', SUPPORTED_FORMAT_PROPERTIES); - mainNgcc({ - basePath: '/node_modules', - targetEntryPointPath: '@angular/common/http/testing', - logger, - }); - expect(logger.logs.debug).toContain([ - 'The target entry-point has already been processed' - ]); - }); - - it('should process the target if any `propertyToConsider` is not marked as processed', - () => { - const logger = new MockLogger(); - markPropertiesAsProcessed('@angular/common/http/testing', ['esm2015', 'fesm2015']); - mainNgcc({ - basePath: '/node_modules', - targetEntryPointPath: '@angular/common/http/testing', - propertiesToConsider: ['fesm2015', 'main', 'esm2015'], - logger, - }); - expect(logger.logs.debug).not.toContain([ - 'The target entry-point has already been processed' - ]); - }); - }); - - describe('[compileAllFormats === false]', () => { - it('should process the target if the first matching `propertyToConsider` is not marked as processed', - () => { - const logger = new MockLogger(); - markPropertiesAsProcessed('@angular/common/http/testing', ['esm2015']); - mainNgcc({ - basePath: '/node_modules', - targetEntryPointPath: '@angular/common/http/testing', - propertiesToConsider: ['main', 'esm2015'], - compileAllFormats: false, - logger, - }); - - expect(logger.logs.debug).not.toContain([ - 'The target entry-point has already been processed' - ]); - }); - - it('should skip all processing if the first matching `propertyToConsider` is marked as processed', - () => { - const logger = new MockLogger(); - markPropertiesAsProcessed('@angular/common/http/testing', ['esm2015']); - mainNgcc({ - basePath: '/node_modules', - targetEntryPointPath: '@angular/common/http/testing', - // Simulate a property that does not exist on the package.json and will be ignored. - propertiesToConsider: ['missing', 'esm2015', 'esm5'], - compileAllFormats: false, - logger, - }); - - expect(logger.logs.debug).toContain([ - 'The target entry-point has already been processed' - ]); - }); - }); - - it('should skip all processing if the first matching `propertyToConsider` is marked as processed', - () => { - const logger = new MockLogger(); - markPropertiesAsProcessed('@angular/common/http/testing', ['esm2015']); - mainNgcc({ - basePath: '/node_modules', - targetEntryPointPath: '@angular/common/http/testing', - // Simulate a property that does not exist on the package.json and will be ignored. - propertiesToConsider: ['missing', 'esm2015', 'esm5'], - compileAllFormats: false, - logger, - }); - - expect(logger.logs.debug).toContain([ - 'The target entry-point has already been processed' - ]); - }); - }); - - function markPropertiesAsProcessed(packagePath: string, properties: EntryPointJsonProperty[]) { - const basePath = _('/node_modules'); - const targetPackageJsonPath = fs.join(basePath, packagePath, 'package.json'); - const targetPackage = loadPackage(packagePath); - markAsProcessed( - pkgJsonUpdater, targetPackage, targetPackageJsonPath, ['typings', ...properties]); - } - - describe('with findEntryPointsFromTsConfigProgram', () => { - it('should only compile the package entry-points (and their dependencies) reachable from the program in tsconfig.json.', - () => { - mainNgcc({basePath: '/node_modules', findEntryPointsFromTsConfigProgram: true}); - // * `common/testing` is a dependency of `./y`, so is compiled. - expect(loadPackage('@angular/common/testing').__processed_by_ivy_ngcc__) - .toEqual(STANDARD_MARKERS); - // * `common/http` is a dependency of `./x`, so is compiled. - expect(loadPackage('@angular/common/http').__processed_by_ivy_ngcc__) - .toEqual(STANDARD_MARKERS); - // * `core` is a dependency of `common/http`, so is compiled. - expect(loadPackage('@angular/core').__processed_by_ivy_ngcc__).toEqual(STANDARD_MARKERS); - // * `common` is a private (only in .js not .d.ts) dependency so is compiled. - expect(loadPackage('@angular/common').__processed_by_ivy_ngcc__) - .toEqual(STANDARD_MARKERS); - // * `common/http/testing` is not a dependency of the program so is not compiled. - expect(loadPackage('@angular/common/http/testing').__processed_by_ivy_ngcc__) - .toBeUndefined(); - }); - }); - - it('should clean up outdated artifacts', () => { - loadTestFiles([ - { - name: _('/node_modules/test-package/index.d.ts'), - contents: ` - export declare class FooDirective { - }`, - }, - { - name: _('/node_modules/test-package/index.js'), - contents: ` - var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { - var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; - if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); - else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; - return c > 3 && r && Object.defineProperty(target, key, r), r; - }; - import { Directive } from '@angular/core'; - let FooDirective = class FooDirective { - }; - FooDirective = __decorate([ - Directive({ selector: '[foo]' }) - ], FooDirective); - export { FooDirective };`, - }, - { - name: _('/node_modules/test-package/index.metadata.json'), - contents: `{}`, - }, - { - name: _('/node_modules/test-package/package.json'), - contents: ` - { - "name": "test-package", - "version": "0.0.1", - "esm2015": "./index.js", - "typings": "./index.d.ts" - }`, - }, - ]); - mainNgcc({ - basePath: '/node_modules', - propertiesToConsider: ['esm2015'], - logger: new MockLogger(), - }); - - // Now hack the files to look like it was processed by an outdated version of ngcc - const packageJson = loadPackage('test-package', _('/node_modules')); - packageJson.__processed_by_ivy_ngcc__!.typings = '8.0.0'; - packageJson.main_ivy_ngcc = '__ivy_ngcc__/main.js'; - fs.writeFile(_('/node_modules/test-package/package.json'), JSON.stringify(packageJson)); - fs.writeFile(_('/node_modules/test-package/x.js'), 'processed content'); - fs.writeFile(_('/node_modules/test-package/x.js.__ivy_ngcc_bak'), 'original content'); - fs.ensureDir(_('/node_modules/test-package/__ivy_ngcc__/foo')); - - // Now run ngcc again to see that it cleans out the outdated artifacts - mainNgcc({ - basePath: '/node_modules', - propertiesToConsider: ['esm2015'], - logger: new MockLogger(), - }); - const newPackageJson = loadPackage('test-package', _('/node_modules')); - expect(newPackageJson.__processed_by_ivy_ngcc__).toEqual({ - esm2015: '0.0.0-PLACEHOLDER', - typings: '0.0.0-PLACEHOLDER', - }); - expect(newPackageJson.module_ivy_ngcc).toBeUndefined(); - expect(fs.exists(_('/node_modules/test-package/x.js'))).toBe(true); - expect(fs.exists(_('/node_modules/test-package/x.js.__ivy_ngcc_bak'))).toBe(false); - expect(fs.readFile(_('/node_modules/test-package/x.js'))).toEqual('original content'); - expect(fs.exists(_('/node_modules/test-package/__ivy_ngcc__'))).toBe(false); - }); - - - describe('with propertiesToConsider', () => { - it('should complain if none of the properties in the `propertiesToConsider` list is supported', - () => { - const propertiesToConsider = ['es1337', 'fesm42']; - const errorMessage = - 'No supported format property to consider among [es1337, fesm42]. Supported ' + - 'properties: fesm2015, fesm5, es2015, esm2015, esm5, main, module, browser'; - - expect(() => mainNgcc({basePath: '/node_modules', propertiesToConsider})) - .toThrowError(errorMessage); - }); - - it('should only compile the entry-point formats given in the `propertiesToConsider` list', - () => { - mainNgcc({ - basePath: '/node_modules', - propertiesToConsider: ['main', 'module'], - logger: new MockLogger(), - }); - - // The ES2015 formats are not compiled as they are not in `propertiesToConsider`. - expect(loadPackage('@angular/core').__processed_by_ivy_ngcc__).toEqual({ - main: '0.0.0-PLACEHOLDER', - // `module` and `es2015` are aliases of `fesm2015`. - module: '0.0.0-PLACEHOLDER', - es2015: '0.0.0-PLACEHOLDER', - fesm2015: '0.0.0-PLACEHOLDER', - typings: '0.0.0-PLACEHOLDER', - }); - expect(loadPackage('@angular/common').__processed_by_ivy_ngcc__).toEqual({ - main: '0.0.0-PLACEHOLDER', - // `module` and `es2015` are aliases of `fesm2015`. - module: '0.0.0-PLACEHOLDER', - es2015: '0.0.0-PLACEHOLDER', - fesm2015: '0.0.0-PLACEHOLDER', - typings: '0.0.0-PLACEHOLDER', - }); - expect(loadPackage('@angular/common/testing').__processed_by_ivy_ngcc__).toEqual({ - main: '0.0.0-PLACEHOLDER', - // `module` and `es2015` are aliases for `fesm2015`. - module: '0.0.0-PLACEHOLDER', - es2015: '0.0.0-PLACEHOLDER', - fesm2015: '0.0.0-PLACEHOLDER', - typings: '0.0.0-PLACEHOLDER', - }); - expect(loadPackage('@angular/common/http').__processed_by_ivy_ngcc__).toEqual({ - main: '0.0.0-PLACEHOLDER', - // `module` and `es2015` are aliases for `fesm2015`. - module: '0.0.0-PLACEHOLDER', - es2015: '0.0.0-PLACEHOLDER', - fesm2015: '0.0.0-PLACEHOLDER', - typings: '0.0.0-PLACEHOLDER', - }); - }); - - it('should mark all matching properties as processed in order not to compile them on a subsequent run', - () => { - const logger = new MockLogger(); - const logs = logger.logs.debug; - - // `fesm2015` and `es2015` map to the same file: `./fesm2015/common.js` - mainNgcc({ - basePath: '/node_modules/@angular/common', - propertiesToConsider: ['fesm2015'], - logger, - }); - - expect(logs).not.toContain(['Skipping @angular/common : es2015 (already compiled).']); - expect(loadPackage('@angular/common').__processed_by_ivy_ngcc__).toEqual({ - // `module` and `es2015` are aliases of `fesm2015`. - module: '0.0.0-PLACEHOLDER', - es2015: '0.0.0-PLACEHOLDER', - fesm2015: '0.0.0-PLACEHOLDER', - typings: '0.0.0-PLACEHOLDER', - }); - - // Now, compiling `es2015` should be a no-op. - mainNgcc({ - basePath: '/node_modules/@angular/common', - propertiesToConsider: ['es2015'], - logger, - }); - - expect(logs).toContain(['Skipping @angular/common : es2015 (already compiled).']); - }); - }); - - describe('with compileAllFormats set to false', () => { - it('should only compile the first matching format', () => { - mainNgcc({ - basePath: '/node_modules', - propertiesToConsider: ['module', 'fesm2015', 'main'], - compileAllFormats: false, - logger: new MockLogger(), - }); - // * In the Angular packages fesm2015, module and `es2015` have the same - // underlying format, so both are marked as compiled. - // * The `main` is not compiled because we stopped after the `fesm2015` format. - expect(loadPackage('@angular/core').__processed_by_ivy_ngcc__).toEqual({ - fesm2015: '0.0.0-PLACEHOLDER', - es2015: '0.0.0-PLACEHOLDER', - module: '0.0.0-PLACEHOLDER', - typings: '0.0.0-PLACEHOLDER', - }); - expect(loadPackage('@angular/common').__processed_by_ivy_ngcc__).toEqual({ - fesm2015: '0.0.0-PLACEHOLDER', - es2015: '0.0.0-PLACEHOLDER', - module: '0.0.0-PLACEHOLDER', - typings: '0.0.0-PLACEHOLDER', - }); - expect(loadPackage('@angular/common/testing').__processed_by_ivy_ngcc__).toEqual({ - fesm2015: '0.0.0-PLACEHOLDER', - es2015: '0.0.0-PLACEHOLDER', - module: '0.0.0-PLACEHOLDER', - typings: '0.0.0-PLACEHOLDER', - }); - expect(loadPackage('@angular/common/http').__processed_by_ivy_ngcc__).toEqual({ - fesm2015: '0.0.0-PLACEHOLDER', - es2015: '0.0.0-PLACEHOLDER', - module: '0.0.0-PLACEHOLDER', - typings: '0.0.0-PLACEHOLDER', - }); - }); - - it('should cope with compiling the same entry-point multiple times with different formats', - () => { - mainNgcc({ - basePath: '/node_modules', - propertiesToConsider: ['main'], - compileAllFormats: false, - logger: new MockLogger(), - - }); - expect(loadPackage('@angular/core').__processed_by_ivy_ngcc__).toEqual({ - main: '0.0.0-PLACEHOLDER', - typings: '0.0.0-PLACEHOLDER', - }); - // If ngcc tries to write out the typings files again, this will throw an exception. - mainNgcc({ - basePath: '/node_modules', - propertiesToConsider: ['esm2015'], - compileAllFormats: false, - logger: new MockLogger(), - }); - expect(loadPackage('@angular/core').__processed_by_ivy_ngcc__).toEqual({ - main: '0.0.0-PLACEHOLDER', - esm2015: '0.0.0-PLACEHOLDER', - typings: '0.0.0-PLACEHOLDER', - }); - }); - }); - - describe('with typingsOnly set to true', () => { - it('should only compile the typings', () => { - mainNgcc({ - basePath: '/node_modules', - propertiesToConsider: ['module', 'fesm2015', 'main'], - typingsOnly: true, - compileAllFormats: true, - logger: new MockLogger(), - }); - expect(loadPackage('@angular/core').__processed_by_ivy_ngcc__).toEqual({ - typings: '0.0.0-PLACEHOLDER', - }); - expect(loadPackage('@angular/common').__processed_by_ivy_ngcc__).toEqual({ - typings: '0.0.0-PLACEHOLDER', - }); - expect(loadPackage('@angular/common/testing').__processed_by_ivy_ngcc__).toEqual({ - typings: '0.0.0-PLACEHOLDER', - }); - expect(loadPackage('@angular/common/http').__processed_by_ivy_ngcc__).toEqual({ - typings: '0.0.0-PLACEHOLDER', - }); - - // Doesn't touch original source files - expect(fs.readFile(_(`/node_modules/@angular/common/esm2015/src/common_module.js`))) - .not.toMatch(ANGULAR_CORE_IMPORT_REGEX); - // Or create a backup of the original - expect(fs.exists( - _(`/node_modules/@angular/common/esm2015/src/common_module.js.__ivy_ngcc_bak`))) - .toBe(false); - - // Overwrites .d.ts files - expect(fs.readFile(_(`/node_modules/@angular/common/common.d.ts`))) - .toMatch(ANGULAR_CORE_IMPORT_REGEX); - // And makes a backup - expect(fs.exists(_(`/node_modules/@angular/common/common.d.ts.__ivy_ngcc_bak`))).toBe(true); - }); - - it('should not compile anything when typings have already been processed', () => { - let logger = new MockLogger(); - mainNgcc({ - basePath: '/node_modules', - propertiesToConsider: ['esm2015'], - targetEntryPointPath: '@angular/core', - typingsOnly: true, - logger, - }); - expect(loadPackage('@angular/core').__processed_by_ivy_ngcc__).toEqual({ - typings: '0.0.0-PLACEHOLDER', - }); - expect(fs.readFile(_(`/node_modules/@angular/core/esm2015/src/application_init.js`))) - .not.toMatch(ANGULAR_CORE_IMPORT_REGEX); - expect(logger.logs.debug).toContain([' Successfully compiled @angular/core : esm2015']); - - // Try to process the typings for @angular/core again, now using a different format - // property, to verify that it does not process the entry-point again and that the JS - // files are still untouched. - logger = new MockLogger(); - mainNgcc({ - basePath: '/node_modules', - propertiesToConsider: ['main'], - targetEntryPointPath: '@angular/core', - typingsOnly: true, - logger, - }); - expect(loadPackage('@angular/core').__processed_by_ivy_ngcc__).toEqual({ - typings: '0.0.0-PLACEHOLDER', - }); - expect(fs.readFile(_(`/node_modules/@angular/core/esm2015/src/application_init.js`))) - .not.toMatch(ANGULAR_CORE_IMPORT_REGEX); - expect(logger.logs.debug).toContain([ - 'Skipping @angular/core : typings have already been processed.' - ]); - - // Now also process the typings for @angular/common to verify that its dependency on - // @angular/core, which has already been processed and will therefore be skipped, is able - // to succeed. - logger = new MockLogger(); - mainNgcc({ - basePath: '/node_modules', - propertiesToConsider: ['esm2015'], - targetEntryPointPath: '@angular/common', - typingsOnly: true, - logger, - }); - expect(loadPackage('@angular/core').__processed_by_ivy_ngcc__).toEqual({ - typings: '0.0.0-PLACEHOLDER', - }); - expect(fs.readFile(_(`/node_modules/@angular/core/esm2015/src/application_init.js`))) - .not.toMatch(ANGULAR_CORE_IMPORT_REGEX); - expect(fs.readFile(_(`/node_modules/@angular/common/esm2015/src/common_module.js`))) - .not.toMatch(ANGULAR_CORE_IMPORT_REGEX); - expect(logger.logs.debug).toContain([ - 'Skipping @angular/core : typings have already been processed.' - ]); - expect(logger.logs.debug).toContain([' Successfully compiled @angular/common : esm2015']); - }); - - it('should cope with compiling the same entry-point multiple times with different formats', - () => { - mainNgcc({ - basePath: '/node_modules', - propertiesToConsider: ['main'], - typingsOnly: true, - logger: new MockLogger(), - }); - expect(loadPackage('@angular/core').__processed_by_ivy_ngcc__).toEqual({ - typings: '0.0.0-PLACEHOLDER', - }); - - // If ngcc tries to write out the typings files again, this will throw an exception. - mainNgcc({ - basePath: '/node_modules', - propertiesToConsider: ['esm2015'], - typingsOnly: true, - logger: new MockLogger(), - }); - expect(loadPackage('@angular/core').__processed_by_ivy_ngcc__).toEqual({ - typings: '0.0.0-PLACEHOLDER', - }); - }); - - it('should cope with compiling typings only followed by javascript formats', () => { - mainNgcc({ - basePath: '/node_modules', - propertiesToConsider: ['esm2015', 'main'], - typingsOnly: true, - logger: new MockLogger(), - }); - expect(loadPackage('@angular/core').__processed_by_ivy_ngcc__).toEqual({ - typings: '0.0.0-PLACEHOLDER', - }); - - // If ngcc tries to write out the typings files again, this will throw an exception. - mainNgcc({ - basePath: '/node_modules', - propertiesToConsider: ['esm2015', 'main'], - logger: new MockLogger(), - }); - expect(loadPackage('@angular/core').__processed_by_ivy_ngcc__).toEqual({ - main: '0.0.0-PLACEHOLDER', - esm2015: '0.0.0-PLACEHOLDER', - typings: '0.0.0-PLACEHOLDER', - }); - }); - }); - - describe('with createNewEntryPointFormats', () => { - it('should create new files rather than overwriting the originals', () => { - mainNgcc({ - basePath: '/node_modules', - createNewEntryPointFormats: true, - propertiesToConsider: ['esm2015'], - logger: new MockLogger(), - }); - - // Updates the package.json - expect(loadPackage('@angular/common').esm2015).toEqual('./esm2015/common.js'); - expect((loadPackage('@angular/common') as any).esm2015_ivy_ngcc) - .toEqual('__ivy_ngcc__/esm2015/common.js'); - - // Doesn't touch original files - expect(fs.readFile(_(`/node_modules/@angular/common/esm2015/src/common_module.js`))) - .not.toMatch(ANGULAR_CORE_IMPORT_REGEX); - // Or create a backup of the original - expect(fs.exists( - _(`/node_modules/@angular/common/esm2015/src/common_module.js.__ivy_ngcc_bak`))) - .toBe(false); - - // Creates new files - expect(fs.readFile( - _(`/node_modules/@angular/common/__ivy_ngcc__/esm2015/src/common_module.js`))) - .toMatch(ANGULAR_CORE_IMPORT_REGEX); - - // Copies over files (unchanged) that did not need compiling - expect(fs.exists(_(`/node_modules/@angular/common/__ivy_ngcc__/esm2015/src/version.js`))) - .toBeTrue(); - expect(fs.readFile(_(`/node_modules/@angular/common/__ivy_ngcc__/esm2015/src/version.js`))) - .toEqual(fs.readFile(_(`/node_modules/@angular/common/esm2015/src/version.js`))); - - // Overwrites .d.ts files (as usual) - expect(fs.readFile(_(`/node_modules/@angular/common/common.d.ts`))) - .toMatch(ANGULAR_CORE_IMPORT_REGEX); - expect(fs.exists(_(`/node_modules/@angular/common/common.d.ts.__ivy_ngcc_bak`))).toBe(true); - }); - - it('should update `package.json` for all matching format properties', () => { - mainNgcc({ - basePath: '/node_modules/@angular/core', - createNewEntryPointFormats: true, - propertiesToConsider: ['fesm2015', 'main'], - }); - - const pkg: any = loadPackage('@angular/core'); - - // `es2015` and `module` are aliases of `fesm2015`. - expect(pkg.fesm2015).toEqual('./fesm2015/core.js'); - expect(pkg.es2015).toEqual('./fesm2015/core.js'); - expect(pkg.module).toEqual('./fesm2015/core.js'); - expect(pkg.fesm2015_ivy_ngcc).toEqual('__ivy_ngcc__/fesm2015/core.js'); - expect(pkg.es2015_ivy_ngcc).toEqual('__ivy_ngcc__/fesm2015/core.js'); - expect(pkg.module_ivy_ngcc).toEqual('__ivy_ngcc__/fesm2015/core.js'); - - expect(pkg.main).toEqual('./bundles/core.umd.js'); - expect(pkg.main_ivy_ngcc).toEqual('__ivy_ngcc__/bundles/core.umd.js'); - }); - - it('should update `package.json` deterministically (regardless of entry-point processing order)', - () => { - // Ensure formats are not marked as processed in `package.json` at the beginning. - let pkg = loadPackage('@angular/core'); - expectNotToHaveProp(pkg, 'main_ivy_ngcc'); - expectNotToHaveProp(pkg, 'esm2015_ivy_ngcc'); - expectNotToHaveProp(pkg, 'fesm2015_ivy_ngcc'); - expectNotToHaveProp(pkg, 'module_ivy_ngcc'); - expectNotToHaveProp(pkg, '__processed_by_ivy_ngcc__'); - - // Process `fesm2015` and update `package.json`. - pkg = processFormatAndUpdatePackageJson('fesm2015'); - expectNotToHaveProp(pkg, 'main_ivy_ngcc'); - expectNotToHaveProp(pkg, 'esm2015_ivy_ngcc'); - expectToHaveProp(pkg, 'fesm2015_ivy_ngcc'); - expectToHaveProp(pkg, 'module_ivy_ngcc'); - expectToHaveProp(pkg.__processed_by_ivy_ngcc__!, 'fesm2015'); - - // Process `esm2015` and update `package.json`. - pkg = processFormatAndUpdatePackageJson('esm2015'); - expectNotToHaveProp(pkg, 'main_ivy_ngcc'); - expectToHaveProp(pkg, 'esm2015_ivy_ngcc'); - expectToHaveProp(pkg, 'fesm2015_ivy_ngcc'); - expectToHaveProp(pkg, 'module_ivy_ngcc'); - expectToHaveProp(pkg.__processed_by_ivy_ngcc__!, 'esm2015'); - - // Process `main` and update `package.json`. - pkg = processFormatAndUpdatePackageJson('main'); - expectToHaveProp(pkg, 'main_ivy_ngcc'); - expectToHaveProp(pkg, 'esm2015_ivy_ngcc'); - expectToHaveProp(pkg, 'fesm2015_ivy_ngcc'); - expectToHaveProp(pkg, 'module_ivy_ngcc'); - expectToHaveProp(pkg.__processed_by_ivy_ngcc__!, 'main'); - - // Ensure the properties are in deterministic order (regardless of processing order). - const pkgKeys = stringifyKeys(pkg); - expect(pkgKeys).toContain('|main_ivy_ngcc|main|'); - expect(pkgKeys).toContain('|fesm2015_ivy_ngcc|fesm2015|'); - expect(pkgKeys).toContain('|esm2015_ivy_ngcc|esm2015|'); - - // NOTE: - // Along with the first format that is processed, the typings are processed as well. - // Also, once a property has been processed, alias properties as also marked as - // processed. Aliases properties are properties that point to the same entry-point file. - // For example: - // - `fesm2015` <=> `module <=> es2015` - expect(stringifyKeys(pkg.__processed_by_ivy_ngcc__!)) - .toBe('|es2015|esm2015|fesm2015|main|module|typings|'); - - // Helpers - function expectNotToHaveProp(obj: object, prop: string) { - expect(obj.hasOwnProperty(prop)) - .toBe( - false, - `Expected object not to have property '${prop}': ${ - JSON.stringify(obj, null, 2)}`); - } - - function expectToHaveProp(obj: object, prop: string) { - expect(obj.hasOwnProperty(prop)) - .toBe( - true, - `Expected object to have property '${prop}': ${JSON.stringify(obj, null, 2)}`); - } - - function processFormatAndUpdatePackageJson(formatProp: string) { - mainNgcc({ - basePath: '/node_modules/@angular/core', - createNewEntryPointFormats: true, - propertiesToConsider: [formatProp], - }); - return loadPackage('@angular/core'); - } - - function stringifyKeys(obj: object) { - return `|${Object.keys(obj).join('|')}|`; - } - }); - }); - - describe('with ignoreEntryPointManifest', () => { - it('should not read the entry-point manifest file', () => { - // Ensure there is a lock-file. Otherwise the manifest will not be written - fs.writeFile(_('/yarn.lock'), 'DUMMY YARN LOCK FILE'); - // Populate the manifest file - mainNgcc( - {basePath: '/node_modules', propertiesToConsider: ['main'], logger: new MockLogger()}); - // Check that common/testing ES5 was processed - let commonTesting = - JSON.parse(fs.readFile(_('/node_modules/@angular/common/testing/package.json'))) as - EntryPointPackageJson; - expect(hasBeenProcessed(commonTesting, 'main')).toBe(true); - expect(hasBeenProcessed(commonTesting, 'esm2015')).toBe(false); - // Modify the manifest to test that is has no effect - let manifest = JSON.parse(fs.readFile(_('/node_modules/__ngcc_entry_points__.json'))) as - EntryPointManifestFile; - manifest.entryPointPaths = - manifest.entryPointPaths.filter(paths => paths[1] !== '@angular/common/testing'); - fs.writeFile(_('/node_modules/__ngcc_entry_points__.json'), JSON.stringify(manifest)); - // Now run ngcc again ignoring this manifest but trying to process ES2015, which are not yet - // processed. - mainNgcc({ - basePath: '/node_modules', - propertiesToConsider: ['esm2015'], - logger: new MockLogger(), - invalidateEntryPointManifest: true, - }); - // Check that common/testing ES2015 is now processed, despite the manifest not listing it - commonTesting = - JSON.parse(fs.readFile(_('/node_modules/@angular/common/testing/package.json'))) as - EntryPointPackageJson; - expect(hasBeenProcessed(commonTesting, 'main')).toBe(true); - expect(hasBeenProcessed(commonTesting, 'esm2015')).toBe(true); - // Check that the newly computed manifest has written to disk, containing the path that we - // had removed earlier. - manifest = JSON.parse(fs.readFile(_('/node_modules/__ngcc_entry_points__.json'))) as - EntryPointManifestFile; - expect(manifest.entryPointPaths).toContain([ - '@angular/common', - '@angular/common/testing', - [ - _('/node_modules/@angular/core'), _('/node_modules/@angular/common'), - _('/node_modules/rxjs') - ], - ]); - }); - }); - - describe('diagnostics', () => { - it('should fail with formatted diagnostics when an error diagnostic is produced, if targetEntryPointPath is provided', - () => { - loadTestFiles([ - { - name: _('/node_modules/fatal-error/package.json'), - contents: - '{"name": "fatal-error", "es2015": "./index.js", "typings": "./index.d.ts"}', - }, - {name: _('/node_modules/fatal-error/index.metadata.json'), contents: 'DUMMY DATA'}, - { - name: _('/node_modules/fatal-error/index.js'), - contents: ` - import {Component} from '@angular/core'; - export class FatalError {} - FatalError.decorators = [ - {type: Component, args: [{selector: 'fatal-error'}]} - ]; - `, - }, - { - name: _('/node_modules/fatal-error/index.d.ts'), - contents: ` - export declare class FatalError {} - `, - }, - ]); - - try { - mainNgcc({ - basePath: '/node_modules', - targetEntryPointPath: 'fatal-error', - propertiesToConsider: ['es2015'] - }); - fail('should have thrown'); - } catch (e: any) { - expect(e.message).toContain( - 'Failed to compile entry-point fatal-error (`es2015` as esm2015) due to compilation errors:'); - expect(e.message).toContain('NG2001'); - expect(e.message).toContain('component is missing a template'); - } - }); - - it('should not fail but log an error with formatted diagnostics when an error diagnostic is produced, if targetEntryPoint is not provided and errorOnFailedEntryPoint is false', - () => { - loadTestFiles([ - { - name: _('/node_modules/fatal-error/package.json'), - contents: - '{"name": "fatal-error", "es2015": "./index.js", "typings": "./index.d.ts"}', - }, - {name: _('/node_modules/fatal-error/index.metadata.json'), contents: 'DUMMY DATA'}, - { - name: _('/node_modules/fatal-error/index.js'), - contents: ` - import {Component} from '@angular/core'; - export class FatalError {} - FatalError.decorators = [ - {type: Component, args: [{selector: 'fatal-error'}]} - ];`, - }, - { - name: _('/node_modules/fatal-error/index.d.ts'), - contents: `export declare class FatalError {}`, - }, - { - name: _('/node_modules/dependent/package.json'), - contents: '{"name": "dependent", "es2015": "./index.js", "typings": "./index.d.ts"}', - }, - {name: _('/node_modules/dependent/index.metadata.json'), contents: 'DUMMY DATA'}, - { - name: _('/node_modules/dependent/index.js'), - contents: ` - import {Component} from '@angular/core'; - import {FatalError} from 'fatal-error'; - export class Dependent {} - Dependent.decorators = [ - {type: Component, args: [{selector: 'dependent', template: ''}]} - ];`, - }, - { - name: _('/node_modules/dependent/index.d.ts'), - contents: `export declare class Dependent {}`, - }, - { - name: _('/node_modules/independent/package.json'), - contents: - '{"name": "independent", "es2015": "./index.js", "typings": "./index.d.ts"}', - }, - {name: _('/node_modules/independent/index.metadata.json'), contents: 'DUMMY DATA'}, - { - name: _('/node_modules/independent/index.js'), - contents: ` - import {Component} from '@angular/core'; - export class Independent {} - Independent.decorators = [ - {type: Component, args: [{selector: 'independent', template: ''}]} - ];`, - }, - { - name: _('/node_modules/independent/index.d.ts'), - contents: `export declare class Independent {}`, - }, - ]); - - const logger = new MockLogger(); - mainNgcc({ - basePath: '/node_modules', - propertiesToConsider: ['es2015'], - errorOnFailedEntryPoint: false, - logger, - }); - expect(logger.logs.error.length).toEqual(1); - const message = logger.logs.error[0][0]; - expect(message).toContain( - 'Failed to compile entry-point fatal-error (`es2015` as esm2015) due to compilation errors:'); - expect(message).toContain('NG2001'); - expect(message).toContain('component is missing a template'); - - expect(hasBeenProcessed(loadPackage('fatal-error', _('/node_modules')), 'es2015')) - .toBe(false); - expect(hasBeenProcessed(loadPackage('dependent', _('/node_modules')), 'es2015')) - .toBe(false); - expect(hasBeenProcessed(loadPackage('independent', _('/node_modules')), 'es2015')) - .toBe(true); - }); - }); - - describe('logger', () => { - it('should log info message to the console by default', () => { - const consoleInfoSpy = spyOn(console, 'info'); - mainNgcc({basePath: '/node_modules', propertiesToConsider: ['esm2015']}); - expect(consoleInfoSpy) - .toHaveBeenCalledWith( - '- @angular/common/http [esm2015/esm2015] (https://github.com/angular/angular.git)'); - }); - - it('should use a custom logger if provided', () => { - const logger = new MockLogger(); - mainNgcc({ - basePath: '/node_modules', - propertiesToConsider: ['esm2015'], - logger, - }); - expect(logger.logs.info).toContain([ - '- @angular/common/http [esm2015/esm2015] (https://github.com/angular/angular.git)' - ]); - }); - }); - - describe('with pathMappings', () => { - it('should infer the @app pathMapping from a local tsconfig.json path', () => { - fs.writeFile( - _('/tsconfig.json'), - JSON.stringify({compilerOptions: {paths: {'@app/*': ['dist/*']}, baseUrl: './'}})); - const logger = new MockLogger(); - mainNgcc({basePath: '/dist', propertiesToConsider: ['es2015'], logger}); - expect(loadPackage('local-package', _('/dist')).__processed_by_ivy_ngcc__).toEqual({ - es2015: '0.0.0-PLACEHOLDER', - typings: '0.0.0-PLACEHOLDER', - }); - expect(loadPackage('local-package-2', _('/dist')).__processed_by_ivy_ngcc__).toEqual({ - es2015: '0.0.0-PLACEHOLDER', - typings: '0.0.0-PLACEHOLDER', - }); - // The local-package-3 and local-package-4 will not be processed because there is no path - // mappings for `@x` and plain local imports. - expect(loadPackage('local-package-3', _('/dist')).__processed_by_ivy_ngcc__) - .toBeUndefined(); - expect(logger.logs.debug).toContain([ - `Invalid entry-point ${_('/dist/local-package-3')}.`, - 'It is missing required dependencies:\n - @x/local-package' - ]); - expect(loadPackage('local-package-4', _('/dist')).__processed_by_ivy_ngcc__) - .toBeUndefined(); - expect(logger.logs.debug).toContain([ - `Invalid entry-point ${_('/dist/local-package-4')}.`, - 'It is missing required dependencies:\n - local-package' - ]); - }); - - it('should read the @x pathMapping from a specified tsconfig.json path', () => { - fs.writeFile( - _('/tsconfig.app.json'), - JSON.stringify({compilerOptions: {paths: {'@x/*': ['dist/*']}, baseUrl: './'}})); - const logger = new MockLogger(); - mainNgcc({ - basePath: '/dist', - propertiesToConsider: ['es2015'], - tsConfigPath: _('/tsconfig.app.json'), - logger - }); - expect(loadPackage('local-package', _('/dist')).__processed_by_ivy_ngcc__).toEqual({ - es2015: '0.0.0-PLACEHOLDER', - typings: '0.0.0-PLACEHOLDER', - }); - expect(loadPackage('local-package-3', _('/dist')).__processed_by_ivy_ngcc__).toEqual({ - es2015: '0.0.0-PLACEHOLDER', - typings: '0.0.0-PLACEHOLDER', - }); - // The local-package-2 and local-package-4 will not be processed because there is no path - // mappings for `@app` and plain local imports. - expect(loadPackage('local-package-2', _('/dist')).__processed_by_ivy_ngcc__) - .toBeUndefined(); - expect(logger.logs.debug).toContain([ - `Invalid entry-point ${_('/dist/local-package-2')}.`, - 'It is missing required dependencies:\n - @app/local-package' - ]); - expect(loadPackage('local-package-4', _('/dist')).__processed_by_ivy_ngcc__) - .toBeUndefined(); - expect(logger.logs.debug).toContain([ - `Invalid entry-point ${_('/dist/local-package-4')}.`, - 'It is missing required dependencies:\n - local-package' - ]); - }); - - it('should use the explicit `pathMappings`, ignoring the local tsconfig.json settings', - () => { - const logger = new MockLogger(); - fs.writeFile( - _('/tsconfig.json'), - JSON.stringify({compilerOptions: {paths: {'@app/*': ['dist/*']}, baseUrl: './'}})); - mainNgcc({ - basePath: '/node_modules', - propertiesToConsider: ['es2015'], - pathMappings: {paths: {'*': ['dist/*']}, baseUrl: '/'}, - logger - }); - expect(loadPackage('@angular/core').__processed_by_ivy_ngcc__).toEqual({ - // `module` and `es2015` are aliases for `fesm2015`. - module: '0.0.0-PLACEHOLDER', - es2015: '0.0.0-PLACEHOLDER', - fesm2015: '0.0.0-PLACEHOLDER', - typings: '0.0.0-PLACEHOLDER', - }); - expect(loadPackage('local-package', _('/dist')).__processed_by_ivy_ngcc__).toEqual({ - es2015: '0.0.0-PLACEHOLDER', - typings: '0.0.0-PLACEHOLDER', - }); - expect(loadPackage('local-package-4', _('/dist')).__processed_by_ivy_ngcc__).toEqual({ - es2015: '0.0.0-PLACEHOLDER', - typings: '0.0.0-PLACEHOLDER', - }); - // The local-package-2 and local-package-3 will not be processed because there is no path - // mappings for `@app` and `@x` local imports. - expect(loadPackage('local-package-2', _('/dist')).__processed_by_ivy_ngcc__) - .toBeUndefined(); - expect(logger.logs.debug).toContain([ - `Invalid entry-point ${_('/dist/local-package-2')}.`, - 'It is missing required dependencies:\n - @app/local-package' - ]); - expect(loadPackage('local-package-3', _('/dist')).__processed_by_ivy_ngcc__) - .toBeUndefined(); - expect(logger.logs.debug).toContain([ - `Invalid entry-point ${_('/dist/local-package-3')}.`, - 'It is missing required dependencies:\n - @x/local-package' - ]); - }); - - it('should not use pathMappings from a local tsconfig.json path if tsConfigPath is null', - () => { - const logger = new MockLogger(); - fs.writeFile( - _('/tsconfig.json'), - JSON.stringify({compilerOptions: {paths: {'@app/*': ['dist/*']}, baseUrl: './'}})); - mainNgcc({ - basePath: '/dist', - propertiesToConsider: ['es2015'], - tsConfigPath: null, - logger, - }); - expect(loadPackage('local-package', _('/dist')).__processed_by_ivy_ngcc__).toEqual({ - es2015: '0.0.0-PLACEHOLDER', - typings: '0.0.0-PLACEHOLDER', - }); - // Since the tsconfig is not loaded, the `@app/local-package` import in `local-package-2` - // is not path-mapped correctly, and so it fails to be processed. - expect(loadPackage('local-package-2', _('/dist')).__processed_by_ivy_ngcc__) - .toBeUndefined(); - expect(logger.logs.debug).toContain([ - `Invalid entry-point ${_('/dist/local-package-2')}.`, - 'It is missing required dependencies:\n - @app/local-package' - ]); - }); - }); - - describe('whitespace preservation', () => { - it('should default not to preserve whitespace', () => { - mainNgcc({basePath: '/dist', propertiesToConsider: ['es2015']}); - expect(loadPackage('local-package', _('/dist')).__processed_by_ivy_ngcc__).toEqual({ - es2015: '0.0.0-PLACEHOLDER', - typings: '0.0.0-PLACEHOLDER', - }); - expect(fs.readFile(_('/dist/local-package/index.js'))) - .toMatch(/ɵɵtext\(\d+, " Hello\\n"\);/); - }); - - it('should preserve whitespace if set in a loaded tsconfig.json', () => { - fs.writeFile( - _('/tsconfig.json'), - JSON.stringify({angularCompilerOptions: {preserveWhitespaces: true}})); - mainNgcc({basePath: '/dist', propertiesToConsider: ['es2015']}); - expect(loadPackage('local-package', _('/dist')).__processed_by_ivy_ngcc__).toEqual({ - es2015: '0.0.0-PLACEHOLDER', - typings: '0.0.0-PLACEHOLDER', - }); - expect(fs.readFile(_('/dist/local-package/index.js'))) - .toMatch(/ɵɵtext\(\d+, "\\n Hello\\n"\);/); - }); - - it('should not preserve whitespace if set to false in a loaded tsconfig.json', () => { - fs.writeFile( - _('/tsconfig.json'), - JSON.stringify({angularCompilerOptions: {preserveWhitespaces: false}})); - mainNgcc({basePath: '/dist', propertiesToConsider: ['es2015']}); - expect(loadPackage('local-package', _('/dist')).__processed_by_ivy_ngcc__).toEqual({ - es2015: '0.0.0-PLACEHOLDER', - typings: '0.0.0-PLACEHOLDER', - }); - expect(fs.readFile(_('/dist/local-package/index.js'))) - .toMatch(/ɵɵtext\(\d+, " Hello\\n"\);/); - }); - }); - - describe('with Closure Compiler', () => { - it('should give closure annotated output with annotateForClosureCompiler: true', () => { - fs.writeFile( - _('/tsconfig.json'), - JSON.stringify({angularCompilerOptions: {annotateForClosureCompiler: true}})); - mainNgcc({basePath: '/dist', propertiesToConsider: ['es2015']}); - const jsContents = fs.readFile(_(`/dist/local-package/index.js`)); - expect(jsContents).toContain('/** @nocollapse */\nAppComponent.ɵcmp ='); - }); - it('should default to not give closure annotated output', () => { - mainNgcc({basePath: '/dist', propertiesToConsider: ['es2015']}); - const jsContents = fs.readFile(_(`/dist/local-package/index.js`)); - expect(jsContents).not.toContain('@nocollapse'); - }); - }); - - describe('with configuration files', () => { - it('should process a configured deep-import as an entry-point', () => { - loadTestFiles([ - { - name: _('/ngcc.config.js'), - contents: `module.exports = { packages: { - 'deep_import': { - entryPoints: { - './entry_point': { override: { typings: '../entry_point.d.ts', es2015: '../entry_point.js' } } - } - } - }};`, - }, - { - name: _('/node_modules/deep_import/package.json'), - contents: '{"name": "deep_import", "es2015": "./index.js", "typings": "./index.d.ts"}', - }, - { - name: _('/node_modules/deep_import/entry_point.js'), - contents: ` - import {Component} from '@angular/core'; - @Component({selector: 'entry-point'}) - export class EntryPoint {} - `, - }, - { - name: _('/node_modules/deep_import/entry_point.d.ts'), - contents: ` - import {Component} from '@angular/core'; - @Component({selector: 'entry-point'}) - export class EntryPoint {} - `, - }, - ]); - mainNgcc({ - basePath: '/node_modules', - targetEntryPointPath: 'deep_import/entry_point', - propertiesToConsider: ['es2015'] - }); - // The containing package is not processed - expect(loadPackage('deep_import').__processed_by_ivy_ngcc__).toBeUndefined(); - // But the configured entry-point and its dependency (@angular/core) are processed. - expect(loadPackage('deep_import/entry_point').__processed_by_ivy_ngcc__).toEqual({ - es2015: '0.0.0-PLACEHOLDER', - typings: '0.0.0-PLACEHOLDER', - }); - expect(loadPackage('@angular/core').__processed_by_ivy_ngcc__).toEqual({ - module: '0.0.0-PLACEHOLDER', - es2015: '0.0.0-PLACEHOLDER', - fesm2015: '0.0.0-PLACEHOLDER', - typings: '0.0.0-PLACEHOLDER', - }); - }); - - it('should not process ignored entry-points', () => { - loadTestFiles([ - { - name: _('/ngcc.config.js'), - contents: ` - module.exports = { - packages: { - '@angular/core': { - entryPoints: { - './testing': {ignore: true}, - }, - }, - '@angular/common': { - entryPoints: { - '.': {ignore: true}, - './http': {override: {fesm2015: undefined}}, - }, - }, - }, - }; - `, - }, - ]); - - mainNgcc({basePath: '/node_modules', propertiesToConsider: ['es2015']}); - - // We process core but not core/testing. - expect(loadPackage('@angular/core').__processed_by_ivy_ngcc__).toEqual({ - module: '0.0.0-PLACEHOLDER', - es2015: '0.0.0-PLACEHOLDER', - fesm2015: '0.0.0-PLACEHOLDER', - typings: '0.0.0-PLACEHOLDER', - }); - expect(loadPackage('@angular/core/testing').__processed_by_ivy_ngcc__).toBeUndefined(); - - // We do not compile common but we do compile its sub-entry-points. - expect(loadPackage('@angular/common').__processed_by_ivy_ngcc__).toBeUndefined(); - expect(loadPackage('@angular/common/http').__processed_by_ivy_ngcc__).toEqual({ - // `fesm2015` is not processed, because the ngcc config removes it. - // fesm2015: '0.0.0-PLACEHOLDER', - module: '0.0.0-PLACEHOLDER', - es2015: '0.0.0-PLACEHOLDER', - typings: '0.0.0-PLACEHOLDER', - }); - }); - - it('should support removing a format property by setting it to `undefined`', () => { - loadTestFiles([ - { - name: _('/ngcc.config.js'), - contents: ` - module.exports = { - packages: { - 'test-package': { - entryPoints: { - '.': { - override: { - fesm2015: undefined, - }, - }, - }, - }, - }, - }; - `, - }, - { - name: _('/node_modules/test-package/package.json'), - contents: ` - { - "name": "test-package", - "fesm2015": "./index.es2015.js", - "fesm5": "./index.es5.js", - "typings": "./index.d.ts" - } - `, - }, - { - name: _('/node_modules/test-package/index.es5.js'), - contents: ` - var TestService = (function () { - function TestService() { - } - return TestService; - }()); - `, - }, - { - name: _('/node_modules/test-package/index.d.js'), - contents: ` - export declare class TestService {} - `, - }, - ]); - - mainNgcc({ - basePath: '/node_modules', - targetEntryPointPath: 'test-package', - propertiesToConsider: ['fesm2015', 'fesm5'], - }); - - expect(loadPackage('test-package').__processed_by_ivy_ngcc__).toEqual({ - fesm5: '0.0.0-PLACEHOLDER', - typings: '0.0.0-PLACEHOLDER', - }); - }); - }); - - describe('undecorated child class migration', () => { - it('should generate a directive definition with CopyDefinitionFeature for an undecorated child directive', - () => { - loadTestFiles([ - { - name: _('/node_modules/test-package/index.d.ts'), - contents: ` - export declare class BaseDir { - } - export declare class DerivedDir extends BaseDir { - } - export declare class Module { - }`, - }, - { - name: _('/node_modules/test-package/index.js'), - contents: ` - var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { - var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; - if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); - else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; - return c > 3 && r && Object.defineProperty(target, key, r), r; - }; - import { Directive, NgModule } from '@angular/core'; - let BaseDir = class BaseDir { - }; - BaseDir = __decorate([ - Directive({ - selector: '[base]', - exportAs: 'base1, base2', - }) - ], BaseDir); - export { BaseDir }; - export class DerivedDir extends BaseDir { - } - let Module = class Module { - }; - Module = __decorate([ - NgModule({ - declarations: [DerivedDir], - }) - ], Module); - export { Module };`, - }, - { - name: _('/node_modules/test-package/index.metadata.json'), - contents: `{}`, - }, - { - name: _('/node_modules/test-package/package.json'), - contents: ` - { - "name": "test-package", - "version": "0.0.1", - "esm2015": "./index.js", - "typings": "./index.d.ts" - }`, - }, - ]); - - mainNgcc({ - basePath: '/node_modules', - targetEntryPointPath: 'test-package', - propertiesToConsider: ['esm2015'], - }); - - - const jsContents = fs.readFile(_(`/node_modules/test-package/index.js`)); - expect(jsContents) - .toContain( - 'DerivedDir.ɵdir = /*@__PURE__*/ ɵngcc0.ɵɵdefineDirective({ type: DerivedDir, ' + - 'selectors: [["", "base", ""]], exportAs: ["base1", "base2"], ' + - 'features: [ɵngcc0.ɵɵInheritDefinitionFeature, ɵngcc0.ɵɵCopyDefinitionFeature] });'); - - const dtsContents = fs.readFile(_(`/node_modules/test-package/index.d.ts`)); - expect(dtsContents) - .toContain( - 'static ɵdir: ɵngcc0.ɵɵDirectiveDeclaration;'); - }); - - it('should generate a component definition with CopyDefinitionFeature for an undecorated child component', - () => { - loadTestFiles([ - { - name: _('/node_modules/test-package/index.d.ts'), - contents: ` - export declare class BaseCmp { - } - export declare class DerivedCmp extends BaseCmp { - } - export declare class Module { - }`, - }, - { - name: _('/node_modules/test-package/index.js'), - contents: ` - var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { - var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; - if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); - else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; - return c > 3 && r && Object.defineProperty(target, key, r), r; - }; - import { Component, NgModule } from '@angular/core'; - let BaseCmp = class BaseCmp { - }; - BaseCmp = __decorate([ - Component({ - selector: '[base]', - template: 'This is the base template', - }) - ], BaseCmp); - export { BaseCmp }; - export class DerivedCmp extends BaseCmp { - } - let Module = class Module { - }; - Module = __decorate([ - NgModule({ - declarations: [DerivedCmp], - }) - ], Module); - export { Module };`, - }, - { - name: _('/node_modules/test-package/index.metadata.json'), - contents: `{}`, - }, - { - name: _('/node_modules/test-package/package.json'), - contents: ` - { - "name": "test-package", - "version": "0.0.1", - "esm2015": "./index.js", - "typings": "./index.d.ts" - }`, - }, - ]); - - mainNgcc({ - basePath: '/node_modules', - targetEntryPointPath: 'test-package', - propertiesToConsider: ['esm2015'], - }); - - - const jsContents = fs.readFile(_(`/node_modules/test-package/index.js`)); - expect(jsContents).toContain('DerivedCmp.ɵcmp = /*@__PURE__*/ ɵngcc0.ɵɵdefineComponent'); - expect(jsContents) - .toContain( - 'features: [ɵngcc0.ɵɵInheritDefinitionFeature, ɵngcc0.ɵɵCopyDefinitionFeature]'); - - const dtsContents = fs.readFile(_(`/node_modules/test-package/index.d.ts`)); - expect(dtsContents) - .toContain( - 'static ɵcmp: ɵngcc0.ɵɵComponentDeclaration;'); - }); - - it('should generate directive definitions with CopyDefinitionFeature for undecorated child directives in a long inheritance chain', - () => { - loadTestFiles([ - { - name: _('/node_modules/test-package/index.d.ts'), - contents: ` - export declare class BaseDir { - } - export declare class DerivedDir1 extends BaseDir { - } - export declare class DerivedDir2 extends DerivedDir1 { - } - export declare class DerivedDir3 extends DerivedDir2 { - } - export declare class Module { - }`, - }, - { - name: _('/node_modules/test-package/index.js'), - contents: ` - var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { - var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; - if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); - else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; - return c > 3 && r && Object.defineProperty(target, key, r), r; - }; - import { Directive, NgModule } from '@angular/core'; - let BaseDir = class BaseDir { - }; - BaseDir = __decorate([ - Directive({ - selector: '[base]', - }) - ], BaseDir); - export { BaseDir }; - export class DerivedDir1 extends BaseDir { - } - export class DerivedDir2 extends DerivedDir1 { - } - export class DerivedDir3 extends DerivedDir2 { - } - let Module = class Module { - }; - Module = __decorate([ - NgModule({ - declarations: [DerivedDir3], - }) - ], Module); - export { Module };`, - }, - { - name: _('/node_modules/test-package/index.metadata.json'), - contents: `{}`, - }, - { - name: _('/node_modules/test-package/package.json'), - contents: ` - { - "name": "test-package", - "version": "0.0.1", - "esm2015": "./index.js", - "typings": "./index.d.ts" - }`, - }, - ]); - - mainNgcc({ - basePath: '/node_modules', - targetEntryPointPath: 'test-package', - propertiesToConsider: ['esm2015'], - }); - - const dtsContents = fs.readFile(_(`/node_modules/test-package/index.d.ts`)); - expect(dtsContents) - .toContain( - 'static ɵdir: ɵngcc0.ɵɵDirectiveDeclaration;'); - expect(dtsContents) - .toContain( - 'static ɵdir: ɵngcc0.ɵɵDirectiveDeclaration;'); - expect(dtsContents) - .toContain( - 'static ɵdir: ɵngcc0.ɵɵDirectiveDeclaration;'); - }); - }); - - describe('aliasing re-exports in commonjs', () => { - it('should add re-exports to commonjs files', () => { - loadTestFiles([ - { - name: _('/node_modules/test-package/package.json'), - contents: ` - { - "name": "test-package", - "main": "./index.js", - "typings": "./index.d.ts" - } - `, - }, - { - name: _('/node_modules/test-package/index.js'), - contents: ` - var __export = null; - __export(require("./module")); - `, - }, - { - name: _('/node_modules/test-package/index.d.ts'), - contents: ` - export * from "./module"; - `, - }, - { - name: _('/node_modules/test-package/index.metadata.json'), - contents: '{}', - }, - { - name: _('/node_modules/test-package/module.js'), - contents: ` - var __decorate = null; - var core_1 = require("@angular/core"); - var directive_1 = require("./directive"); - var LocalDir = /** @class */ (function () { - function LocalDir() { - } - LocalDir = __decorate([ - core_1.Directive({ - selector: '[local]', - }) - ], LocalDir); - return LocalDir; - }()); - var FooModule = /** @class */ (function () { - function FooModule() { - } - FooModule = __decorate([ - core_1.NgModule({ - declarations: [directive_1.Foo, LocalDir], - exports: [directive_1.Foo, LocalDir], - }) - ], FooModule); - return FooModule; - }()); - exports.LocalDir = LocalDir; - exports.FooModule = FooModule; - `, - }, - { - name: _('/node_modules/test-package/module.d.ts'), - contents: ` - export declare class LocalDir {} - export declare class FooModule {} - `, - }, - { - name: _('/node_modules/test-package/module.metadata.json'), - contents: '{}', - }, - { - name: _('/node_modules/test-package/directive.js'), - contents: ` - var __decorate = null; - var core_1 = require("@angular/core"); - var Foo = /** @class */ (function () { - function Foo() { - } - Foo = __decorate([ - core_1.Directive({ - selector: '[foo]', - }) - ], Foo); - return Foo; - }()); - exports.Foo = Foo; - `, - }, - { - name: _('/node_modules/test-package/directive.d.ts'), - contents: ` - export declare class Foo {} - `, - }, - { - name: _('/node_modules/test-package/directive.metadata.json'), - contents: '{}', - }, - { - name: _('/ngcc.config.js'), - contents: ` - module.exports = { - packages: { - 'test-package': { - entryPoints: { - '.': { - generateDeepReexports: true - }, - }, - }, - }, - }; - `, - } - ]); - - mainNgcc({ - basePath: '/node_modules', - targetEntryPointPath: 'test-package', - propertiesToConsider: ['main'], - }); - - expect(loadPackage('test-package').__processed_by_ivy_ngcc__).toEqual({ - main: '0.0.0-PLACEHOLDER', - typings: '0.0.0-PLACEHOLDER', - }); - - const jsContents = fs.readFile(_(`/node_modules/test-package/module.js`)); - const dtsContents = fs.readFile(_(`/node_modules/test-package/module.d.ts`)); - expect(jsContents).toContain(`var ɵngcc1 = require('./directive');`); - expect(jsContents).toContain('exports.ɵngExportɵFooModuleɵFoo = ɵngcc1.Foo;'); - expect(dtsContents) - .toContain(`export {Foo as ɵngExportɵFooModuleɵFoo} from './directive';`); - expect(dtsContents.match(/ɵngExportɵFooModuleɵFoo/g)!.length).toBe(1); - expect(dtsContents).not.toContain(`ɵngExportɵFooModuleɵLocalDir`); - }); - }); - - describe('legacy message ids', () => { - it('should render legacy message ids when compiling i18n tags in component templates', () => { - loadTestFiles([ - { - name: _('/node_modules/test-package/src/index.d.ts'), - contents: ` - export declare class AppComponent { - }`, - }, - { - name: _('/node_modules/test-package/esm2015/src/index.js'), - contents: ` - var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { - var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; - if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); - else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; - return c > 3 && r && Object.defineProperty(target, key, r), r; - }; - import { Component } from '@angular/core'; - let AppComponent = class AppComponent { - }; - AppComponent = __decorate([ - Component({ - selector: '[base]', - template: '
Some message
' - }) - ], AppComponent); - export { AppComponent };`, - }, - { - name: _('/node_modules/test-package/esm2015/index.js'), - contents: `export * from './src/index';`, - }, - { - name: _('/node_modules/test-package/esm5/src/index.js'), - contents: ` - var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { - var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; - if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); - else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; - return c > 3 && r && Object.defineProperty(target, key, r), r; - }; - import { Component } from '@angular/core'; - var AppComponent = /** @class */ (function () { - function AppComponent() { - } - AppComponent = __decorate([ - Component({ - selector: '[base]', - template: '
Some message
' - }) - ], AppComponent); - return AppComponent; - }()); - export { AppComponent };`, - }, - { - name: _('/node_modules/test-package/esm5/index.js'), - contents: `export * from './src/index';`, - }, - { - name: _('/node_modules/test-package/index.d.ts'), - contents: `export * from './src/index';`, - }, - { - name: _('/node_modules/test-package/index.metadata.json'), - contents: `{}`, - }, - { - name: _('/node_modules/test-package/package.json'), - contents: ` - { - "name": "test-package", - "version": "0.0.1", - "esm5": "./esm5/index.js", - "esm2015": "./esm2015/index.js", - "module": "./esm2015/index.js", - "typings": "./index.d.ts" - }`, - }, - ]); - - mainNgcc({ - basePath: '/node_modules', - targetEntryPointPath: 'test-package', - propertiesToConsider: ['esm2015'], - }); - - - const jsContents = fs.readFile(_(`/node_modules/test-package/esm2015/src/index.js`)); - expect(jsContents) - .toContain( - '$localize `:␟888aea0e46f7e9dddbd95fc1ef380a3ff70ada9d␟1812794354835616626:Some message'); - }); - - it('should not render legacy message ids when compiling i18n tags in component templates if `enableI18nLegacyMessageIdFormat` is false', - () => { - loadTestFiles([ - { - name: _('/node_modules/test-package/src/index.d.ts'), - contents: ` - export declare class AppComponent { - }`, - }, - { - name: _('/node_modules/test-package/esm2015/src/index.js'), - contents: ` - var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { - var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; - if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); - else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; - return c > 3 && r && Object.defineProperty(target, key, r), r; - }; - import { Component } from '@angular/core'; - let AppComponent = class AppComponent { - }; - AppComponent = __decorate([ - Component({ - selector: '[base]', - template: '
Some message
' - }) - ], AppComponent); - export { AppComponent };`, - }, - { - name: _('/node_modules/test-package/esm2015/index.js'), - contents: `export * from './src/index';`, - }, - { - name: _('/node_modules/test-package/esm5/src/index.js'), - contents: ` - var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { - var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; - if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); - else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; - return c > 3 && r && Object.defineProperty(target, key, r), r; - }; - import { Component } from '@angular/core'; - var AppComponent = /** @class */ (function () { - function AppComponent() { - } - AppComponent = __decorate([ - Component({ - selector: '[base]', - template: '
Some message
' - }) - ], AppComponent); - return AppComponent; - }()); - export { AppComponent };`, - }, - { - name: _('/node_modules/test-package/esm5/index.js'), - contents: `export * from './src/index';`, - }, - { - name: _('/node_modules/test-package/index.d.ts'), - contents: `export * from './src/index';`, - }, - { - name: _('/node_modules/test-package/index.metadata.json'), - contents: `{}`, - }, - { - name: _('/node_modules/test-package/package.json'), - contents: ` - { - "name": "test-package", - "version": "0.0.1", - "esm5": "./esm5/index.js", - "esm2015": "./esm2015/index.js", - "module": "./esm2015/index.js", - "typings": "./index.d.ts" - }`, - }, - ]); - - mainNgcc({ - basePath: '/node_modules', - targetEntryPointPath: 'test-package', - propertiesToConsider: ['esm2015'], - enableI18nLegacyMessageIdFormat: false, - }); - - - const jsContents = fs.readFile(_(`/node_modules/test-package/esm2015/src/index.js`)); - expect(jsContents).not.toContain('␟888aea0e46f7e9dddbd95fc1ef380a3ff70ada9d'); - expect(jsContents).not.toContain('␟1812794354835616626'); - expect(jsContents).not.toContain('␟'); - }); - }); - - function loadPackage( - packageName: string, basePath: AbsoluteFsPath = _('/node_modules')): EntryPointPackageJson { - return JSON.parse(fs.readFile(fs.resolve(basePath, packageName, 'package.json'))) as - EntryPointPackageJson; - } - - function initMockFileSystem(fs: FileSystem, testFiles: Folder) { - if (fs instanceof MockFileSystem) { - fs.init(testFiles); - fs.ensureDir(fs.dirname(LockFilePathResolver.resolve(fs))); - } - - // a random test package that no metadata.json file so not compiled by Angular. - loadTestFiles([ - { - name: _('/node_modules/test-package/package.json'), - contents: '{"name": "test-package", "es2015": "./index.js", "typings": "./index.d.ts"}' - }, - { - name: _('/node_modules/test-package/index.js'), - contents: - 'import {AppModule} from "@angular/common"; export class MyApp extends AppModule {};' - }, - { - name: _('/node_modules/test-package/index.d.ts'), - contents: - 'import {AppModule} from "@angular/common"; export declare class MyApp extends AppModule;' - }, - ]); - - // Angular packages that have been built locally and stored in the `dist` directory. - loadTestFiles([ - { - name: _('/dist/local-package/package.json'), - contents: '{"name": "local-package", "es2015": "./index.js", "typings": "./index.d.ts"}' - }, - {name: _('/dist/local-package/index.metadata.json'), contents: 'DUMMY DATA'}, - { - name: _('/dist/local-package/index.js'), - contents: - `import {Component} from '@angular/core';\nexport class AppComponent {};\nAppComponent.decorators = [\n{ type: Component, args: [{selector: 'app', template: '

\\n Hello\\n

'}] }\n];` - }, - { - name: _('/dist/local-package/index.d.ts'), - contents: `export declare class AppComponent {};` - }, - // local-package-2 depends upon local-package, via an `@app` aliased import. - { - name: _('/dist/local-package-2/package.json'), - contents: '{"name": "local-package-2", "es2015": "./index.js", "typings": "./index.d.ts"}' - }, - {name: _('/dist/local-package-2/index.metadata.json'), contents: 'DUMMY DATA'}, - { - name: _('/dist/local-package-2/index.js'), - contents: - `import {Component} from '@angular/core';\nexport {AppComponent} from '@app/local-package';` - }, - { - name: _('/dist/local-package-2/index.d.ts'), - contents: - `import {Component} from '@angular/core';\nexport {AppComponent} from '@app/local-package';` - }, - // local-package-3 depends upon local-package, via an `@x` aliased import. - { - name: _('/dist/local-package-3/package.json'), - contents: '{"name": "local-package-3", "es2015": "./index.js", "typings": "./index.d.ts"}' - }, - {name: _('/dist/local-package-3/index.metadata.json'), contents: 'DUMMY DATA'}, - { - name: _('/dist/local-package-3/index.js'), - contents: - `import {Component} from '@angular/core';\nexport {AppComponent} from '@x/local-package';` - }, - { - name: _('/dist/local-package-3/index.d.ts'), - contents: - `import {Component} from '@angular/core';\nexport {AppComponent} from '@x/local-package';` - }, - // local-package-4 depends upon local-package, via a plain import. - { - name: _('/dist/local-package-4/package.json'), - contents: '{"name": "local-package-", "es2015": "./index.js", "typings": "./index.d.ts"}' - }, - {name: _('/dist/local-package-4/index.metadata.json'), contents: 'DUMMY DATA'}, - { - name: _('/dist/local-package-4/index.js'), - contents: - `import {Component} from '@angular/core';\nexport {AppComponent} from 'local-package';` - }, - { - name: _('/dist/local-package-4/index.d.ts'), - contents: - `import {Component} from '@angular/core';\nexport {AppComponent} from 'local-package';` - }, - ]); - - // An Angular package that has a missing dependency - loadTestFiles([ - { - name: _('/node_modules/invalid-package/package.json'), - contents: '{"name": "invalid-package", "es2015": "./index.js", "typings": "./index.d.ts"}' - }, - { - name: _('/node_modules/invalid-package/index.js'), - contents: ` - import {AppModule} from "@angular/missing"; - import {Component} from '@angular/core'; - export class AppComponent {}; - AppComponent.decorators = [ - { type: Component, args: [{selector: 'app', template: '

Hello

'}] } - ]; - ` - }, - { - name: _('/node_modules/invalid-package/index.d.ts'), - contents: `export declare class AppComponent {}` - }, - {name: _('/node_modules/invalid-package/index.metadata.json'), contents: 'DUMMY DATA'}, - ]); - - // A sample application that imports entry-points - loadTestFiles([ - {name: _('/tsconfig.json'), contents: '{"files": ["src/index.ts"]}'}, - {name: _('/src/index.ts'), contents: `import {X} from './x';\nimport {Y} from './y';`}, - {name: _('/src/x.ts'), contents: `import '@angular/common/http';\nexport class X {}`}, - { - name: _('/src/y.ts'), - contents: `import * as t from '@angular/common/testing';\n export class Y {}` - }, - ]); - } - }); -}); - -function countOccurrences(haystack: string, needle: string): number { - const matches = haystack.match(new RegExp(needle, 'g')); - return matches !== null ? matches.length : 0; -} diff --git a/packages/compiler-cli/ngcc/test/integration/util.ts b/packages/compiler-cli/ngcc/test/integration/util.ts deleted file mode 100644 index 661cde2a868..00000000000 --- a/packages/compiler-cli/ngcc/test/integration/util.ts +++ /dev/null @@ -1,30 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ - -import {AbsoluteFsPath} from '../../../src/ngtsc/file_system'; -import {Folder, MockFileSystemPosix} from '../../../src/ngtsc/file_system/testing'; -import {loadTestDirectory, loadTsLib, resolveFromRunfiles} from '../../../src/ngtsc/testing'; - -export function loadNgccIntegrationTestFiles(): Folder { - const tmpFs = new MockFileSystemPosix(true); - const basePath = '/' as AbsoluteFsPath; - - loadTsLib(tmpFs, basePath); - loadTestDirectory( - tmpFs, resolveFromRunfiles('npm/node_modules/@angular/core-12'), - tmpFs.resolve('/node_modules/@angular/core')); - loadTestDirectory( - tmpFs, resolveFromRunfiles('npm/node_modules/@angular/common-12'), - tmpFs.resolve('/node_modules/@angular/common')); - loadTestDirectory( - tmpFs, resolveFromRunfiles('npm/node_modules/typescript'), - tmpFs.resolve('/node_modules/typescript')); - loadTestDirectory( - tmpFs, resolveFromRunfiles('npm/node_modules/rxjs'), tmpFs.resolve('/node_modules/rxjs')); - return tmpFs.dump(); -} diff --git a/packages/compiler-cli/ngcc/test/locking/async_locker_spec.ts b/packages/compiler-cli/ngcc/test/locking/async_locker_spec.ts deleted file mode 100644 index 28812124cbb..00000000000 --- a/packages/compiler-cli/ngcc/test/locking/async_locker_spec.ts +++ /dev/null @@ -1,183 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import {getFileSystem} from '../../../src/ngtsc/file_system'; -import {runInEachFileSystem} from '../../../src/ngtsc/file_system/testing'; -import {MockLogger} from '../../../src/ngtsc/logging/testing'; -import {AsyncLocker} from '../../src/locking/async_locker'; -import {MockLockFile} from '../helpers/mock_lock_file'; - -runInEachFileSystem(() => { - describe('AsyncLocker', () => { - describe('lock()', () => { - it('should guard the `fn()` with calls to `write()` and `remove()`', async () => { - const fs = getFileSystem(); - const log: string[] = []; - const lockFile = new MockLockFile(fs, log); - const locker = new AsyncLocker(lockFile, new MockLogger(), 100, 10); - - await locker.lock(async () => { - log.push('fn() - before'); - // This promise forces node to do a tick in this function, ensuring that we are truly - // testing an async scenario. - await Promise.resolve(); - log.push('fn() - after'); - }); - expect(log).toEqual(['write()', 'fn() - before', 'fn() - after', 'remove()']); - }); - - it('should guard the `fn()` with calls to `write()` and `remove()`, even if it throws', - async () => { - let error: string = ''; - const fs = getFileSystem(); - const log: string[] = []; - const lockFile = new MockLockFile(fs, log); - const locker = new AsyncLocker(lockFile, new MockLogger(), 100, 10); - - try { - await locker.lock(async () => { - log.push('fn()'); - throw new Error('ERROR'); - }); - } catch (e) { - error = (e as Error).message; - } - expect(error).toEqual('ERROR'); - expect(log).toEqual(['write()', 'fn()', 'remove()']); - }); - - it('should retry if another process is locking', async () => { - const fs = getFileSystem(); - const log: string[] = []; - const lockFile = new MockLockFile(fs, log); - const logger = new MockLogger(); - const locker = new AsyncLocker(lockFile, logger, 100, 10); - - let lockFileContents: string|null = '188'; - spyOn(lockFile, 'write').and.callFake(() => { - log.push('write()'); - if (lockFileContents) { - throw {code: 'EEXIST'}; - } - }); - spyOn(lockFile, 'read').and.callFake(() => { - log.push('read() => ' + lockFileContents); - if (lockFileContents === null) { - throw {code: 'ENOENT'}; - } - return lockFileContents; - }); - - const promise = locker.lock(async () => log.push('fn()')); - // The lock is now waiting on the lock-file becoming free, so no `fn()` in the log. - expect(log).toEqual(['write()', 'read() => 188']); - expect(logger.logs.info).toEqual([[ - 'Another process, with id 188, is currently running ngcc.\nWaiting up to 1s for it to finish.\n' + - `(If you are sure no ngcc process is running then you should delete the lock-file at ${ - lockFile.path}.)` - ]]); - - lockFileContents = null; - // The lock-file has been removed, so we can create our own lock-file, call `fn()` and then - // remove the lock-file. - await promise; - expect(log).toEqual(['write()', 'read() => 188', 'write()', 'fn()', 'remove()']); - }); - - it('should extend the retry timeout if the other process locking the file changes', async () => { - const fs = getFileSystem(); - const log: string[] = []; - const lockFile = new MockLockFile(fs, log); - const logger = new MockLogger(); - const locker = new AsyncLocker(lockFile, logger, 200, 5); - - let lockFileContents: string|null = '188'; - spyOn(lockFile, 'write').and.callFake(() => { - log.push('write()'); - if (lockFileContents) { - throw {code: 'EEXIST'}; - } - }); - spyOn(lockFile, 'read').and.callFake(() => { - log.push('read() => ' + lockFileContents); - if (lockFileContents === null) { - throw {code: 'ENOENT'}; - } - return lockFileContents; - }); - - const promise = locker.lock(async () => log.push('fn()')); - // The lock is now waiting on the lock-file becoming free, so no `fn()` in the log. - expect(log).toEqual(['write()', 'read() => 188']); - expect(logger.logs.info).toEqual([[ - 'Another process, with id 188, is currently running ngcc.\nWaiting up to 1s for it to finish.\n' + - `(If you are sure no ngcc process is running then you should delete the lock-file at ${ - lockFile.path}.)` - ]]); - - lockFileContents = '444'; - // The lock-file has been taken over by another process - wait for the next attempt - await new Promise(resolve => setTimeout(resolve, 250)); - expect(log).toEqual(['write()', 'read() => 188', 'write()', 'read() => 444']); - expect(logger.logs.info).toEqual([ - ['Another process, with id 188, is currently running ngcc.\nWaiting up to 1s for it to finish.\n' + - `(If you are sure no ngcc process is running then you should delete the lock-file at ${ - lockFile.path}.)`], - ['Another process, with id 444, is currently running ngcc.\nWaiting up to 1s for it to finish.\n' + - `(If you are sure no ngcc process is running then you should delete the lock-file at ${ - lockFile.path}.)`] - ]); - - lockFileContents = null; - // The lock-file has been removed, so we can create our own lock-file, call `fn()` and - // then remove the lock-file. - await promise; - expect(log).toEqual([ - 'write()', 'read() => 188', 'write()', 'read() => 444', 'write()', 'fn()', 'remove()' - ]); - }); - - it('should error if another process does not release the lock-file before this times out', - async () => { - const fs = getFileSystem(); - const log: string[] = []; - const lockFile = new MockLockFile(fs, log); - const logger = new MockLogger(); - const locker = new AsyncLocker(lockFile, logger, 100, 2); - - let lockFileContents: string|null = '188'; - spyOn(lockFile, 'write').and.callFake(() => { - log.push('write()'); - if (lockFileContents) { - throw {code: 'EEXIST'}; - } - }); - spyOn(lockFile, 'read').and.callFake(() => { - log.push('read() => ' + lockFileContents); - if (lockFileContents === null) { - throw {code: 'ENOENT'}; - } - return lockFileContents; - }); - - const promise = locker.lock(async () => log.push('fn()')); - - // The lock is now waiting on the lock-file becoming free, so no `fn()` in the log. - expect(log).toEqual(['write()', 'read() => 188']); - // Do not remove the lock-file and let the call to `lock()` timeout. - let error: Error; - await promise.catch(e => error = e); - expect(log).toEqual(['write()', 'read() => 188', 'write()', 'read() => 188']); - expect(error!.message) - .toEqual( - `Timed out waiting 0.2s for another ngcc process, with id 188, to complete.\n` + - `(If you are sure no ngcc process is running then you should delete the lock-file at ${ - lockFile.path}.)`); - }); - }); - }); -}); diff --git a/packages/compiler-cli/ngcc/test/locking/lockfile_with_child_process/index_spec.ts b/packages/compiler-cli/ngcc/test/locking/lockfile_with_child_process/index_spec.ts deleted file mode 100644 index 7f16e47fbc8..00000000000 --- a/packages/compiler-cli/ngcc/test/locking/lockfile_with_child_process/index_spec.ts +++ /dev/null @@ -1,133 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import {ChildProcess} from 'child_process'; -import * as process from 'process'; - -import {FileSystem, getFileSystem} from '../../../../src/ngtsc/file_system'; -import {runInEachFileSystem} from '../../../../src/ngtsc/file_system/testing'; -import {MockLogger} from '../../../../src/ngtsc/logging/testing'; -import {LockFilePathResolver} from '../../../src/locking/lock_file'; -import {LockFileWithChildProcess} from '../../../src/locking/lock_file_with_child_process'; -import {mockRequireResolveForLockfile} from '../../helpers/utils'; - -runInEachFileSystem(() => { - describe('LockFileWithChildProcess', () => { - /** - * This class allows us to test ordering of the calls, and to avoid actually attaching signal - * handlers and most importantly not actually exiting the process. - */ - class LockFileUnderTest extends LockFileWithChildProcess { - // Note that log is initialized in the `createUnlocker()` function that is called from - // super(), so we can't initialize it here. - log!: string[]; - constructor(fs: FileSystem) { - super(fs, new MockLogger()); - fs.ensureDir(fs.dirname(this.path)); - } - override remove() { - this.log.push('remove()'); - super.remove(); - } - override write() { - this.log.push('write()'); - super.write(); - } - override read() { - const contents = super.read(); - this.log.push('read() => ' + contents); - return contents; - } - override createUnlocker(): ChildProcess { - this.log = this.log || []; - this.log.push('createUnlocker()'); - const log = this.log; - // Normally this would fork a child process and return it. - // But we don't want to do that in these tests. - return { - disconnect() { - log.push('unlocker.disconnect()'); - } - }; - } - } - - beforeEach(() => { - mockRequireResolveForLockfile(); - }); - - describe('constructor', () => { - it('should create the unlocker process', () => { - const fs = getFileSystem(); - const lockFile = new LockFileUnderTest(fs); - expect(lockFile.log).toEqual(['createUnlocker()']); - }); - }); - - describe('write()', () => { - it('should write the lock-file to disk', () => { - const fs = getFileSystem(); - const lockFile = new LockFileUnderTest(fs); - const lockPath = LockFilePathResolver.resolve(fs); - expect(fs.exists(lockPath)).toBe(false); - lockFile.write(); - expect(fs.exists(lockPath)).toBe(true); - expect(fs.readFile(lockPath)).toEqual('' + process.pid); - }); - - it('should create the unlocker process if it is not already created', () => { - const fs = getFileSystem(); - const lockFile = new LockFileUnderTest(fs); - lockFile.log = []; - (lockFile as any).unlocker = null; - lockFile.write(); - expect(lockFile.log).toEqual(['write()', 'createUnlocker()']); - expect((lockFile as any).unlocker).not.toBe(null); - }); - }); - - describe('read()', () => { - it('should return the contents of the lock-file', () => { - const fs = getFileSystem(); - const lockFile = new LockFileUnderTest(fs); - fs.writeFile(lockFile.path, '' + process.pid); - expect(lockFile.read()).toEqual('' + process.pid); - }); - - it('should return `{unknown}` if the lock-file does not exist', () => { - const fs = getFileSystem(); - const lockFile = new LockFileUnderTest(fs); - expect(lockFile.read()).toEqual('{unknown}'); - }); - }); - - describe('remove()', () => { - it('should remove the lock file from the file-system', () => { - const fs = getFileSystem(); - const lockFile = new LockFileUnderTest(fs); - fs.writeFile(lockFile.path, '' + process.pid); - lockFile.remove(); - expect(fs.exists(lockFile.path)).toBe(false); - }); - - it('should not error if the lock file does not exist', () => { - const fs = getFileSystem(); - const lockFile = new LockFileUnderTest(fs); - expect(() => lockFile.remove()).not.toThrow(); - }); - - it('should disconnect the unlocker child process', () => { - const fs = getFileSystem(); - const lockFile = new LockFileUnderTest(fs); - fs.writeFile(lockFile.path, '' + process.pid); - lockFile.remove(); - expect(lockFile.log).toEqual(['createUnlocker()', 'remove()', 'unlocker.disconnect()']); - expect((lockFile as any).unlocker).toBe(null); - }); - }); - }); -}); diff --git a/packages/compiler-cli/ngcc/test/locking/lockfile_with_child_process/unlocker_spec.ts b/packages/compiler-cli/ngcc/test/locking/lockfile_with_child_process/unlocker_spec.ts deleted file mode 100644 index 28d1d932ba9..00000000000 --- a/packages/compiler-cli/ngcc/test/locking/lockfile_with_child_process/unlocker_spec.ts +++ /dev/null @@ -1,19 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ - -/// - -describe('unlocker', () => { - it('should attach a handler to the `disconnect` event', async () => { - spyOn(process, 'on'); - await import('../../../src/locking/lock_file_with_child_process/ngcc_lock_unlocker'); - // TODO: @JiaLiPassion, need to wait for @types/jasmine to handle the override case - // https://github.com/DefinitelyTyped/DefinitelyTyped/issues/42455 - expect(process.on).toHaveBeenCalledWith('disconnect' as any, jasmine.any(Function)); - }); -}); diff --git a/packages/compiler-cli/ngcc/test/locking/lockfile_with_child_process/util_spec.ts b/packages/compiler-cli/ngcc/test/locking/lockfile_with_child_process/util_spec.ts deleted file mode 100644 index 3247dce7cbc..00000000000 --- a/packages/compiler-cli/ngcc/test/locking/lockfile_with_child_process/util_spec.ts +++ /dev/null @@ -1,52 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import {absoluteFrom, AbsoluteFsPath, FileSystem, getFileSystem} from '../../../../src/ngtsc/file_system'; -import {runInEachFileSystem} from '../../../../src/ngtsc/file_system/testing'; -import {MockLogger} from '../../../../src/ngtsc/logging/testing'; -import {removeLockFile} from '../../../src/locking/lock_file_with_child_process/util'; - -runInEachFileSystem(() => { - describe('LockFileWithChildProcess utils', () => { - let lockFilePath: AbsoluteFsPath; - let fs: FileSystem; - let logger: MockLogger; - - beforeEach(() => { - fs = getFileSystem(); - logger = new MockLogger(); - lockFilePath = absoluteFrom('/lockfile/path'); - fs.ensureDir(absoluteFrom('/lockfile')); - }); - - describe('removeLockFile()', () => { - it('should do nothing if there is no file to remove', () => { - removeLockFile(fs, logger, absoluteFrom('/lockfile/path'), '1234'); - }); - - it('should do nothing if the pid does not match', () => { - fs.writeFile(lockFilePath, '888'); - removeLockFile(fs, logger, lockFilePath, '1234'); - expect(fs.exists(lockFilePath)).toBe(true); - expect(fs.readFile(lockFilePath)).toEqual('888'); - }); - - it('should remove the file if the pid matches', () => { - fs.writeFile(lockFilePath, '1234'); - removeLockFile(fs, logger, lockFilePath, '1234'); - expect(fs.exists(lockFilePath)).toBe(false); - }); - - it('should re-throw any other error', () => { - spyOn(fs, 'removeFile').and.throwError('removeFile() error'); - fs.writeFile(lockFilePath, '1234'); - expect(() => removeLockFile(fs, logger, lockFilePath, '1234')) - .toThrowError('removeFile() error'); - }); - }); - }); -}); diff --git a/packages/compiler-cli/ngcc/test/locking/sync_locker_spec.ts b/packages/compiler-cli/ngcc/test/locking/sync_locker_spec.ts deleted file mode 100644 index d8d7952b34e..00000000000 --- a/packages/compiler-cli/ngcc/test/locking/sync_locker_spec.ts +++ /dev/null @@ -1,67 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import {getFileSystem} from '../../../src/ngtsc/file_system'; -import {runInEachFileSystem} from '../../../src/ngtsc/file_system/testing'; -import {SyncLocker} from '../../src/locking/sync_locker'; -import {MockLockFile} from '../helpers/mock_lock_file'; - -runInEachFileSystem(() => { - describe('SyncLocker', () => { - describe('lock()', () => { - it('should guard the `fn()` with calls to `write()` and `remove()`', () => { - const fs = getFileSystem(); - const log: string[] = []; - const lockFile = new MockLockFile(fs, log); - const locker = new SyncLocker(lockFile); - - locker.lock(() => log.push('fn()')); - - expect(log).toEqual(['write()', 'fn()', 'remove()']); - }); - - it('should guard the `fn()` with calls to `write()` and `remove()`, even if it throws', - () => { - let error: string = ''; - const fs = getFileSystem(); - const log: string[] = []; - const lockFile = new MockLockFile(fs, log); - const locker = new SyncLocker(lockFile); - - try { - locker.lock(() => { - log.push('fn()'); - throw new Error('ERROR'); - }); - } catch (e) { - error = (e as Error).message; - } - expect(error).toEqual('ERROR'); - expect(log).toEqual(['write()', 'fn()', 'remove()']); - }); - - it('should error if a lock file already exists', () => { - const fs = getFileSystem(); - const log: string[] = []; - const lockFile = new MockLockFile(fs, log); - const locker = new SyncLocker(lockFile); - - spyOn(lockFile, 'write').and.callFake(() => { - throw {code: 'EEXIST'}; - }); - spyOn(lockFile, 'read').and.returnValue('188'); - - expect(() => locker.lock(() => {})) - .toThrowError( - `ngcc is already running at process with id 188.\n` + - `If you are running multiple builds in parallel then you might try pre-processing your node_modules via the command line ngcc tool before starting the builds.\n` + - `(If you are sure no ngcc process is running then you should delete the lock-file at ${ - lockFile.path}.)`); - }); - }); - }); -}); diff --git a/packages/compiler-cli/ngcc/test/migrations/missing_injectable_migration_spec.ts b/packages/compiler-cli/ngcc/test/migrations/missing_injectable_migration_spec.ts deleted file mode 100644 index 9e5fd669583..00000000000 --- a/packages/compiler-cli/ngcc/test/migrations/missing_injectable_migration_spec.ts +++ /dev/null @@ -1,595 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import ts from 'typescript'; - -import {absoluteFrom, AbsoluteFsPath, getFileSystem} from '../../../src/ngtsc/file_system'; -import {runInEachFileSystem, TestFile} from '../../../src/ngtsc/file_system/testing'; -import {MockLogger} from '../../../src/ngtsc/logging/testing'; -import {loadFakeCore, loadTestFiles} from '../../../src/ngtsc/testing'; -import {DecorationAnalyzer} from '../../src/analysis/decoration_analyzer'; -import {NgccReferencesRegistry} from '../../src/analysis/ngcc_references_registry'; -import {DecorationAnalyses} from '../../src/analysis/types'; -import {Esm2015ReflectionHost} from '../../src/host/esm2015_host'; -import {getAngularCoreDecoratorName, MissingInjectableMigration} from '../../src/migrations/missing_injectable_migration'; -import {getRootFiles, makeTestEntryPointBundle} from '../helpers/utils'; - -runInEachFileSystem(() => { - describe('MissingInjectableMigration', () => { - let _: typeof absoluteFrom; - let INDEX_FILENAME: AbsoluteFsPath; - beforeEach(() => { - _ = absoluteFrom; - INDEX_FILENAME = _('/node_modules/test-package/index.js'); - }); - - describe('NgModule', () => runTests('NgModule', 'providers')); - describe('Directive', () => runTests('Directive', 'providers')); - - describe('Component', () => { - runTests('Component', 'providers'); - runTests('Component', 'viewProviders'); - - it('should migrate all providers defined in "viewProviders" and "providers" in the same ' + - 'component', - () => { - const {program, analysis} = setUpAndAnalyzeProgram([{ - name: INDEX_FILENAME, - contents: ` - import {Component} from '@angular/core'; - - export class ServiceA {} - export class ServiceB {} - export class ServiceC {} - - export class TestClass {} - TestClass.decorators = [ - { type: Component, args: [{ - template: "", - providers: [ServiceA], - viewProviders: [ServiceB], - }] - } - ]; - `, - }]); - - const index = program.getSourceFile(INDEX_FILENAME)!; - expect(hasInjectableDecorator(index, analysis, 'ServiceA')).toBe(true); - expect(hasInjectableDecorator(index, analysis, 'ServiceB')).toBe(true); - expect(hasInjectableDecorator(index, analysis, 'ServiceC')).toBe(false); - }); - }); - - function runTests( - type: 'NgModule'|'Directive'|'Component', propName: 'providers'|'viewProviders') { - const args = type === 'Component' ? 'template: "", ' : ''; - - it(`should migrate type provider in ${type}`, () => { - const {program, analysis} = setUpAndAnalyzeProgram([{ - name: INDEX_FILENAME, - contents: ` - import {${type}} from '@angular/core'; - - export class MyService {} - export class OtherService {} - - export class TestClass {} - TestClass.decorators = [ - { type: ${type}, args: [{${args}${propName}: [MyService]}] } - ]; - `, - }]); - - const index = program.getSourceFile(INDEX_FILENAME)!; - expect(hasInjectableDecorator(index, analysis, 'MyService')).toBe(true); - expect(hasInjectableDecorator(index, analysis, 'OtherService')).toBe(false); - }); - - it(`should migrate object literal provider in ${type}`, () => { - const {program, analysis} = setUpAndAnalyzeProgram([{ - name: INDEX_FILENAME, - contents: ` - import {${type}} from '@angular/core'; - - export class MyService {} - export class OtherService {} - - export class TestClass {} - TestClass.decorators = [ - { type: ${type}, args: [{${args}${propName}: [{provide: MyService}]}] } - ]; - `, - }]); - - const index = program.getSourceFile(INDEX_FILENAME)!; - expect(hasInjectableDecorator(index, analysis, 'MyService')).toBe(true); - expect(hasInjectableDecorator(index, analysis, 'OtherService')).toBe(false); - }); - - it(`should migrate object literal provider with forwardRef in ${type}`, async () => { - const {program, analysis} = setUpAndAnalyzeProgram([{ - name: INDEX_FILENAME, - contents: ` - import {${type}, forwardRef} from '@angular/core'; - - export class MyService {} - - export class TestClass {} - TestClass.decorators = [ - { type: ${type}, args: [{${args}${ - propName}: [{provide: forwardRef(() => MyService) }]}] } - ]; - `, - }]); - - const index = program.getSourceFile(INDEX_FILENAME)!; - expect(hasInjectableDecorator(index, analysis, 'MyService')).toBe(true); - }); - - it(`should not migrate object literal provider with "useValue" in ${type}`, () => { - const {program, analysis} = setUpAndAnalyzeProgram([{ - name: INDEX_FILENAME, - contents: ` - import {${type}} from '@angular/core'; - - export class MyService {} - - export class TestClass {} - TestClass.decorators = [ - { type: ${type}, args: [{${args}${ - propName}: [{provide: MyService, useValue: null }]}] } - ]; - `, - }]); - - const index = program.getSourceFile(INDEX_FILENAME)!; - expect(hasInjectableDecorator(index, analysis, 'MyService')).toBe(false); - }); - - it(`should not migrate object literal provider with "useFactory" in ${type}`, () => { - const {program, analysis} = setUpAndAnalyzeProgram([{ - name: INDEX_FILENAME, - contents: ` - import {${type}} from '@angular/core'; - - export class MyService {} - - export class TestClass {} - TestClass.decorators = [ - { type: ${type}, args: [{${args}${ - propName}: [{provide: MyService, useFactory: () => null }]}] } - ]; - `, - }]); - - const index = program.getSourceFile(INDEX_FILENAME)!; - expect(hasInjectableDecorator(index, analysis, 'MyService')).toBe(false); - }); - - it(`should not migrate object literal provider with "useExisting" in ${type}`, () => { - const {program, analysis} = setUpAndAnalyzeProgram([{ - name: INDEX_FILENAME, - contents: ` - import {${type}} from '@angular/core'; - - export class MyService {} - export class MyToken {} - export class MyTokenAlias {} - - export class TestClass {} - TestClass.decorators = [ - { type: ${type}, args: [{${args}${propName}: [ - MyService, - {provide: MyToken, useExisting: MyService}, - {provide: MyTokenAlias, useExisting: MyToken}, - ]}] } - ]; - `, - }]); - - const index = program.getSourceFile(INDEX_FILENAME)!; - expect(hasInjectableDecorator(index, analysis, 'MyService')).toBe(true); - expect(hasInjectableDecorator(index, analysis, 'MyToken')).toBe(false); - expect(hasInjectableDecorator(index, analysis, 'MyTokenAlias')).toBe(false); - }); - - it(`should migrate object literal provider with "useClass" in ${type}`, () => { - const {program, analysis} = setUpAndAnalyzeProgram([{ - name: INDEX_FILENAME, - contents: ` - import {${type}} from '@angular/core'; - - export class MyService {} - export class MyToken {} - - export class TestClass {} - TestClass.decorators = [ - { type: ${type}, args: [{${args}${ - propName}: [{provide: MyToken, useClass: MyService}]}] } - ]; - `, - }]); - - const index = program.getSourceFile(INDEX_FILENAME)!; - expect(hasInjectableDecorator(index, analysis, 'MyService')).toBe(true); - expect(hasInjectableDecorator(index, analysis, 'MyToken')).toBe(false); - }); - - it('should not migrate provider which is already decorated with @Injectable', () => { - const {program, analysis} = setUpAndAnalyzeProgram([{ - name: INDEX_FILENAME, - contents: ` - import {Injectable, ${type}} from '@angular/core'; - - export class MyService {} - MyService.decorators = [ - { type: Injectable } - ]; - - export class TestClass {} - TestClass.decorators = [ - { type: ${type}, args: [{${args}${propName}: [MyService]}] } - ]; - `, - }]); - - const index = program.getSourceFile(INDEX_FILENAME)!; - expect(getInjectableDecorators(index, analysis, 'MyService').length).toBe(1); - }); - - it('should not migrate provider which is already decorated with @Directive', () => { - const {program, analysis} = setUpAndAnalyzeProgram([{ - name: INDEX_FILENAME, - contents: ` - import {Directive, ${type}} from '@angular/core'; - - export class MyService {} - MyService.decorators = [ - { type: Directive } - ]; - - export class TestClass {} - TestClass.decorators = [ - { type: ${type}, args: [{${args}${propName}: [MyService]}] } - ]; - `, - }]); - - const index = program.getSourceFile(INDEX_FILENAME)!; - expect(hasInjectableDecorator(index, analysis, 'MyService')).toBe(false); - }); - - it('should not migrate provider which is already decorated with @Component', () => { - const {program, analysis} = setUpAndAnalyzeProgram([{ - name: INDEX_FILENAME, - contents: ` - import {Component, ${type}} from '@angular/core'; - - export class MyService {} - MyService.decorators = [ - { type: Component, args: [{template: ""}] } - ]; - - export class TestClass {} - TestClass.decorators = [ - { type: ${type}, args: [{${args}${propName}: [MyService]}] } - ]; - `, - }]); - - const index = program.getSourceFile(INDEX_FILENAME)!; - expect(hasInjectableDecorator(index, analysis, 'MyService')).toBe(false); - }); - - it('should not migrate provider which is already decorated with @Pipe', () => { - const {program, analysis} = setUpAndAnalyzeProgram([{ - name: INDEX_FILENAME, - contents: ` - import {Pipe, ${type}} from '@angular/core'; - - export class MyService {} - MyService.decorators = [ - { type: Pipe, args: [{name: "pipe"}] } - ]; - - export class TestClass {} - TestClass.decorators = [ - { type: ${type}, args: [{${args}${propName}: [MyService]}] } - ]; - `, - }]); - - const index = program.getSourceFile(INDEX_FILENAME)!; - expect(hasInjectableDecorator(index, analysis, 'MyService')).toBe(false); - }); - - it(`should migrate multiple providers in same ${type}`, () => { - const {program, analysis} = setUpAndAnalyzeProgram([{ - name: INDEX_FILENAME, - contents: ` - import {${type}} from '@angular/core'; - - export class ServiceA {} - export class ServiceB {} - - export class TestClass {} - TestClass.decorators = [ - { type: ${type}, args: [{${args}${propName}: [ServiceA, ServiceB]}] } - ]; - `, - }]); - - const index = program.getSourceFile(INDEX_FILENAME)!; - expect(hasInjectableDecorator(index, analysis, 'ServiceA')).toBe(true); - expect(hasInjectableDecorator(index, analysis, 'ServiceB')).toBe(true); - }); - - it(`should migrate multiple mixed providers in same ${type}`, () => { - const {program, analysis} = setUpAndAnalyzeProgram([{ - name: INDEX_FILENAME, - contents: ` - import {${type}} from '@angular/core'; - - export class ServiceA {} - export class ServiceB {} - export class ServiceC {} - export class ServiceD {} - - export class TestClass {} - TestClass.decorators = [ - { type: ${type}, args: [{${args}${propName}: [ - ServiceA, - {provide: ServiceB}, - {provide: SomeToken, useClass: ServiceC}, - ] - }] } - ]; - `, - }]); - - const index = program.getSourceFile(INDEX_FILENAME)!; - expect(hasInjectableDecorator(index, analysis, 'ServiceA')).toBe(true); - expect(hasInjectableDecorator(index, analysis, 'ServiceB')).toBe(true); - expect(hasInjectableDecorator(index, analysis, 'ServiceC')).toBe(true); - expect(hasInjectableDecorator(index, analysis, 'ServiceD')).toBe(false); - }); - - - it(`should migrate multiple nested providers in same ${type}`, () => { - const {program, analysis} = setUpAndAnalyzeProgram([{ - name: INDEX_FILENAME, - contents: ` - import {${type}} from '@angular/core'; - - export class ServiceA {} - export class ServiceB {} - export class ServiceC {} - export class ServiceD {} - - export class TestClass {} - TestClass.decorators = [ - { type: ${type}, args: [{${args}${propName}: [ - ServiceA, - [ - {provide: ServiceB}, - ServiceC, - ], - ]}] - } - ]; - `, - }]); - - const index = program.getSourceFile(INDEX_FILENAME)!; - expect(hasInjectableDecorator(index, analysis, 'ServiceA')).toBe(true); - expect(hasInjectableDecorator(index, analysis, 'ServiceB')).toBe(true); - expect(hasInjectableDecorator(index, analysis, 'ServiceC')).toBe(true); - expect(hasInjectableDecorator(index, analysis, 'ServiceD')).toBe(false); - }); - - it('should migrate providers referenced indirectly', () => { - const {program, analysis} = setUpAndAnalyzeProgram([{ - name: INDEX_FILENAME, - contents: ` - import {${type}} from '@angular/core'; - - export class ServiceA {} - export class ServiceB {} - export class ServiceC {} - - const PROVIDERS = [ServiceA, ServiceB]; - - export class TestClass {} - TestClass.decorators = [ - { type: ${type}, args: [{${args}${propName}: PROVIDERS}] } - ]; - ` - }]); - - const index = program.getSourceFile(INDEX_FILENAME)!; - expect(hasInjectableDecorator(index, analysis, 'ServiceA')).toBe(true); - expect(hasInjectableDecorator(index, analysis, 'ServiceB')).toBe(true); - expect(hasInjectableDecorator(index, analysis, 'ServiceC')).toBe(false); - }); - - it(`should migrate provider once if referenced in multiple ${type} definitions`, () => { - const {program, analysis} = setUpAndAnalyzeProgram([{ - name: INDEX_FILENAME, - contents: ` - import {${type}} from '@angular/core'; - - export class ServiceA {} - export class ServiceB {} - - export class TestClassA {} - TestClassA.decorators = [ - { type: ${type}, args: [{${args}${propName}: [ServiceA]}] } - ]; - - export class TestClassB {} - TestClassB.decorators = [ - { type: ${type}, args: [{${args}${propName}: [ServiceA, ServiceB]}] } - ]; - ` - }]); - - const index = program.getSourceFile(INDEX_FILENAME)!; - expect(getInjectableDecorators(index, analysis, 'ServiceA').length).toBe(1); - expect(getInjectableDecorators(index, analysis, 'ServiceB').length).toBe(1); - }); - - type !== 'Component' && it(`should support @${type} without metadata argument`, () => { - const {program, analysis} = setUpAndAnalyzeProgram([{ - name: INDEX_FILENAME, - contents: ` - import {${type}} from '@angular/core'; - - export class ServiceA {} - - export class TestClass {} - TestClass.decorators = [ - { type: ${type}, args: [] } - ]; - `, - }]); - - const index = program.getSourceFile(INDEX_FILENAME)!; - expect(hasInjectableDecorator(index, analysis, 'ServiceA')).toBe(false); - }); - - it(`should migrate services in a different file`, () => { - const SERVICE_FILENAME = _('/node_modules/test-package/service.js'); - const {program, analysis} = setUpAndAnalyzeProgram([ - { - name: INDEX_FILENAME, - contents: ` - import {${type}} from '@angular/core'; - import {MyService} from './service'; - - export class TestClass {} - TestClass.decorators = [ - { type: ${type}, args: [{${args}${propName}: [MyService]}] } - ]; - `, - }, - { - name: SERVICE_FILENAME, - contents: ` - export declare class MyService {} - `, - } - ]); - - const index = program.getSourceFile(SERVICE_FILENAME)!; - expect(hasInjectableDecorator(index, analysis, 'MyService')).toBe(true); - }); - - it(`should not migrate services in a different package`, () => { - const SERVICE_FILENAME = _('/node_modules/external/index.d.ts'); - const {program, analysis} = setUpAndAnalyzeProgram([ - { - name: INDEX_FILENAME, - contents: ` - import {${type}} from '@angular/core'; - import {MyService} from 'external'; - - export class TestClass {} - TestClass.decorators = [ - { type: ${type}, args: [{${args}${propName}: [MyService]}] } - ]; - `, - }, - { - name: SERVICE_FILENAME, - contents: ` - export declare class MyService {} - `, - } - ]); - - const index = program.getSourceFile(SERVICE_FILENAME)!; - expect(hasInjectableDecorator(index, analysis, 'MyService')).toBe(false); - }); - - it(`should deal with renamed imports for @${type}`, () => { - const {program, analysis} = setUpAndAnalyzeProgram([{ - name: INDEX_FILENAME, - contents: ` - import {${type} as Renamed} from '@angular/core'; - - export class MyService {} - - export class TestClass {} - TestClass.decorators = [ - { type: Renamed, args: [{${args}${propName}: [MyService]}] } - ]; - `, - }]); - - const index = program.getSourceFile(INDEX_FILENAME)!; - expect(hasInjectableDecorator(index, analysis, 'MyService')).toBe(true); - }); - - it(`should deal with decorators named @${type} not from '@angular/core'`, () => { - const {program, analysis} = setUpAndAnalyzeProgram([{ - name: INDEX_FILENAME, - contents: ` - import {${type}} from 'other'; - - export class MyService {} - - export class TestClass {} - TestClass.decorators = [ - { type: ${type}, args: [{${args}${propName}: [MyService]}] } - ]; - `, - }]); - - const index = program.getSourceFile(INDEX_FILENAME)!; - expect(hasInjectableDecorator(index, analysis, 'MyService')).toBe(false); - }); - } - - function setUpAndAnalyzeProgram(testFiles: TestFile[]) { - loadTestFiles(testFiles); - loadFakeCore(getFileSystem()); - const errors: ts.Diagnostic[] = []; - const rootFiles = getRootFiles(testFiles); - const bundle = makeTestEntryPointBundle('test-package', 'esm2015', false, rootFiles); - const program = bundle.src.program; - - const reflectionHost = new Esm2015ReflectionHost(new MockLogger(), false, bundle.src); - const referencesRegistry = new NgccReferencesRegistry(reflectionHost); - const analyzer = new DecorationAnalyzer( - getFileSystem(), bundle, reflectionHost, referencesRegistry, error => errors.push(error)); - analyzer.migrations = [new MissingInjectableMigration()]; - return {program, analysis: analyzer.analyzeProgram(), errors}; - } - - function getInjectableDecorators( - sourceFile: ts.SourceFile, analysis: DecorationAnalyses, className: string) { - const file = analysis.get(sourceFile); - if (file === undefined) { - return []; - } - - const clazz = file.compiledClasses.find(c => c.name === className); - if (clazz === undefined || clazz.decorators === null) { - return []; - } - - return clazz.decorators.filter( - decorator => getAngularCoreDecoratorName(decorator) === 'Injectable'); - } - - function hasInjectableDecorator( - sourceFile: ts.SourceFile, analysis: DecorationAnalyses, className: string) { - return getInjectableDecorators(sourceFile, analysis, className).length > 0; - } - }); -}); diff --git a/packages/compiler-cli/ngcc/test/migrations/undecorated_parent_migration_spec.ts b/packages/compiler-cli/ngcc/test/migrations/undecorated_parent_migration_spec.ts deleted file mode 100644 index fae47bfa89f..00000000000 --- a/packages/compiler-cli/ngcc/test/migrations/undecorated_parent_migration_spec.ts +++ /dev/null @@ -1,264 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import ts from 'typescript'; - -import {absoluteFrom, AbsoluteFsPath, getFileSystem} from '../../../src/ngtsc/file_system'; -import {runInEachFileSystem, TestFile} from '../../../src/ngtsc/file_system/testing'; -import {MockLogger} from '../../../src/ngtsc/logging/testing'; -import {loadFakeCore, loadTestFiles} from '../../../src/ngtsc/testing'; -import {DecorationAnalyzer} from '../../src/analysis/decoration_analyzer'; -import {NgccReferencesRegistry} from '../../src/analysis/ngcc_references_registry'; -import {Esm2015ReflectionHost} from '../../src/host/esm2015_host'; -import {UndecoratedParentMigration} from '../../src/migrations/undecorated_parent_migration'; -import {getRootFiles, makeTestEntryPointBundle} from '../helpers/utils'; - -runInEachFileSystem(() => { - describe('UndecoratedParentMigration', () => { - let _: typeof absoluteFrom; - let INDEX_FILENAME: AbsoluteFsPath; - beforeEach(() => { - _ = absoluteFrom; - INDEX_FILENAME = _('/node_modules//test-package/index.js'); - }); - - it('should ignore undecorated classes', () => { - const {program, analysis, errors} = setUpAndAnalyzeProgram([{ - name: INDEX_FILENAME, - contents: ` - export class DerivedClass extends BaseClass {} - export class BaseClass {} - ` - }]); - expect(errors).toEqual([]); - const file = analysis.get(program.getSourceFile(INDEX_FILENAME)!); - expect(file).toBeUndefined(); - }); - - it('should ignore an undecorated base class if the derived class has a constructor', () => { - const {program, analysis, errors} = setUpAndAnalyzeProgram([{ - name: INDEX_FILENAME, - contents: ` - import {Directive, ViewContainerRef} from '@angular/core'; - export class DerivedClass extends BaseClass { - constructor(private vcr: ViewContainerRef) {} - } - DerivedClass.decorators = [ - { type: Directive, args: [{ selector: '[dir]' }] } - ]; - DerivedClass.ctorParameters = () => [ - { type: ViewContainerRef, } - ]; - export class BaseClass {} - ` - }]); - expect(errors).toEqual([]); - const file = analysis.get(program.getSourceFile(INDEX_FILENAME)!)!; - expect(file.compiledClasses.find(c => c.name === 'DerivedClass')).toBeDefined(); - expect(file.compiledClasses.find(c => c.name === 'BaseClass')).toBeUndefined(); - }); - - it('should add a decorator to an undecorated base class if the derived class is a Directive with no constructor', - () => { - const {program, analysis, errors} = setUpAndAnalyzeProgram([{ - name: INDEX_FILENAME, - contents: ` - import {Directive, ViewContainerRef} from '@angular/core'; - export class DerivedClass extends BaseClass { - } - DerivedClass.decorators = [ - { type: Directive, args: [{ selector: '[dir]' }] } - ]; - export class BaseClass { - constructor(private vcr: ViewContainerRef) {} - } - BaseClass.ctorParameters = () => [ - { type: ViewContainerRef, } - ]; - ` - }]); - expect(errors).toEqual([]); - const file = analysis.get(program.getSourceFile(INDEX_FILENAME)!)!; - expect(file.compiledClasses.find(c => c.name === 'DerivedClass')).toBeDefined(); - const baseClass = file.compiledClasses.find(c => c.name === 'BaseClass')!; - expect(baseClass.decorators!.length).toEqual(1); - const decorator = baseClass.decorators![0]; - expect(decorator.name).toEqual('Directive'); - expect(decorator.identifier).toBeNull('The decorator must be synthesized'); - expect(decorator.import).toEqual({from: '@angular/core', name: 'Directive'}); - expect(decorator.args!.length).toEqual(0); - }); - - it('should not add a decorator to a base class that is already decorated', () => { - const {program, analysis, errors} = setUpAndAnalyzeProgram([{ - name: INDEX_FILENAME, - contents: ` - import {Directive, ViewContainerRef} from '@angular/core'; - export class DerivedClass extends BaseClass { - } - DerivedClass.decorators = [ - { type: Directive, args: [{ selector: '[dir]' }] } - ]; - export class BaseClass { - constructor(private vcr: ViewContainerRef) {} - } - BaseClass.decorators = [ - { type: Directive, args: [] } - ]; - BaseClass.ctorParameters = () => [ - { type: ViewContainerRef, } - ]; - ` - }]); - expect(errors).toEqual([]); - const file = analysis.get(program.getSourceFile(INDEX_FILENAME)!)!; - expect(file.compiledClasses.find(c => c.name === 'DerivedClass')).toBeDefined(); - const baseClass = file.compiledClasses.find(c => c.name === 'BaseClass')!; - expect(baseClass.decorators!.length).toEqual(1); - const decorator = baseClass.decorators![0]; - expect(decorator.name).toEqual('Directive'); - expect(decorator.identifier).not.toBeNull('The decorator must not be synthesized'); - }); - - it('should add decorators to all classes in an inheritance chain until a constructor is found', - () => { - const {program, analysis, errors} = setUpAndAnalyzeProgram([{ - name: INDEX_FILENAME, - contents: ` - import {Directive, ViewContainerRef} from '@angular/core'; - export class DerivedClass extends IntermediateClass { - } - DerivedClass.decorators = [ - { type: Directive, args: [{ selector: '[dir]' }] } - ]; - export class IntermediateClass extends BaseClass {} - export class BaseClass extends RealBaseClass { - constructor(private vcr: ViewContainerRef) {} - } - BaseClass.ctorParameters = () => [ - { type: ViewContainerRef, } - ]; - export class RealBaseClass {} - ` - }]); - expect(errors).toEqual([]); - const file = analysis.get(program.getSourceFile(INDEX_FILENAME)!)!; - expect(file.compiledClasses.find(c => c.name === 'DerivedClass')).toBeDefined(); - expect(file.compiledClasses.find(c => c.name === 'RealBaseClass')).toBeUndefined(); - - const intermediateClass = file.compiledClasses.find(c => c.name === 'IntermediateClass')!; - expect(intermediateClass.decorators!.length).toEqual(1); - const intermediateDecorator = intermediateClass.decorators![0]; - expect(intermediateDecorator.name).toEqual('Directive'); - expect(intermediateDecorator.identifier).toBeNull('The decorator must be synthesized'); - expect(intermediateDecorator.import).toEqual({from: '@angular/core', name: 'Directive'}); - expect(intermediateDecorator.args!.length).toEqual(0); - - const baseClass = file.compiledClasses.find(c => c.name === 'BaseClass')!; - expect(baseClass.decorators!.length).toEqual(1); - const baseDecorator = baseClass.decorators![0]; - expect(baseDecorator.name).toEqual('Directive'); - expect(baseDecorator.identifier).toBeNull('The decorator must be synthesized'); - expect(baseDecorator.import).toEqual({from: '@angular/core', name: 'Directive'}); - expect(baseDecorator.args!.length).toEqual(0); - }); - - it('should handle the base class being in a different file (same package) as the derived class', - () => { - const BASE_FILENAME = _('/node_modules/test-package/base.js'); - const {program, analysis, errors} = setUpAndAnalyzeProgram([ - { - name: INDEX_FILENAME, - contents: ` - import {Directive, ViewContainerRef} from '@angular/core'; - import {BaseClass} from './base'; - export class DerivedClass extends BaseClass { - } - DerivedClass.decorators = [ - { type: Directive, args: [{ selector: '[dir]' }] } - ]; - ` - }, - { - name: BASE_FILENAME, - contents: ` - export class BaseClass { - constructor(private vcr: ViewContainerRef) {} - } - BaseClass.ctorParameters = () => [ - { type: ViewContainerRef, } - ]; - ` - } - ]); - expect(errors).toEqual([]); - const file = analysis.get(program.getSourceFile(BASE_FILENAME)!)!; - const baseClass = file.compiledClasses.find(c => c.name === 'BaseClass')!; - expect(baseClass.decorators!.length).toEqual(1); - const decorator = baseClass.decorators![0]; - expect(decorator.name).toEqual('Directive'); - expect(decorator.identifier).toBeNull('The decorator must be synthesized'); - expect(decorator.import).toEqual({from: '@angular/core', name: 'Directive'}); - expect(decorator.args!.length).toEqual(0); - }); - - it('should skip the base class if it is in a different package from the derived class', () => { - const BASE_FILENAME = _('/node_modules/other-package/index.js'); - const {errors} = setUpAndAnalyzeProgram([ - { - name: INDEX_FILENAME, - contents: ` - import {Directive, ViewContainerRef} from '@angular/core'; - import {BaseClass} from 'other-package'; - export class DerivedClass extends BaseClass { - } - DerivedClass.decorators = [ - { type: Directive, args: [{ selector: '[dir]' }] } - ]; - ` - }, - { - name: BASE_FILENAME, - contents: ` - export class BaseClass { - constructor(private vcr: ViewContainerRef) {} - } - BaseClass.ctorParameters = () => [ - { type: ViewContainerRef, } - ]; - ` - } - ]); - - expect(errors.length).toBe(1); - expect(errors[0].messageText) - .toBe( - `The directive DerivedClass inherits its constructor ` + - `from BaseClass, but the latter does not have an Angular ` + - `decorator of its own. Dependency injection will not be ` + - `able to resolve the parameters of BaseClass's ` + - `constructor. Either add a @Directive decorator to ` + - `BaseClass, or add an explicit constructor to DerivedClass.`); - }); - }); - - function setUpAndAnalyzeProgram(testFiles: TestFile[]) { - loadTestFiles(testFiles); - loadFakeCore(getFileSystem()); - const errors: ts.Diagnostic[] = []; - const rootFiles = getRootFiles(testFiles); - const bundle = makeTestEntryPointBundle('test-package', 'esm2015', false, rootFiles); - const program = bundle.src.program; - - const reflectionHost = new Esm2015ReflectionHost(new MockLogger(), false, bundle.src); - const referencesRegistry = new NgccReferencesRegistry(reflectionHost); - const analyzer = new DecorationAnalyzer( - getFileSystem(), bundle, reflectionHost, referencesRegistry, error => errors.push(error)); - analyzer.migrations = [new UndecoratedParentMigration()]; - return {program, analysis: analyzer.analyzeProgram(), errors}; - } -}); diff --git a/packages/compiler-cli/ngcc/test/ngcc_options_spec.ts b/packages/compiler-cli/ngcc/test/ngcc_options_spec.ts deleted file mode 100644 index 5f9c9c56246..00000000000 --- a/packages/compiler-cli/ngcc/test/ngcc_options_spec.ts +++ /dev/null @@ -1,202 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ - -// Note: We do not use a namespace import here because this will result in the -// named exports being modified if we apply jasmine spies on `realFs`. Using -// the default export gives us an object where we can patch properties on. -import os from 'os'; - -import {absoluteFrom, AbsoluteFsPath, FileSystem, getFileSystem} from '../../src/ngtsc/file_system'; -import {runInEachFileSystem} from '../../src/ngtsc/file_system/testing'; -import {MockLogger} from '../../src/ngtsc/logging/testing'; -import {clearTsConfigCache, getMaxNumberOfWorkers, getSharedSetup, NgccOptions} from '../src/ngcc_options'; - - - -runInEachFileSystem(() => { - let fs: FileSystem; - let _abs: typeof absoluteFrom; - let projectPath: AbsoluteFsPath; - - beforeEach(() => { - fs = getFileSystem(); - _abs = absoluteFrom; - projectPath = _abs('/project'); - }); - - describe('getSharedSetup()', () => { - let pathToProjectTsConfig: AbsoluteFsPath; - let pathToCustomTsConfig: AbsoluteFsPath; - - beforeEach(() => { - clearTsConfigCache(); - pathToProjectTsConfig = fs.resolve(projectPath, 'tsconfig.json'); - fs.ensureDir(fs.dirname(pathToProjectTsConfig)); - fs.writeFile(pathToProjectTsConfig, '{"files": ["src/index.ts"]}'); - pathToCustomTsConfig = _abs('/path/to/tsconfig.json'); - fs.ensureDir(fs.dirname(pathToCustomTsConfig)); - fs.writeFile(pathToCustomTsConfig, '{"files": ["custom/index.ts"]}'); - }); - - it('should load the tsconfig.json at the project root if tsConfigPath is `undefined`', () => { - const setup = getSharedSetup({...createOptions()}); - expect(setup.tsConfigPath).toBeUndefined(); - expect(setup.tsConfig?.rootNames).toEqual([fs.resolve(projectPath, 'src/index.ts')]); - }); - - it('should load a specific tsconfig.json if tsConfigPath is a string', () => { - const setup = getSharedSetup({...createOptions(), tsConfigPath: pathToCustomTsConfig}); - expect(setup.tsConfigPath).toEqual(pathToCustomTsConfig); - expect(setup.tsConfig?.rootNames).toEqual([_abs('/path/to/custom/index.ts')]); - }); - - it('should not load a tsconfig.json if tsConfigPath is `null`', () => { - const setup = getSharedSetup({...createOptions(), tsConfigPath: null}); - expect(setup.tsConfigPath).toBe(null); - expect(setup.tsConfig).toBe(null); - }); - - it('should warn about a solution-style tsconfig if the tsConfigPath is inferred', () => { - fs.writeFile(fs.resolve(projectPath, 'tsconfig.app.json'), '{"files": ["src/index.ts"]}'); - fs.writeFile(fs.resolve(projectPath, 'tsconfig.test.json'), '{"files": ["src/test.ts"]}'); - fs.writeFile(pathToProjectTsConfig, JSON.stringify({ - 'files': [], - 'references': [ - {'path': 'tsconfig.app.json'}, - {'path': 'tsconfig.test.json'}, - ] - })); - const setup = getSharedSetup({...createOptions()}); - expect(setup.tsConfigPath).toBeUndefined(); - expect(setup.tsConfig?.rootNames).toEqual([]); - expect((setup.logger as MockLogger).logs.warn).toEqual([[ - `The inferred tsconfig file "${ - pathToProjectTsConfig}" appears to be "solution-style" since it contains no root files but does contain project references.\n` + - `This is probably not wanted, since ngcc is unable to infer settings like "paths" mappings from such a file.\n` + - `Perhaps you should have explicitly specified one of the referenced projects using the --tsconfig option. For example:\n\n` + - ` ngcc ... --tsconfig "tsconfig.app.json"\n` + - ` ngcc ... --tsconfig "tsconfig.test.json"\n` + - `\nFind out more about solution-style tsconfig at https://devblogs.microsoft.com/typescript/announcing-typescript-3-9/#solution-style-tsconfig.\n` + - `If you did intend to use this file, then you can hide this warning by providing it explicitly:\n\n` + - ` ngcc ... --tsconfig "tsconfig.json"` - ]]); - }); - - it('should not warn about a solution-style tsconfig if the tsConfigPath is explicit', () => { - fs.writeFile(fs.resolve(projectPath, 'tsconfig.app.json'), '{"files": ["src/index.ts"]}'); - fs.writeFile(fs.resolve(projectPath, 'tsconfig.test.json'), '{"files": ["src/test.ts"]}'); - fs.writeFile(pathToProjectTsConfig, JSON.stringify({ - 'files': [], - 'references': [ - {'path': 'tsconfig.app.json'}, - {'path': 'tsconfig.test.json'}, - ] - })); - const setup = getSharedSetup({...createOptions(), tsConfigPath: pathToProjectTsConfig}); - expect(setup.tsConfigPath).toEqual(pathToProjectTsConfig); - expect(setup.tsConfig?.rootNames).toEqual([]); - expect((setup.logger as MockLogger).logs.warn).toEqual([]); - }); - - it('should not modify `compileAllFormats` if `typingsOnly` is falsy', () => { - let setup = getSharedSetup({...createOptions(), compileAllFormats: true, typingsOnly: false}); - expect(setup.typingsOnly).toBe(false); - expect(setup.compileAllFormats).toBe(true); - - setup = getSharedSetup({...createOptions(), compileAllFormats: true}); - expect(setup.typingsOnly).toBe(false); - expect(setup.compileAllFormats).toBe(true); - }); - - it('should force `compileAllFormats` to false if `typingsOnly` is true', () => { - const setup = - getSharedSetup({...createOptions(), compileAllFormats: true, typingsOnly: true}); - expect(setup.typingsOnly).toBe(true); - expect(setup.compileAllFormats).toBe(false); - }); - }); - - describe('getMaxNumberOfWorkers', () => { - let processEnv: NodeJS.ProcessEnv; - let cpuSpy: jasmine.Spy; - beforeEach(() => { - processEnv = process.env; - process.env = {...process.env}; - cpuSpy = spyOn(os, 'cpus'); - }); - afterEach(() => { - process.env = processEnv; - }); - - it('should use NGCC_MAX_WORKERS environment variable if set', () => { - process.env.NGCC_MAX_WORKERS = '16'; - expect(getMaxNumberOfWorkers()).toBe(16); - process.env.NGCC_MAX_WORKERS = '8'; - expect(getMaxNumberOfWorkers()).toBe(8); - process.env.NGCC_MAX_WORKERS = ' 8 '; - expect(getMaxNumberOfWorkers()).toBe(8); - }); - - it('should throw an error if NGCC_MAX_WORKERS is less than 1', () => { - process.env.NGCC_MAX_WORKERS = '0'; - expect(() => getMaxNumberOfWorkers()) - .toThrow(new Error('NGCC_MAX_WORKERS should be at least 1.')); - process.env.NGCC_MAX_WORKERS = '-1'; - expect(() => getMaxNumberOfWorkers()) - .toThrow(new Error('NGCC_MAX_WORKERS should be at least 1.')); - }); - - it('should throw an error if NGCC_MAX_WORKERS is not an integer', () => { - process.env.NGCC_MAX_WORKERS = 'a'; - expect(() => getMaxNumberOfWorkers()) - .toThrow(new Error('NGCC_MAX_WORKERS should be an integer.')); - process.env.NGCC_MAX_WORKERS = '1.5'; - expect(() => getMaxNumberOfWorkers()) - .toThrow(new Error('NGCC_MAX_WORKERS should be an integer.')); - process.env.NGCC_MAX_WORKERS = '-'; - expect(() => getMaxNumberOfWorkers()) - .toThrow(new Error('NGCC_MAX_WORKERS should be an integer.')); - }); - - it('should fallback to the number of cpus, minus one (for the master process), with a maximum of 4 workers', - () => { - simulateNumberOfCpus(1); - expect(getMaxNumberOfWorkers()).toBe(1); - simulateNumberOfCpus(2); - expect(getMaxNumberOfWorkers()).toBe(1); - simulateNumberOfCpus(4); - expect(getMaxNumberOfWorkers()).toBe(3); - simulateNumberOfCpus(6); - expect(getMaxNumberOfWorkers()).toBe(4); - simulateNumberOfCpus(8); - expect(getMaxNumberOfWorkers()).toBe(4); - }); - - function simulateNumberOfCpus(cpus: number): void { - cpuSpy.and.returnValue(new Array(cpus).fill({model: 'Mock CPU'} as any)); - } - }); - - /** - * This function creates an object that contains the minimal required properties for NgccOptions. - */ - function createOptions(): NgccOptions { - return { - async: false, - basePath: fs.resolve(projectPath, 'node_modules'), - propertiesToConsider: ['es2015'], - compileAllFormats: false, - createNewEntryPointFormats: false, - logger: new MockLogger(), - fileSystem: getFileSystem(), - errorOnFailedEntryPoint: true, - enableI18nLegacyMessageIdFormat: true, - invalidateEntryPointManifest: false, - }; - } -}); diff --git a/packages/compiler-cli/ngcc/test/packages/adjust_cjs_exports_spec.ts b/packages/compiler-cli/ngcc/test/packages/adjust_cjs_exports_spec.ts deleted file mode 100644 index 475fc607049..00000000000 --- a/packages/compiler-cli/ngcc/test/packages/adjust_cjs_exports_spec.ts +++ /dev/null @@ -1,48 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import {adjustElementAccessExports} from '../../src/packages/adjust_cjs_umd_exports'; - -describe('adjustElementAccessExports', () => { - it('should replace exports using element access syntax', () => { - expectChanged(`exports['Foo']`, `exports. Foo `); - expectChanged( - `exports['Foo']; exports['Bar']; exports['Baz'];`, - `exports. Foo ; exports. Bar ; exports. Baz ;`); - expectChanged( - `function(exports) { exports['Foo'] = Foo; }`, - `function(exports) { exports. Foo = Foo; }`); - expectChanged( - `function(exports) { exports['ɵFoo'] = Foo; }`, - `function(exports) { exports. ɵFoo = Foo; }`); - }); - - it('should not replace invalid identifier names', () => { - expectUnchanged(`exports['']`); - expectUnchanged(`exports['let']`); - expectUnchanged(`exports['var']`); - expectUnchanged(`exports['const']`); - }); - - it('should not replace export syntax in comments and strings', () => { - expectUnchanged(`/* exports['Foo'] = Foo; */`); - expectUnchanged(`"exports['Foo'] = Foo;"`); - expectChanged( - `/* exports['Foo'] = Foo; */ exports['Foo'] = Foo;`, - `/* exports['Foo'] = Foo; */ exports. Foo = Foo;`); - }); -}); - -function expectChanged(input: string, expected: string) { - const result = adjustElementAccessExports(input); - expect(result).toEqual(expected); -} - -function expectUnchanged(input: string) { - const result = adjustElementAccessExports(input); - expect(result).toEqual(input); -} diff --git a/packages/compiler-cli/ngcc/test/packages/build_marker_spec.ts b/packages/compiler-cli/ngcc/test/packages/build_marker_spec.ts deleted file mode 100644 index 2d462e6bbee..00000000000 --- a/packages/compiler-cli/ngcc/test/packages/build_marker_spec.ts +++ /dev/null @@ -1,305 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import {absoluteFrom, getFileSystem} from '../../../src/ngtsc/file_system'; -import {runInEachFileSystem} from '../../../src/ngtsc/file_system/testing'; -import {loadTestFiles} from '../../../src/ngtsc/testing'; -import {cleanPackageJson, hasBeenProcessed, markAsProcessed, needsCleaning, NGCC_VERSION} from '../../src/packages/build_marker'; -import {EntryPointPackageJson} from '../../src/packages/entry_point'; -import {DirectPackageJsonUpdater} from '../../src/writing/package_json_updater'; - -runInEachFileSystem(() => { - describe('Marker files', () => { - let _: typeof absoluteFrom; - beforeEach(() => { - _ = absoluteFrom; - loadTestFiles([ - { - name: _('/node_modules/@angular/common/package.json'), - contents: - `{"fesm2015": "./fesm2015/common.js", "fesm5": "./fesm5/common.js", "typings": "./common.d.ts"}` - }, - {name: _('/node_modules/@angular/common/fesm2015/common.js'), contents: 'DUMMY CONTENT'}, - {name: _('/node_modules/@angular/common/fesm2015/http.js'), contents: 'DUMMY CONTENT'}, - { - name: _('/node_modules/@angular/common/fesm2015/http/testing.js'), - contents: 'DUMMY CONTENT' - }, - {name: _('/node_modules/@angular/common/fesm2015/testing.js'), contents: 'DUMMY CONTENT'}, - { - name: _('/node_modules/@angular/common/http/package.json'), - contents: - `{"fesm2015": "../fesm2015/http.js", "fesm5": "../fesm5/http.js", "typings": "./http.d.ts"}` - }, - { - name: _('/node_modules/@angular/common/http/testing/package.json'), - contents: - `{"fesm2015": "../../fesm2015/http/testing.js", "fesm5": "../../fesm5/http/testing.js", "typings": "../http/testing.d.ts" }` - }, - {name: _('/node_modules/@angular/common/other/package.json'), contents: '{ }'}, - { - name: _('/node_modules/@angular/common/testing/package.json'), - contents: - `{"fesm2015": "../fesm2015/testing.js", "fesm5": "../fesm5/testing.js", "typings": "../testing.d.ts"}` - }, - {name: _('/node_modules/@angular/common/node_modules/tslib/package.json'), contents: '{ }'}, - { - name: _( - '/node_modules/@angular/common/node_modules/tslib/node_modules/other-lib/package.json'), - contents: '{ }' - }, - { - name: _('/node_modules/@angular/no-typings/package.json'), - contents: `{ "fesm2015": "./fesm2015/index.js" }` - }, - {name: _('/node_modules/@angular/no-typings/fesm2015/index.js'), contents: 'DUMMY CONTENT'}, - { - name: _('/node_modules/@angular/no-typings/fesm2015/index.d.ts'), - contents: 'DUMMY CONTENT' - }, - { - name: _('/node_modules/@angular/other/not-package.json'), - contents: '{ "fesm2015": "./fesm2015/other.js" }' - }, - { - name: _('/node_modules/@angular/other/package.jsonot'), - contents: '{ "fesm5": "./fesm5/other.js" }' - }, - { - name: _('/node_modules/@angular/other2/node_modules_not/lib1/package.json'), - contents: '{ }' - }, - { - name: _('/node_modules/@angular/other2/not_node_modules/lib2/package.json'), - contents: '{ }' - }, - ]); - }); - - describe('markAsProcessed', () => { - it('should write properties in the package.json containing the version placeholder', () => { - const COMMON_PACKAGE_PATH = _('/node_modules/@angular/common/package.json'); - const fs = getFileSystem(); - const pkgUpdater = new DirectPackageJsonUpdater(fs); - // TODO: Determine the correct/best type for the `pkg` type. - let pkg = JSON.parse(fs.readFile(COMMON_PACKAGE_PATH)) as EntryPointPackageJson; - expect(pkg.__processed_by_ivy_ngcc__).toBeUndefined(); - expect(pkg.scripts).toBeUndefined(); - - markAsProcessed(pkgUpdater, pkg, COMMON_PACKAGE_PATH, ['fesm2015', 'fesm5']); - pkg = JSON.parse(fs.readFile(COMMON_PACKAGE_PATH)) as EntryPointPackageJson; - expect(pkg.__processed_by_ivy_ngcc__!.fesm2015).toBe('0.0.0-PLACEHOLDER'); - expect(pkg.__processed_by_ivy_ngcc__!.fesm5).toBe('0.0.0-PLACEHOLDER'); - expect(pkg.__processed_by_ivy_ngcc__!.esm2015).toBeUndefined(); - expect(pkg.__processed_by_ivy_ngcc__!.esm5).toBeUndefined(); - expect(pkg.scripts!.prepublishOnly).toBeDefined(); - - markAsProcessed(pkgUpdater, pkg, COMMON_PACKAGE_PATH, ['esm2015', 'esm5']); - pkg = JSON.parse(fs.readFile(COMMON_PACKAGE_PATH)) as EntryPointPackageJson; - expect(pkg.__processed_by_ivy_ngcc__!.fesm2015).toBe('0.0.0-PLACEHOLDER'); - expect(pkg.__processed_by_ivy_ngcc__!.fesm5).toBe('0.0.0-PLACEHOLDER'); - expect(pkg.__processed_by_ivy_ngcc__!.esm2015).toBe('0.0.0-PLACEHOLDER'); - expect(pkg.__processed_by_ivy_ngcc__!.esm5).toBe('0.0.0-PLACEHOLDER'); - expect(pkg.scripts!.prepublishOnly).toBeDefined(); - }); - - it('should update the packageJson object in-place', () => { - const COMMON_PACKAGE_PATH = _('/node_modules/@angular/common/package.json'); - const fs = getFileSystem(); - const pkgUpdater = new DirectPackageJsonUpdater(fs); - const pkg = JSON.parse(fs.readFile(COMMON_PACKAGE_PATH)) as EntryPointPackageJson; - expect(pkg.__processed_by_ivy_ngcc__).toBeUndefined(); - expect(pkg.scripts).toBeUndefined(); - - markAsProcessed(pkgUpdater, pkg, COMMON_PACKAGE_PATH, ['fesm2015', 'fesm5']); - expect(pkg.__processed_by_ivy_ngcc__!.fesm2015).toBe('0.0.0-PLACEHOLDER'); - expect(pkg.__processed_by_ivy_ngcc__!.fesm5).toBe('0.0.0-PLACEHOLDER'); - expect(pkg.__processed_by_ivy_ngcc__!.esm2015).toBeUndefined(); - expect(pkg.__processed_by_ivy_ngcc__!.esm5).toBeUndefined(); - expect(pkg.scripts!.prepublishOnly).toBeDefined(); - - markAsProcessed(pkgUpdater, pkg, COMMON_PACKAGE_PATH, ['esm2015', 'esm5']); - expect(pkg.__processed_by_ivy_ngcc__!.fesm2015).toBe('0.0.0-PLACEHOLDER'); - expect(pkg.__processed_by_ivy_ngcc__!.fesm5).toBe('0.0.0-PLACEHOLDER'); - expect(pkg.__processed_by_ivy_ngcc__!.esm2015).toBe('0.0.0-PLACEHOLDER'); - expect(pkg.__processed_by_ivy_ngcc__!.esm5).toBe('0.0.0-PLACEHOLDER'); - expect(pkg.scripts!.prepublishOnly).toBeDefined(); - }); - - it('should one perform one write operation for all updated properties', () => { - const COMMON_PACKAGE_PATH = _('/node_modules/@angular/common/package.json'); - const fs = getFileSystem(); - const pkgUpdater = new DirectPackageJsonUpdater(fs); - const writeFileSpy = spyOn(fs, 'writeFile'); - let pkg = JSON.parse(fs.readFile(COMMON_PACKAGE_PATH)) as EntryPointPackageJson; - - markAsProcessed( - pkgUpdater, pkg, COMMON_PACKAGE_PATH, ['fesm2015', 'fesm5', 'esm2015', 'esm5']); - expect(writeFileSpy).toHaveBeenCalledTimes(1); - }); - - it(`should keep backup of existing 'prepublishOnly' script`, () => { - const COMMON_PACKAGE_PATH = _('/node_modules/@angular/common/package.json'); - const fs = getFileSystem(); - const pkgUpdater = new DirectPackageJsonUpdater(fs); - const prepublishOnly = 'existing script'; - let pkg = JSON.parse(fs.readFile(COMMON_PACKAGE_PATH)) as EntryPointPackageJson; - pkg.scripts = {prepublishOnly}; - - markAsProcessed(pkgUpdater, pkg, COMMON_PACKAGE_PATH, ['fesm2015']); - pkg = JSON.parse(fs.readFile(COMMON_PACKAGE_PATH)) as EntryPointPackageJson; - expect(pkg.scripts!.prepublishOnly).toContain('This is not allowed'); - expect(pkg.scripts!.prepublishOnly__ivy_ngcc_bak).toBe(prepublishOnly); - }); - - it(`should not keep backup of overwritten 'prepublishOnly' script`, () => { - const COMMON_PACKAGE_PATH = _('/node_modules/@angular/common/package.json'); - const fs = getFileSystem(); - const pkgUpdater = new DirectPackageJsonUpdater(fs); - let pkg = JSON.parse(fs.readFile(COMMON_PACKAGE_PATH)) as EntryPointPackageJson; - - markAsProcessed(pkgUpdater, pkg, COMMON_PACKAGE_PATH, ['fesm2015']); - - pkg = JSON.parse(fs.readFile(COMMON_PACKAGE_PATH)) as EntryPointPackageJson; - expect(pkg.scripts!.prepublishOnly).toContain('This is not allowed'); - expect(pkg.scripts!.prepublishOnly__ivy_ngcc_bak).toBeUndefined(); - - // Running again, now that there is `prepublishOnly` script (created by `ngcc`), it should - // still not back it up as `prepublishOnly__ivy_ngcc_bak`. - markAsProcessed(pkgUpdater, pkg, COMMON_PACKAGE_PATH, ['fesm2015']); - - pkg = JSON.parse(fs.readFile(COMMON_PACKAGE_PATH)) as EntryPointPackageJson; - expect(pkg.scripts!.prepublishOnly).toContain('This is not allowed'); - expect(pkg.scripts!.prepublishOnly__ivy_ngcc_bak).toBeUndefined(); - }); - }); - - describe('hasBeenProcessed', () => { - it('should return true if the marker exists for the given format property', () => { - expect(hasBeenProcessed( - {name: 'test', __processed_by_ivy_ngcc__: {'fesm2015': '0.0.0-PLACEHOLDER'}}, - 'fesm2015')) - .toBe(true); - }); - - it('should return false if the marker does not exist for the given format property', () => { - expect(hasBeenProcessed( - {name: 'test', __processed_by_ivy_ngcc__: {'fesm2015': '0.0.0-PLACEHOLDER'}}, - 'module')) - .toBe(false); - }); - - it('should return false if no markers exist', () => { - expect(hasBeenProcessed({name: 'test'}, 'module')).toBe(false); - }); - }); - - describe('needsCleaning()', () => { - it('should return true if any format has been compiled with a different version', () => { - expect(needsCleaning({ - name: 'test', - __processed_by_ivy_ngcc__: {'fesm2015': '8.0.0', 'esm5': NGCC_VERSION} - })).toBe(true); - }); - - it('should return false if all formats have been compiled with the current version', () => { - expect(needsCleaning({name: 'test', __processed_by_ivy_ngcc__: {'fesm2015': NGCC_VERSION}})) - .toBe(false); - }); - - it('should return false if no formats have been compiled', () => { - expect(needsCleaning({name: 'test', __processed_by_ivy_ngcc__: {}})).toBe(false); - expect(needsCleaning({name: 'test'})).toBe(false); - }); - }); - - describe('cleanPackageJson()', () => { - it('should not touch the object if there is no build marker', () => { - const packageJson: EntryPointPackageJson = {name: 'test-package'}; - const result = cleanPackageJson(packageJson); - expect(result).toBe(false); - expect(packageJson).toEqual({name: 'test-package'}); - }); - - it('should remove the processed marker', () => { - const packageJson: EntryPointPackageJson = { - name: 'test-package', - __processed_by_ivy_ngcc__: {'fesm2015': '8.0.0'} - }; - const result = cleanPackageJson(packageJson); - expect(result).toBe(true); - expect(packageJson).toEqual({name: 'test-package'}); - }); - - it('should remove new entry-point format properties', () => { - const packageJson: EntryPointPackageJson = { - name: 'test-package', - __processed_by_ivy_ngcc__: {'fesm2015': '8.0.0'}, - fesm2015: 'index.js', - fesm2015_ivy_ngcc: '__ivy_ngcc__/index.js' - }; - const result = cleanPackageJson(packageJson); - expect(result).toBe(true); - expect(packageJson).toEqual({name: 'test-package', fesm2015: 'index.js'}); - }); - - it('should remove the prepublish script if there was a processed marker', () => { - const packageJson: EntryPointPackageJson = { - name: 'test-package', - __processed_by_ivy_ngcc__: {'fesm2015': '8.0.0'}, - scripts: {prepublishOnly: 'added by ngcc', test: 'do testing'}, - }; - const result = cleanPackageJson(packageJson); - expect(result).toBe(true); - expect(packageJson).toEqual({ - name: 'test-package', - scripts: {test: 'do testing'}, - }); - }); - - it('should revert and remove the backup for the prepublish script if there was a processed marker', - () => { - const packageJson: EntryPointPackageJson = { - name: 'test-package', - __processed_by_ivy_ngcc__: {'fesm2015': '8.0.0'}, - scripts: { - prepublishOnly: 'added by ngcc', - prepublishOnly__ivy_ngcc_bak: 'original', - test: 'do testing' - }, - }; - const result = cleanPackageJson(packageJson); - expect(result).toBe(true); - expect(packageJson).toEqual({ - name: 'test-package', - scripts: {prepublishOnly: 'original', test: 'do testing'}, - }); - }); - - it('should not touch the scripts if there was no processed marker', () => { - const packageJson: EntryPointPackageJson = { - name: 'test-package', - scripts: { - prepublishOnly: 'added by ngcc', - prepublishOnly__ivy_ngcc_bak: 'original', - test: 'do testing' - }, - }; - const result = cleanPackageJson(packageJson); - expect(result).toBe(false); - expect(packageJson).toEqual({ - name: 'test-package', - scripts: { - prepublishOnly: 'added by ngcc', - prepublishOnly__ivy_ngcc_bak: 'original', - test: 'do testing' - } - }); - }); - }); - }); -}); diff --git a/packages/compiler-cli/ngcc/test/packages/configuration_spec.ts b/packages/compiler-cli/ngcc/test/packages/configuration_spec.ts deleted file mode 100644 index 5c35964643d..00000000000 --- a/packages/compiler-cli/ngcc/test/packages/configuration_spec.ts +++ /dev/null @@ -1,888 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import {createHash} from 'crypto'; - -import {absoluteFrom, getFileSystem, ReadonlyFileSystem} from '../../../src/ngtsc/file_system'; -import {runInEachFileSystem} from '../../../src/ngtsc/file_system/testing'; -import {loadTestFiles} from '../../../src/ngtsc/testing'; -import {DEFAULT_NGCC_CONFIG, NgccConfiguration, NgccProjectConfig, PartiallyProcessedConfig, ProcessLockingConfiguration} from '../../src/packages/configuration'; - - -runInEachFileSystem(() => { - let _Abs: typeof absoluteFrom; - let fs: ReadonlyFileSystem; - - beforeEach(() => { - _Abs = absoluteFrom; - fs = getFileSystem(); - }); - - describe('NgccConfiguration', () => { - describe('constructor', () => { - it('should error if a project level config file is badly formatted', () => { - loadTestFiles([{name: _Abs('/project-1/ngcc.config.js'), contents: `bad js code`}]); - expect(() => new NgccConfiguration(fs, _Abs('/project-1'))) - .toThrowError(`Invalid project configuration file at "${ - _Abs('/project-1/ngcc.config.js')}": Unexpected identifier`); - }); - }); - - describe('hash', () => { - it('should compute a hash from the loaded and processed project configuration', () => { - const project1 = _Abs('/project-1'); - const project1Config = fs.resolve(project1, 'ngcc.config.js'); - - loadTestFiles([{ - name: project1Config, - contents: ` - module.exports = { - packages: { - 'package-1': {entryPoints: {'./entry-point-1': {}}}, - }, - };` - }]); - const project1Conf = new NgccConfiguration(fs, project1); - const expectedProject1Config = - `{"packages":{"package-1":[{"entryPoints":{"./entry-point-1":{}},"versionRange":"*"}]},"locking":{},"hashAlgorithm":"sha256"}`; - expect(project1Conf.hash) - .toEqual(createHash('sha256').update(expectedProject1Config).digest('hex')); - - const project2 = _Abs('/project-2'); - const project2Config = fs.resolve(project2, 'ngcc.config.js'); - - loadTestFiles([{ - name: project2Config, - contents: ` - module.exports = { - packages: { - 'package-1': {entryPoints: {'./entry-point-1': {ignore: true}}}, - }, - };` - }]); - const project2Conf = new NgccConfiguration(fs, project2); - const expectedProject2Config = - `{"packages":{"package-1":[{"entryPoints":{"./entry-point-1":{"ignore":true}},"versionRange":"*"}]},"locking":{},"hashAlgorithm":"sha256"}`; - expect(project2Conf.hash) - .toEqual(createHash('sha256').update(expectedProject2Config).digest('hex')); - }); - - it('should compute a hash even if there is no project configuration', () => { - loadTestFiles([{name: _Abs('/project-1/empty.js'), contents: ``}]); - const configuration = new NgccConfiguration(fs, _Abs('/project-1')); - expect(configuration.hash) - .toEqual(createHash('sha256') - .update(new PartiallyProcessedConfig({}).toJson()) - .digest('hex')); - }); - - it('should use a custom hash algorithm if specified in the config', () => { - const project1 = _Abs('/project-1'); - const project1Config = fs.resolve(project1, 'ngcc.config.js'); - - loadTestFiles([{ - name: project1Config, - contents: ` - module.exports = { - packages: { - 'package-1': {entryPoints: {'./entry-point-1': {}}}, - }, - hashAlgorithm: 'md5', - };` - }]); - const project1Conf = new NgccConfiguration(fs, project1); - const expectedProject1Config = - `{"packages":{"package-1":[{"entryPoints":{"./entry-point-1":{}},"versionRange":"*"}]},"locking":{},"hashAlgorithm":"md5"}`; - expect((project1Conf as any).projectConfig.toJson()).toEqual(expectedProject1Config); - expect(project1Conf.hash) - .toEqual(createHash('md5').update(expectedProject1Config).digest('hex')); - }); - }); - - describe('getPackageConfig()', () => { - describe('at the package level', () => { - it('should return configuration for a package found in a package level file, with a matching version', - () => { - loadTestFiles(packageWithConfigFiles('package-1', 'entry-point-1', '1.0.0')); - const readFileSpy = spyOn(fs, 'readFile').and.callThrough(); - const configuration = new NgccConfiguration(fs, _Abs('/project-1')); - const config = configuration.getPackageConfig( - 'package-1', _Abs('/project-1/node_modules/package-1'), '1.0.0'); - - expect(config).toEqual(jasmine.objectContaining({ - ignorableDeepImportMatchers: [], - entryPoints: new Map([ - [_Abs('/project-1/node_modules/package-1/entry-point-1'), {}], - ]), - })); - expect(readFileSpy) - .toHaveBeenCalledWith(_Abs('/project-1/node_modules/package-1/ngcc.config.js')); - }); - - it('should cope with configurations missing an `entryPoints` property', () => { - loadTestFiles([ - { - name: _Abs('/project-1/node_modules/package-1/package.json'), - contents: '{"version": "1.0.0"}', - }, - { - name: _Abs('/project-1/node_modules/package-1/ngcc.config.js'), - contents: ` - module.exports = { - ignorableDeepImportMatchers: [ /xxx/ ], - }; - `, - }, - ]); - - const configuration = new NgccConfiguration(fs, _Abs('/project-1')); - const config = configuration.getPackageConfig( - 'package-1', _Abs('/project-1/node_modules/package-1'), '1.0.0'); - - expect(config).toEqual(jasmine.objectContaining({ - ignorableDeepImportMatchers: [/xxx/], - entryPoints: new Map(), - })); - }); - - it('should handle packages with an Object member name when no config is present', () => { - loadTestFiles([ - { - name: _Abs('/project-1/node_modules/constructor/package.json'), - contents: '{"version": "1.0.0"}', - }, - ]); - - const configuration = new NgccConfiguration(fs, _Abs('/project-1')); - const config = configuration.getPackageConfig( - 'constructor', _Abs('/project-1/node_modules/constructor'), '1.0.0'); - - expect(config).toEqual(jasmine.objectContaining({ - ignorableDeepImportMatchers: [], - entryPoints: new Map(), - })); - }); - - it('should read extra package config from package level file', () => { - loadTestFiles(packageWithConfigFiles( - 'package-1', 'entry-point-1', '1.0.0', 'ignorableDeepImportMatchers: [ /xxx/ ]')); - const configuration = new NgccConfiguration(fs, _Abs('/project-1')); - const config = configuration.getPackageConfig( - 'package-1', _Abs('/project-1/node_modules/package-1'), '1.0.0'); - - expect(config).toEqual(jasmine.objectContaining({ - ignorableDeepImportMatchers: [/xxx/], - entryPoints: new Map([ - [_Abs('/project-1/node_modules/package-1/entry-point-1'), {}], - ]), - })); - }); - - it('should used cached configuration for a package if available', () => { - loadTestFiles(packageWithConfigFiles('package-1', 'entry-point-1', '1.0.0')); - const configuration = new NgccConfiguration(fs, _Abs('/project-1')); - - // Populate the cache - configuration.getPackageConfig( - 'package-1', _Abs('/project-1/node_modules/package-1'), '1.0.0'); - - const readFileSpy = spyOn(fs, 'readFile').and.callThrough(); - const config = configuration.getPackageConfig( - 'package-1', _Abs('/project-1/node_modules/package-1'), '1.0.0'); - - expect(config).toEqual(jasmine.objectContaining({ - ignorableDeepImportMatchers: [], - entryPoints: new Map([ - [_Abs('/project-1/node_modules/package-1/entry-point-1'), {}], - ]), - })); - expect(readFileSpy).not.toHaveBeenCalled(); - }); - - it('should return an empty configuration object if there is no matching configuration for the package', - () => { - loadTestFiles(packageWithConfigFiles('package-2', 'entry-point-1', '1.0.0')); - const configuration = new NgccConfiguration(fs, _Abs('/project-1')); - const config = configuration.getPackageConfig( - 'package-1', _Abs('/project-1/node_modules/package-1'), '1.0.0'); - expect(config).toEqual(jasmine.objectContaining({ - ignorableDeepImportMatchers: [], - entryPoints: new Map(), - })); - }); - - it('should error if a package level config file is badly formatted', () => { - loadTestFiles([{ - name: _Abs('/project-1/node_modules/package-1/ngcc.config.js'), - contents: `bad js code` - }]); - const configuration = new NgccConfiguration(fs, _Abs('/project-1')); - expect( - () => configuration.getPackageConfig( - 'package-1', _Abs('/project-1/node_modules/package-1'), '1.0.0')) - .toThrowError(`Invalid package configuration file at "${ - _Abs( - '/project-1/node_modules/package-1/ngcc.config.js')}": Unexpected identifier`); - }); - - it('should correctly differentiate packages in nested `node_modules/`', () => { - loadTestFiles([ - ...packageWithConfigFiles('package-1', 'entry-point-1', '1.0.0'), - ...packageWithConfigFiles('package-2/node_modules/package-1', 'entry-point-2', '2.0.0'), - ]); - - const configuration = new NgccConfiguration(fs, _Abs('/project-1')); - - expect(configuration.getPackageConfig( - 'package-1', _Abs('/project-1/node_modules/package-1'), '1.0.0')) - .toEqual(jasmine.objectContaining({ - ignorableDeepImportMatchers: [], - entryPoints: new Map([ - [_Abs('/project-1/node_modules/package-1/entry-point-1'), {}], - ]), - })); - - expect(configuration.getPackageConfig( - 'package-1', _Abs('/project-1/node_modules/package-2/node_modules/package-1'), - '2.0.0')) - .toEqual(jasmine.objectContaining({ - ignorableDeepImportMatchers: [], - entryPoints: new Map([ - [ - _Abs('/project-1/node_modules/package-2/node_modules/package-1/entry-point-2'), - {}, - ], - ]), - })); - - // It should also be able to return a cached config for a package name/version - // combination, but adjust the entry-point paths. - // NOTE: While the package does not exist on the test file system, we are able to retrieve - // the config from cache. - expect(configuration.getPackageConfig( - 'package-1', _Abs('/project-1/node_modules/package-3/node_modules/package-1'), - '1.0.0')) - .toEqual(jasmine.objectContaining({ - ignorableDeepImportMatchers: [], - entryPoints: new Map([ - [ - _Abs('/project-1/node_modules/package-3/node_modules/package-1/entry-point-1'), - {}, - ], - ]), - })); - }); - }); - - describe('at the project level', () => { - it('should return configuration for a package found in a project level file', () => { - loadTestFiles([{ - name: _Abs('/project-1/ngcc.config.js'), - contents: ` - module.exports = { - packages: { - 'package-1': { - entryPoints: { - './entry-point-1': {} - }, - ignorableDeepImportMatchers: [ /xxx/ ], - }, - }, - };` - }]); - const configuration = new NgccConfiguration(fs, _Abs('/project-1')); - const config = configuration.getPackageConfig( - 'package-1', _Abs('/project-1/node_modules/package-1'), '1.0.0'); - expect(config).toEqual(jasmine.objectContaining({ - ignorableDeepImportMatchers: [/xxx/], - entryPoints: new Map([ - [_Abs('/project-1/node_modules/package-1/entry-point-1'), {}], - ]), - })); - }); - - it('should cope with configurations missing an `entryPoints` property', () => { - loadTestFiles([ - { - name: _Abs('/project-1/ngcc.config.js'), - contents: ` - module.exports = { - packages: { - 'package-1': { - ignorableDeepImportMatchers: [ /xxx/ ], - }, - }, - }; - `, - }, - ]); - - const configuration = new NgccConfiguration(fs, _Abs('/project-1')); - const config = configuration.getPackageConfig( - 'package-1', _Abs('/project-1/node_modules/package-1'), '1.0.0'); - - expect(config).toEqual(jasmine.objectContaining({ - ignorableDeepImportMatchers: [/xxx/], - entryPoints: new Map(), - })); - }); - - it('should return configuration for the correct version of a package found in a project level file', - () => { - loadTestFiles([{ - name: _Abs('/project-1/ngcc.config.js'), - contents: ` - module.exports = { - packages: { - 'package-1@1.0.0': { - entryPoints: { - './entry-point-1': {} - }, - }, - 'package-1@2.*': { - entryPoints: { - './entry-point-2': {} - }, - }, - 'package-1@^3.2.0': { - entryPoints: { - './entry-point-3': {} - }, - }, - }, - };` - }]); - const configuration = new NgccConfiguration(fs, _Abs('/project-1')); - - expect(configuration.getPackageConfig( - 'package-1', _Abs('/project-1/node_modules/package-1'), '1.0.0')) - .toEqual(jasmine.objectContaining({ - ignorableDeepImportMatchers: [], - entryPoints: new Map([ - [_Abs('/project-1/node_modules/package-1/entry-point-1'), {}], - ]), - })); - expect(configuration.getPackageConfig( - 'package-1', _Abs('/project-1/node_modules/package-1'), '2.5.0')) - .toEqual(jasmine.objectContaining({ - ignorableDeepImportMatchers: [], - entryPoints: new Map([ - [_Abs('/project-1/node_modules/package-1/entry-point-2'), {}], - ]), - })); - expect(configuration.getPackageConfig( - 'package-1', _Abs('/project-1/node_modules/package-1'), '3.2.5')) - .toEqual(jasmine.objectContaining({ - ignorableDeepImportMatchers: [], - entryPoints: new Map([ - [_Abs('/project-1/node_modules/package-1/entry-point-3'), {}], - ]), - })); - expect(configuration.getPackageConfig( - 'package-1', _Abs('/project-1/node_modules/package-1'), '4.0.0')) - .toEqual(jasmine.objectContaining({ - ignorableDeepImportMatchers: [], - entryPoints: new Map(), - })); - }); - - it('should correctly handle pre-release versions and version ranges', () => { - loadTestFiles([ - { - name: _Abs('/project-1/ngcc.config.js'), - contents: ` - module.exports = { - packages: { - 'package-1': { - entryPoints: { - './entry-point-1': {}, - }, - }, - 'package-2@1.0.0-beta.2': { - entryPoints: { - './entry-point-2': {}, - }, - }, - 'package-3@>=1.0.0-beta.2': { - entryPoints: { - './entry-point-3': {}, - }, - }, - }, - }; - `, - }, - ]); - - const NO_CONFIG = jasmine.objectContaining({ - ignorableDeepImportMatchers: [], - entryPoints: new Map(), - }); - const PACKAGE_1_CONFIG = jasmine.objectContaining({ - ignorableDeepImportMatchers: [], - entryPoints: new Map([ - [_Abs('/project-1/node_modules/package-1/entry-point-1'), {}], - ]), - }); - const PACKAGE_2_CONFIG = jasmine.objectContaining({ - ignorableDeepImportMatchers: [], - entryPoints: new Map([ - [_Abs('/project-1/node_modules/package-2/entry-point-2'), {}], - ]), - }); - const PACKAGE_3_CONFIG = jasmine.objectContaining({ - ignorableDeepImportMatchers: [], - entryPoints: new Map([ - [_Abs('/project-1/node_modules/package-3/entry-point-3'), {}], - ]), - }); - - const configuration = new NgccConfiguration(fs, _Abs('/project-1')); - const getConfig = (packageName: string, version: string|null) => - configuration.getPackageConfig( - packageName, _Abs(`/project-1/node_modules/${packageName}`), version); - - // Default version range: * - expect(getConfig('package-1', '1.0.0-beta.2')) - .toEqual(PACKAGE_1_CONFIG, 'Config for package-1@1.0.0-beta.2'); - - // Version range: 1.0.0-beta.2 - expect(getConfig('package-2', '1.0.0-beta.2')) - .toEqual(PACKAGE_2_CONFIG, 'Config for package-2@1.0.0-beta.2'); - - expect(getConfig('package-2', '1.0.0')).toEqual(NO_CONFIG, 'Config for package-2@1.0.0'); - - expect(getConfig('package-2', null)) - .toEqual(PACKAGE_2_CONFIG, 'Config for package-2@null'); - - // Version range: >=1.0.0-beta.2 - expect(getConfig('package-3', '1.0.0-beta.2')) - .toEqual(PACKAGE_3_CONFIG, 'Config for package-3@1.0.0-beta.2'); - - expect(getConfig('package-3', '1.0.0')) - .toEqual(PACKAGE_3_CONFIG, 'Config for package-3@1.0.0'); - - expect(getConfig('package-3', '2.0.0')) - .toEqual(PACKAGE_3_CONFIG, 'Config for package-3@2.0.0'); - - expect(getConfig('package-3', '1.0.0-beta.1')) - .toEqual(NO_CONFIG, 'Config for package-3@1.0.0-beta.1'); - - expect(getConfig('package-3', '0.9.99')) - .toEqual(NO_CONFIG, 'Config for package-3@0.9.99'); - }); - - it('should not get confused by the @ in namespaced packages', () => { - loadTestFiles([{ - name: _Abs('/project-1/ngcc.config.js'), - contents: ` - module.exports = { - packages: { - '@angular/common': { - entryPoints: { - '.': {} - }, - }, - }, - };` - }]); - const configuration = new NgccConfiguration(fs, _Abs('/project-1')); - - expect(configuration.getPackageConfig( - '@angular/common', _Abs('/project-1/node_modules/@angular/common'), '1.0.0')) - .toEqual(jasmine.objectContaining({ - ignorableDeepImportMatchers: [], - entryPoints: new Map([ - [_Abs('/project-1/node_modules/@angular/common'), {}], - ]), - })); - }); - - it('should override package level config with project level config per package', () => { - loadTestFiles([{ - name: _Abs('/project-1/ngcc.config.js'), - contents: ` - module.exports = { - packages: { - 'package-2': { - entryPoints: { - './project-setting-entry-point': {} - }, - }, - }, - };`, - }]); - loadTestFiles( - packageWithConfigFiles('package-1', 'package-setting-entry-point', '1.0.0')); - loadTestFiles( - packageWithConfigFiles('package-2', 'package-setting-entry-point', '1.0.0')); - - const readFileSpy = spyOn(fs, 'readFile').and.callThrough(); - const configuration = new NgccConfiguration(fs, _Abs('/project-1')); - expect(readFileSpy).toHaveBeenCalledWith(_Abs('/project-1/ngcc.config.js')); - - const package1Config = configuration.getPackageConfig( - 'package-1', _Abs('/project-1/node_modules/package-1'), '1.0.0'); - expect(package1Config).toEqual(jasmine.objectContaining({ - ignorableDeepImportMatchers: [], - entryPoints: new Map([ - [_Abs('/project-1/node_modules/package-1/package-setting-entry-point'), {}], - ]), - })); - expect(readFileSpy) - .toHaveBeenCalledWith(_Abs('/project-1/node_modules/package-1/ngcc.config.js')); - - // Note that for `package-2` only the project level entry-point is left. - // This is because overriding happens for packages as a whole and there is no attempt to - // merge entry-points. - const package2Config = configuration.getPackageConfig( - 'package-2', _Abs('/project-1/node_modules/package-2'), '1.0.0'); - expect(package2Config).toEqual(jasmine.objectContaining({ - ignorableDeepImportMatchers: [], - entryPoints: new Map([ - [_Abs('/project-1/node_modules/package-2/project-setting-entry-point'), {}], - ]), - })); - expect(readFileSpy) - .not.toHaveBeenCalledWith(_Abs('/project-1/node_modules/package-2/ngcc.config.js')); - }); - - it('should correctly match packages in nested `node_modules/` (and adjust entry-point paths)', - () => { - loadTestFiles([ - { - name: _Abs('/project-1/ngcc.config.js'), - contents: ` - module.exports = { - packages: { - 'package-1': { - entryPoints: { - '.': {}, - 'foo': {}, - './bar': {}, - }, - }, - }, - }; - `, - }, - ]); - - const configuration = new NgccConfiguration(fs, _Abs('/project-1')); - - expect(configuration.getPackageConfig( - 'package-1', _Abs('/project-1/node_modules/package-1'), '1.0.0')) - .toEqual(jasmine.objectContaining({ - ignorableDeepImportMatchers: [], - entryPoints: new Map([ - [_Abs('/project-1/node_modules/package-1'), {}], - [_Abs('/project-1/node_modules/package-1/foo'), {}], - [_Abs('/project-1/node_modules/package-1/bar'), {}], - ]), - })); - - expect(configuration.getPackageConfig( - 'package-1', - _Abs('/project-1/node_modules/other-package/node_modules/package-1'), - '2.0.0')) - .toEqual(jasmine.objectContaining({ - ignorableDeepImportMatchers: [], - entryPoints: new Map([ - [_Abs('/project-1/node_modules/other-package/node_modules/package-1'), {}], - [_Abs('/project-1/node_modules/other-package/node_modules/package-1/foo'), {}], - [_Abs('/project-1/node_modules/other-package/node_modules/package-1/bar'), {}], - ]), - })); - }); - }); - - describe('at the default level', () => { - const originalDefaultConfig = JSON.stringify(DEFAULT_NGCC_CONFIG.packages); - beforeEach(() => { - DEFAULT_NGCC_CONFIG.packages!['package-1'] = { - entryPoints: {'./default-level-entry-point': {}}, - }; - }); - afterEach( - () => DEFAULT_NGCC_CONFIG.packages = - JSON.parse(originalDefaultConfig) as NgccProjectConfig['packages']); - - it('should return configuration for a package found in the default config', () => { - const readFileSpy = spyOn(fs, 'readFile').and.callThrough(); - const configuration = new NgccConfiguration(fs, _Abs('/project-1')); - expect(readFileSpy).not.toHaveBeenCalled(); - - const config = configuration.getPackageConfig( - 'package-1', _Abs('/project-1/node_modules/package-1'), '1.0.0'); - expect(config).toEqual(jasmine.objectContaining({ - ignorableDeepImportMatchers: [], - entryPoints: new Map([ - [_Abs('/project-1/node_modules/package-1/default-level-entry-point'), {}], - ]), - })); - }); - - it('should cope with configurations missing an `entryPoints` property', () => { - DEFAULT_NGCC_CONFIG.packages!['package-1'] = { - ignorableDeepImportMatchers: [/xxx/], - }; - - const configuration = new NgccConfiguration(fs, _Abs('/project-1')); - const config = configuration.getPackageConfig( - 'package-1', _Abs('/project-1/node_modules/package-1'), '1.0.0'); - - expect(config).toEqual(jasmine.objectContaining({ - ignorableDeepImportMatchers: [/xxx/], - entryPoints: new Map(), - })); - }); - - it('should override default level config with package level config, if provided', () => { - loadTestFiles(packageWithConfigFiles('package-1', 'package-level-entry-point', '1.0.0')); - const configuration = new NgccConfiguration(fs, _Abs('/project-1')); - const config = configuration.getPackageConfig( - 'package-1', _Abs('/project-1/node_modules/package-1'), '1.0.0'); - // Note that only the package-level-entry-point is left. - // This is because overriding happens for packages as a whole and there is no attempt to - // merge entry-points. - expect(config).toEqual(jasmine.objectContaining({ - ignorableDeepImportMatchers: [], - entryPoints: new Map([ - [_Abs('/project-1/node_modules/package-1/package-level-entry-point'), {}], - ]), - })); - }); - - it('should override default level config with project level config, if provided', () => { - loadTestFiles(packageWithConfigFiles('package-1', 'package-level-entry-point', '1.0.0')); - loadTestFiles([ - { - name: _Abs('/project-1/node_modules/package-1/ngcc.config.js'), - contents: ` - module.exports = { - entryPoints: {'./package-level-entry-point': {}}, - };`, - }, - { - name: _Abs('/project-1/ngcc.config.js'), - contents: ` - module.exports = { - packages: { - 'package-1': { - entryPoints: { - './project-level-entry-point': {} - }, - }, - }, - };`, - }, - ]); - - const configuration = new NgccConfiguration(fs, _Abs('/project-1')); - const config = configuration.getPackageConfig( - 'package-1', _Abs('/project-1/node_modules/package-1'), '1.0.0'); - // Note that only the project-level-entry-point is left. - // This is because overriding happens for packages as a whole and there is no attempt to - // merge entry-points. - expect(config).toEqual(jasmine.objectContaining({ - ignorableDeepImportMatchers: [], - entryPoints: new Map([ - [_Abs('/project-1/node_modules/package-1/project-level-entry-point'), {}], - ]), - })); - }); - - it('should correctly handle pre-release versions and version ranges', () => { - Object.assign(DEFAULT_NGCC_CONFIG.packages!, { - 'package-1': { - entryPoints: { - './entry-point-1': {}, - }, - }, - 'package-2@1.0.0-beta.2': { - entryPoints: { - './entry-point-2': {}, - }, - }, - 'package-3@>=1.0.0-beta.2': { - entryPoints: { - './entry-point-3': {}, - }, - }, - }); - - const NO_CONFIG = jasmine.objectContaining({ - ignorableDeepImportMatchers: [], - entryPoints: new Map(), - }); - const PACKAGE_1_CONFIG = jasmine.objectContaining({ - ignorableDeepImportMatchers: [], - entryPoints: new Map([ - [_Abs('/project-1/node_modules/package-1/entry-point-1'), {}], - ]), - }); - const PACKAGE_2_CONFIG = jasmine.objectContaining({ - ignorableDeepImportMatchers: [], - entryPoints: new Map([ - [_Abs('/project-1/node_modules/package-2/entry-point-2'), {}], - ]), - }); - const PACKAGE_3_CONFIG = jasmine.objectContaining({ - ignorableDeepImportMatchers: [], - entryPoints: new Map([ - [_Abs('/project-1/node_modules/package-3/entry-point-3'), {}], - ]), - }); - - const configuration = new NgccConfiguration(fs, _Abs('/project-1')); - const getConfig = (packageName: string, version: string|null) => - configuration.getPackageConfig( - packageName, _Abs(`/project-1/node_modules/${packageName}`), version); - - // Default version range: * - expect(getConfig('package-1', '1.0.0-beta.2')) - .toEqual(PACKAGE_1_CONFIG, 'Config for package-1@1.0.0-beta.2'); - - // Version range: 1.0.0-beta.2 - expect(getConfig('package-2', '1.0.0-beta.2')) - .toEqual(PACKAGE_2_CONFIG, 'Config for package-2@1.0.0-beta.2'); - - expect(getConfig('package-2', '1.0.0')).toEqual(NO_CONFIG, 'Config for package-2@1.0.0'); - - expect(getConfig('package-2', null)) - .toEqual(PACKAGE_2_CONFIG, 'Config for package-2@null'); - - // Version range: >=1.0.0-beta.2 - expect(getConfig('package-3', '1.0.0-beta.2')) - .toEqual(PACKAGE_3_CONFIG, 'Config for package-3@1.0.0-beta.2'); - - expect(getConfig('package-3', '1.0.0')) - .toEqual(PACKAGE_3_CONFIG, 'Config for package-3@1.0.0'); - - expect(getConfig('package-3', '2.0.0')) - .toEqual(PACKAGE_3_CONFIG, 'Config for package-3@2.0.0'); - - expect(getConfig('package-3', '1.0.0-beta.1')) - .toEqual(NO_CONFIG, 'Config for package-3@1.0.0-beta.1'); - - expect(getConfig('package-3', '0.9.99')) - .toEqual(NO_CONFIG, 'Config for package-3@0.9.99'); - }); - - it('should correctly match packages in nested `node_modules/` (and adjust entry-point paths)', - () => { - DEFAULT_NGCC_CONFIG.packages!['package-1'] = { - entryPoints: { - '.': {}, - 'foo': {}, - './bar': {}, - }, - }; - - const configuration = new NgccConfiguration(fs, _Abs('/project-1')); - - expect(configuration.getPackageConfig( - 'package-1', _Abs('/project-1/node_modules/package-1'), '1.0.0')) - .toEqual(jasmine.objectContaining({ - ignorableDeepImportMatchers: [], - entryPoints: new Map([ - [_Abs('/project-1/node_modules/package-1'), {}], - [_Abs('/project-1/node_modules/package-1/foo'), {}], - [_Abs('/project-1/node_modules/package-1/bar'), {}], - ]), - })); - - expect(configuration.getPackageConfig( - 'package-1', - _Abs('/project-1/node_modules/other-package/node_modules/package-1'), - '2.0.0')) - .toEqual(jasmine.objectContaining({ - ignorableDeepImportMatchers: [], - entryPoints: new Map([ - [_Abs('/project-1/node_modules/other-package/node_modules/package-1'), {}], - [_Abs('/project-1/node_modules/other-package/node_modules/package-1/foo'), {}], - [_Abs('/project-1/node_modules/other-package/node_modules/package-1/bar'), {}], - ]), - })); - }); - }); - }); - - describe('getLockingConfig()', () => { - let originalDefaultConfig: ProcessLockingConfiguration|undefined; - beforeEach(() => { - originalDefaultConfig = DEFAULT_NGCC_CONFIG.locking; - DEFAULT_NGCC_CONFIG.locking = {retryAttempts: 17, retryDelay: 400}; - }); - afterEach(() => DEFAULT_NGCC_CONFIG.locking = originalDefaultConfig); - - it('should return configuration for locking found in a project level file', () => { - loadTestFiles([{ - name: _Abs('/project-1/ngcc.config.js'), - contents: ` - module.exports = { - locking: { - retryAttempts: 4, - retryDelay: 56, - }, - };` - }]); - const configuration = new NgccConfiguration(fs, _Abs('/project-1')); - const config = configuration.getLockingConfig(); - expect(config).toEqual({ - retryAttempts: 4, - retryDelay: 56, - }); - }); - - it('should return configuration for locking partially found in a project level file', () => { - loadTestFiles([{ - name: _Abs('/project-1/ngcc.config.js'), - contents: ` - module.exports = { - locking: { - retryAttempts: 4, - }, - };` - }]); - const configuration = new NgccConfiguration(fs, _Abs('/project-1')); - const config = configuration.getLockingConfig(); - expect(config).toEqual({ - retryAttempts: 4, - retryDelay: 400, - }); - }); - - it('should return default configuration for locking if no project level file', () => { - const configuration = new NgccConfiguration(fs, _Abs('/project-1')); - const config = configuration.getLockingConfig(); - expect(config).toEqual({ - retryAttempts: 17, - retryDelay: 400, - }); - }); - }); - }); - - function packageWithConfigFiles( - packageName: string, entryPointName: string, version: string, extraConfig: string = '') { - return [ - { - name: _Abs(`/project-1/node_modules/${packageName}/ngcc.config.js`), - contents: ` - module.exports = { - entryPoints: { './${entryPointName}': {} }, - ${extraConfig} - };` - }, - { - name: _Abs(`/project-1/node_modules/${packageName}/package.json`), - contents: `{ "version": "${version}" }` - } - ]; - } -}); diff --git a/packages/compiler-cli/ngcc/test/packages/entry_point_bundle_spec.ts b/packages/compiler-cli/ngcc/test/packages/entry_point_bundle_spec.ts deleted file mode 100644 index 187511291a0..00000000000 --- a/packages/compiler-cli/ngcc/test/packages/entry_point_bundle_spec.ts +++ /dev/null @@ -1,428 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import {absoluteFrom, getFileSystem} from '../../../src/ngtsc/file_system'; -import {runInEachFileSystem} from '../../../src/ngtsc/file_system/testing'; -import {loadTestFiles} from '../../../src/ngtsc/testing'; -import {DtsProcessing} from '../../src/execution/tasks/api'; -import {EntryPoint} from '../../src/packages/entry_point'; -import {makeEntryPointBundle} from '../../src/packages/entry_point_bundle'; -import {createModuleResolutionCache, SharedFileCache} from '../../src/packages/source_file_cache'; - -runInEachFileSystem(() => { - describe('entry point bundle', () => { - let _: typeof absoluteFrom; - beforeEach(() => { - _ = absoluteFrom; - }); - - function setupMockFileSystem(): void { - loadTestFiles([ - { - name: _('/node_modules/test/package.json'), - contents: - '{"module": "./index.js", "es2015": "./es2015/index.js", "typings": "./index.d.ts"}' - }, - {name: _('/node_modules/test/index.d.ts'), contents: 'export * from "./public_api";'}, - {name: _('/node_modules/test/index.js'), contents: 'export * from "./public_api";'}, - {name: _('/node_modules/test/index.metadata.json'), contents: '...'}, - { - name: _('/node_modules/test/public_api.d.ts'), - contents: ` - // Note: no import from './internal'; - export * from "test/secondary"; - export * from "./nested"; - export declare class TestClass {}; - ` - }, - { - name: _('/node_modules/test/public_api.js'), - contents: ` - import {internal} from './internal'; - export * from "test/secondary"; - export * from "./nested"; - export const TestClass = function() {}; - ` - }, - { - name: _('/node_modules/test/root.d.ts'), - contents: ` - import * from 'other'; - export declare class RootClass {}; - ` - }, - { - name: _('/node_modules/test/root.js'), - contents: ` - import * from 'other'; - export const RootClass = function() {}; - ` - }, - { - name: _('/node_modules/test/internal.d.ts'), - contents: `export declare function internal(): void;` - }, - {name: _('/node_modules/test/internal.js'), contents: `export function internal() {}`}, - {name: _('/node_modules/test/nested/index.d.ts'), contents: 'export * from "../root";'}, - {name: _('/node_modules/test/nested/index.js'), contents: 'export * from "../root";'}, - {name: _('/node_modules/test/es2015/index.js'), contents: 'export * from "./public_api";'}, - { - name: _('/node_modules/test/es2015/public_api.js'), - contents: 'export class TestClass {};' - }, - { - name: _('/node_modules/test/es2015/root.js'), - contents: ` - import * from 'other'; - export class RootClass {}; - ` - }, - { - name: _('/node_modules/test/es2015/nested/index.js'), - contents: 'export * from "../root";' - }, - { - name: _('/node_modules/test/secondary/package.json'), - contents: - '{"module": "./index.js", "es2015": "./es2015/index.js", "typings": "./index.d.ts"}' - }, - { - name: _('/node_modules/test/secondary/index.d.ts'), - contents: 'export * from "./public_api";' - }, - { - name: _('/node_modules/test/secondary/index.js'), - contents: 'export * from "./public_api";' - }, - {name: _('/node_modules/test/secondary/index.metadata.json'), contents: '...'}, - { - name: _('/node_modules/test/secondary/public_api.d.ts'), - contents: 'export declare class SecondaryClass {};' - }, - { - name: _('/node_modules/test/secondary/public_api.js'), - contents: 'export class SecondaryClass {};' - }, - { - name: _('/node_modules/test/secondary/es2015/index.js'), - contents: 'export * from "./public_api";' - }, - { - name: _('/node_modules/test/secondary/es2015/public_api.js'), - contents: 'export class SecondaryClass {};' - }, - { - name: _('/node_modules/other/package.json'), - contents: - '{"module": "./index.js", "es2015": "./es2015/index.js", "typings": "./index.d.ts"}' - }, - {name: _('/node_modules/other/index.d.ts'), contents: 'export * from "./public_api";'}, - {name: _('/node_modules/other/index.js'), contents: 'export * from "./public_api";'}, - {name: _('/node_modules/other/index.metadata.json'), contents: '...'}, - { - name: _('/node_modules/other/public_api.d.ts'), - contents: 'export declare class OtherClass {};' - }, - {name: _('/node_modules/other/public_api.js'), contents: 'export class OtherClass {};'}, - {name: _('/node_modules/other/es2015/index.js'), contents: 'export * from "./public_api";'}, - { - name: _('/node_modules/other/es2015/public_api.js'), - contents: 'export class OtherClass {};' - }, - - // Mimic an AFP package with declaration files in a different tree than the sources - {name: _('/node_modules/internal/index.d.ts'), contents: 'export * from "./src/index";'}, - {name: _('/node_modules/internal/src/index.d.ts'), contents: ''}, - { - name: _('/node_modules/internal/src/internal.d.ts'), - contents: 'export declare class Internal {}' - }, - { - name: _('/node_modules/internal/esm2015/index.js'), - contents: 'export * from "./src/index";' - }, - { - name: _('/node_modules/internal/esm2015/src/index.js'), - contents: 'import {Internal} from "./internal";' - }, - { - name: _('/node_modules/internal/esm2015/src/internal.js'), - contents: 'export class Internal {}' - }, - - // A package with a secondary entry-point that has source files in a different tree - { - name: _('/node_modules/primary/secondary/index.d.ts'), - contents: 'export declare function secondary();' - }, - { - name: _('/node_modules/primary/esm2015/secondary/index.js'), - contents: 'export function secondary();' - }, - ]); - } - - // https://github.com/angular/angular/issues/29939 - it('should resolve JavaScript sources instead of declaration files if they are adjacent', - () => { - setupMockFileSystem(); - const fs = getFileSystem(); - const entryPoint: EntryPoint = { - name: 'test', - path: absoluteFrom('/node_modules/test'), - packageName: 'test', - packagePath: absoluteFrom('/node_modules/test'), - repositoryUrl: 'https://github.com/some_package', - packageJson: {name: 'test'}, - typings: absoluteFrom('/node_modules/test/index.d.ts'), - compiledByAngular: true, - ignoreMissingDependencies: false, - generateDeepReexports: false, - }; - const moduleResolutionCache = createModuleResolutionCache(fs); - const esm5bundle = makeEntryPointBundle( - fs, entryPoint, new SharedFileCache(fs), moduleResolutionCache, './index.js', false, - 'esm5', DtsProcessing.Yes); - - expect(esm5bundle.src.program.getSourceFiles().map(sf => sf.fileName)) - .toEqual(jasmine.arrayWithExactContents([ - // Modules from the entry-point itself should be source files - '/node_modules/test/index.js', - '/node_modules/test/public_api.js', - '/node_modules/test/nested/index.js', - '/node_modules/test/root.js', - '/node_modules/test/internal.js', - - // Modules from a secondary entry-point should be declaration files - '/node_modules/test/secondary/public_api.d.ts', - '/node_modules/test/secondary/index.d.ts', - - // Modules resolved from "other" should be declaration files - '/node_modules/other/public_api.d.ts', - '/node_modules/other/index.d.ts', - ].map(p => absoluteFrom(p).toString()))); - - expect(esm5bundle.dts!.program.getSourceFiles().map(sf => sf.fileName)) - .toEqual(jasmine.arrayWithExactContents([ - // All modules in the dts program should be declaration files - '/node_modules/test/index.d.ts', - '/node_modules/test/public_api.d.ts', - '/node_modules/test/nested/index.d.ts', - '/node_modules/test/root.d.ts', - '/node_modules/test/secondary/public_api.d.ts', - '/node_modules/test/secondary/index.d.ts', - '/node_modules/other/public_api.d.ts', - '/node_modules/other/index.d.ts', - ].map(p => absoluteFrom(p).toString()))); - }); - - it('does not include .js files outside of the package when no .d.ts file is available', () => { - // Declare main "test" package with "entry" entry-point that imports all sorts of - // internal and external modules. - loadTestFiles([ - { - name: _('/node_modules/test/entry/package.json'), - contents: `{"name": "test", "main": "./index.js", "typings": "./index.d.ts"}`, - }, - { - name: _('/node_modules/test/entry/index.d.ts'), - contents: ` - import 'external-js'; - import 'external-ts'; - import 'nested-js'; - import './local'; - import '../package'; - `, - }, - { - name: _('/node_modules/test/entry/index.js'), - contents: ` - import 'external-js'; - import 'external-ts'; - import 'nested-js'; - import './local'; - import '../package'; - `, - }, - {name: _('/node_modules/test/entry/local.d.ts'), contents: `export {};`}, - {name: _('/node_modules/test/entry/local.js'), contents: `export {};`}, - {name: _('/node_modules/test/package.d.ts'), contents: `export {};`}, - {name: _('/node_modules/test/package.js'), contents: `export {};`}, - ]); - - // Declare "external-js" package outside of the "test" package without .d.ts files, should - // not be included in the program. - loadTestFiles([ - { - name: _('/node_modules/external-js/package.json'), - contents: `{"name": "external-js", "main": "./index.js"}`, - }, - {name: _('/node_modules/external-js/index.js'), contents: 'export {};'}, - ]); - - // Same as "external-js" but located in a nested node_modules directory, which should also - // not be included in the program. - loadTestFiles([ - { - name: _('/node_modules/test/node_modules/nested-js/package.json'), - contents: `{"name": "nested-js", "main": "./index.js"}`, - }, - {name: _('/node_modules/test/node_modules/nested-js/index.js'), contents: 'export {}'}, - ]); - - // Declare "external-ts" which does have .d.ts files, so the .d.ts should be - // loaded into the program. - loadTestFiles([ - { - name: _('/node_modules/external-ts/package.json'), - contents: `{"name": "external-ts", "main": "./index.js", "typings": "./index.d.ts"}`, - }, - {name: _('/node_modules/external-ts/index.d.ts'), contents: 'export {};'}, - {name: _('/node_modules/external-ts/index.js'), contents: 'export {};'}, - ]); - - const fs = getFileSystem(); - const entryPoint: EntryPoint = { - name: 'test/entry', - path: absoluteFrom('/node_modules/test/entry'), - packageName: 'test', - packagePath: absoluteFrom('/node_modules/test'), - repositoryUrl: 'https://github.com/some_package', - packageJson: {name: 'test/entry'}, - typings: absoluteFrom('/node_modules/test/entry/index.d.ts'), - compiledByAngular: true, - ignoreMissingDependencies: false, - generateDeepReexports: false, - }; - const moduleResolutionCache = createModuleResolutionCache(fs); - const esm5bundle = makeEntryPointBundle( - fs, entryPoint, new SharedFileCache(fs), moduleResolutionCache, './index.js', false, - 'esm5', DtsProcessing.Yes, - /* pathMappings */ undefined, /* mirrorDtsFromSrc */ true); - - expect(esm5bundle.src.program.getSourceFiles().map(sf => _(sf.fileName))) - .toEqual(jasmine.arrayWithExactContents([ - _('/node_modules/test/entry/index.js'), - _('/node_modules/test/entry/local.js'), - _('/node_modules/test/package.js'), - _('/node_modules/external-ts/index.d.ts'), - ])); - expect(esm5bundle.dts!.program.getSourceFiles().map(sf => _(sf.fileName))) - .toEqual(jasmine.arrayWithExactContents([ - _('/node_modules/test/entry/index.d.ts'), - _('/node_modules/test/entry/local.d.ts'), - _('/node_modules/test/package.d.ts'), - _('/node_modules/external-ts/index.d.ts'), - ])); - }); - - describe( - 'including equivalently named, internally imported, src files in the typings program', - () => { - it('does include internal .d.ts files if `mirrorDtsFromSrc` is true', () => { - setupMockFileSystem(); - const fs = getFileSystem(); - const entryPoint: EntryPoint = { - name: 'test', - path: absoluteFrom('/node_modules/test'), - packageName: 'test', - packagePath: absoluteFrom('/node_modules/test'), - repositoryUrl: 'https://github.com/some_package', - packageJson: {name: 'test'}, - typings: absoluteFrom('/node_modules/test/index.d.ts'), - compiledByAngular: true, - ignoreMissingDependencies: false, - generateDeepReexports: false, - }; - const moduleResolutionCache = createModuleResolutionCache(fs); - const esm5bundle = makeEntryPointBundle( - fs, entryPoint, new SharedFileCache(fs), moduleResolutionCache, './index.js', false, - 'esm5', DtsProcessing.Yes, - /* pathMappings */ undefined, /* mirrorDtsFromSrc */ true); - expect(esm5bundle.src.program.getSourceFiles().map(sf => sf.fileName)) - .toContain(absoluteFrom('/node_modules/test/internal.js')); - expect(esm5bundle.dts!.program.getSourceFiles().map(sf => sf.fileName)) - .toContain(absoluteFrom('/node_modules/test/internal.d.ts')); - }); - - it('should work when the .d.ts files are in a different tree than the sources', () => { - setupMockFileSystem(); - const fs = getFileSystem(); - const entryPoint: EntryPoint = { - name: 'internal', - path: absoluteFrom('/node_modules/internal'), - packageName: 'internal', - packagePath: absoluteFrom('/node_modules/internal'), - repositoryUrl: 'https://github.com/some_package', - packageJson: {name: 'internal'}, - typings: absoluteFrom('/node_modules/internal/index.d.ts'), - compiledByAngular: true, - ignoreMissingDependencies: false, - generateDeepReexports: false, - }; - const moduleResolutionCache = createModuleResolutionCache(fs); - const esm5bundle = makeEntryPointBundle( - fs, entryPoint, new SharedFileCache(fs), moduleResolutionCache, - './esm2015/index.js', false, 'esm2015', DtsProcessing.Yes, - /* pathMappings */ undefined, /* mirrorDtsFromSrc */ true); - expect(esm5bundle.src.program.getSourceFiles().map(sf => sf.fileName)) - .toContain(absoluteFrom('/node_modules/internal/esm2015/src/internal.js')); - expect(esm5bundle.dts!.program.getSourceFiles().map(sf => sf.fileName)) - .toContain(absoluteFrom('/node_modules/internal/src/internal.d.ts')); - }); - - it('ignores internal .d.ts files if `mirrorDtsFromSrc` is false', () => { - setupMockFileSystem(); - const fs = getFileSystem(); - const entryPoint: EntryPoint = { - name: 'test', - path: absoluteFrom('/node_modules/test'), - packageName: 'test', - packagePath: absoluteFrom('/node_modules/test'), - repositoryUrl: 'https://github.com/some_package', - packageJson: {name: 'test'}, - typings: absoluteFrom('/node_modules/test/index.d.ts'), - compiledByAngular: true, - ignoreMissingDependencies: false, - generateDeepReexports: false, - }; - const moduleResolutionCache = createModuleResolutionCache(fs); - const esm5bundle = makeEntryPointBundle( - fs, entryPoint, new SharedFileCache(fs), moduleResolutionCache, './index.js', false, - 'esm5', DtsProcessing.Yes, - /* pathMappings */ undefined, /* mirrorDtsFromSrc */ false); - expect(esm5bundle.src.program.getSourceFiles().map(sf => sf.fileName)) - .toContain(absoluteFrom('/node_modules/test/internal.js')); - expect(esm5bundle.dts!.program.getSourceFiles().map(sf => sf.fileName)) - .not.toContain(absoluteFrom('/node_modules/test/internal.d.ts')); - }); - }); - - it('should set the `rootDir` to the package path not the entry-point path', () => { - setupMockFileSystem(); - const fs = getFileSystem(); - const entryPoint: EntryPoint = { - name: 'secondary', - path: absoluteFrom('/node_modules/primary/secondary'), - packageName: 'primary', - packagePath: absoluteFrom('/node_modules/primary'), - repositoryUrl: 'https://github.com/some_package', - packageJson: {name: 'secondary'}, - typings: absoluteFrom('/node_modules/primary/secondary/index.d.ts'), - compiledByAngular: true, - ignoreMissingDependencies: false, - generateDeepReexports: false, - }; - const moduleResolutionCache = createModuleResolutionCache(fs); - const bundle = makeEntryPointBundle( - fs, entryPoint, new SharedFileCache(fs), moduleResolutionCache, './index.js', false, - 'esm2015', DtsProcessing.Yes, - /* pathMappings */ undefined, /* mirrorDtsFromSrc */ true); - expect(bundle.rootDirs).toEqual([absoluteFrom('/node_modules/primary')]); - }); - }); -}); diff --git a/packages/compiler-cli/ngcc/test/packages/entry_point_manifest_spec.ts b/packages/compiler-cli/ngcc/test/packages/entry_point_manifest_spec.ts deleted file mode 100644 index a5d5fcabaa2..00000000000 --- a/packages/compiler-cli/ngcc/test/packages/entry_point_manifest_spec.ts +++ /dev/null @@ -1,365 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import {createHash} from 'crypto'; - -import {absoluteFrom, FileSystem, getFileSystem, relativeFrom} from '../../../src/ngtsc/file_system'; -import {runInEachFileSystem} from '../../../src/ngtsc/file_system/testing'; -import {MockLogger} from '../../../src/ngtsc/logging/testing'; -import {loadTestFiles} from '../../../src/ngtsc/testing'; -import {EntryPointWithDependencies} from '../../src/dependencies/dependency_host'; -import {NGCC_VERSION} from '../../src/packages/build_marker'; -import {NgccConfiguration, ProcessedNgccPackageConfig} from '../../src/packages/configuration'; -import {EntryPointManifest, EntryPointManifestFile} from '../../src/packages/entry_point_manifest'; - -import {createPackageJson} from './entry_point_spec'; - -runInEachFileSystem(() => { - describe('EntryPointManifest', () => { - let fs: FileSystem; - let _Abs: typeof absoluteFrom; - let config: NgccConfiguration; - let logger: MockLogger; - let manifest: EntryPointManifest; - - beforeEach(() => { - _Abs = absoluteFrom; - fs = getFileSystem(); - fs.ensureDir(_Abs('/project/node_modules')); - config = new NgccConfiguration(fs, _Abs('/project')); - logger = new MockLogger(); - manifest = new EntryPointManifest(fs, config, logger); - }); - - describe('readEntryPointsUsingManifest()', () => { - let manifestFile: EntryPointManifestFile; - beforeEach(() => { - manifestFile = { - ngccVersion: NGCC_VERSION, - lockFileHash: createHash('sha256').update('LOCK FILE CONTENTS').digest('hex'), - configFileHash: config.hash, - entryPointPaths: [] - }; - }); - - it('should return null if the base path is not node_modules', () => { - fs.ensureDir(_Abs('/project/dist')); - fs.writeFile(_Abs('/project/yarn.lock'), 'LOCK FILE CONTENTS'); - fs.writeFile( - _Abs('/project/dist/__ngcc_entry_points__.json'), JSON.stringify(manifestFile)); - const entryPoints = manifest.readEntryPointsUsingManifest(_Abs('/project/dist')); - expect(entryPoints).toBe(null); - }); - - it('should return null if there is no package lock-file', () => { - fs.ensureDir(_Abs('/project/node_modules')); - fs.writeFile( - _Abs('/project/node_modules/__ngcc_entry_points__.json'), JSON.stringify(manifestFile)); - const entryPoints = manifest.readEntryPointsUsingManifest(_Abs('/project/node_modules')); - expect(entryPoints).toBe(null); - }); - - it('should return null if there is no manifest file', () => { - fs.ensureDir(_Abs('/project/node_modules')); - fs.writeFile(_Abs('/project/yarn.lock'), 'LOCK FILE CONTENTS'); - const entryPoints = manifest.readEntryPointsUsingManifest(_Abs('/project/node_modules')); - expect(entryPoints).toBe(null); - }); - - it('should return null if the ngcc version does not match', () => { - fs.ensureDir(_Abs('/project/node_modules')); - fs.writeFile(_Abs('/project/yarn.lock'), 'LOCK FILE CONTENTS'); - manifestFile.ngccVersion = 'bad-version'; - fs.writeFile( - _Abs('/project/node_modules/__ngcc_entry_points__.json'), JSON.stringify(manifestFile)); - const entryPoints = manifest.readEntryPointsUsingManifest(_Abs('/project/node_modules')); - expect(entryPoints).toBe(null); - }); - - it('should return null if the config hash does not match', () => { - fs.ensureDir(_Abs('/project/node_modules')); - fs.writeFile(_Abs('/project/yarn.lock'), 'LOCK FILE CONTENTS'); - manifestFile.configFileHash = 'bad-hash'; - fs.writeFile( - _Abs('/project/node_modules/__ngcc_entry_points__.json'), JSON.stringify(manifestFile)); - const entryPoints = manifest.readEntryPointsUsingManifest(_Abs('/project/node_modules')); - expect(entryPoints).toBe(null); - }); - - ['yarn.lock', 'package-lock.json'].forEach(packageLockFilePath => { - it('should return null if the lockfile hash does not match', () => { - fs.ensureDir(_Abs('/project/node_modules')); - fs.writeFile(_Abs(`/project/${packageLockFilePath}`), 'LOCK FILE CONTENTS'); - manifestFile.lockFileHash = 'bad-hash'; - fs.writeFile( - _Abs('/project/node_modules/__ngcc_entry_points__.json'), - JSON.stringify(manifestFile)); - const entryPoints = manifest.readEntryPointsUsingManifest(_Abs('/project/node_modules')); - expect(entryPoints).toBe(null); - }); - - it('should return an array of entry-points if all the checks pass', () => { - fs.ensureDir(_Abs('/project/node_modules')); - fs.writeFile(_Abs(`/project/${packageLockFilePath}`), 'LOCK FILE CONTENTS'); - fs.writeFile( - _Abs('/project/node_modules/__ngcc_entry_points__.json'), - JSON.stringify(manifestFile)); - const entryPoints = manifest.readEntryPointsUsingManifest(_Abs('/project/node_modules')); - expect(entryPoints).toEqual([]); - }); - }); - - it('should read in all the entry-point info', () => { - fs.ensureDir(_Abs('/project/node_modules')); - fs.writeFile(_Abs('/project/yarn.lock'), 'LOCK FILE CONTENTS'); - loadTestFiles([ - { - name: _Abs('/project/node_modules/some_package/valid_entry_point/package.json'), - contents: createPackageJson('valid_entry_point') - }, - { - name: _Abs( - '/project/node_modules/some_package/valid_entry_point/valid_entry_point.metadata.json'), - contents: 'some meta data' - }, - ]); - manifestFile.entryPointPaths.push([ - _Abs('/project/node_modules/some_package'), - _Abs('/project/node_modules/some_package/valid_entry_point'), - [ - _Abs('/project/node_modules/other_package_1'), - _Abs('/project/node_modules/other_package_2'), - ], - [ - _Abs('/project/node_modules/missing_1'), - relativeFrom('missing_2'), - ], - [ - _Abs('/project/node_modules/deep/import/path'), - ], - ]); - fs.writeFile( - _Abs('/project/node_modules/__ngcc_entry_points__.json'), JSON.stringify(manifestFile)); - const entryPoints = manifest.readEntryPointsUsingManifest(_Abs('/project/node_modules')); - expect(entryPoints).toEqual([{ - entryPoint: { - name: 'some_package/valid_entry_point', - path: _Abs('/project/node_modules/some_package/valid_entry_point'), - packageName: 'some_package', - packagePath: _Abs('/project/node_modules/some_package'), - repositoryUrl: '', - packageJson: jasmine.any(Object), - typings: - _Abs('/project/node_modules/some_package/valid_entry_point/valid_entry_point.d.ts'), - compiledByAngular: true, - ignoreMissingDependencies: false, - generateDeepReexports: false, - } as any, - depInfo: { - dependencies: new Set([ - _Abs('/project/node_modules/other_package_1'), - _Abs('/project/node_modules/other_package_2'), - ]), - missing: new Set([ - _Abs('/project/node_modules/missing_1'), - relativeFrom('missing_2'), - ]), - deepImports: new Set([ - _Abs('/project/node_modules/deep/import/path'), - ]) - } - }]); - }); - - it('should return null if any of the entry-points are not valid', () => { - fs.ensureDir(_Abs('/project/node_modules')); - fs.writeFile(_Abs('/project/yarn.lock'), 'LOCK FILE CONTENTS'); - manifestFile.entryPointPaths.push([ - _Abs('/project/node_modules/some_package'), - _Abs('/project/node_modules/some_package/valid_entry_point'), [], [], [] - ]); - fs.writeFile( - _Abs('/project/node_modules/__ngcc_entry_points__.json'), JSON.stringify(manifestFile)); - const entryPoints = manifest.readEntryPointsUsingManifest(_Abs('/project/node_modules')); - expect(entryPoints).toEqual(null); - }); - - it('should return null if any of the entry-points are ignored by a config', () => { - fs.ensureDir(_Abs('/project/node_modules')); - fs.writeFile(_Abs('/project/yarn.lock'), 'LOCK FILE CONTENTS'); - loadTestFiles([ - { - name: _Abs('/project/node_modules/some_package/valid_entry_point/package.json'), - contents: createPackageJson('valid_entry_point'), - }, - { - name: _Abs( - '/project/node_modules/some_package/valid_entry_point/valid_entry_point.metadata.json'), - contents: 'some meta data', - }, - { - name: _Abs('/project/node_modules/some_package/ignored_entry_point/package.json'), - contents: createPackageJson('ignored_entry_point'), - }, - { - name: _Abs( - '/project/node_modules/some_package/ignored_entry_point/ignored_entry_point.metadata.json'), - contents: 'some meta data', - }, - ]); - manifestFile.entryPointPaths.push( - [ - _Abs('/project/node_modules/some_package'), - _Abs('/project/node_modules/some_package/valid_entry_point'), [], [], [] - ], - [ - _Abs('/project/node_modules/some_package'), - _Abs('/project/node_modules/some_package/ignored_entry_point'), [], [], [] - ], - ); - fs.writeFile( - _Abs('/project/node_modules/__ngcc_entry_points__.json'), JSON.stringify(manifestFile)); - - spyOn(config, 'getPackageConfig') - .and.returnValue( - new ProcessedNgccPackageConfig(fs, _Abs('/project/node_modules/some_package'), { - entryPoints: { - './ignored_entry_point': {ignore: true}, - }, - })); - - const entryPoints = manifest.readEntryPointsUsingManifest(_Abs('/project/node_modules')); - expect(entryPoints).toEqual(null); - }); - }); - - describe('writeEntryPointManifest()', () => { - it('should do nothing if there is no package lock-file', () => { - manifest.writeEntryPointManifest(_Abs('/project/node_modules'), []); - expect(fs.exists(_Abs('/project/node_modules/__ngcc_entry_points__.json'))).toBe(false); - }); - - it('should do nothing if the basePath is not node_modules', () => { - fs.writeFile(_Abs('/project/yarn.lock'), 'LOCK FILE CONTENTS'); - manifest.writeEntryPointManifest(_Abs('/project/dist'), []); - expect(fs.exists(_Abs('/project/dist/__ngcc_entry_points__.json'))).toBe(false); - }); - - it('should write an __ngcc_entry_points__.json file below the base path if there is a yarn.lock file', - () => { - fs.writeFile(_Abs('/project/yarn.lock'), 'LOCK FILE CONTENTS'); - manifest.writeEntryPointManifest(_Abs('/project/node_modules'), []); - expect(fs.exists(_Abs('/project/node_modules/__ngcc_entry_points__.json'))).toBe(true); - }); - - it('should write an __ngcc_entry_points__.json file below the base path if there is a package-lock.json file', - () => { - fs.writeFile(_Abs('/project/package-lock.json'), 'LOCK FILE CONTENTS'); - manifest.writeEntryPointManifest(_Abs('/project/node_modules'), []); - expect(fs.exists(_Abs('/project/node_modules/__ngcc_entry_points__.json'))).toBe(true); - }); - - it('should write the ngcc version', () => { - fs.writeFile(_Abs('/project/yarn.lock'), 'LOCK FILE CONTENTS'); - manifest.writeEntryPointManifest(_Abs('/project/node_modules'), []); - const file = - JSON.parse(fs.readFile(_Abs('/project/node_modules/__ngcc_entry_points__.json'))) as - EntryPointManifestFile; - expect(file.ngccVersion).toEqual(NGCC_VERSION); - }); - - it('should write a hash of the yarn.lock file', () => { - fs.writeFile(_Abs('/project/yarn.lock'), 'LOCK FILE CONTENTS'); - manifest.writeEntryPointManifest(_Abs('/project/node_modules'), []); - const file = - JSON.parse(fs.readFile(_Abs('/project/node_modules/__ngcc_entry_points__.json'))) as - EntryPointManifestFile; - expect(file.lockFileHash) - .toEqual(createHash('sha256').update('LOCK FILE CONTENTS').digest('hex')); - }); - - it('should write a hash of the package-lock.json file', () => { - fs.writeFile(_Abs('/project/package-lock.json'), 'LOCK FILE CONTENTS'); - manifest.writeEntryPointManifest(_Abs('/project/node_modules'), []); - const file = - JSON.parse(fs.readFile(_Abs('/project/node_modules/__ngcc_entry_points__.json'))) as - EntryPointManifestFile; - expect(file.lockFileHash) - .toEqual(createHash('sha256').update('LOCK FILE CONTENTS').digest('hex')); - }); - - it('should write a hash of the project config', () => { - fs.writeFile(_Abs('/project/package-lock.json'), 'LOCK FILE CONTENTS'); - manifest.writeEntryPointManifest(_Abs('/project/node_modules'), []); - const file = - JSON.parse(fs.readFile(_Abs('/project/node_modules/__ngcc_entry_points__.json'))) as - EntryPointManifestFile; - expect(file.configFileHash).toEqual(config.hash); - }); - - it('should write the package path and entry-point path of each entry-point provided', () => { - fs.writeFile(_Abs('/project/package-lock.json'), 'LOCK FILE CONTENTS'); - const entryPoint1: EntryPointWithDependencies = { - entryPoint: { - path: _Abs('/project/node_modules/package-1/'), - packagePath: _Abs('/project/node_modules/package-1/'), - } as any, - depInfo: { - dependencies: new Set([ - _Abs('/project/node_modules/other_package_1'), - _Abs('/project/node_modules/other_package_2'), - ]), - missing: new Set(), - deepImports: new Set() - } - }; - const entryPoint2: EntryPointWithDependencies = { - entryPoint: { - path: _Abs('/project/node_modules/package-2/entry-point'), - packagePath: _Abs('/project/node_modules/package-2/'), - } as any, - depInfo: { - dependencies: new Set(), - missing: new Set([ - _Abs('/project/node_modules/missing_1'), - relativeFrom('missing_2'), - ]), - deepImports: new Set([ - _Abs('/project/node_modules/deep/import/path'), - ]) - } - }; - manifest.writeEntryPointManifest(_Abs('/project/node_modules'), [entryPoint1, entryPoint2]); - const file = - JSON.parse(fs.readFile(_Abs('/project/node_modules/__ngcc_entry_points__.json'))) as - EntryPointManifestFile; - expect(file.entryPointPaths).toEqual([ - [ - 'package-1', - 'package-1', - [ - _Abs('/project/node_modules/other_package_1'), - _Abs('/project/node_modules/other_package_2'), - ], - ], - [ - 'package-2', - 'package-2/entry-point', - [], - [ - _Abs('/project/node_modules/missing_1'), - relativeFrom('missing_2'), - ], - [ - _Abs('/project/node_modules/deep/import/path'), - ], - ] - ]); - }); - }); - }); -}); diff --git a/packages/compiler-cli/ngcc/test/packages/entry_point_spec.ts b/packages/compiler-cli/ngcc/test/packages/entry_point_spec.ts deleted file mode 100644 index 25b92279bc9..00000000000 --- a/packages/compiler-cli/ngcc/test/packages/entry_point_spec.ts +++ /dev/null @@ -1,1032 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ - -import {absoluteFrom, AbsoluteFsPath, getFileSystem, ReadonlyFileSystem} from '../../../src/ngtsc/file_system'; -import {runInEachFileSystem} from '../../../src/ngtsc/file_system/testing'; -import {MockLogger} from '../../../src/ngtsc/logging/testing'; -import {loadTestFiles} from '../../../src/ngtsc/testing'; -import {NgccConfiguration, ProcessedNgccPackageConfig} from '../../src/packages/configuration'; -import {EntryPoint, EntryPointJsonProperty, EntryPointPackageJson, getEntryPointFormat, getEntryPointInfo, IGNORED_ENTRY_POINT, INCOMPATIBLE_ENTRY_POINT, isEntryPoint, NO_ENTRY_POINT, SUPPORTED_FORMAT_PROPERTIES} from '../../src/packages/entry_point'; - -runInEachFileSystem(() => { - describe('getEntryPointInfo()', () => { - let SOME_PACKAGE: AbsoluteFsPath; - let _: typeof absoluteFrom; - let fs: ReadonlyFileSystem; - - beforeEach(() => { - _ = absoluteFrom; - SOME_PACKAGE = _('/project/node_modules/some_package'); - fs = getFileSystem(); - }); - - it('should return an object containing absolute paths to the formats of the specified entry-point', - () => { - loadTestFiles([ - { - name: _('/project/node_modules/some_package/valid_entry_point/package.json'), - contents: createPackageJson('valid_entry_point') - }, - { - name: _( - '/project/node_modules/some_package/valid_entry_point/valid_entry_point.metadata.json'), - contents: 'some meta data' - }, - ]); - const config = new NgccConfiguration(fs, _('/project')); - const entryPoint = getEntryPointInfo( - fs, config, new MockLogger(), SOME_PACKAGE, - _('/project/node_modules/some_package/valid_entry_point')); - expect(entryPoint).toEqual({ - name: 'some_package/valid_entry_point', - path: _('/project/node_modules/some_package/valid_entry_point'), - packageName: 'some_package', - packagePath: SOME_PACKAGE, - repositoryUrl: '', - packageJson: loadPackageJson(fs, '/project/node_modules/some_package/valid_entry_point'), - typings: - _(`/project/node_modules/some_package/valid_entry_point/valid_entry_point.d.ts`), - compiledByAngular: true, - ignoreMissingDependencies: false, - generateDeepReexports: false, - }); - }); - - it('should return an object containing repositoryUrl extracted from the package level package.json (defined as an object)', - () => { - const packagePath = '/project/node_modules/some_package'; - loadTestFiles([ - { - name: _(`${packagePath}/package.json`), - contents: - '{ "name": "some_package", "repository": { "url": "https://github.com/some_org/some_package" } }', - }, - { - name: _(`${packagePath}/valid_entry_point/package.json`), - contents: createPackageJson('valid_entry_point') - }, - { - name: _(`${packagePath}/valid_entry_point/valid_entry_point.metadata.json`), - contents: 'some meta data' - }, - ]); - const config = new NgccConfiguration(fs, _('/project')); - const entryPoint = getEntryPointInfo( - fs, config, new MockLogger(), SOME_PACKAGE, _(`${packagePath}/valid_entry_point`)); - expect(entryPoint).toEqual(jasmine.objectContaining({ - repositoryUrl: 'https://github.com/some_org/some_package', - })); - }); - - it('should return an object containing repositoryUrl extracted from the package level package.json (defined as a string)', - () => { - const packagePath = '/project/node_modules/some_package'; - loadTestFiles([ - { - name: _(`${packagePath}/package.json`), - contents: - '{ "name": "some_package", "repository": "https://github.com/some_org/some_package" }', - }, - { - name: _(`${packagePath}/valid_entry_point/package.json`), - contents: createPackageJson('valid_entry_point') - }, - { - name: _(`${packagePath}/valid_entry_point/valid_entry_point.metadata.json`), - contents: 'some meta data' - }, - ]); - const config = new NgccConfiguration(fs, _('/project')); - const entryPoint = getEntryPointInfo( - fs, config, new MockLogger(), SOME_PACKAGE, _(`${packagePath}/valid_entry_point`)); - expect(entryPoint).toEqual(jasmine.objectContaining({ - repositoryUrl: 'https://github.com/some_org/some_package', - })); - }); - - it('should return an object containing an empty repositoryUrl if the package level package.json does not define it', - () => { - const packagePath = '/project/node_modules/some_package'; - loadTestFiles([ - { - name: _(`${packagePath}/package.json`), - contents: '{ "name": "some_package" } }', - }, - { - name: _(`${packagePath}/valid_entry_point/package.json`), - contents: createPackageJson('valid_entry_point') - }, - { - name: _(`${packagePath}/valid_entry_point/valid_entry_point.metadata.json`), - contents: 'some meta data' - }, - ]); - const config = new NgccConfiguration(fs, _('/project')); - const entryPoint = getEntryPointInfo( - fs, config, new MockLogger(), SOME_PACKAGE, _(`${packagePath}/valid_entry_point`)); - expect(entryPoint).toEqual(jasmine.objectContaining({ - repositoryUrl: '', - })); - }); - - it('should return `IGNORED_ENTRY_POINT` if configured to ignore the specified entry-point', () => { - loadTestFiles([ - { - name: _('/project/node_modules/some_package/valid_entry_point/package.json'), - contents: createPackageJson('valid_entry_point'), - }, - { - name: _( - '/project/node_modules/some_package/valid_entry_point/valid_entry_point.metadata.json'), - contents: 'some meta data', - }, - ]); - const config = new NgccConfiguration(fs, _('/project')); - spyOn(config, 'getPackageConfig') - .and.returnValue(new ProcessedNgccPackageConfig( - fs, _('/project/node_modules/some_package'), - {entryPoints: {'./valid_entry_point': {ignore: true}}})); - const entryPoint = getEntryPointInfo( - fs, config, new MockLogger(), SOME_PACKAGE, - _('/project/node_modules/some_package/valid_entry_point')); - expect(entryPoint).toBe(IGNORED_ENTRY_POINT); - }); - - it('should retrieve the entry-point\'s version from the package\'s `package.json`', () => { - const entryPointPath = fs.join(SOME_PACKAGE, 'valid_entry_point'); - - loadTestFiles([ - { - name: _('/project/ngcc.config.js'), - contents: ` - module.exports = { - packages: { - 'some_package@3': { - entryPoints: {valid_entry_point: {override: {packageVersion: '3'}}}, - }, - 'some_package@2': { - entryPoints: {valid_entry_point: {override: {packageVersion: '2'}}}, - }, - 'some_package@1': { - entryPoints: {valid_entry_point: {override: {packageVersion: '1'}}}, - }, - }, - }; - `, - }, - { - name: fs.join(SOME_PACKAGE, 'package.json'), - contents: createPackageJson('', {version: '1.0.0'}), - }, - { - name: fs.join(entryPointPath, 'package.json'), - contents: createPackageJson('valid_entry_point', {version: '2.0.0'}), - }, - { - name: fs.join(entryPointPath, 'valid_entry_point.metadata.json'), - contents: 'some meta data', - }, - ]); - - const config = new NgccConfiguration(fs, _('/project')); - const info = getEntryPointInfo(fs, config, new MockLogger(), SOME_PACKAGE, entryPointPath) as - EntryPoint; - - expect(info.packageJson).toEqual(jasmine.objectContaining({packageVersion: '1'})); - }); - - it('should use `null` for version if it cannot be retrieved from a `package.json`', () => { - const entryPointPath = fs.join(SOME_PACKAGE, 'valid_entry_point'); - - loadTestFiles([ - { - name: _('/project/ngcc.config.js'), - contents: ` - module.exports = { - packages: { - 'some_package@3': { - entryPoints: {valid_entry_point: {override: {packageVersion: '3'}}}, - }, - 'some_package@2': { - entryPoints: {valid_entry_point: {override: {packageVersion: '2'}}}, - }, - 'some_package@1': { - entryPoints: {valid_entry_point: {override: {packageVersion: '1'}}}, - }, - }, - }; - `, - }, - { - name: fs.join(SOME_PACKAGE, 'package.json'), - contents: createPackageJson(''), - }, - { - name: fs.join(entryPointPath, 'package.json'), - contents: createPackageJson('valid_entry_point'), - }, - { - name: fs.join(entryPointPath, 'valid_entry_point.metadata.json'), - contents: 'some meta data', - }, - ]); - - const config = new NgccConfiguration(fs, _('/project')); - const info = getEntryPointInfo(fs, config, new MockLogger(), SOME_PACKAGE, entryPointPath) as - EntryPoint; - - expect(info.packageJson).toEqual(jasmine.objectContaining({packageVersion: '3'})); - }); - - it('should override the properties on package.json if the entry-point is configured', () => { - loadTestFiles([ - { - name: _('/project/node_modules/some_package/valid_entry_point/package.json'), - contents: createPackageJson('valid_entry_point'), - }, - { - name: _( - '/project/node_modules/some_package/valid_entry_point/valid_entry_point.metadata.json'), - contents: 'some meta data', - }, - ]); - const config = new NgccConfiguration(fs, _('/project')); - const override = { - typings: './some_other.d.ts', - esm2015: './some_other.js', - }; - spyOn(config, 'getPackageConfig') - .and.returnValue(new ProcessedNgccPackageConfig( - fs, _('/project/node_modules/some_package'), - {entryPoints: {'./valid_entry_point': {override}}})); - const entryPoint = getEntryPointInfo( - fs, config, new MockLogger(), SOME_PACKAGE, - _('/project/node_modules/some_package/valid_entry_point')); - const overriddenPackageJson = { - ...loadPackageJson(fs, '/project/node_modules/some_package/valid_entry_point'), - ...override - }; - expect(entryPoint).toEqual({ - name: 'some_package/valid_entry_point', - path: _('/project/node_modules/some_package/valid_entry_point'), - packageName: 'some_package', - packagePath: SOME_PACKAGE, - repositoryUrl: '', - packageJson: overriddenPackageJson, - typings: _('/project/node_modules/some_package/valid_entry_point/some_other.d.ts'), - compiledByAngular: true, - ignoreMissingDependencies: false, - generateDeepReexports: false, - }); - }); - - it('should return `NO_ENTRY_POINT` if there is no package.json at the entry-point path', () => { - loadTestFiles([ - { - name: _( - '/project/node_modules/some_package/missing_package_json/missing_package_json.metadata.json'), - contents: 'some meta data' - }, - ]); - const config = new NgccConfiguration(fs, _('/project')); - const entryPoint = getEntryPointInfo( - fs, config, new MockLogger(), SOME_PACKAGE, - _('/project/node_modules/some_package/missing_package_json')); - expect(entryPoint).toBe(NO_ENTRY_POINT); - }); - - it('should return a configured entry-point if there is no package.json at the entry-point path', - () => { - loadTestFiles([ - // no package.json! - { - name: _( - '/project/node_modules/some_package/missing_package_json/missing_package_json.metadata.json'), - contents: 'some meta data', - }, - ]); - const config = new NgccConfiguration(fs, _('/project')); - const override = - JSON.parse(createPackageJson('missing_package_json', {excludes: ['name']})) as - Partial; - spyOn(config, 'getPackageConfig') - .and.returnValue(new ProcessedNgccPackageConfig( - fs, _('/project/node_modules/some_package/'), - {entryPoints: {'./missing_package_json': {override}}})); - const entryPoint = getEntryPointInfo( - fs, config, new MockLogger(), SOME_PACKAGE, - _('/project/node_modules/some_package/missing_package_json')); - expect(entryPoint).toEqual({ - name: 'some_package/missing_package_json', - path: _('/project/node_modules/some_package/missing_package_json'), - packageName: 'some_package', - packagePath: SOME_PACKAGE, - repositoryUrl: '', - packageJson: {name: 'some_package/missing_package_json', ...override}, - typings: _( - '/project/node_modules/some_package/missing_package_json/missing_package_json.d.ts'), - compiledByAngular: true, - ignoreMissingDependencies: false, - generateDeepReexports: false, - }); - }); - - [false, true].forEach(isScoped => { - const nameWithScope = (baseName: string) => `${isScoped ? '@some-scope/' : ''}${baseName}`; - const getPackageName = (packagePath: AbsoluteFsPath, entryPointPath: AbsoluteFsPath) => { - const config = new NgccConfiguration(fs, _('/project')); - const logger = new MockLogger(); - const entryPoint = getEntryPointInfo(fs, config, logger, packagePath, entryPointPath); - - if (!isEntryPoint(entryPoint)) { - return fail(`Expected an entry point but got ${entryPoint}`); - } - - return entryPoint.packageName; - }; - const setUpPackageWithEntryPointPackageJson = - (entryPointName: string, entryPointPath: AbsoluteFsPath) => { - // Ensure a `package.json` exists for the entry-point (containing `entryPointName`). - loadTestFiles([ - { - name: fs.join(entryPointPath, 'package.json'), - contents: JSON.stringify({name: entryPointName, typings: './index.d.ts'}), - }, - ]); - }; - const setUpPackageWithoutEntryPointPackageJson = - (packagePath: AbsoluteFsPath, entryPointPath: AbsoluteFsPath) => { - // Ensure there is an ngcc config for the entry-point providing a `typings` field to - // avoid returning `INCOMPATIBLE_ENTRY_POINT` (since there is no `package.json`). - loadTestFiles([ - { - name: fs.join(packagePath, 'ngcc.config.js'), - contents: ` - module.exports = { - entryPoints: { - '${fs.relative(packagePath, entryPointPath)}': { - override: {typings: './index.d.ts'}, - }, - }, - }; - `, - }, - ]); - }; - - describe(`should compute the containing ${isScoped ? 'scoped ' : ''}package's name`, () => { - it('for a primary entry-point with a `package.json`', () => { - const packagePath = _(`/project/node_modules/${nameWithScope('on-disk-package-name')}`); - const entryPointPath = packagePath; - const expectedPackageName = nameWithScope('package-json-package-name'); - - setUpPackageWithEntryPointPackageJson(expectedPackageName, entryPointPath); - - expect(getPackageName(packagePath, entryPointPath)).toBe(expectedPackageName); - }); - - it('for a primary entry-point without a `package.json`', () => { - const packagePath = _(`/project/node_modules/${nameWithScope('on-disk-package-name')}`); - const entryPointPath = packagePath; - const expectedPackageName = nameWithScope('on-disk-package-name'); - - setUpPackageWithoutEntryPointPackageJson(packagePath, entryPointPath); - - expect(getPackageName(packagePath, entryPointPath)).toBe(expectedPackageName); - }); - - it('for a secondary entry-point with a `package.json`', () => { - const packagePath = _(`/project/node_modules/${nameWithScope('on-disk-package-name')}`); - const entryPointPath = fs.join(packagePath, 'some-entry-point'); - const expectedPackageName = nameWithScope('package-json-package-name'); - - setUpPackageWithEntryPointPackageJson( - `${expectedPackageName}/some-entry-point`, entryPointPath); - - expect(getPackageName(packagePath, entryPointPath)).toBe(expectedPackageName); - }); - - it('for a secondary entry-point without a `package.json`', () => { - const packagePath = _(`/project/node_modules/${nameWithScope('on-disk-package-name')}`); - const entryPointPath = fs.join(packagePath, 'some-entry-point'); - const expectedPackageName = nameWithScope('on-disk-package-name'); - - setUpPackageWithoutEntryPointPackageJson(packagePath, entryPointPath); - - expect(getPackageName(packagePath, entryPointPath)).toBe(expectedPackageName); - }); - - it('for a primary entry-point without a `package.json` in nested `node_modules/`', () => { - const packagePath = _(`/project/node_modules/other-package/node_modules/${ - nameWithScope('on-disk-package-name')}`); - const entryPointPath = packagePath; - const expectedPackageName = nameWithScope('on-disk-package-name'); - - setUpPackageWithoutEntryPointPackageJson(packagePath, entryPointPath); - - expect(getPackageName(packagePath, entryPointPath)).toBe(expectedPackageName); - }); - - it('for a secondary entry-point without a `package.json` in nested `node_modules/`', () => { - const packagePath = _(`/project/node_modules/other-package/node_modules/${ - nameWithScope('on-disk-package-name')}`); - const entryPointPath = fs.join(packagePath, 'some-entry-point'); - const expectedPackageName = nameWithScope('on-disk-package-name'); - - setUpPackageWithoutEntryPointPackageJson(packagePath, entryPointPath); - - expect(getPackageName(packagePath, entryPointPath)).toBe(expectedPackageName); - }); - - it('for a primary entry-point with a `package.json` outside `node_modules/`', () => { - const packagePath = _(`/project/libs/${nameWithScope('on-disk-package-name')}`); - const entryPointPath = packagePath; - const expectedPackageName = nameWithScope('package-json-package-name'); - - setUpPackageWithEntryPointPackageJson(expectedPackageName, entryPointPath); - - expect(getPackageName(packagePath, entryPointPath)).toBe(expectedPackageName); - }); - - it('for a primary entry-point without a `package.json` outside `node_modules/`', () => { - const packagePath = _(`/project/libs/${nameWithScope('on-disk-package-name')}`); - const entryPointPath = packagePath; - const expectedPackageName = nameWithScope('on-disk-package-name'); - - setUpPackageWithoutEntryPointPackageJson(packagePath, entryPointPath); - - expect(getPackageName(packagePath, entryPointPath)).toBe(expectedPackageName); - }); - - it('for a secondary entry-point with a `package.json` outside `node_modules/`', () => { - const packagePath = _(`/project/libs/${nameWithScope('on-disk-package-name')}`); - const entryPointPath = fs.join(packagePath, 'some-entry-point'); - const expectedPackageName = nameWithScope('package-json-package-name'); - - setUpPackageWithEntryPointPackageJson(expectedPackageName, entryPointPath); - - expect(getPackageName(packagePath, entryPointPath)).toBe(expectedPackageName); - }); - - it('for a secondary entry-point without a `package.json` outside `node_modules/`', () => { - const packagePath = _(`/project/libs/${nameWithScope('on-disk-package-name')}`); - const entryPointPath = fs.join(packagePath, 'some-entry-point'); - const expectedPackageName = nameWithScope('on-disk-package-name'); - - setUpPackageWithoutEntryPointPackageJson(packagePath, entryPointPath); - - expect(getPackageName(packagePath, entryPointPath)).toBe(expectedPackageName); - }); - }); - }); - - it('should return `INCOMPATIBLE_ENTRY_POINT` if there is no typings or types field in the package.json', - () => { - loadTestFiles([ - { - name: _('/project/node_modules/some_package/missing_typings/package.json'), - contents: createPackageJson('missing_typings', {excludes: ['typings']}) - }, - { - name: _( - '/project/node_modules/some_package/missing_typings/missing_typings.metadata.json'), - contents: 'some meta data' - }, - ]); - const config = new NgccConfiguration(fs, _('/project')); - const entryPoint = getEntryPointInfo( - fs, config, new MockLogger(), SOME_PACKAGE, - _('/project/node_modules/some_package/missing_typings')); - expect(entryPoint).toBe(INCOMPATIBLE_ENTRY_POINT); - }); - - it('should return `INCOMPATIBLE_ENTRY_POINT` if the typings or types field is not a string in the package.json', - () => { - loadTestFiles([ - { - name: _('/project/node_modules/some_package/typings_array/package.json'), - contents: createPackageJson('typings_array', {typingsIsArray: true}) - }, - { - name: _( - '/project/node_modules/some_package/typings_array/missing_typings.metadata.json'), - contents: 'some meta data' - }, - ]); - const config = new NgccConfiguration(fs, _('/project')); - const entryPoint = getEntryPointInfo( - fs, config, new MockLogger(), SOME_PACKAGE, - _('/project/node_modules/some_package/typings_array')); - expect(entryPoint).toBe(INCOMPATIBLE_ENTRY_POINT); - }); - - for (let prop of SUPPORTED_FORMAT_PROPERTIES) { - // Ignore the UMD format - if (prop === 'main' || prop === 'browser') continue; - // Let's give 'module' a specific path, otherwise compute it based on the property. - const typingsPath = prop === 'module' ? 'index' : `${prop}/missing_typings`; - - it(`should return an object if it can guess the typings path from the "${prop}" field`, - () => { - loadTestFiles([ - { - name: _('/project/node_modules/some_package/missing_typings/package.json'), - contents: createPackageJson('missing_typings', {excludes: ['typings']}), - }, - { - name: _(`/project/node_modules/some_package/missing_typings/${ - typingsPath}.metadata.json`), - contents: 'some meta data', - }, - { - name: _(`/project/node_modules/some_package/missing_typings/${typingsPath}.d.ts`), - contents: '// some typings file', - }, - ]); - const config = new NgccConfiguration(fs, _('/project')); - const entryPoint = getEntryPointInfo( - fs, config, new MockLogger(), SOME_PACKAGE, - _('/project/node_modules/some_package/missing_typings')); - expect(entryPoint).toEqual({ - name: 'some_package/missing_typings', - path: _('/project/node_modules/some_package/missing_typings'), - packageName: 'some_package', - packagePath: SOME_PACKAGE, - repositoryUrl: '', - packageJson: loadPackageJson(fs, '/project/node_modules/some_package/missing_typings'), - typings: _(`/project/node_modules/some_package/missing_typings/${typingsPath}.d.ts`), - compiledByAngular: true, - ignoreMissingDependencies: false, - generateDeepReexports: false, - }); - }); - } - - it('should return an object with `compiledByAngular` set to false if there is no metadata.json file next to the typing file', - () => { - loadTestFiles([ - { - name: _('/project/node_modules/some_package/missing_metadata/package.json'), - contents: createPackageJson('missing_metadata') - }, - ]); - const config = new NgccConfiguration(fs, _('/project')); - const entryPoint = getEntryPointInfo( - fs, config, new MockLogger(), SOME_PACKAGE, - _('/project/node_modules/some_package/missing_metadata')); - expect(entryPoint).toEqual({ - name: 'some_package/missing_metadata', - path: _('/project/node_modules/some_package/missing_metadata'), - packageName: 'some_package', - packagePath: SOME_PACKAGE, - repositoryUrl: '', - packageJson: loadPackageJson(fs, '/project/node_modules/some_package/missing_metadata'), - typings: _(`/project/node_modules/some_package/missing_metadata/missing_metadata.d.ts`), - compiledByAngular: false, - ignoreMissingDependencies: false, - generateDeepReexports: false, - }); - }); - - it('should return an object with `compiledByAngular` set to true if there is no metadata.json file but the entry-point has a configuration', - () => { - loadTestFiles([ - { - name: _('/project/node_modules/some_package/missing_metadata/package.json'), - contents: createPackageJson('missing_metadata'), - }, - // no metadata.json! - ]); - const config = new NgccConfiguration(fs, _('/project')); - spyOn(config, 'getPackageConfig') - .and.returnValue(new ProcessedNgccPackageConfig( - fs, _('/project/node_modules/some_package'), - {entryPoints: {'./missing_metadata': {}}})); - const entryPoint = getEntryPointInfo( - fs, config, new MockLogger(), SOME_PACKAGE, - _('/project/node_modules/some_package/missing_metadata')); - expect(entryPoint).toEqual({ - name: 'some_package/missing_metadata', - path: _('/project/node_modules/some_package/missing_metadata'), - packageName: 'some_package', - packagePath: SOME_PACKAGE, - repositoryUrl: '', - packageJson: loadPackageJson(fs, '/project/node_modules/some_package/missing_metadata'), - typings: _('/project/node_modules/some_package/missing_metadata/missing_metadata.d.ts'), - compiledByAngular: true, - ignoreMissingDependencies: false, - generateDeepReexports: false, - }); - }); - - it('should work if the typings field is named `types', () => { - loadTestFiles([ - { - name: _('/project/node_modules/some_package/types_rather_than_typings/package.json'), - contents: createPackageJson('types_rather_than_typings', {typingsProp: 'types'}) - }, - { - name: _( - '/project/node_modules/some_package/types_rather_than_typings/types_rather_than_typings.metadata.json'), - contents: 'some meta data' - }, - ]); - const config = new NgccConfiguration(fs, _('/project')); - const entryPoint = getEntryPointInfo( - fs, config, new MockLogger(), SOME_PACKAGE, - _('/project/node_modules/some_package/types_rather_than_typings')); - expect(entryPoint).toEqual({ - name: 'some_package/types_rather_than_typings', - path: _('/project/node_modules/some_package/types_rather_than_typings'), - packageName: 'some_package', - packagePath: SOME_PACKAGE, - repositoryUrl: '', - packageJson: - loadPackageJson(fs, '/project/node_modules/some_package/types_rather_than_typings'), - typings: _( - `/project/node_modules/some_package/types_rather_than_typings/types_rather_than_typings.d.ts`), - compiledByAngular: true, - ignoreMissingDependencies: false, - generateDeepReexports: false, - }); - }); - - it('should work with Angular Material style package.json', () => { - loadTestFiles([ - { - name: _('/project/node_modules/some_package/material_style/package.json'), - contents: `{ - "name": "some_package/material_style", - "typings": "./material_style.d.ts", - "main": "./bundles/material_style.umd.js", - "module": "./esm5/material_style.es5.js", - "es2015": "./esm2015/material_style.js" - }` - }, - { - name: _('/project/node_modules/some_package/material_style/material_style.metadata.json'), - contents: 'some meta data' - }, - ]); - const config = new NgccConfiguration(fs, _('/project')); - const entryPoint = getEntryPointInfo( - fs, config, new MockLogger(), SOME_PACKAGE, - _('/project/node_modules/some_package/material_style')); - expect(entryPoint).toEqual({ - name: 'some_package/material_style', - path: _('/project/node_modules/some_package/material_style'), - packageName: 'some_package', - packagePath: SOME_PACKAGE, - repositoryUrl: '', - packageJson: loadPackageJson(fs, '/project/node_modules/some_package/material_style'), - typings: _(`/project/node_modules/some_package/material_style/material_style.d.ts`), - compiledByAngular: true, - ignoreMissingDependencies: false, - generateDeepReexports: false, - }); - }); - - it('should return `INCOMPATIBLE_ENTRY_POINT` if the package.json is not valid JSON', () => { - loadTestFiles([ - // package.json might not be a valid JSON - // for example, @schematics/angular contains a package.json blueprint - // with unexpected symbols - { - name: _('/project/node_modules/some_package/unexpected_symbols/package.json'), - contents: '{"devDependencies": {<% if (!minimal) { %>"@types/jasmine": "~2.8.8" <% } %>}}' - }, - ]); - const config = new NgccConfiguration(fs, _('/project')); - const entryPoint = getEntryPointInfo( - fs, config, new MockLogger(), SOME_PACKAGE, - _('/project/node_modules/some_package/unexpected_symbols')); - expect(entryPoint).toBe(INCOMPATIBLE_ENTRY_POINT); - }); - - describe('when processing APF v14+ secondary entry-points', () => { - it('should retrieve info from primary `package.json`', () => { - const packagePath = _('/project/node_modules/primary'); - const entryPointPath = fs.join(packagePath, 'second/ary'); - - loadTestFiles([ - { - name: fs.join(packagePath, 'package.json'), - contents: `{ - "name": "primary-package", - "exports": { - ".": { - "types": "/something/primary.d.ts" - }, - "./second/ary": { - "types": "/something/secondary.d.ts" - } - } - }`, - }, - ]); - - const config = new NgccConfiguration(fs, _('/project')); - const entryPoint = - getEntryPointInfo(fs, config, new MockLogger(), packagePath, entryPointPath); - - expect(entryPoint).toEqual({ - name: 'primary-package/second/ary', - path: entryPointPath, - packageName: 'primary-package', - packagePath, - repositoryUrl: '', - packageJson: { - synthesized: true, - name: 'primary-package/second/ary', - types: jasmine.any(String), - }, - typings: _('/something/secondary.d.ts'), - compiledByAngular: false, - ignoreMissingDependencies: false, - generateDeepReexports: false, - }); - }); - - it('should only keep supported format properties', () => { - const packagePath = _('/project/node_modules/primary'); - const entryPointPath = fs.join(packagePath, 'second/ary'); - - loadTestFiles([ - { - name: fs.join(packagePath, 'package.json'), - contents: `{ - "name": "primary-package", - "exports": { - "./second/ary": { - "types": "/types/index.d.ts", - "typings": "/typings/index.d.ts", - "default": "/default/index.js", - "other": "/other/index.js", - ${ - SUPPORTED_FORMAT_PROPERTIES.map(prop => `"${prop}": "/${prop}/index.js"`) - .join(', ')} - } - } - }`, - }, - ]); - - const config = new NgccConfiguration(fs, _('/project')); - const entryPoint = - getEntryPointInfo(fs, config, new MockLogger(), packagePath, entryPointPath) as - EntryPoint; - const packageJsonProps = Object.keys(entryPoint.packageJson); - - expect(packageJsonProps).not.toContain('default'); - expect(packageJsonProps).not.toContain('other'); - expect(packageJsonProps).toEqual(jasmine.arrayWithExactContents([ - 'synthesized', - 'name', - 'types', - 'typings', - ...SUPPORTED_FORMAT_PROPERTIES, - ])); - }); - - it('should convert format paths to be relative to entry-point directory', () => { - const packagePath = _('/project/node_modules/primary'); - const entryPointPath = fs.join(packagePath, 'second/ary'); - - loadTestFiles([ - { - name: fs.join(packagePath, 'package.json'), - contents: `{ - "name": "primary-package", - "main": "./bundles/index.js", - "exports": { - "./second/ary": { - "fesm2015": "./fesm2015/secondary.js", - "main": "./bundles/secondary.js", - "types": "./second/ary/secondary.d.ts" - } - } - }`, - }, - ]); - - const config = new NgccConfiguration(fs, _('/project')); - const entryPoint = - getEntryPointInfo(fs, config, new MockLogger(), packagePath, entryPointPath) as - EntryPoint; - - expect(entryPoint.packageJson).toEqual(jasmine.objectContaining({ - fesm2015: '../../fesm2015/secondary.js', - main: '../../bundles/secondary.js', - types: './secondary.d.ts', - })); - }); - }); - }); - - describe('getEntryPointFormat()', () => { - let SOME_PACKAGE: AbsoluteFsPath; - let _: typeof absoluteFrom; - let fs: ReadonlyFileSystem; - let entryPoint: EntryPoint; - - beforeEach(() => { - _ = absoluteFrom; - SOME_PACKAGE = _('/project/node_modules/some_package'); - fs = getFileSystem(); - loadTestFiles([{ - name: _('/project/node_modules/some_package/valid_entry_point/package.json'), - contents: createPackageJson('valid_entry_point') - }]); - const config = new NgccConfiguration(fs, _('/project')); - const result = getEntryPointInfo( - fs, config, new MockLogger(), SOME_PACKAGE, - _('/project/node_modules/some_package/valid_entry_point')); - if (!isEntryPoint(result)) { - return fail(`Expected an entry point but got ${result}`); - } - entryPoint = result; - }); - - it('should return `esm2015` format for `fesm2015` property', () => { - expect(getEntryPointFormat(fs, entryPoint, 'fesm2015')).toBe('esm2015'); - }); - - it('should return `esm5` format for `fesm5` property', () => { - expect(getEntryPointFormat(fs, entryPoint, 'fesm5')).toBe('esm5'); - }); - - it('should return `esm2015` format for `es2015` property', () => { - expect(getEntryPointFormat(fs, entryPoint, 'es2015')).toBe('esm2015'); - }); - - it('should return `esm2015` format for `esm2015` property', () => { - expect(getEntryPointFormat(fs, entryPoint, 'esm2015')).toBe('esm2015'); - }); - - it('should return `esm5` format for `esm5` property', () => { - expect(getEntryPointFormat(fs, entryPoint, 'esm5')).toBe('esm5'); - }); - - it('should return `esm5` format for `module` property', () => { - expect(getEntryPointFormat(fs, entryPoint, 'module')).toBe('esm5'); - }); - - it('should return `esm2015` format for `module` property if it points to esm2015 output', - () => { - entryPoint.packageJson['module'] = '../fesm2015/valid-entry-point.js'; - expect(getEntryPointFormat(fs, entryPoint, 'module')).toBe('esm2015'); - }); - - (['browser', 'main'] as EntryPointJsonProperty[]).forEach(browserOrMain => { - it('should return `esm5` for `' + browserOrMain + - '` if the file contains import or export statements', - () => { - const name = _( - '/project/node_modules/some_package/valid_entry_point/bundles/valid_entry_point/index.js'); - loadTestFiles([{name, contents: `import * as core from '@angular/core;`}]); - expect(getEntryPointFormat(fs, entryPoint, browserOrMain)).toBe('esm5'); - - loadTestFiles([{name, contents: `import {Component} from '@angular/core;`}]); - expect(getEntryPointFormat(fs, entryPoint, browserOrMain)).toBe('esm5'); - - loadTestFiles([{name, contents: `export function foo() {}`}]); - expect(getEntryPointFormat(fs, entryPoint, browserOrMain)).toBe('esm5'); - - loadTestFiles([{name, contents: `export * from 'abc';`}]); - expect(getEntryPointFormat(fs, entryPoint, browserOrMain)).toBe('esm5'); - }); - - it('should return `umd` for `' + browserOrMain + - '` if the file contains a UMD wrapper function', - () => { - loadTestFiles([{ - name: _( - '/project/node_modules/some_package/valid_entry_point/bundles/valid_entry_point/index.js'), - contents: ` - (function (global, factory) { - typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('@angular/core')) : - typeof define === 'function' && define.amd ? define('@angular/common', ['exports', '@angular/core'], factory) : - (global = global || self, factory((global.ng = global.ng || {}, global.ng.common = {}), global.ng.core)); - }(this, function (exports, core) { 'use strict'; })); - ` - }]); - expect(getEntryPointFormat(fs, entryPoint, browserOrMain)).toBe('umd'); - }); - - it('should return `commonjs` for `' + browserOrMain + - '` if the file does not contain a UMD wrapper function', - () => { - loadTestFiles([{ - name: _( - '/project/node_modules/some_package/valid_entry_point/bundles/valid_entry_point/index.js'), - contents: ` - const core = require('@angular/core); - module.exports = {}; - ` - }]); - expect(getEntryPointFormat(fs, entryPoint, browserOrMain)).toBe('commonjs'); - }); - - it('should resolve the format path with suitable postfixes', () => { - loadTestFiles([{ - name: _( - '/project/node_modules/some_package/valid_entry_point/bundles/valid_entry_point/index.js'), - contents: ` - (function (global, factory) { - typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('@angular/core')) : - typeof define === 'function' && define.amd ? define('@angular/common', ['exports', '@angular/core'], factory) : - (global = global || self, factory((global.ng = global.ng || {}, global.ng.common = {}), global.ng.core)); - }(this, function (exports, core) { 'use strict'; })); - ` - }]); - - entryPoint.packageJson.main = './bundles/valid_entry_point/index'; - expect(getEntryPointFormat(fs, entryPoint, browserOrMain)).toBe('umd'); - - entryPoint.packageJson.main = './bundles/valid_entry_point'; - expect(getEntryPointFormat(fs, entryPoint, browserOrMain)).toBe('umd'); - }); - - it('should match alternate UMD format', () => { - // Note that in this format, instead of the whole wrapper being enclosed in parentheses, - // only the wrapper function is enclosed and the call is not inside the parentheses. - loadTestFiles([{ - name: _( - '/project/node_modules/some_package/valid_entry_point/bundles/valid_entry_point/index.js'), - contents: ` - (function (global, factory) { - typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('@angular/core')) : - typeof define === 'function' && define.amd ? define('@angular/common', ['exports', '@angular/core'], factory) : - (global = global || self, factory((global.ng = global.ng || {}, global.ng.common = {}), global.ng.core)); - })(this, function (exports, core) { 'use strict'; }); - ` - }]); - - entryPoint.packageJson.main = './bundles/valid_entry_point'; - expect(getEntryPointFormat(fs, entryPoint, browserOrMain)).toBe('umd'); - }); - - it('should detect UMD even if the wrapper function is not of a supported format', () => { - // In order to correctly process UMD, we require that at least one CommonJS variant is - // present. However, for the purposes of detecting the module format, it should not matter - // what the body of the wrapper function looks like. - loadTestFiles([{ - name: _( - '/project/node_modules/some_package/valid_entry_point/bundles/valid_entry_point/index.js'), - contents: ` - (function (global, factory) { - // This is not a supported format for the wrapper function body, but it should still - // be detected as UMD (since it is closer to UMD than any other of the supported - // formats). - typeof define === 'function' && define.amd ? define([], factory) : factory(); - })(this, function () { 'use strict'; }); - `, - }]); - - entryPoint.packageJson.main = './bundles/valid_entry_point'; - expect(getEntryPointFormat(fs, entryPoint, browserOrMain)).toBe('umd'); - }); - }); - - it('should return `undefined` if the `browser` property is not a string', () => { - entryPoint.packageJson.browser = {} as any; - expect(getEntryPointFormat(fs, entryPoint, 'browser')).toBeUndefined(); - }); - }); -}); - -export function createPackageJson( - entryPointName: string, {excludes, typingsProp = 'typings', typingsIsArray, version}: { - excludes?: string[], - typingsProp?: string, - typingsIsArray?: boolean, - version?: string - } = {}): string { - const packageJson: EntryPointPackageJson = { - name: (entryPointName === '') ? 'some_package' : `some_package/${entryPointName}`, - version, - [typingsProp]: typingsIsArray ? [`./${entryPointName}.d.ts`] : `./${entryPointName}.d.ts`, - fesm2015: `./fesm2015/${entryPointName}.js`, - esm2015: `./esm2015/${entryPointName}.js`, - es2015: `./es2015/${entryPointName}.js`, - fesm5: `./fesm5/${entryPointName}.js`, - esm5: `./esm5/${entryPointName}.js`, - main: `./bundles/${entryPointName}/index.js`, - browser: `./bundles/${entryPointName}/index.js`, - module: './index.js', - }; - if (excludes) { - excludes.forEach(exclude => delete packageJson[exclude]); - } - return JSON.stringify(packageJson); -} - -export function loadPackageJson(fs: ReadonlyFileSystem, packagePath: string) { - return JSON.parse(fs.readFile(fs.resolve(packagePath + '/package.json'))) as - EntryPointPackageJson; -} diff --git a/packages/compiler-cli/ngcc/test/packages/source_file_cache_spec.ts b/packages/compiler-cli/ngcc/test/packages/source_file_cache_spec.ts deleted file mode 100644 index 4b2b88e028a..00000000000 --- a/packages/compiler-cli/ngcc/test/packages/source_file_cache_spec.ts +++ /dev/null @@ -1,231 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import ts from 'typescript'; - -import {absoluteFrom, FileSystem, getFileSystem} from '../../../src/ngtsc/file_system'; -import {runInEachFileSystem} from '../../../src/ngtsc/file_system/testing'; -import {loadTestFiles} from '../../../src/ngtsc/testing'; -import {EntryPointFileCache, isAngularDts, isDefaultLibrary, SharedFileCache} from '../../src/packages/source_file_cache'; - -runInEachFileSystem(() => { - describe('caching', () => { - let _: typeof absoluteFrom; - let fs: FileSystem; - beforeEach(() => { - _ = absoluteFrom; - fs = getFileSystem(); - loadTestFiles([ - { - name: _('/node_modules/typescript/lib/lib.es5.d.ts'), - contents: `export declare interface Array {}`, - }, - { - name: _('/node_modules/typescript/lib/lib.dom.d.ts'), - contents: `export declare interface Window {}`, - }, - { - name: _('/node_modules/@angular/core/core.d.ts'), - contents: `export declare interface Component {}`, - }, - { - name: _('/node_modules/@angular/common/common.d.ts'), - contents: `export declare interface NgIf {}`, - }, - { - name: _('/index.ts'), - contents: `export const index = true;`, - }, - { - name: _('/main.ts'), - contents: `export const main = true;`, - }, - ]); - }); - - describe('SharedFileCache', () => { - it('should cache a parsed source file for default libraries', () => { - const cache = new SharedFileCache(fs); - - const libEs5 = cache.getCachedSourceFile('/node_modules/typescript/lib/lib.es5.d.ts')!; - expect(libEs5).not.toBeUndefined(); - expect(libEs5.text).toContain('Array'); - - const libDom = cache.getCachedSourceFile('/node_modules/typescript/lib/lib.dom.d.ts')!; - expect(libDom).not.toBeUndefined(); - expect(libDom.text).toContain('Window'); - - const libEs5_2 = cache.getCachedSourceFile('/node_modules/typescript/lib/lib.es5.d.ts')!; - expect(libEs5_2).toBe(libEs5); - - const libDom_2 = cache.getCachedSourceFile('/node_modules/typescript/lib/lib.dom.d.ts')!; - expect(libDom_2).toBe(libDom); - }); - - it('should cache a parsed source file for @angular scoped packages', () => { - const cache = new SharedFileCache(fs); - - const core = cache.getCachedSourceFile('/node_modules/@angular/core/core.d.ts')!; - expect(core).not.toBeUndefined(); - expect(core.text).toContain('Component'); - - const common = cache.getCachedSourceFile('/node_modules/@angular/common/common.d.ts')!; - expect(common).not.toBeUndefined(); - expect(common.text).toContain('NgIf'); - - const core_2 = cache.getCachedSourceFile('/node_modules/@angular/core/core.d.ts')!; - expect(core_2).toBe(core); - - const common_2 = cache.getCachedSourceFile('/node_modules/@angular/common/common.d.ts')!; - expect(common_2).toBe(common); - }); - - it('should reparse @angular d.ts files when they change', () => { - const cache = new SharedFileCache(fs); - - const core = cache.getCachedSourceFile('/node_modules/@angular/core/core.d.ts')!; - expect(core).not.toBeUndefined(); - expect(core.text).toContain('Component'); - - const common = cache.getCachedSourceFile('/node_modules/@angular/common/common.d.ts')!; - expect(common).not.toBeUndefined(); - expect(common.text).toContain('NgIf'); - - fs.writeFile( - _('/node_modules/@angular/core/core.d.ts'), `export declare interface Directive {}`); - - const core_2 = cache.getCachedSourceFile('/node_modules/@angular/core/core.d.ts')!; - expect(core_2).not.toBe(core); - expect(core_2.text).toContain('Directive'); - - const core_3 = cache.getCachedSourceFile('/node_modules/@angular/core/core.d.ts')!; - expect(core_3).toBe(core_2); - - const common_2 = cache.getCachedSourceFile('/node_modules/@angular/common/common.d.ts')!; - expect(common_2).toBe(common); - }); - - it('should not cache files that are not default library files inside of the typescript package', - () => { - const cache = new SharedFileCache(fs); - - expect(cache.getCachedSourceFile('/node_modules/typescript/lib/typescript.d.ts')) - .toBeUndefined(); - expect(cache.getCachedSourceFile('/typescript/lib.es5.d.ts')).toBeUndefined(); - }); - }); - - describe('isDefaultLibrary()', () => { - it('should accept lib files inside of the typescript package', () => { - expect(isDefaultLibrary(_('/node_modules/typescript/lib/lib.es5.d.ts'), fs)).toBe(true); - expect(isDefaultLibrary(_('/node_modules/typescript/lib/lib.dom.d.ts'), fs)).toBe(true); - expect(isDefaultLibrary(_('/node_modules/typescript/lib/lib.es2015.core.d.ts'), fs)) - .toBe(true); - expect(isDefaultLibrary(_('/root/node_modules/typescript/lib/lib.es5.d.ts'), fs)) - .toBe(true); - }); - it('should reject non lib files inside of the typescript package', () => { - expect(isDefaultLibrary(_('/node_modules/typescript/lib/typescript.d.ts'), fs)).toBe(false); - expect(isDefaultLibrary(_('/node_modules/typescript/lib/lib.es5.ts'), fs)).toBe(false); - expect(isDefaultLibrary(_('/node_modules/typescript/lib/lib.d.ts'), fs)).toBe(false); - expect(isDefaultLibrary(_('/node_modules/typescript/lib.es5.d.ts'), fs)).toBe(false); - }); - it('should reject lib files outside of the typescript package', () => { - expect(isDefaultLibrary(_('/node_modules/ttypescript/lib/lib.es5.d.ts'), fs)).toBe(false); - expect(isDefaultLibrary(_('/node_modules/ttypescript/lib/lib.es5.d.ts'), fs)).toBe(false); - expect(isDefaultLibrary(_('/typescript/lib/lib.es5.d.ts'), fs)).toBe(false); - }); - }); - - describe('isAngularDts()', () => { - it('should accept .d.ts files inside of the @angular scope', () => { - expect(isAngularDts(_('/node_modules/@angular/core/core.d.ts'), fs)).toBe(true); - expect(isAngularDts(_('/node_modules/@angular/common/common.d.ts'), fs)).toBe(true); - }); - it('should reject non-.d.ts files inside @angular scoped packages', () => { - expect(isAngularDts(_('/node_modules/@angular/common/src/common.ts'), fs)).toBe(false); - }); - it('should reject .d.ts files nested deeply inside @angular scoped packages', () => { - expect(isAngularDts(_('/node_modules/@angular/common/src/common.d.ts'), fs)).toBe(false); - }); - it('should reject .d.ts files directly inside the @angular scope', () => { - expect(isAngularDts(_('/node_modules/@angular/common.d.ts'), fs)).toBe(false); - }); - it('should reject files that are not inside node_modules', () => { - expect(isAngularDts(_('/@angular/core/core.d.ts'), fs)).toBe(false); - }); - }); - - describe('EntryPointFileCache', () => { - let sharedFileCache: SharedFileCache; - beforeEach(() => { - sharedFileCache = new SharedFileCache(fs); - }); - - it('should prefer source files cached in SharedFileCache', () => { - const cache1 = new EntryPointFileCache(fs, sharedFileCache, sourceText => sourceText); - const libEs5_1 = cache1.getCachedSourceFile( - '/node_modules/typescript/lib/lib.es5.d.ts', ts.ScriptTarget.ESNext)!; - expect(libEs5_1).not.toBeUndefined(); - expect(libEs5_1.text).toContain('Array'); - expect(libEs5_1.languageVersion).toBe(ts.ScriptTarget.ES2015); - - const cache2 = new EntryPointFileCache(fs, sharedFileCache, sourceText => sourceText); - const libEs5_2 = cache2.getCachedSourceFile( - '/node_modules/typescript/lib/lib.es5.d.ts', ts.ScriptTarget.ESNext)!; - expect(libEs5_1).toBe(libEs5_2); - }); - - it('should cache source files that are not default library files', () => { - const cache = new EntryPointFileCache(fs, sharedFileCache, sourceText => sourceText); - const index = cache.getCachedSourceFile('/index.ts', ts.ScriptTarget.ESNext)!; - expect(index).not.toBeUndefined(); - expect(index.text).toContain('index'); - expect(index.languageVersion).toBe(ts.ScriptTarget.ESNext); - - const main = cache.getCachedSourceFile('/main.ts', ts.ScriptTarget.ESNext)!; - expect(main).not.toBeUndefined(); - expect(main.text).toContain('main'); - expect(main.languageVersion).toBe(ts.ScriptTarget.ESNext); - - const index_2 = cache.getCachedSourceFile('/index.ts', ts.ScriptTarget.ESNext)!; - expect(index_2).toBe(index); - - const main_2 = cache.getCachedSourceFile('/main.ts', ts.ScriptTarget.ESNext)!; - expect(main_2).toBe(main); - }); - - it('should not share non-library files across multiple cache instances', () => { - const cache1 = new EntryPointFileCache(fs, sharedFileCache, sourceText => sourceText); - const cache2 = new EntryPointFileCache(fs, sharedFileCache, sourceText => sourceText); - - const index1 = cache1.getCachedSourceFile('/index.ts', ts.ScriptTarget.ESNext)!; - const index2 = cache2.getCachedSourceFile('/index.ts', ts.ScriptTarget.ESNext)!; - expect(index1).not.toBe(index2); - }); - - it('should return undefined if the file does not exist', () => { - const cache = new EntryPointFileCache(fs, sharedFileCache, sourceText => sourceText); - expect(cache.getCachedSourceFile('/nonexistent.ts', ts.ScriptTarget.ESNext)) - .toBeUndefined(); - }); - - it('should return undefined if the path is a directory', () => { - const cache = new EntryPointFileCache(fs, sharedFileCache, sourceText => sourceText); - expect(cache.getCachedSourceFile('/node_modules', ts.ScriptTarget.ESNext)).toBeUndefined(); - }); - - it('should apply the source text transform', () => { - const cache = new EntryPointFileCache( - fs, sharedFileCache, sourceText => sourceText.replace('main', 'replaced')); - const main = cache.getCachedSourceFile('/main.ts', ts.ScriptTarget.ESNext)!; - expect(main).not.toBeUndefined(); - expect(main.text).toBe(`export const replaced = true;`); - }); - }); - }); -}); diff --git a/packages/compiler-cli/ngcc/test/rendering/commonjs_rendering_formatter_spec.ts b/packages/compiler-cli/ngcc/test/rendering/commonjs_rendering_formatter_spec.ts deleted file mode 100644 index 272b15858db..00000000000 --- a/packages/compiler-cli/ngcc/test/rendering/commonjs_rendering_formatter_spec.ts +++ /dev/null @@ -1,483 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import {DeclareVarStmt, LiteralExpr, StmtModifier} from '@angular/compiler'; -import MagicString from 'magic-string'; -import ts from 'typescript'; - -import {absoluteFrom, absoluteFromSourceFile, AbsoluteFsPath, getFileSystem, getSourceFileOrError} from '../../../src/ngtsc/file_system'; -import {runInEachFileSystem, TestFile} from '../../../src/ngtsc/file_system/testing'; -import {NoopImportRewriter} from '../../../src/ngtsc/imports'; -import {MockLogger} from '../../../src/ngtsc/logging/testing'; -import {getDeclaration, loadTestFiles} from '../../../src/ngtsc/testing'; -import {ImportManager} from '../../../src/ngtsc/translator'; -import {DecorationAnalyzer} from '../../src/analysis/decoration_analyzer'; -import {NgccReferencesRegistry} from '../../src/analysis/ngcc_references_registry'; -import {CommonJsReflectionHost} from '../../src/host/commonjs_host'; -import {CommonJsRenderingFormatter} from '../../src/rendering/commonjs_rendering_formatter'; -import {makeTestEntryPointBundle} from '../helpers/utils'; - -runInEachFileSystem(() => { - describe('CommonJsRenderingFormatter', () => { - let _: typeof absoluteFrom; - let PROGRAM: TestFile; - let PROGRAM_DECORATE_HELPER: TestFile; - - beforeEach(() => { - _ = absoluteFrom; - PROGRAM = { - name: _('/node_modules/test-package/some/file.js'), - contents: ` -/* A copyright notice */ -require('some-side-effect'); -var core = require('@angular/core'); -var A = (function() { - function A() {} - A.decorators = [ - { type: core.Directive, args: [{ selector: '[a]' }] }, - { type: OtherA } - ]; - A.prototype.ngDoCheck = function() { - // - }; - return A; -}()); - -var B = (function() { - function B() {} - B.decorators = [ - { type: OtherB }, - { type: core.Directive, args: [{ selector: '[b]' }] } - ]; - return B; -}()); - -var C = (function() { - function C() {} - C.decorators = [ - { type: core.Directive, args: [{ selector: '[c]' }] }, - ]; - return C; -}()); - -function NoIife() {} - -var BadIife = (function() { - function BadIife() {} - BadIife.decorators = [ - { type: core.Directive, args: [{ selector: '[c]' }] }, - ]; -}()); - -// Some other content -exports.A = A; -exports.B = B; -exports.C = C; -exports.NoIife = NoIife; -exports.BadIife = BadIife;` - }; - - PROGRAM_DECORATE_HELPER = { - name: _('/node_modules/test-package/some/file.js'), - contents: ` -var tslib_1 = require("tslib"); -/* A copyright notice */ -var core = require('@angular/core'); -var OtherA = function () { return function (node) { }; }; -var OtherB = function () { return function (node) { }; }; -var A = /** @class */ (function () { - function A() { - } - A = tslib_1.__decorate([ - core.Directive({ selector: '[a]' }), - OtherA() - ], A); - return A; -}()); -exports.A = A; -var B = /** @class */ (function () { - function B() { - } - B = tslib_1.__decorate([ - OtherB(), - core.Directive({ selector: '[b]' }) - ], B); - return B; -}()); -exports.B = B; -var C = /** @class */ (function () { - function C() { - } - C = tslib_1.__decorate([ - core.Directive({ selector: '[c]' }) - ], C); - return C; -}()); -exports.C = C; -var D = /** @class */ (function () { - function D() { - } - D_1 = D; - var D_1; - D = D_1 = tslib_1.__decorate([ - core.Directive({ selector: '[d]', providers: [D_1] }) - ], D); - return D; -}()); -exports.D = D; -// Some other content` - }; - }); - - function setup(file: {name: AbsoluteFsPath, contents: string}) { - loadTestFiles([file]); - const fs = getFileSystem(); - const logger = new MockLogger(); - const bundle = makeTestEntryPointBundle('test-package', 'commonjs', false, [file.name]); - const host = new CommonJsReflectionHost(logger, false, bundle.src); - const referencesRegistry = new NgccReferencesRegistry(host); - const decorationAnalyses = - new DecorationAnalyzer(fs, bundle, host, referencesRegistry).analyzeProgram(); - const renderer = new CommonJsRenderingFormatter(fs, host, false); - const importManager = new ImportManager(new NoopImportRewriter(), 'i'); - return { - host, - program: bundle.src.program, - sourceFile: bundle.src.file, - renderer, - decorationAnalyses, - importManager - }; - } - - describe('addImports', () => { - it('should insert the given imports after existing imports of the source file', () => { - const {renderer, sourceFile} = setup(PROGRAM); - const output = new MagicString(PROGRAM.contents); - renderer.addImports( - output, - [ - {specifier: '@angular/core', qualifier: ts.factory.createIdentifier('i0')}, - {specifier: '@angular/common', qualifier: ts.factory.createIdentifier('i1')} - ], - sourceFile); - expect(output.toString()).toContain(`/* A copyright notice */ -require('some-side-effect'); -var core = require('@angular/core'); -var i0 = require('@angular/core'); -var i1 = require('@angular/common');`); - }); - - it('should leave the file unchanged if there are no imports to add', () => { - const {renderer, sourceFile} = setup(PROGRAM); - const output = new MagicString(PROGRAM.contents); - const contentsBefore = output.toString(); - - renderer.addImports(output, [], sourceFile); - const contentsAfter = output.toString(); - - expect(contentsAfter).toBe(contentsBefore); - }); - }); - - describe('addExports', () => { - it('should insert the given exports at the end of the source file', () => { - const {importManager, renderer, sourceFile} = setup(PROGRAM); - const output = new MagicString(PROGRAM.contents); - renderer.addExports( - output, _(PROGRAM.name.replace(/\.js$/, '')), - [ - { - from: _('/node_modules/test-package/some/a.js'), - dtsFrom: _('/node_modules/test-package/some/a.d.ts'), - identifier: 'ComponentA1' - }, - { - from: _('/node_modules/test-package/some/a.js'), - dtsFrom: _('/node_modules/test-package/some/a.d.ts'), - identifier: 'ComponentA2' - }, - { - from: _('/node_modules/test-package/some/foo/b.js'), - dtsFrom: _('/node_modules/test-package/some/foo/b.d.ts'), - identifier: 'ComponentB' - }, - {from: PROGRAM.name, dtsFrom: PROGRAM.name, identifier: 'TopLevelComponent'}, - ], - importManager, sourceFile); - expect(output.toString()).toContain(` -// Some other content -exports.A = A; -exports.B = B; -exports.C = C; -exports.NoIife = NoIife; -exports.BadIife = BadIife; -exports.ComponentA1 = i0.ComponentA1; -exports.ComponentA2 = i0.ComponentA2; -exports.ComponentB = i1.ComponentB; -exports.TopLevelComponent = TopLevelComponent;`); - }); - }); - - describe('addConstants', () => { - it('should insert the given constants after imports in the source file', () => { - const {renderer, program} = setup(PROGRAM); - const file = getSourceFileOrError(program, _('/node_modules/test-package/some/file.js')); - const output = new MagicString(PROGRAM.contents); - renderer.addConstants(output, 'var x = 3;', file); - expect(output.toString()).toContain(` -var core = require('@angular/core'); - -var x = 3; -var A = (function() {`); - }); - - it('should insert constants after inserted imports', () => { - const {renderer, program} = setup(PROGRAM); - const file = getSourceFileOrError(program, _('/node_modules/test-package/some/file.js')); - const output = new MagicString(PROGRAM.contents); - renderer.addConstants(output, 'var x = 3;', file); - renderer.addImports( - output, [{specifier: '@angular/core', qualifier: ts.factory.createIdentifier('i0')}], - file); - expect(output.toString()).toContain(` -var core = require('@angular/core'); -var i0 = require('@angular/core'); - -var x = 3; -var A = (function() {`); - }); - }); - - describe('addDefinitions', () => { - it('should insert the definitions directly before the return statement of the class IIFE', - () => { - const {renderer, decorationAnalyses, sourceFile} = setup(PROGRAM); - const output = new MagicString(PROGRAM.contents); - const compiledClass = - decorationAnalyses.get(sourceFile)!.compiledClasses.find(c => c.name === 'A')!; - renderer.addDefinitions(output, compiledClass, 'SOME DEFINITION TEXT'); - expect(output.toString()).toContain(` - A.prototype.ngDoCheck = function() { - // - }; -SOME DEFINITION TEXT - return A; -`); - }); - - it('should error if the compiledClass is not valid', () => { - const {renderer, sourceFile, program} = setup(PROGRAM); - const output = new MagicString(PROGRAM.contents); - - const noIifeDeclaration = getDeclaration( - program, absoluteFromSourceFile(sourceFile), 'NoIife', ts.isFunctionDeclaration); - const mockNoIifeClass: any = {declaration: noIifeDeclaration, name: 'NoIife'}; - expect(() => renderer.addDefinitions(output, mockNoIifeClass, 'SOME DEFINITION TEXT')) - .toThrowError( - `Compiled class "NoIife" in "${ - _('/node_modules/test-package/some/file.js')}" does not have a valid syntax.\n` + - `Expected an ES5 IIFE wrapped function. But got:\n` + - `function NoIife() {}`); - - const badIifeDeclaration = getDeclaration( - program, absoluteFromSourceFile(sourceFile), 'BadIife', ts.isVariableDeclaration); - const mockBadIifeClass: any = {declaration: badIifeDeclaration, name: 'BadIife'}; - expect(() => renderer.addDefinitions(output, mockBadIifeClass, 'SOME DEFINITION TEXT')) - .toThrowError( - `Compiled class wrapper IIFE does not have a return statement: BadIife in ${ - _('/node_modules/test-package/some/file.js')}`); - }); - }); - - describe('addAdjacentStatements', () => { - const contents = `var core = require('@angular/core');\n` + - `var SomeDirective = /** @class **/ (function () {\n` + - ` function SomeDirective(zone, cons) {}\n` + - ` SomeDirective.prototype.method = function() {}\n` + - ` SomeDirective.decorators = [\n` + - ` { type: core.Directive, args: [{ selector: '[a]' }] },\n` + - ` { type: OtherA }\n` + - ` ];\n` + - ` SomeDirective.ctorParameters = function() { return [\n` + - ` { type: core.NgZone },\n` + - ` { type: core.Console }\n` + - ` ]; };\n` + - ` return SomeDirective;\n` + - `}());\n` + - `export {SomeDirective};`; - - it('should insert the statements after all the static methods of the class', () => { - const program = {name: _('/node_modules/test-package/some/file.js'), contents}; - const {renderer, decorationAnalyses, sourceFile} = setup(program); - const output = new MagicString(contents); - const compiledClass = decorationAnalyses.get(sourceFile)!.compiledClasses.find( - c => c.name === 'SomeDirective')!; - renderer.addAdjacentStatements(output, compiledClass, 'SOME STATEMENTS'); - expect(output.toString()) - .toContain( - ` SomeDirective.ctorParameters = function() { return [\n` + - ` { type: core.NgZone },\n` + - ` { type: core.Console }\n` + - ` ]; };\n` + - `SOME STATEMENTS\n` + - ` return SomeDirective;\n`); - }); - - it('should insert the statements after any definitions', () => { - const program = {name: _('/node_modules/test-package/some/file.js'), contents}; - const {renderer, decorationAnalyses, sourceFile} = setup(program); - const output = new MagicString(contents); - const compiledClass = decorationAnalyses.get(sourceFile)!.compiledClasses.find( - c => c.name === 'SomeDirective')!; - renderer.addDefinitions(output, compiledClass, 'SOME DEFINITIONS'); - renderer.addAdjacentStatements(output, compiledClass, 'SOME STATEMENTS'); - const definitionsPosition = output.toString().indexOf('SOME DEFINITIONS'); - const statementsPosition = output.toString().indexOf('SOME STATEMENTS'); - expect(definitionsPosition).not.toEqual(-1, 'definitions should exist'); - expect(statementsPosition).not.toEqual(-1, 'statements should exist'); - expect(statementsPosition).toBeGreaterThan(definitionsPosition); - }); - }); - - describe('removeDecorators', () => { - it('should delete the decorator (and following comma) that was matched in the analysis', - () => { - const {renderer, decorationAnalyses, sourceFile} = setup(PROGRAM); - const output = new MagicString(PROGRAM.contents); - const compiledClass = - decorationAnalyses.get(sourceFile)!.compiledClasses.find(c => c.name === 'A')!; - const decorator = compiledClass.decorators![0]; - const decoratorsToRemove = new Map(); - decoratorsToRemove.set(decorator.node!.parent!, [decorator.node!]); - renderer.removeDecorators(output, decoratorsToRemove); - expect(output.toString()) - .not.toContain(`{ type: core.Directive, args: [{ selector: '[a]' }] },`); - expect(output.toString()).toContain(`{ type: OtherA }`); - expect(output.toString()) - .toContain(`{ type: core.Directive, args: [{ selector: '[b]' }] }`); - expect(output.toString()).toContain(`{ type: OtherB }`); - expect(output.toString()) - .toContain(`{ type: core.Directive, args: [{ selector: '[c]' }] }`); - }); - - - it('should delete the decorator (but cope with no trailing comma) that was matched in the analysis', - () => { - const {renderer, decorationAnalyses, sourceFile} = setup(PROGRAM); - const output = new MagicString(PROGRAM.contents); - const compiledClass = - decorationAnalyses.get(sourceFile)!.compiledClasses.find(c => c.name === 'B')!; - const decorator = compiledClass.decorators![0]; - const decoratorsToRemove = new Map(); - decoratorsToRemove.set(decorator.node!.parent!, [decorator.node!]); - renderer.removeDecorators(output, decoratorsToRemove); - expect(output.toString()) - .toContain(`{ type: core.Directive, args: [{ selector: '[a]' }] },`); - expect(output.toString()).toContain(`{ type: OtherA }`); - expect(output.toString()) - .not.toContain(`{ type: core.Directive, args: [{ selector: '[b]' }] }`); - expect(output.toString()).toContain(`{ type: OtherB }`); - expect(output.toString()) - .toContain(`{ type: core.Directive, args: [{ selector: '[c]' }] }`); - }); - - - it('should delete the decorator (and its container if there are not other decorators left) that was matched in the analysis', - () => { - const {renderer, decorationAnalyses, sourceFile} = setup(PROGRAM); - const output = new MagicString(PROGRAM.contents); - const compiledClass = - decorationAnalyses.get(sourceFile)!.compiledClasses.find(c => c.name === 'C')!; - const decorator = compiledClass.decorators![0]; - const decoratorsToRemove = new Map(); - decoratorsToRemove.set(decorator.node!.parent!, [decorator.node!]); - renderer.removeDecorators(output, decoratorsToRemove); - renderer.addDefinitions(output, compiledClass, 'SOME DEFINITION TEXT'); - expect(output.toString()) - .toContain(`{ type: core.Directive, args: [{ selector: '[a]' }] },`); - expect(output.toString()).toContain(`{ type: OtherA }`); - expect(output.toString()) - .toContain(`{ type: core.Directive, args: [{ selector: '[b]' }] }`); - expect(output.toString()).toContain(`{ type: OtherB }`); - expect(output.toString()) - .toContain(`function C() {}\nSOME DEFINITION TEXT\n return C;`); - expect(output.toString()).not.toContain(`C.decorators`); - }); - }); - - describe('[__decorate declarations]', () => { - it('should delete the decorator (and following comma) that was matched in the analysis', - () => { - const {renderer, decorationAnalyses, sourceFile} = setup(PROGRAM_DECORATE_HELPER); - const output = new MagicString(PROGRAM_DECORATE_HELPER.contents); - const compiledClass = - decorationAnalyses.get(sourceFile)!.compiledClasses.find(c => c.name === 'A')!; - const decorator = compiledClass.decorators!.find(d => d.name === 'Directive')!; - const decoratorsToRemove = new Map(); - decoratorsToRemove.set(decorator.node!.parent!, [decorator.node!]); - renderer.removeDecorators(output, decoratorsToRemove); - expect(output.toString()).not.toContain(`Directive({ selector: '[a]' }),`); - expect(output.toString()).toContain(`OtherA()`); - expect(output.toString()).toContain(`Directive({ selector: '[b]' })`); - expect(output.toString()).toContain(`OtherB()`); - expect(output.toString()).toContain(`Directive({ selector: '[c]' })`); - }); - - it('should delete the decorator (but cope with no trailing comma) that was matched in the analysis', - () => { - const {renderer, decorationAnalyses, sourceFile} = setup(PROGRAM_DECORATE_HELPER); - const output = new MagicString(PROGRAM_DECORATE_HELPER.contents); - const compiledClass = - decorationAnalyses.get(sourceFile)!.compiledClasses.find(c => c.name === 'B')!; - const decorator = compiledClass.decorators!.find(d => d.name === 'Directive')!; - const decoratorsToRemove = new Map(); - decoratorsToRemove.set(decorator.node!.parent!, [decorator.node!]); - renderer.removeDecorators(output, decoratorsToRemove); - expect(output.toString()).toContain(`Directive({ selector: '[a]' }),`); - expect(output.toString()).toContain(`OtherA()`); - expect(output.toString()).not.toContain(`Directive({ selector: '[b]' })`); - expect(output.toString()).toContain(`OtherB()`); - expect(output.toString()).toContain(`Directive({ selector: '[c]' })`); - }); - - - it('should delete the decorator (and its container if there are no other decorators left) that was matched in the analysis', - () => { - const {renderer, decorationAnalyses, sourceFile} = setup(PROGRAM_DECORATE_HELPER); - const output = new MagicString(PROGRAM_DECORATE_HELPER.contents); - const compiledClass = - decorationAnalyses.get(sourceFile)!.compiledClasses.find(c => c.name === 'C')!; - const decorator = compiledClass.decorators!.find(d => d.name === 'Directive')!; - const decoratorsToRemove = new Map(); - decoratorsToRemove.set(decorator.node!.parent!, [decorator.node!]); - renderer.removeDecorators(output, decoratorsToRemove); - expect(output.toString()).toContain(`Directive({ selector: '[a]' }),`); - expect(output.toString()).toContain(`OtherA()`); - expect(output.toString()).toContain(`Directive({ selector: '[b]' })`); - expect(output.toString()).toContain(`OtherB()`); - expect(output.toString()).not.toContain(`Directive({ selector: '[c]' })`); - expect(output.toString()).not.toContain(`C = tslib_1.__decorate([`); - expect(output.toString()).toContain(`function C() {\n }\n return C;`); - }); - }); - - describe('printStatement', () => { - it('should transpile code to ES5', () => { - const {renderer, sourceFile, importManager} = setup(PROGRAM); - - const stmt1 = new DeclareVarStmt('foo', new LiteralExpr(42), null, StmtModifier.Static); - const stmt2 = new DeclareVarStmt('bar', new LiteralExpr(true)); - const stmt3 = new DeclareVarStmt('baz', new LiteralExpr('qux')); - - expect(renderer.printStatement(stmt1, sourceFile, importManager)).toBe('var foo = 42;'); - expect(renderer.printStatement(stmt2, sourceFile, importManager)).toBe('var bar = true;'); - expect(renderer.printStatement(stmt3, sourceFile, importManager)).toBe('var baz = "qux";'); - }); - }); - }); -}); diff --git a/packages/compiler-cli/ngcc/test/rendering/dts_renderer_spec.ts b/packages/compiler-cli/ngcc/test/rendering/dts_renderer_spec.ts deleted file mode 100644 index 8276227c5f9..00000000000 --- a/packages/compiler-cli/ngcc/test/rendering/dts_renderer_spec.ts +++ /dev/null @@ -1,239 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import {encode} from '@jridgewell/sourcemap-codec'; -import mapHelpers from 'convert-source-map'; -import MagicString from 'magic-string'; -import ts from 'typescript'; - -import {absoluteFrom, getFileSystem} from '../../../src/ngtsc/file_system'; -import {runInEachFileSystem, TestFile} from '../../../src/ngtsc/file_system/testing'; -import {Reexport} from '../../../src/ngtsc/imports'; -import {MockLogger} from '../../../src/ngtsc/logging/testing'; -import {loadTestFiles} from '../../../src/ngtsc/testing'; -import {Import, ImportManager} from '../../../src/ngtsc/translator'; -import {DecorationAnalyzer} from '../../src/analysis/decoration_analyzer'; -import {ModuleWithProvidersAnalyzer, ModuleWithProvidersInfo} from '../../src/analysis/module_with_providers_analyzer'; -import {NgccReferencesRegistry} from '../../src/analysis/ngcc_references_registry'; -import {ExportInfo, PrivateDeclarationsAnalyzer} from '../../src/analysis/private_declarations_analyzer'; -import {CompiledClass} from '../../src/analysis/types'; -import {Esm2015ReflectionHost} from '../../src/host/esm2015_host'; -import {DtsRenderer} from '../../src/rendering/dts_renderer'; -import {RedundantDecoratorMap, RenderingFormatter} from '../../src/rendering/rendering_formatter'; -import {getRootFiles, makeTestEntryPointBundle} from '../helpers/utils'; - -class TestRenderingFormatter implements RenderingFormatter { - addImports(output: MagicString, imports: Import[], sf: ts.SourceFile) { - output.prepend('\n// ADD IMPORTS\n'); - } - addExports(output: MagicString, baseEntryPointPath: string, exports: ExportInfo[]) { - output.prepend('\n// ADD EXPORTS\n'); - } - addDirectExports(output: MagicString, exports: Reexport[]) { - output.prepend('\n// ADD DIRECT EXPORTS\n'); - } - addConstants(output: MagicString, constants: string, file: ts.SourceFile): void { - output.prepend('\n// ADD CONSTANTS\n'); - } - addDefinitions(output: MagicString, compiledClass: CompiledClass, definitions: string) { - output.prepend('\n// ADD DEFINITIONS\n'); - } - addAdjacentStatements(output: MagicString, compiledClass: CompiledClass, statements: string) { - output.prepend('\n// ADD ADJACENT STATEMENTS\n'); - } - removeDecorators(output: MagicString, decoratorsToRemove: RedundantDecoratorMap) { - output.prepend('\n// REMOVE DECORATORS\n'); - } - addModuleWithProvidersParams( - output: MagicString, moduleWithProviders: ModuleWithProvidersInfo[], - importManager: ImportManager): void { - output.prepend('\n// ADD MODUlE WITH PROVIDERS PARAMS\n'); - } - printStatement(): string { - return 'IGNORED'; - } -} - -function createTestRenderer( - packageName: string, files: TestFile[], dtsFiles?: TestFile[], mappingFiles?: TestFile[]) { - const logger = new MockLogger(); - loadTestFiles(files); - if (dtsFiles) { - loadTestFiles(dtsFiles); - } - if (mappingFiles) { - loadTestFiles(mappingFiles); - } - const fs = getFileSystem(); - const isCore = packageName === '@angular/core'; - const bundle = makeTestEntryPointBundle( - 'test-package', 'esm2015', isCore, getRootFiles(files), dtsFiles && getRootFiles(dtsFiles)); - const host = new Esm2015ReflectionHost(logger, isCore, bundle.src, bundle.dts); - const referencesRegistry = new NgccReferencesRegistry(host); - const decorationAnalyses = - new DecorationAnalyzer(fs, bundle, host, referencesRegistry).analyzeProgram(); - const moduleWithProvidersAnalyses = - new ModuleWithProvidersAnalyzer( - host, bundle.src.program.getTypeChecker(), referencesRegistry, true) - .analyzeProgram(bundle.src.program); - const privateDeclarationsAnalyses = - new PrivateDeclarationsAnalyzer(host, referencesRegistry).analyzeProgram(bundle.src.program); - const testFormatter = new TestRenderingFormatter(); - spyOn(testFormatter, 'addExports').and.callThrough(); - spyOn(testFormatter, 'addImports').and.callThrough(); - spyOn(testFormatter, 'addDefinitions').and.callThrough(); - spyOn(testFormatter, 'addAdjacentStatements').and.callThrough(); - spyOn(testFormatter, 'addConstants').and.callThrough(); - spyOn(testFormatter, 'removeDecorators').and.callThrough(); - spyOn(testFormatter, 'addModuleWithProvidersParams').and.callThrough(); - spyOn(testFormatter, 'printStatement').and.callThrough(); - - const renderer = new DtsRenderer(testFormatter, fs, logger, host, bundle); - - return { - renderer, - testFormatter, - decorationAnalyses, - moduleWithProvidersAnalyses, - privateDeclarationsAnalyses, - bundle - }; -} - -runInEachFileSystem(() => { - describe('DtsRenderer', () => { - let _: typeof absoluteFrom; - let INPUT_PROGRAM: TestFile; - let INPUT_DTS_PROGRAM: TestFile; - - beforeEach(() => { - _ = absoluteFrom; - INPUT_PROGRAM = { - name: _('/node_modules/test-package/src/file.js'), - contents: - `import { Directive } from '@angular/core';\nexport class A {\n foo(x) {\n return x;\n }\n}\nA.decorators = [\n { type: Directive, args: [{ selector: '[a]' }] }\n];\n` - }; - INPUT_DTS_PROGRAM = { - name: _('/node_modules/test-package/typings/file.d.ts'), - contents: `export declare class A {\nfoo(x: number): number;\n}\n` - }; - }); - - it('should render extract types into typings files', () => { - const { - renderer, - decorationAnalyses, - privateDeclarationsAnalyses, - moduleWithProvidersAnalyses - } = createTestRenderer('test-package', [INPUT_PROGRAM], [INPUT_DTS_PROGRAM]); - const result = renderer.renderProgram( - decorationAnalyses, privateDeclarationsAnalyses, moduleWithProvidersAnalyses); - - const typingsFile = - result.find(f => f.path === _('/node_modules/test-package/typings/file.d.ts'))!; - expect(typingsFile.contents) - .toContain( - 'foo(x: number): number;\n static ɵfac: ɵngcc0.ɵɵFactoryDeclaration;\n static ɵdir: ɵngcc0.ɵɵDirectiveDeclaration'); - }); - - it('should render imports into typings files', () => { - const { - renderer, - decorationAnalyses, - privateDeclarationsAnalyses, - moduleWithProvidersAnalyses - } = createTestRenderer('test-package', [INPUT_PROGRAM], [INPUT_DTS_PROGRAM]); - const result = renderer.renderProgram( - decorationAnalyses, privateDeclarationsAnalyses, moduleWithProvidersAnalyses); - - const typingsFile = - result.find(f => f.path === _('/node_modules/test-package/typings/file.d.ts'))!; - expect(typingsFile.contents).toContain(`\n// ADD IMPORTS\n`); - }); - - it('should render exports into typings files', () => { - const { - renderer, - decorationAnalyses, - privateDeclarationsAnalyses, - moduleWithProvidersAnalyses - } = createTestRenderer('test-package', [INPUT_PROGRAM], [INPUT_DTS_PROGRAM]); - - // Add a mock export to trigger export rendering - privateDeclarationsAnalyses.push({ - identifier: 'ComponentB', - from: _('/node_modules/test-package/src/file.js'), - dtsFrom: _('/typings/b.d.ts') - }); - - const result = renderer.renderProgram( - decorationAnalyses, privateDeclarationsAnalyses, moduleWithProvidersAnalyses); - - const typingsFile = - result.find(f => f.path === _('/node_modules/test-package/typings/file.d.ts'))!; - expect(typingsFile.contents).toContain(`\n// ADD EXPORTS\n`); - }); - - it('should render ModuleWithProviders type params', () => { - const { - renderer, - decorationAnalyses, - privateDeclarationsAnalyses, - moduleWithProvidersAnalyses - } = createTestRenderer('test-package', [INPUT_PROGRAM], [INPUT_DTS_PROGRAM]); - - const result = renderer.renderProgram( - decorationAnalyses, privateDeclarationsAnalyses, moduleWithProvidersAnalyses); - - const typingsFile = - result.find(f => f.path === _('/node_modules/test-package/typings/file.d.ts'))!; - expect(typingsFile.contents).toContain(`\n// ADD MODUlE WITH PROVIDERS PARAMS\n`); - }); - - it('should render an external source map for files whose original file does not have a source map', - () => { - const { - renderer, - decorationAnalyses, - privateDeclarationsAnalyses, - moduleWithProvidersAnalyses - } = createTestRenderer('test-package', [INPUT_PROGRAM], [INPUT_DTS_PROGRAM]); - - const result = renderer.renderProgram( - decorationAnalyses, privateDeclarationsAnalyses, moduleWithProvidersAnalyses); - - const typingsFile = - result.find(f => f.path === _('/node_modules/test-package/typings/file.d.ts'))!; - expect(typingsFile.contents).toContain('//# sourceMappingURL=file.d.ts.map'); - }); - - it('should render an internal source map for files whose original file has an internal source map', - () => { - const sourceMap = mapHelpers.fromObject({ - 'version': 3, - 'file': 'file.d.ts', - 'sources': ['file.d.ts'], - 'names': [], - 'mappings': encode([[]]), - 'sourcesContent': [INPUT_DTS_PROGRAM.contents], - }); - INPUT_DTS_PROGRAM.contents += sourceMap.toComment(); - const { - renderer, - decorationAnalyses, - privateDeclarationsAnalyses, - moduleWithProvidersAnalyses - } = createTestRenderer('test-package', [INPUT_PROGRAM], [INPUT_DTS_PROGRAM]); - const result = renderer.renderProgram( - decorationAnalyses, privateDeclarationsAnalyses, moduleWithProvidersAnalyses); - - const typingsFile = - result.find(f => f.path === _('/node_modules/test-package/typings/file.d.ts'))!; - expect(typingsFile.contents).toContain('//# sourceMappingURL=data:application/json'); - }); - }); -}); diff --git a/packages/compiler-cli/ngcc/test/rendering/esm5_rendering_formatter_spec.ts b/packages/compiler-cli/ngcc/test/rendering/esm5_rendering_formatter_spec.ts deleted file mode 100644 index f87acc55ff4..00000000000 --- a/packages/compiler-cli/ngcc/test/rendering/esm5_rendering_formatter_spec.ts +++ /dev/null @@ -1,514 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import {DeclareVarStmt, LiteralExpr, StmtModifier} from '@angular/compiler'; -import MagicString from 'magic-string'; -import ts from 'typescript'; - -import {absoluteFrom, absoluteFromSourceFile, AbsoluteFsPath, getFileSystem, getSourceFileOrError} from '../../../src/ngtsc/file_system'; -import {runInEachFileSystem, TestFile} from '../../../src/ngtsc/file_system/testing'; -import {NoopImportRewriter} from '../../../src/ngtsc/imports'; -import {MockLogger} from '../../../src/ngtsc/logging/testing'; -import {getDeclaration, loadTestFiles} from '../../../src/ngtsc/testing'; -import {ImportManager} from '../../../src/ngtsc/translator'; -import {DecorationAnalyzer} from '../../src/analysis/decoration_analyzer'; -import {NgccReferencesRegistry} from '../../src/analysis/ngcc_references_registry'; -import {IMPORT_PREFIX} from '../../src/constants'; -import {Esm5ReflectionHost} from '../../src/host/esm5_host'; -import {Esm5RenderingFormatter} from '../../src/rendering/esm5_rendering_formatter'; -import {makeTestEntryPointBundle} from '../helpers/utils'; - -function setup(file: {name: AbsoluteFsPath, contents: string}) { - loadTestFiles([file]); - const fs = getFileSystem(); - const logger = new MockLogger(); - const bundle = makeTestEntryPointBundle('test-package', 'esm5', false, [file.name]); - const host = new Esm5ReflectionHost(logger, false, bundle.src); - const referencesRegistry = new NgccReferencesRegistry(host); - const decorationAnalyses = - new DecorationAnalyzer(fs, bundle, host, referencesRegistry).analyzeProgram(); - const renderer = new Esm5RenderingFormatter(fs, host, false); - const importManager = new ImportManager(new NoopImportRewriter(), IMPORT_PREFIX); - return { - host, - program: bundle.src.program, - sourceFile: bundle.src.file, - renderer, - decorationAnalyses, - importManager - }; -} - -runInEachFileSystem(() => { - describe('Esm5RenderingFormatter', () => { - let _: typeof absoluteFrom; - let PROGRAM: TestFile; - let PROGRAM_DECORATE_HELPER: TestFile; - - beforeEach(() => { - _ = absoluteFrom; - PROGRAM = { - name: _('/node_modules/test-package/some/file.js'), - contents: ` -/* A copyright notice */ -import 'some-side-effect'; -import {Directive} from '@angular/core'; -var A = (function() { - function A() {} - A.decorators = [ - { type: Directive, args: [{ selector: '[a]' }] }, - { type: OtherA } - ]; - A.prototype.ngDoCheck = function() { - // - }; - return A; -}()); - -var B = (function() { - function B() {} - B.decorators = [ - { type: OtherB }, - { type: Directive, args: [{ selector: '[b]' }] } - ]; - return B; -}()); - -var C = (function() { - function C() {} - C.decorators = [ - { type: Directive, args: [{ selector: '[c]' }] }, - ]; - return C; -}()); - -function NoIife() {} - -var BadIife = (function() { - function BadIife() {} - BadIife.decorators = [ - { type: Directive, args: [{ selector: '[c]' }] }, - ]; -}()); - -// Some other content -export {A, B, C, NoIife, BadIife};` - }; - - PROGRAM_DECORATE_HELPER = { - name: _('/node_modules/test-package/some/file.js'), - contents: ` -import * as tslib_1 from "tslib"; -/* A copyright notice */ -import { Directive } from '@angular/core'; -var OtherA = function () { return function (node) { }; }; -var OtherB = function () { return function (node) { }; }; -var A = /** @class */ (function () { - function A() { - } - A = tslib_1.__decorate([ - Directive({ selector: '[a]' }), - OtherA() - ], A); - return A; -}()); -export { A }; -var B = /** @class */ (function () { - function B() { - } - B = tslib_1.__decorate([ - OtherB(), - Directive({ selector: '[b]' }) - ], B); - return B; -}()); -export { B }; -var C = /** @class */ (function () { - function C() { - } - C = tslib_1.__decorate([ - Directive({ selector: '[c]' }) - ], C); - return C; -}()); -export { C }; -var D = /** @class */ (function () { - function D() { - } - D_1 = D; - var D_1; - D = D_1 = tslib_1.__decorate([ - Directive({ selector: '[d]', providers: [D_1] }) - ], D); - return D; -}()); -export { D }; -var E = /** @class */ (function () { - function E() {} - return E = tslib_1.__decorate([ - Directive({ selector: '[e]' }) - ], E); -}()); -export { E }; -var F = /** @class */ (function () { - function F() {} - return F = tslib_1.__decorate([ - Directive({ selector: '[f]' }) - ], F) -}()); -export { F }; -// Some other content` - }; - }); - - describe('addImports', () => { - it('should insert the given imports after existing imports of the source file', () => { - const {renderer, sourceFile} = setup(PROGRAM); - const output = new MagicString(PROGRAM.contents); - renderer.addImports( - output, - [ - {specifier: '@angular/core', qualifier: ts.factory.createIdentifier('i0')}, - {specifier: '@angular/common', qualifier: ts.factory.createIdentifier('i1')} - ], - sourceFile); - expect(output.toString()).toContain(`/* A copyright notice */ -import 'some-side-effect'; -import {Directive} from '@angular/core'; -import * as i0 from '@angular/core'; -import * as i1 from '@angular/common';`); - }); - - it('should leave the file unchanged if there are no imports to add', () => { - const {renderer, sourceFile} = setup(PROGRAM); - const output = new MagicString(PROGRAM.contents); - const contentsBefore = output.toString(); - - renderer.addImports(output, [], sourceFile); - const contentsAfter = output.toString(); - - expect(contentsAfter).toBe(contentsBefore); - }); - }); - - describe('addExports', () => { - it('should insert the given exports at the end of the source file', () => { - const {importManager, renderer, sourceFile} = setup(PROGRAM); - const output = new MagicString(PROGRAM.contents); - renderer.addExports( - output, _(PROGRAM.name.replace(/\.js$/, '')), - [ - { - from: _('/node_modules/test-package/some/a.js'), - dtsFrom: _('/node_modules/test-package/some/a.d.ts'), - identifier: 'ComponentA1' - }, - { - from: _('/node_modules/test-package/some/a.js'), - dtsFrom: _('/node_modules/test-package/some/a.d.ts'), - identifier: 'ComponentA2' - }, - { - from: _('/node_modules/test-package/some/foo/b.js'), - dtsFrom: _('/node_modules/test-package/some/foo/b.d.ts'), - identifier: 'ComponentB' - }, - {from: PROGRAM.name, dtsFrom: PROGRAM.name, identifier: 'TopLevelComponent'}, - ], - importManager, sourceFile); - expect(output.toString()).toContain(` -export {A, B, C, NoIife, BadIife}; -export {ComponentA1} from './a'; -export {ComponentA2} from './a'; -export {ComponentB} from './foo/b'; -export {TopLevelComponent};`); - }); - }); - - describe('addConstants', () => { - it('should insert the given constants after imports in the source file', () => { - const {renderer, program} = setup(PROGRAM); - const file = getSourceFileOrError(program, _('/node_modules/test-package/some/file.js')); - const output = new MagicString(PROGRAM.contents); - renderer.addConstants(output, 'var x = 3;', file); - expect(output.toString()).toContain(` -import {Directive} from '@angular/core'; - -var x = 3; -var A = (function() {`); - }); - - it('should insert constants after inserted imports', () => { - const {renderer, program} = setup(PROGRAM); - const file = getSourceFileOrError(program, _('/node_modules/test-package/some/file.js')); - const output = new MagicString(PROGRAM.contents); - renderer.addConstants(output, 'var x = 3;', file); - renderer.addImports( - output, [{specifier: '@angular/core', qualifier: ts.factory.createIdentifier('i0')}], - file); - expect(output.toString()).toContain(` -import {Directive} from '@angular/core'; -import * as i0 from '@angular/core'; - -var x = 3; -var A = (function() {`); - }); - }); - - describe('addDefinitions', () => { - it('should insert the definitions directly before the return statement of the class IIFE', - () => { - const {renderer, decorationAnalyses, sourceFile} = setup(PROGRAM); - const output = new MagicString(PROGRAM.contents); - const compiledClass = - decorationAnalyses.get(sourceFile)!.compiledClasses.find(c => c.name === 'A')!; - renderer.addDefinitions(output, compiledClass, 'SOME DEFINITION TEXT'); - expect(output.toString()).toContain(` - A.prototype.ngDoCheck = function() { - // - }; -SOME DEFINITION TEXT - return A; -`); - }); - - it('should error if the compiledClass is not valid', () => { - const {renderer, sourceFile, program} = setup(PROGRAM); - const output = new MagicString(PROGRAM.contents); - - const noIifeDeclaration = getDeclaration( - program, absoluteFromSourceFile(sourceFile), 'NoIife', ts.isFunctionDeclaration); - const mockNoIifeClass: any = {declaration: noIifeDeclaration, name: 'NoIife'}; - expect(() => renderer.addDefinitions(output, mockNoIifeClass, 'SOME DEFINITION TEXT')) - .toThrowError( - `Compiled class "NoIife" in "${ - _('/node_modules/test-package/some/file.js')}" does not have a valid syntax.\n` + - `Expected an ES5 IIFE wrapped function. But got:\n` + - `function NoIife() {}`); - - const badIifeDeclaration = getDeclaration( - program, absoluteFromSourceFile(sourceFile), 'BadIife', ts.isVariableDeclaration); - const mockBadIifeClass: any = {declaration: badIifeDeclaration, name: 'BadIife'}; - expect(() => renderer.addDefinitions(output, mockBadIifeClass, 'SOME DEFINITION TEXT')) - .toThrowError( - `Compiled class wrapper IIFE does not have a return statement: BadIife in ${ - _('/node_modules/test-package/some/file.js')}`); - }); - }); - - describe('addAdjacentStatements', () => { - const contents = `import {Directive, NgZone, Console} from '@angular/core';\n` + - `var SomeDirective = /** @class **/ (function () {\n` + - ` function SomeDirective(zone, cons) {}\n` + - ` SomeDirective.prototype.method = function() {}\n` + - ` SomeDirective.decorators = [\n` + - ` { type: Directive, args: [{ selector: '[a]' }] },\n` + - ` { type: OtherA }\n` + - ` ];\n` + - ` SomeDirective.ctorParameters = function() { return [\n` + - ` { type: NgZone },\n` + - ` { type: Console }\n` + - ` ]; };\n` + - ` return SomeDirective;\n` + - `}());\n` + - `export {SomeDirective};`; - - it('should insert the statements after all the static methods of the class', () => { - const program = {name: _('/node_modules/test-package/some/file.js'), contents}; - const {renderer, decorationAnalyses, sourceFile} = setup(program); - const output = new MagicString(contents); - const compiledClass = decorationAnalyses.get(sourceFile)!.compiledClasses.find( - c => c.name === 'SomeDirective')!; - renderer.addAdjacentStatements(output, compiledClass, 'SOME STATEMENTS'); - expect(output.toString()) - .toContain( - ` SomeDirective.ctorParameters = function() { return [\n` + - ` { type: NgZone },\n` + - ` { type: Console }\n` + - ` ]; };\n` + - `SOME STATEMENTS\n` + - ` return SomeDirective;\n`); - }); - - it('should insert the statements after any definitions', () => { - const program = {name: _('/node_modules/test-package/some/file.js'), contents}; - const {renderer, decorationAnalyses, sourceFile} = setup(program); - const output = new MagicString(contents); - const compiledClass = decorationAnalyses.get(sourceFile)!.compiledClasses.find( - c => c.name === 'SomeDirective')!; - renderer.addDefinitions(output, compiledClass, 'SOME DEFINITIONS'); - renderer.addAdjacentStatements(output, compiledClass, 'SOME STATEMENTS'); - const definitionsPosition = output.toString().indexOf('SOME DEFINITIONS'); - const statementsPosition = output.toString().indexOf('SOME STATEMENTS'); - expect(definitionsPosition).not.toEqual(-1, 'definitions should exist'); - expect(statementsPosition).not.toEqual(-1, 'statements should exist'); - expect(statementsPosition).toBeGreaterThan(definitionsPosition); - }); - }); - - - describe('removeDecorators', () => { - it('should delete the decorator (and following comma) that was matched in the analysis', - () => { - const {renderer, decorationAnalyses, sourceFile} = setup(PROGRAM); - const output = new MagicString(PROGRAM.contents); - const compiledClass = - decorationAnalyses.get(sourceFile)!.compiledClasses.find(c => c.name === 'A')!; - const decorator = compiledClass.decorators![0]; - const decoratorsToRemove = new Map(); - decoratorsToRemove.set(decorator.node!.parent!, [decorator.node!]); - renderer.removeDecorators(output, decoratorsToRemove); - expect(output.toString()) - .not.toContain(`{ type: Directive, args: [{ selector: '[a]' }] },`); - expect(output.toString()).toContain(`{ type: OtherA }`); - expect(output.toString()).toContain(`{ type: Directive, args: [{ selector: '[b]' }] }`); - expect(output.toString()).toContain(`{ type: OtherB }`); - expect(output.toString()).toContain(`{ type: Directive, args: [{ selector: '[c]' }] }`); - }); - - - it('should delete the decorator (but cope with no trailing comma) that was matched in the analysis', - () => { - const {renderer, decorationAnalyses, sourceFile} = setup(PROGRAM); - const output = new MagicString(PROGRAM.contents); - const compiledClass = - decorationAnalyses.get(sourceFile)!.compiledClasses.find(c => c.name === 'B')!; - const decorator = compiledClass.decorators![0]; - const decoratorsToRemove = new Map(); - decoratorsToRemove.set(decorator.node!.parent!, [decorator.node!]); - renderer.removeDecorators(output, decoratorsToRemove); - expect(output.toString()).toContain(`{ type: Directive, args: [{ selector: '[a]' }] },`); - expect(output.toString()).toContain(`{ type: OtherA }`); - expect(output.toString()) - .not.toContain(`{ type: Directive, args: [{ selector: '[b]' }] }`); - expect(output.toString()).toContain(`{ type: OtherB }`); - expect(output.toString()).toContain(`{ type: Directive, args: [{ selector: '[c]' }] }`); - }); - - - it('should delete the decorator (and its container if there are not other decorators left) that was matched in the analysis', - () => { - const {renderer, decorationAnalyses, sourceFile} = setup(PROGRAM); - const output = new MagicString(PROGRAM.contents); - const compiledClass = - decorationAnalyses.get(sourceFile)!.compiledClasses.find(c => c.name === 'C')!; - const decorator = compiledClass.decorators![0]; - const decoratorsToRemove = new Map(); - decoratorsToRemove.set(decorator.node!.parent!, [decorator.node!]); - renderer.removeDecorators(output, decoratorsToRemove); - renderer.addDefinitions(output, compiledClass, 'SOME DEFINITION TEXT'); - expect(output.toString()).toContain(`{ type: Directive, args: [{ selector: '[a]' }] },`); - expect(output.toString()).toContain(`{ type: OtherA }`); - expect(output.toString()).toContain(`{ type: Directive, args: [{ selector: '[b]' }] }`); - expect(output.toString()).toContain(`{ type: OtherB }`); - expect(output.toString()) - .toContain(`function C() {}\nSOME DEFINITION TEXT\n return C;`); - expect(output.toString()).not.toContain(`C.decorators`); - }); - - - describe('[__decorate declarations]', () => { - it('should delete the decorator (and following comma) that was matched in the analysis', - () => { - const {renderer, decorationAnalyses, sourceFile} = setup(PROGRAM_DECORATE_HELPER); - const output = new MagicString(PROGRAM_DECORATE_HELPER.contents); - const compiledClass = - decorationAnalyses.get(sourceFile)!.compiledClasses.find(c => c.name === 'A')!; - const decorator = compiledClass.decorators!.find(d => d.name === 'Directive')!; - const decoratorsToRemove = new Map(); - decoratorsToRemove.set(decorator.node!.parent!, [decorator.node!]); - renderer.removeDecorators(output, decoratorsToRemove); - expect(output.toString()).not.toContain(`Directive({ selector: '[a]' }),`); - expect(output.toString()).toContain(`OtherA()`); - expect(output.toString()).toContain(`Directive({ selector: '[b]' })`); - expect(output.toString()).toContain(`OtherB()`); - expect(output.toString()).toContain(`Directive({ selector: '[c]' })`); - }); - - it('should delete the decorator (but cope with no trailing comma) that was matched in the analysis', - () => { - const {renderer, decorationAnalyses, sourceFile} = setup(PROGRAM_DECORATE_HELPER); - const output = new MagicString(PROGRAM_DECORATE_HELPER.contents); - const compiledClass = - decorationAnalyses.get(sourceFile)!.compiledClasses.find(c => c.name === 'B')!; - const decorator = compiledClass.decorators!.find(d => d.name === 'Directive')!; - const decoratorsToRemove = new Map(); - decoratorsToRemove.set(decorator.node!.parent!, [decorator.node!]); - renderer.removeDecorators(output, decoratorsToRemove); - expect(output.toString()).toContain(`Directive({ selector: '[a]' }),`); - expect(output.toString()).toContain(`OtherA()`); - expect(output.toString()).not.toContain(`Directive({ selector: '[b]' })`); - expect(output.toString()).toContain(`OtherB()`); - expect(output.toString()).toContain(`Directive({ selector: '[c]' })`); - }); - - it('should delete the decorator (and its container if there are not other decorators left) that was matched in the analysis', - () => { - const {renderer, decorationAnalyses, sourceFile} = setup(PROGRAM_DECORATE_HELPER); - const output = new MagicString(PROGRAM_DECORATE_HELPER.contents); - const compiledClass = - decorationAnalyses.get(sourceFile)!.compiledClasses.find(c => c.name === 'C')!; - const decorator = compiledClass.decorators!.find(d => d.name === 'Directive')!; - const decoratorsToRemove = new Map(); - decoratorsToRemove.set(decorator.node!.parent!, [decorator.node!]); - renderer.removeDecorators(output, decoratorsToRemove); - expect(output.toString()).toContain(`Directive({ selector: '[a]' }),`); - expect(output.toString()).toContain(`OtherA()`); - expect(output.toString()).toContain(`Directive({ selector: '[b]' })`); - expect(output.toString()).toContain(`OtherB()`); - expect(output.toString()).not.toContain(`Directive({ selector: '[c]' })`); - expect(output.toString()).not.toContain(`C = tslib_1.__decorate([`); - expect(output.toString()).toContain(`function C() {\n }\n return C;`); - }); - - it('should delete call to `__decorate` but keep a return statement and semi-colon if there are no other decorators left', - () => { - const {renderer, decorationAnalyses, sourceFile} = setup(PROGRAM_DECORATE_HELPER); - const output = new MagicString(PROGRAM_DECORATE_HELPER.contents); - const compiledClass = - decorationAnalyses.get(sourceFile)!.compiledClasses.find(c => c.name === 'E')!; - const decorator = compiledClass.decorators!.find(d => d.name === 'Directive')!; - const decoratorsToRemove = new Map(); - decoratorsToRemove.set(decorator.node!.parent!, [decorator.node!]); - renderer.removeDecorators(output, decoratorsToRemove); - expect(output.toString()).not.toContain(`Directive({ selector: '[e]' })`); - expect(output.toString()).not.toContain(`E = tslib_1.__decorate([`); - expect(output.toString()).toContain(`function E() {}\n return E;`); - }); - - it('should delete call to `__decorate` but keep a return statement and no semi-colon if there are no other decorators left', - () => { - const {renderer, decorationAnalyses, sourceFile} = setup(PROGRAM_DECORATE_HELPER); - const output = new MagicString(PROGRAM_DECORATE_HELPER.contents); - const compiledClass = - decorationAnalyses.get(sourceFile)!.compiledClasses.find(c => c.name === 'F')!; - const decorator = compiledClass.decorators!.find(d => d.name === 'Directive')!; - const decoratorsToRemove = new Map(); - decoratorsToRemove.set(decorator.node!.parent!, [decorator.node!]); - renderer.removeDecorators(output, decoratorsToRemove); - expect(output.toString()).not.toContain(`Directive({ selector: '[f]' })`); - expect(output.toString()).not.toContain(`F = tslib_1.__decorate([`); - expect(output.toString()).toContain(`function F() {}\n return F\n`); - }); - }); - }); - - describe('printStatement', () => { - it('should transpile code to ES5', () => { - const {renderer, sourceFile, importManager} = setup(PROGRAM); - - const stmt1 = new DeclareVarStmt('foo', new LiteralExpr(42), null, StmtModifier.Static); - const stmt2 = new DeclareVarStmt('bar', new LiteralExpr(true)); - const stmt3 = new DeclareVarStmt('baz', new LiteralExpr('qux')); - - expect(renderer.printStatement(stmt1, sourceFile, importManager)).toBe('var foo = 42;'); - expect(renderer.printStatement(stmt2, sourceFile, importManager)).toBe('var bar = true;'); - expect(renderer.printStatement(stmt3, sourceFile, importManager)).toBe('var baz = "qux";'); - }); - }); - }); -}); diff --git a/packages/compiler-cli/ngcc/test/rendering/esm_rendering_formatter_spec.ts b/packages/compiler-cli/ngcc/test/rendering/esm_rendering_formatter_spec.ts deleted file mode 100644 index b2b11e9c20d..00000000000 --- a/packages/compiler-cli/ngcc/test/rendering/esm_rendering_formatter_spec.ts +++ /dev/null @@ -1,644 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import {DeclareVarStmt, LiteralExpr, StmtModifier} from '@angular/compiler'; -import MagicString from 'magic-string'; -import ts from 'typescript'; - -import {absoluteFrom, getFileSystem, getSourceFileOrError} from '../../../src/ngtsc/file_system'; -import {runInEachFileSystem, TestFile} from '../../../src/ngtsc/file_system/testing'; -import {NoopImportRewriter} from '../../../src/ngtsc/imports'; -import {MockLogger} from '../../../src/ngtsc/logging/testing'; -import {loadFakeCore, loadTestFiles} from '../../../src/ngtsc/testing'; -import {ImportManager} from '../../../src/ngtsc/translator'; -import {DecorationAnalyzer} from '../../src/analysis/decoration_analyzer'; -import {ModuleWithProvidersAnalyzer} from '../../src/analysis/module_with_providers_analyzer'; -import {NgccReferencesRegistry} from '../../src/analysis/ngcc_references_registry'; -import {IMPORT_PREFIX} from '../../src/constants'; -import {Esm2015ReflectionHost} from '../../src/host/esm2015_host'; -import {EsmRenderingFormatter} from '../../src/rendering/esm_rendering_formatter'; -import {getRootFiles, makeTestEntryPointBundle} from '../helpers/utils'; - -function setup(files: TestFile[], dtsFiles?: TestFile[]) { - const fs = getFileSystem(); - loadFakeCore(fs); - loadTestFiles(files); - if (dtsFiles) { - loadTestFiles(dtsFiles); - } - const logger = new MockLogger(); - const bundle = makeTestEntryPointBundle( - 'test-package', 'esm2015', false, getRootFiles(files), dtsFiles && getRootFiles(dtsFiles))!; - const host = new Esm2015ReflectionHost(logger, false, bundle.src, bundle.dts); - const referencesRegistry = new NgccReferencesRegistry(host); - const decorationAnalyses = - new DecorationAnalyzer(fs, bundle, host, referencesRegistry).analyzeProgram(); - const renderer = new EsmRenderingFormatter(fs, host, false); - const importManager = new ImportManager(new NoopImportRewriter(), IMPORT_PREFIX); - return { - host, - bundle, - program: bundle.src.program, - sourceFile: bundle.src.file, - renderer, - decorationAnalyses, - importManager, - }; -} - -const PROGRAM_CONTENT: Record = { - 'top-level': ` -/* A copyright notice */ -import 'some-side-effect'; -import {Directive} from '@angular/core'; -export class A {} -A.decorators = [ - { type: Directive, args: [{ selector: '[a]' }] }, - { type: OtherA } -]; -export class B {} -B.decorators = [ - { type: OtherB }, - { type: Directive, args: [{ selector: '[b]' }] } -]; -let C_1; -let C = C_1 = class C {}; -C.decorators = [ - { type: Directive, args: [{ selector: '[c]' }] }, -]; -export C; - -// Some other content`, - - - 'iife-wrapped': ` -/* A copyright notice */ -import 'some-side-effect'; -import {Directive} from '@angular/core'; -let A = /** @class */ (() => { -class A {} -A.decorators = [ - { type: Directive, args: [{ selector: '[a]' }] }, - { type: OtherA } -]; -return A; -})(); -let B = /** @class */ (() => { -class B {} -B.decorators = [ - { type: OtherB }, - { type: Directive, args: [{ selector: '[b]' }] } -]; -return B; -})(); -let C_1; -let C = C_1 = /** @class */ (() => { -class C {} -C.decorators = [ - { type: Directive, args: [{ selector: '[c]' }] }, -]; -return C; -})(); -export A, B, C; -// Some other content` -}; - -runInEachFileSystem(() => { - ['top-level', 'iife-wrapped'].forEach(classLayout => { - describe(`EsmRenderingFormatter {${classLayout} classes}`, () => { - let _: typeof absoluteFrom; - let PROGRAM: TestFile; - - beforeEach(() => { - _ = absoluteFrom; - - PROGRAM = { - name: _('/node_modules/test-package/some/file.js'), - contents: PROGRAM_CONTENT[classLayout], - }; - }); - - describe('addImports', () => { - it('should insert the given imports after existing imports of the source file', () => { - const {renderer, sourceFile} = setup([PROGRAM]); - const output = new MagicString(PROGRAM.contents); - renderer.addImports( - output, - [ - {specifier: '@angular/core', qualifier: ts.factory.createIdentifier('i0')}, - {specifier: '@angular/common', qualifier: ts.factory.createIdentifier('i1')} - ], - sourceFile); - expect(output.toString()).toContain(`/* A copyright notice */ -import 'some-side-effect'; -import {Directive} from '@angular/core'; -import * as i0 from '@angular/core'; -import * as i1 from '@angular/common';`); - }); - - it('should leave the file unchanged if there are no imports to add', () => { - const {renderer, sourceFile} = setup([PROGRAM]); - const output = new MagicString(PROGRAM.contents); - const contentsBefore = output.toString(); - - renderer.addImports(output, [], sourceFile); - const contentsAfter = output.toString(); - - expect(contentsAfter).toBe(contentsBefore); - }); - }); - - describe('addExports', () => { - it('should insert the given exports at the end of the source file', () => { - const {importManager, renderer, sourceFile} = setup([PROGRAM]); - const output = new MagicString(PROGRAM.contents); - renderer.addExports( - output, _(PROGRAM.name.replace(/\.js$/, '')), - [ - { - from: _('/node_modules/test-package/some/a.js'), - dtsFrom: _('/node_modules/test-package/some/a.d.ts'), - identifier: 'ComponentA1' - }, - { - from: _('/node_modules/test-package/some/a.js'), - dtsFrom: _('/node_modules/test-package/some/a.d.ts'), - identifier: 'ComponentA2' - }, - { - from: _('/node_modules/test-package/some/foo/b.js'), - dtsFrom: _('/node_modules/test-package/some/foo/b.d.ts'), - identifier: 'ComponentB' - }, - {from: PROGRAM.name, dtsFrom: PROGRAM.name, identifier: 'TopLevelComponent'}, - ], - importManager, sourceFile); - expect(output.toString()).toContain(` -// Some other content -export {ComponentA1} from './a'; -export {ComponentA2} from './a'; -export {ComponentB} from './foo/b'; -export {TopLevelComponent};`); - }); - }); - - describe('addConstants', () => { - it('should insert the given constants after imports in the source file', () => { - const {renderer, program} = setup([PROGRAM]); - const file = getSourceFileOrError(program, _('/node_modules/test-package/some/file.js')); - const output = new MagicString(PROGRAM.contents); - renderer.addConstants(output, 'const x = 3;', file); - expect(output.toString()).toContain(` -import {Directive} from '@angular/core'; - -const x = 3; -`); - }); - - it('should insert constants after inserted imports', () => { - const {renderer, program} = setup([PROGRAM]); - const file = getSourceFileOrError(program, _('/node_modules/test-package/some/file.js')); - const output = new MagicString(PROGRAM.contents); - renderer.addConstants(output, 'const x = 3;', file); - renderer.addImports( - output, [{specifier: '@angular/core', qualifier: ts.factory.createIdentifier('i0')}], - file); - expect(output.toString()).toContain(` -import {Directive} from '@angular/core'; -import * as i0 from '@angular/core'; - -const x = 3; -`); - }); - }); - - describe('addDefinitions', () => { - it('should insert the definitions directly after the class declaration', () => { - const {renderer, decorationAnalyses, sourceFile} = setup([PROGRAM]); - const output = new MagicString(PROGRAM.contents); - const compiledClass = - decorationAnalyses.get(sourceFile)!.compiledClasses.find(c => c.name === 'A')!; - renderer.addDefinitions(output, compiledClass, 'SOME DEFINITION TEXT'); - expect(output.toString()).toContain(`class A {} -SOME DEFINITION TEXT -A.decorators = [ -`); - }); - - it('should insert the definitions after the variable declaration of class expressions', - () => { - const {renderer, decorationAnalyses, sourceFile} = setup([PROGRAM]); - const output = new MagicString(PROGRAM.contents); - const compiledClass = - decorationAnalyses.get(sourceFile)!.compiledClasses.find(c => c.name === 'C')!; - renderer.addDefinitions(output, compiledClass, 'SOME DEFINITION TEXT'); - expect(output.toString()) - .toMatch(/class C \{\};?\nSOME DEFINITION TEXT\nC.decorators = \[/); - }); - }); - - describe('addAdjacentStatements', () => { - const contents = `import {Directive, NgZone, Console} from '@angular/core';\n` + - `export class SomeDirective {\n` + - ` constructor(zone, cons) {}\n` + - ` method() {}\n` + - `}\n` + - `SomeDirective.decorators = [\n` + - ` { type: Directive, args: [{ selector: '[a]' }] },\n` + - ` { type: OtherA }\n` + - `];\n` + - `SomeDirective.ctorParameters = () => [\n` + - ` { type: NgZone },\n` + - ` { type: Console }\n` + - `];`; - - it('should insert the statements after all the static methods of the class', () => { - const program = {name: _('/node_modules/test-package/some/file.js'), contents}; - const {renderer, decorationAnalyses, sourceFile} = setup([program]); - const output = new MagicString(contents); - const compiledClass = decorationAnalyses.get(sourceFile)!.compiledClasses.find( - c => c.name === 'SomeDirective')!; - renderer.addAdjacentStatements(output, compiledClass, 'SOME STATEMENTS'); - expect(output.toString()) - .toContain( - `SomeDirective.ctorParameters = () => [\n` + - ` { type: NgZone },\n` + - ` { type: Console }\n` + - `];\n` + - `SOME STATEMENTS`); - }); - - it('should insert the statements after any definitions', () => { - const program = {name: _('/node_modules/test-package/some/file.js'), contents}; - const {renderer, decorationAnalyses, sourceFile} = setup([program]); - const output = new MagicString(contents); - const compiledClass = decorationAnalyses.get(sourceFile)!.compiledClasses.find( - c => c.name === 'SomeDirective')!; - renderer.addDefinitions(output, compiledClass, 'SOME DEFINITIONS'); - renderer.addAdjacentStatements(output, compiledClass, 'SOME STATEMENTS'); - const definitionsPosition = output.toString().indexOf('SOME DEFINITIONS'); - const statementsPosition = output.toString().indexOf('SOME STATEMENTS'); - expect(definitionsPosition).not.toEqual(-1, 'definitions should exist'); - expect(statementsPosition).not.toEqual(-1, 'statements should exist'); - expect(statementsPosition).toBeGreaterThan(definitionsPosition); - }); - }); - - describe('removeDecorators', () => { - describe('[static property declaration]', () => { - it('should delete the decorator (and following comma) that was matched in the analysis', - () => { - const {decorationAnalyses, sourceFile, renderer} = setup([PROGRAM]); - const output = new MagicString(PROGRAM.contents); - const compiledClass = - decorationAnalyses.get(sourceFile)!.compiledClasses.find(c => c.name === 'A')!; - const decorator = compiledClass.decorators![0]; - const decoratorsToRemove = new Map(); - decoratorsToRemove.set(decorator.node!.parent!, [decorator.node!]); - renderer.removeDecorators(output, decoratorsToRemove); - expect(output.toString()) - .not.toContain(`{ type: Directive, args: [{ selector: '[a]' }] },`); - expect(output.toString()).toContain(`{ type: OtherA }`); - expect(output.toString()) - .toContain(`{ type: Directive, args: [{ selector: '[b]' }] }`); - expect(output.toString()).toContain(`{ type: OtherB }`); - expect(output.toString()) - .toContain(`{ type: Directive, args: [{ selector: '[c]' }] }`); - }); - - - it('should delete the decorator (but cope with no trailing comma) that was matched in the analysis', - () => { - const {decorationAnalyses, sourceFile, renderer} = setup([PROGRAM]); - const output = new MagicString(PROGRAM.contents); - const compiledClass = - decorationAnalyses.get(sourceFile)!.compiledClasses.find(c => c.name === 'B')!; - const decorator = compiledClass.decorators![0]; - const decoratorsToRemove = new Map(); - decoratorsToRemove.set(decorator.node!.parent!, [decorator.node!]); - renderer.removeDecorators(output, decoratorsToRemove); - expect(output.toString()) - .toContain(`{ type: Directive, args: [{ selector: '[a]' }] },`); - expect(output.toString()).toContain(`{ type: OtherA }`); - expect(output.toString()) - .not.toContain(`{ type: Directive, args: [{ selector: '[b]' }] }`); - expect(output.toString()).toContain(`{ type: OtherB }`); - expect(output.toString()) - .toContain(`{ type: Directive, args: [{ selector: '[c]' }] }`); - }); - - it('should handle a decorator with a trailing comment', () => { - const text = ` -import {Directive} from '@angular/core'; -export class A {} -A.decorators = [ - { type: Directive, args: [{ selector: '[a]' }] }, - { type: OtherA } -]; - `; - const file = {name: _('/node_modules/test-package/index.js'), contents: text}; - const {decorationAnalyses, sourceFile, renderer} = setup([file]); - const output = new MagicString(text); - const compiledClass = - decorationAnalyses.get(sourceFile)!.compiledClasses.find(c => c.name === 'A')!; - const decorator = compiledClass.decorators![0]; - const decoratorsToRemove = new Map(); - decoratorsToRemove.set(decorator.node!.parent!, [decorator.node!]); - renderer.removeDecorators(output, decoratorsToRemove); - // The decorator should have been removed correctly. - expect(output.toString()).toContain('A.decorators = [ { type: OtherA }'); - }); - - - it('should delete the decorator (and its container if there are no other decorators left) that was matched in the analysis', - () => { - const {decorationAnalyses, sourceFile, renderer} = setup([PROGRAM]); - const output = new MagicString(PROGRAM.contents); - const compiledClass = - decorationAnalyses.get(sourceFile)!.compiledClasses.find(c => c.name === 'C')!; - const decorator = compiledClass.decorators![0]; - const decoratorsToRemove = new Map(); - decoratorsToRemove.set(decorator.node!.parent!, [decorator.node!]); - renderer.removeDecorators(output, decoratorsToRemove); - expect(output.toString()) - .toContain(`{ type: Directive, args: [{ selector: '[a]' }] },`); - expect(output.toString()).toContain(`{ type: OtherA }`); - expect(output.toString()) - .toContain(`{ type: Directive, args: [{ selector: '[b]' }] }`); - expect(output.toString()).toContain(`{ type: OtherB }`); - expect(output.toString()) - .not.toContain(`{ type: Directive, args: [{ selector: '[c]' }] }`); - expect(output.toString()).not.toContain(`C.decorators = [`); - }); - }); - }); - - describe('[__decorate declarations]', () => { - let PROGRAM_DECORATE_HELPER: TestFile; - - beforeEach(() => { - PROGRAM_DECORATE_HELPER = { - name: _('/node_modules/test-package/some/file.js'), - contents: ` -import * as tslib_1 from "tslib"; -let D_1; -/* A copyright notice */ -import { Directive } from '@angular/core'; -const OtherA = () => (node) => { }; -const OtherB = () => (node) => { }; -let A = class A { -}; -A = tslib_1.__decorate([ - Directive({ selector: '[a]' }), - OtherA() -], A); -export { A }; -let B = class B { -}; -B = tslib_1.__decorate([ - OtherB(), - Directive({ selector: '[b]' }) -], B); -export { B }; -let C = class C { -}; -C = tslib_1.__decorate([ - Directive({ selector: '[c]' }) -], C); -export { C }; -let D = D_1 = class D { -}; -D = D_1 = tslib_1.__decorate([ - Directive({ selector: '[d]', providers: [D_1] }) -], D); -export { D }; -// Some other content` - }; - }); - - it('should delete the decorator (and following comma) that was matched in the analysis', - () => { - const {renderer, decorationAnalyses, sourceFile} = setup([PROGRAM_DECORATE_HELPER]); - const output = new MagicString(PROGRAM_DECORATE_HELPER.contents); - const compiledClass = - decorationAnalyses.get(sourceFile)!.compiledClasses.find(c => c.name === 'A')!; - const decorator = compiledClass.decorators!.find(d => d.name === 'Directive')!; - const decoratorsToRemove = new Map(); - decoratorsToRemove.set(decorator.node!.parent!, [decorator.node!]); - renderer.removeDecorators(output, decoratorsToRemove); - expect(output.toString()).not.toContain(`Directive({ selector: '[a]' }),`); - expect(output.toString()).toContain(`OtherA()`); - expect(output.toString()).toContain(`Directive({ selector: '[b]' })`); - expect(output.toString()).toContain(`OtherB()`); - expect(output.toString()).toContain(`Directive({ selector: '[c]' })`); - }); - - it('should delete the decorator (but cope with no trailing comma) that was matched in the analysis', - () => { - const {renderer, decorationAnalyses, sourceFile} = setup([PROGRAM_DECORATE_HELPER]); - const output = new MagicString(PROGRAM_DECORATE_HELPER.contents); - const compiledClass = - decorationAnalyses.get(sourceFile)!.compiledClasses.find(c => c.name === 'B')!; - const decorator = compiledClass.decorators!.find(d => d.name === 'Directive')!; - const decoratorsToRemove = new Map(); - decoratorsToRemove.set(decorator.node!.parent!, [decorator.node!]); - renderer.removeDecorators(output, decoratorsToRemove); - expect(output.toString()).toContain(`Directive({ selector: '[a]' }),`); - expect(output.toString()).toContain(`OtherA()`); - expect(output.toString()).not.toContain(`Directive({ selector: '[b]' })`); - expect(output.toString()).toContain(`OtherB()`); - expect(output.toString()).toContain(`Directive({ selector: '[c]' })`); - }); - - - it('should delete the decorator (and its container if there are not other decorators left) that was matched in the analysis', - () => { - const {renderer, decorationAnalyses, sourceFile} = setup([PROGRAM_DECORATE_HELPER]); - const output = new MagicString(PROGRAM_DECORATE_HELPER.contents); - const compiledClass = - decorationAnalyses.get(sourceFile)!.compiledClasses.find(c => c.name === 'C')!; - const decorator = compiledClass.decorators!.find(d => d.name === 'Directive')!; - const decoratorsToRemove = new Map(); - decoratorsToRemove.set(decorator.node!.parent!, [decorator.node!]); - renderer.removeDecorators(output, decoratorsToRemove); - expect(output.toString()).toContain(`Directive({ selector: '[a]' }),`); - expect(output.toString()).toContain(`OtherA()`); - expect(output.toString()).toContain(`Directive({ selector: '[b]' })`); - expect(output.toString()).toContain(`OtherB()`); - expect(output.toString()).not.toContain(`Directive({ selector: '[c]' })`); - expect(output.toString()).not.toContain(`C = tslib_1.__decorate([`); - expect(output.toString()).toContain(`let C = class C {\n};\nexport { C };`); - }); - }); - - describe('addModuleWithProvidersParams', () => { - let MODULE_WITH_PROVIDERS_PROGRAM: TestFile[]; - let MODULE_WITH_PROVIDERS_DTS_PROGRAM: TestFile[]; - beforeEach(() => { - MODULE_WITH_PROVIDERS_PROGRAM = [ - { - name: _('/node_modules/test-package/src/index.js'), - contents: ` - import {ExternalModule} from './module'; - import {LibraryModule} from 'some-library'; - export class SomeClass {} - export class SomeModule { - static withProviders1() { return {ngModule: SomeModule}; } - static withProviders2() { return {ngModule: SomeModule}; } - static withProviders3() { return {ngModule: SomeClass}; } - static withProviders4() { return {ngModule: ExternalModule}; } - static withProviders5() { return {ngModule: ExternalModule}; } - static withProviders6() { return {ngModule: LibraryModule}; } - static withProviders7() { return {ngModule: SomeModule, providers: []}; }; - static withProviders8() { return {ngModule: SomeModule}; } - } - export function withProviders1() { return {ngModule: SomeModule}; } - export function withProviders2() { return {ngModule: SomeModule}; } - export function withProviders3() { return {ngModule: SomeClass}; } - export function withProviders4() { return {ngModule: ExternalModule}; } - export function withProviders5() { return {ngModule: ExternalModule}; } - export function withProviders6() { return {ngModule: LibraryModule}; } - export function withProviders7() { return {ngModule: SomeModule, providers: []}; } - export function withProviders8() { return {ngModule: SomeModule}; } - export {ExternalModule} from './module'; - ` - }, - { - name: _('/node_modules/test-package/src/module.js'), - contents: ` - export class ExternalModule { - static withProviders1() { return {ngModule: ExternalModule}; } - static withProviders2() { return {ngModule: ExternalModule}; } - }` - }, - { - name: _('/node_modules/some-library/index.d.ts'), - contents: 'export declare class LibraryModule {}' - }, - ]; - - MODULE_WITH_PROVIDERS_DTS_PROGRAM = [ - { - name: _('/node_modules/test-package/typings/index.d.ts'), - contents: ` - import {ModuleWithProviders} from '@angular/core'; - export declare class SomeClass {} - export interface MyModuleWithProviders extends ModuleWithProviders {} - export declare class SomeModule { - static withProviders1(): ModuleWithProviders; - static withProviders2(): ModuleWithProviders; - static withProviders3(): ModuleWithProviders; - static withProviders4(): ModuleWithProviders; - static withProviders5(); - static withProviders6(): ModuleWithProviders; - static withProviders7(): {ngModule: SomeModule, providers: any[]}; - static withProviders8(): MyModuleWithProviders; - } - export declare function withProviders1(): ModuleWithProviders; - export declare function withProviders2(): ModuleWithProviders; - export declare function withProviders3(): ModuleWithProviders; - export declare function withProviders4(): ModuleWithProviders; - export declare function withProviders5(); - export declare function withProviders6(): ModuleWithProviders; - export declare function withProviders7(): {ngModule: SomeModule, providers: any[]}; - export declare function withProviders8(): MyModuleWithProviders; - export {ExternalModule} from './module'; - ` - }, - { - name: _('/node_modules/test-package/typings/module.d.ts'), - contents: ` - export interface ModuleWithProviders {} - export declare class ExternalModule { - static withProviders1(): ModuleWithProviders; - static withProviders2(): ModuleWithProviders; - }` - }, - { - name: _('/node_modules/some-library/index.d.ts'), - contents: 'export declare class LibraryModule {}' - }, - ]; - }); - - it('should fixup functions/methods that return ModuleWithProviders structures', () => { - const {bundle, renderer, host} = - setup(MODULE_WITH_PROVIDERS_PROGRAM, MODULE_WITH_PROVIDERS_DTS_PROGRAM); - - const referencesRegistry = new NgccReferencesRegistry(host); - const moduleWithProvidersAnalyses = - new ModuleWithProvidersAnalyzer( - host, bundle.src.program.getTypeChecker(), referencesRegistry, true) - .analyzeProgram(bundle.src.program); - const typingsFile = getSourceFileOrError( - bundle.dts!.program, _('/node_modules/test-package/typings/index.d.ts')); - const moduleWithProvidersInfo = moduleWithProvidersAnalyses.get(typingsFile)!; - - const output = new MagicString(MODULE_WITH_PROVIDERS_DTS_PROGRAM[0].contents); - const importManager = new ImportManager(new NoopImportRewriter(), 'i'); - renderer.addModuleWithProvidersParams(output, moduleWithProvidersInfo, importManager); - - expect(output.toString()).toContain(` - static withProviders1(): ModuleWithProviders; - static withProviders2(): ModuleWithProviders; - static withProviders3(): ModuleWithProviders; - static withProviders4(): ModuleWithProviders; - static withProviders5(): i1.ModuleWithProviders; - static withProviders6(): ModuleWithProviders; - static withProviders7(): ({ngModule: SomeModule, providers: any[]})&{ngModule:SomeModule}; - static withProviders8(): (MyModuleWithProviders)&{ngModule:SomeModule};`); - expect(output.toString()).toContain(` - export declare function withProviders1(): ModuleWithProviders; - export declare function withProviders2(): ModuleWithProviders; - export declare function withProviders3(): ModuleWithProviders; - export declare function withProviders4(): ModuleWithProviders; - export declare function withProviders5(): i1.ModuleWithProviders; - export declare function withProviders6(): ModuleWithProviders; - export declare function withProviders7(): ({ngModule: SomeModule, providers: any[]})&{ngModule:SomeModule}; - export declare function withProviders8(): (MyModuleWithProviders)&{ngModule:SomeModule};`); - }); - - it('should not mistake `ModuleWithProviders` types that are not imported from `@angular/core', - () => { - const {bundle, renderer, host} = - setup(MODULE_WITH_PROVIDERS_PROGRAM, MODULE_WITH_PROVIDERS_DTS_PROGRAM); - - const referencesRegistry = new NgccReferencesRegistry(host); - const moduleWithProvidersAnalyses = - new ModuleWithProvidersAnalyzer( - host, bundle.src.program.getTypeChecker(), referencesRegistry, true) - .analyzeProgram(bundle.src.program); - const typingsFile = getSourceFileOrError( - bundle.dts!.program, _('/node_modules/test-package/typings/module.d.ts')); - const moduleWithProvidersInfo = moduleWithProvidersAnalyses.get(typingsFile)!; - - const output = new MagicString(MODULE_WITH_PROVIDERS_DTS_PROGRAM[1].contents); - const importManager = new ImportManager(new NoopImportRewriter(), 'i'); - renderer.addModuleWithProvidersParams(output, moduleWithProvidersInfo, importManager); - expect(output.toString()).toContain(` - static withProviders1(): (ModuleWithProviders)&{ngModule:ExternalModule}; - static withProviders2(): (ModuleWithProviders)&{ngModule:ExternalModule};`); - }); - }); - - describe('printStatement', () => { - it('should transpile code to ES2015', () => { - const {renderer, sourceFile, importManager} = setup([PROGRAM]); - - const stmt1 = new DeclareVarStmt('foo', new LiteralExpr(42), null, StmtModifier.Final); - const stmt2 = new DeclareVarStmt('bar', new LiteralExpr(true)); - const stmt3 = new DeclareVarStmt('baz', new LiteralExpr('qux')); - - expect(renderer.printStatement(stmt1, sourceFile, importManager)).toBe('const foo = 42;'); - expect(renderer.printStatement(stmt2, sourceFile, importManager)).toBe('let bar = true;'); - expect(renderer.printStatement(stmt3, sourceFile, importManager)) - .toBe('let baz = "qux";'); - }); - }); - }); - }); -}); diff --git a/packages/compiler-cli/ngcc/test/rendering/renderer_spec.ts b/packages/compiler-cli/ngcc/test/rendering/renderer_spec.ts deleted file mode 100644 index 98acb3ac10a..00000000000 --- a/packages/compiler-cli/ngcc/test/rendering/renderer_spec.ts +++ /dev/null @@ -1,582 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import {Statement} from '@angular/compiler'; -import mapHelpers from 'convert-source-map'; -import MagicString from 'magic-string'; -import ts from 'typescript'; - -import {absoluteFrom, getFileSystem} from '../../../src/ngtsc/file_system'; -import {runInEachFileSystem, TestFile} from '../../../src/ngtsc/file_system/testing'; -import {Reexport} from '../../../src/ngtsc/imports'; -import {MockLogger} from '../../../src/ngtsc/logging/testing'; -import {loadTestFiles} from '../../../src/ngtsc/testing'; -import {Import, ImportManager, translateStatement} from '../../../src/ngtsc/translator'; -import {DecorationAnalyzer} from '../../src/analysis/decoration_analyzer'; -import {ModuleWithProvidersInfo} from '../../src/analysis/module_with_providers_analyzer'; -import {NgccReferencesRegistry} from '../../src/analysis/ngcc_references_registry'; -import {ExportInfo, PrivateDeclarationsAnalyzer} from '../../src/analysis/private_declarations_analyzer'; -import {CompiledClass} from '../../src/analysis/types'; -import {Esm2015ReflectionHost} from '../../src/host/esm2015_host'; -import {Esm5ReflectionHost} from '../../src/host/esm5_host'; -import {Renderer} from '../../src/rendering/renderer'; -import {RedundantDecoratorMap, RenderingFormatter} from '../../src/rendering/rendering_formatter'; -import {getRootFiles, makeTestEntryPointBundle} from '../helpers/utils'; - -class TestRenderingFormatter implements RenderingFormatter { - private printer = ts.createPrinter({newLine: ts.NewLineKind.LineFeed}); - - constructor(private isEs5: boolean) {} - - addImports(output: MagicString, imports: Import[], sf: ts.SourceFile) { - output.prepend('\n// ADD IMPORTS\n'); - } - addExports(output: MagicString, baseEntryPointPath: string, exports: ExportInfo[]) { - output.prepend('\n// ADD EXPORTS\r\n'); - } - addDirectExports(output: MagicString, exports: Reexport[]): void { - output.prepend('\n// ADD DIRECT EXPORTS\n'); - } - addConstants(output: MagicString, constants: string, file: ts.SourceFile): void { - output.prepend('\n// ADD CONSTANTS\n'); - } - addDefinitions(output: MagicString, compiledClass: CompiledClass, definitions: string) { - output.prepend('\n// ADD DEFINITIONS\n'); - } - addAdjacentStatements(output: MagicString, compiledClass: CompiledClass, statements: string) { - output.prepend('\n// ADD ADJACENT STATEMENTS\n'); - } - removeDecorators(output: MagicString, decoratorsToRemove: RedundantDecoratorMap) { - output.prepend('\n// REMOVE DECORATORS\n'); - } - addModuleWithProvidersParams( - output: MagicString, moduleWithProviders: ModuleWithProvidersInfo[], - importManager: ImportManager): void { - output.prepend('\n// ADD MODUlE WITH PROVIDERS PARAMS\n'); - } - printStatement(stmt: Statement, sourceFile: ts.SourceFile, importManager: ImportManager): string { - const node = translateStatement( - stmt, importManager, - {downlevelTaggedTemplates: this.isEs5, downlevelVariableDeclarations: this.isEs5}); - const code = this.printer.printNode(ts.EmitHint.Unspecified, node, sourceFile); - - return `// TRANSPILED\n${code}`; - } -} - -function createTestRenderer( - packageName: string, files: TestFile[], dtsFiles?: TestFile[], mappingFiles?: TestFile[], - isEs5 = false) { - const logger = new MockLogger(); - loadTestFiles(files); - if (dtsFiles) { - loadTestFiles(dtsFiles); - } - if (mappingFiles) { - loadTestFiles(mappingFiles); - } - const fs = getFileSystem(); - const isCore = packageName === '@angular/core'; - const bundle = makeTestEntryPointBundle( - 'test-package', 'esm5', isCore, getRootFiles(files), dtsFiles && getRootFiles(dtsFiles)); - const host = isEs5 ? new Esm5ReflectionHost(logger, isCore, bundle.src, bundle.dts) : - new Esm2015ReflectionHost(logger, isCore, bundle.src, bundle.dts); - const referencesRegistry = new NgccReferencesRegistry(host); - const decorationAnalyses = - new DecorationAnalyzer(fs, bundle, host, referencesRegistry).analyzeProgram(); - const privateDeclarationsAnalyses = - new PrivateDeclarationsAnalyzer(host, referencesRegistry).analyzeProgram(bundle.src.program); - const testFormatter = new TestRenderingFormatter(isEs5); - spyOn(testFormatter, 'addExports').and.callThrough(); - spyOn(testFormatter, 'addImports').and.callThrough(); - spyOn(testFormatter, 'addDefinitions').and.callThrough(); - spyOn(testFormatter, 'addAdjacentStatements').and.callThrough(); - spyOn(testFormatter, 'addConstants').and.callThrough(); - spyOn(testFormatter, 'removeDecorators').and.callThrough(); - spyOn(testFormatter, 'addModuleWithProvidersParams').and.callThrough(); - spyOn(testFormatter, 'printStatement').and.callThrough(); - - const renderer = new Renderer(host, testFormatter, fs, logger, bundle); - - return {renderer, testFormatter, decorationAnalyses, privateDeclarationsAnalyses, bundle}; -} - -runInEachFileSystem(() => { - describe('Renderer', () => { - let _: typeof absoluteFrom; - let TS_CONTENT: TestFile; - let JS_CONTENT: TestFile; - let COMPONENT_PROGRAM: TestFile; - let NGMODULE_PROGRAM: TestFile; - let JS_CONTENT_MAP: mapHelpers.SourceMapConverter; - let RENDERED_CONTENTS: string; - let OUTPUT_PROGRAM_MAP: mapHelpers.SourceMapConverter; - let MERGED_OUTPUT_PROGRAM_MAP: mapHelpers.SourceMapConverter; - - beforeEach(() => { - _ = absoluteFrom; - - TS_CONTENT = { - name: _('/node_modules/test-package/src/file.ts'), - contents: - `import {Directive} from '@angular/core';\n@Directive({selector: '[a]'})\nexport class A {\n foo(x: number): number { return x; }\n}` - }; - - JS_CONTENT = { - name: _('/node_modules/test-package/src/file.js'), - contents: - `import { Directive } from '@angular/core';\nexport class A {\n foo(x) {\r\n return x;\n }\r\n}\nA.decorators = [\n { type: Directive, args: [{ selector: '[a]' }] }\r\n];\n` - }; - - COMPONENT_PROGRAM = { - name: _('/node_modules/test-package/src/component.js'), - contents: - `import { Component } from '@angular/core';\nexport class A {}\nA.decorators = [\n { type: Component, args: [{ selector: 'a', template: '{{ person!.name }}' }] }\n];\n` - }; - - NGMODULE_PROGRAM = { - name: _('/node_modules/test-package/src/ngmodule.js'), - contents: - `import { NgModule } from '@angular/core';\nexport class A {}\nA.decorators = [\n { type: NgModule, args: [{}] }\n];\n` - }; - - JS_CONTENT_MAP = mapHelpers.fromObject({ - 'version': 3, - 'file': 'file.js', - 'sourceRoot': '', - 'sources': ['file.ts'], - 'sourcesContent': [TS_CONTENT.contents], - 'names': [], - 'mappings': ';;;;;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA', - }); - - RENDERED_CONTENTS = - `\n// ADD IMPORTS\n\n// ADD EXPORTS\r\n\n// ADD CONSTANTS\n\n// ADD ADJACENT STATEMENTS\n\n// ADD DEFINITIONS\n\n// REMOVE DECORATORS\n` + - JS_CONTENT.contents; - - OUTPUT_PROGRAM_MAP = mapHelpers.fromObject({ - 'version': 3, - 'file': 'file.js', - 'sources': ['file.js'], - 'names': [], - 'mappings': ';;;;;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA', - 'sourcesContent': [JS_CONTENT.contents], - }); - - MERGED_OUTPUT_PROGRAM_MAP = mapHelpers.fromObject({ - 'version': 3, - 'file': 'file.js', - 'sources': ['file.ts'], - 'names': [], - 'mappings': ';;;;;;;;;;;;;;;;;;;;;;AAAA,AACA,AACA,AACA,AACA,AACA,AACA,AACA,AACA,AACA,AACA', - 'sourcesContent': [TS_CONTENT.contents], - }); - }); - - describe('renderProgram()', () => { - it('should render the modified contents; with an inline map file, if the original provided no map file.', - () => { - const {renderer, decorationAnalyses, privateDeclarationsAnalyses} = - createTestRenderer('test-package', [JS_CONTENT]); - const [sourceFile, mapFile] = - renderer.renderProgram(decorationAnalyses, privateDeclarationsAnalyses); - expect(sourceFile.path).toEqual(_('/node_modules/test-package/src/file.js')); - expect(sourceFile.contents).toContain(RENDERED_CONTENTS); - expect(mapHelpers.fromSource(sourceFile.contents)!.toObject()) - .toEqual(OUTPUT_PROGRAM_MAP.toObject()); - expect(mapFile).toBeUndefined(); - }); - - - it('should render as JavaScript', () => { - const {renderer, decorationAnalyses, privateDeclarationsAnalyses, testFormatter} = - createTestRenderer('test-package', [COMPONENT_PROGRAM]); - renderer.renderProgram(decorationAnalyses, privateDeclarationsAnalyses); - const addDefinitionsSpy = testFormatter.addDefinitions as jasmine.Spy; - expect(addDefinitionsSpy.calls.first().args[2]).toEqual(`// TRANSPILED -A.ɵfac = function A_Factory(t) { return new (t || A)(); }; -// TRANSPILED -A.ɵcmp = /*@__PURE__*/ ɵngcc0.ɵɵdefineComponent({ type: A, selectors: [["a"]], decls: 1, vars: 1, template: function A_Template(rf, ctx) { if (rf & 1) { - ɵngcc0.ɵɵtext(0); - } if (rf & 2) { - ɵngcc0.ɵɵtextInterpolate(ctx.person.name); - } }, encapsulation: 2 });`); - - const addAdjacentStatementsSpy = testFormatter.addAdjacentStatements as jasmine.Spy; - expect(addAdjacentStatementsSpy.calls.first().args[2]).toEqual(`// TRANSPILED -(function () { (typeof ngDevMode === "undefined" || ngDevMode) && ɵngcc0.ɵsetClassMetadata(A, [{ - type: Component, - args: [{ selector: 'a', template: '{{ person!.name }}' }] - }], null, null); })();`); - }); - - - describe('calling RenderingFormatter methods', () => { - it('should call addImports with the source code and info about the core Angular library.', - () => { - const {renderer, decorationAnalyses, privateDeclarationsAnalyses, testFormatter} = - createTestRenderer('test-package', [JS_CONTENT]); - const result = renderer.renderProgram(decorationAnalyses, privateDeclarationsAnalyses); - const addImportsSpy = testFormatter.addImports as jasmine.Spy; - expect(addImportsSpy.calls.first().args[0].toString()).toEqual(RENDERED_CONTENTS); - expect(addImportsSpy.calls.first().args[1]).toEqual([ - {specifier: '@angular/core', qualifier: jasmine.objectContaining({text: 'ɵngcc0'})} - ]); - }); - - it('should call addDefinitions with the source code, the analyzed class and the rendered definitions.', - () => { - const {renderer, decorationAnalyses, privateDeclarationsAnalyses, testFormatter} = - createTestRenderer('test-package', [JS_CONTENT]); - renderer.renderProgram(decorationAnalyses, privateDeclarationsAnalyses); - const addDefinitionsSpy = testFormatter.addDefinitions as jasmine.Spy; - expect(addDefinitionsSpy.calls.first().args[0].toString()).toEqual(RENDERED_CONTENTS); - expect(addDefinitionsSpy.calls.first().args[1]).toEqual(jasmine.objectContaining({ - name: 'A', - decorators: [jasmine.objectContaining({name: 'Directive'})] - })); - expect(addDefinitionsSpy.calls.first().args[2]).toEqual(`// TRANSPILED -A.ɵfac = function A_Factory(t) { return new (t || A)(); }; -// TRANSPILED -A.ɵdir = /*@__PURE__*/ ɵngcc0.ɵɵdefineDirective({ type: A, selectors: [["", "a", ""]] });`); - }); - - it('should call addAdjacentStatements with the source code, the analyzed class and the rendered statements', - () => { - const {renderer, decorationAnalyses, privateDeclarationsAnalyses, testFormatter} = - createTestRenderer('test-package', [JS_CONTENT]); - renderer.renderProgram(decorationAnalyses, privateDeclarationsAnalyses); - const addAdjacentStatementsSpy = testFormatter.addAdjacentStatements as jasmine.Spy; - expect(addAdjacentStatementsSpy.calls.first().args[0].toString()) - .toEqual(RENDERED_CONTENTS); - expect(addAdjacentStatementsSpy.calls.first().args[1]) - .toEqual(jasmine.objectContaining( - {name: 'A', decorators: [jasmine.objectContaining({name: 'Directive'})]})); - expect(addAdjacentStatementsSpy.calls.first().args[2]).toEqual(`// TRANSPILED -(function () { (typeof ngDevMode === "undefined" || ngDevMode) && ɵngcc0.ɵsetClassMetadata(A, [{ - type: Directive, - args: [{ selector: '[a]' }] - }], null, null); })();`); - }); - - it('should call removeDecorators with the source code, a map of class decorators that have been analyzed', - () => { - const {renderer, decorationAnalyses, privateDeclarationsAnalyses, testFormatter} = - createTestRenderer('test-package', [JS_CONTENT]); - renderer.renderProgram(decorationAnalyses, privateDeclarationsAnalyses); - const removeDecoratorsSpy = testFormatter.removeDecorators as jasmine.Spy; - expect(removeDecoratorsSpy.calls.first().args[0].toString()) - .toEqual(RENDERED_CONTENTS); - - // Each map key is the TS node of the decorator container - // Each map value is an array of TS nodes that are the decorators to remove - const map = removeDecoratorsSpy.calls.first().args[1] as Map; - const keys = Array.from(map.keys()); - expect(keys.length).toEqual(1); - expect(keys[0].getText()) - .toEqual(`[\n { type: Directive, args: [{ selector: '[a]' }] }\r\n]`); - const values = Array.from(map.values()); - expect(values.length).toEqual(1); - expect(values[0].length).toEqual(1); - expect(values[0][0].getText()) - .toEqual(`{ type: Directive, args: [{ selector: '[a]' }] }`); - }); - - it('should render definitions as static fields', () => { - const {renderer, decorationAnalyses, privateDeclarationsAnalyses, testFormatter} = - createTestRenderer('test-package', [NGMODULE_PROGRAM]); - renderer.renderProgram(decorationAnalyses, privateDeclarationsAnalyses); - const addDefinitionsSpy = testFormatter.addDefinitions as jasmine.Spy; - const definitions: string = addDefinitionsSpy.calls.first().args[2]; - expect(definitions).toContain('A.ɵmod = /*@__PURE__*/ ɵngcc0.ɵɵdefineNgModule('); - expect(definitions).toContain('A.ɵinj = /*@__PURE__*/ ɵngcc0.ɵɵdefineInjector('); - }); - - it('should render adjacent statements', () => { - const {renderer, decorationAnalyses, privateDeclarationsAnalyses, testFormatter} = - createTestRenderer('test-package', [NGMODULE_PROGRAM]); - renderer.renderProgram(decorationAnalyses, privateDeclarationsAnalyses); - const addAdjacentStatementsSpy = testFormatter.addAdjacentStatements as jasmine.Spy; - const statements: string = addAdjacentStatementsSpy.calls.first().args[2]; - expect(statements).toContain('ɵsetClassMetadata(A'); - }); - - it('should render directives using the inner class name if different from outer', () => { - const {renderer, decorationAnalyses, privateDeclarationsAnalyses, testFormatter} = - createTestRenderer( - 'test-package', [{ - name: _('/node_modules/test-package/src/file.js'), - contents: ` - import { Directive } from '@angular/core'; - var OuterClass = /** @class */ (function () { - function InnerClass() {} - return InnerClass; - }()); - OuterClass.decorators = [{ type: Directive, args: [{ selector: '[test]' }] - export OuterClass;` - }], - undefined, undefined, /* isEs5 */ true); - - renderer.renderProgram(decorationAnalyses, privateDeclarationsAnalyses); - - const addDefinitionsSpy = testFormatter.addDefinitions as jasmine.Spy; - const definitions = addDefinitionsSpy.calls.first().args[2]; - expect(definitions).toContain('InnerClass.ɵfac'); - expect(definitions).toContain('new (t || InnerClass)'); - expect(definitions).toContain('InnerClass.ɵdir'); - expect(definitions).toContain('type: InnerClass'); - - const addAdjacentStatementsSpy = testFormatter.addAdjacentStatements as jasmine.Spy; - const statements = addAdjacentStatementsSpy.calls.first().args[2]; - expect(statements).toContain('ɵsetClassMetadata(InnerClass'); - }); - - it('should render injectables using the inner class name if different from outer', () => { - const {renderer, decorationAnalyses, privateDeclarationsAnalyses, testFormatter} = - createTestRenderer( - 'test-package', [{ - name: _('/node_modules/test-package/src/file.js'), - contents: ` - import { Injectable } from '@angular/core'; - var OuterClass = /** @class */ (function () { - function InnerClass() {} - return InnerClass; - }()); - OuterClass.decorators = [{ type: Injectable }] - export OuterClass;` - }], - undefined, undefined, /* isEs5 */ true); - - renderer.renderProgram(decorationAnalyses, privateDeclarationsAnalyses); - - const addDefinitionsSpy = testFormatter.addDefinitions as jasmine.Spy; - const definitions = addDefinitionsSpy.calls.first().args[2]; - expect(definitions).toContain('InnerClass.ɵfac'); - expect(definitions).toContain('new (t || InnerClass)()'); - expect(definitions).toContain('InnerClass.ɵprov'); - expect(definitions).toContain('token: InnerClass'); - - const addAdjacentStatementsSpy = testFormatter.addAdjacentStatements as jasmine.Spy; - const statements = addAdjacentStatementsSpy.calls.first().args[2]; - expect(statements).toContain('ɵsetClassMetadata(InnerClass'); - }); - - it('should render ng-modules using the inner class name if different from outer', () => { - const {renderer, decorationAnalyses, privateDeclarationsAnalyses, testFormatter} = - createTestRenderer( - 'test-package', [{ - name: _('/node_modules/test-package/src/file.js'), - contents: ` - import { NgModule, Directive } from '@angular/core'; - var DirectiveClass = /** @class */ (function () { - function DirectiveClass() {} - return DirectiveClass; - }()); - DirectiveClass.decorators = [{ type: Directive, args: [{selector: 'x'}] }]; - var OuterClass = /** @class */ (function () { - function InnerClass() {} - return InnerClass; - }()); - OuterClass.decorators = [{ type: NgModule, args: [{declarations: [DirectiveClass], exports: [DirectiveClass]}] - export OuterClass;` - }], - undefined, undefined, /* isEs5 */ true); - - renderer.renderProgram(decorationAnalyses, privateDeclarationsAnalyses); - - const addDefinitionsSpy = testFormatter.addDefinitions as jasmine.Spy; - const definitions = addDefinitionsSpy.calls.all()[1].args[2]; - expect(definitions).toContain('InnerClass.ɵmod'); - expect(definitions).toContain('type: InnerClass'); - - - const addAdjacentStatementsSpy = testFormatter.addAdjacentStatements as jasmine.Spy; - const statements = addAdjacentStatementsSpy.calls.all()[1].args[2]; - expect(statements).toContain('ɵɵsetNgModuleScope(InnerClass'); - expect(statements).toContain('ɵsetClassMetadata(InnerClass'); - }); - - it('should render pipes using the inner class name if different from outer', () => { - const {renderer, decorationAnalyses, privateDeclarationsAnalyses, testFormatter} = - createTestRenderer( - 'test-package', [{ - name: _('/node_modules/test-package/src/file.js'), - contents: ` - import { Pipe } from '@angular/core'; - var OuterClass = /** @class */ (function () { - function InnerClass() {} - return InnerClass; - }()); - OuterClass.decorators = [{ type: Pipe, args: [{name: 'pipe'}] - export OuterClass;` - }], - undefined, undefined, /* isEs5 */ true); - - renderer.renderProgram(decorationAnalyses, privateDeclarationsAnalyses); - - const addDefinitionsSpy = testFormatter.addDefinitions as jasmine.Spy; - const definitions = addDefinitionsSpy.calls.first().args[2]; - expect(definitions).toContain('InnerClass.ɵfac'); - expect(definitions).toContain('new (t || InnerClass)()'); - expect(definitions).toContain('InnerClass.ɵpipe'); - - const addAdjacentStatementsSpy = testFormatter.addAdjacentStatements as jasmine.Spy; - const statements = addAdjacentStatementsSpy.calls.first().args[2]; - expect(statements).toContain('ɵsetClassMetadata(InnerClass'); - }); - - it('should render classes without decorators if class fields are decorated', () => { - const {renderer, decorationAnalyses, privateDeclarationsAnalyses, testFormatter} = - createTestRenderer('test-package', [{ - name: _('/node_modules/test-package/src/file.js'), - contents: ` - import { Directive, ViewChild } from '@angular/core'; - - export class UndecoratedBase { test = null; } - - UndecoratedBase.propDecorators = { - test: [{ - type: ViewChild, - args: ["test", {static: true}] - }], - }; - ` - }]); - - renderer.renderProgram(decorationAnalyses, privateDeclarationsAnalyses); - - const addDefinitionsSpy = testFormatter.addDefinitions as jasmine.Spy; - expect(addDefinitionsSpy.calls.first().args[2]).toEqual(`// TRANSPILED -UndecoratedBase.ɵfac = function UndecoratedBase_Factory(t) { return new (t || UndecoratedBase)(); }; -// TRANSPILED -UndecoratedBase.ɵdir = /*@__PURE__*/ ɵngcc0.ɵɵdefineDirective({ type: UndecoratedBase, viewQuery: function UndecoratedBase_Query(rf, ctx) { if (rf & 1) { - ɵngcc0.ɵɵviewQuery(_c0, 7); - } if (rf & 2) { - let _t; - ɵngcc0.ɵɵqueryRefresh(_t = ɵngcc0.ɵɵloadQuery()) && (ctx.test = _t.first); - } } });`); - }); - - it('should call renderImports after other abstract methods', () => { - // This allows the other methods to add additional imports if necessary - const {renderer, decorationAnalyses, privateDeclarationsAnalyses, testFormatter} = - createTestRenderer('test-package', [JS_CONTENT]); - const addExportsSpy = testFormatter.addExports as jasmine.Spy; - const addDefinitionsSpy = testFormatter.addDefinitions as jasmine.Spy; - const addAdjacentStatementsSpy = testFormatter.addAdjacentStatements as jasmine.Spy; - const addConstantsSpy = testFormatter.addConstants as jasmine.Spy; - const addImportsSpy = testFormatter.addImports as jasmine.Spy; - renderer.renderProgram(decorationAnalyses, privateDeclarationsAnalyses); - expect(addExportsSpy).toHaveBeenCalledBefore(addImportsSpy); - expect(addDefinitionsSpy).toHaveBeenCalledBefore(addImportsSpy); - expect(addAdjacentStatementsSpy).toHaveBeenCalledBefore(addImportsSpy); - expect(addConstantsSpy).toHaveBeenCalledBefore(addImportsSpy); - }); - }); - - describe('source map merging', () => { - it('should merge any inline source map from the original file and write the output as an inline source map', - () => { - const sourceFiles: TestFile[] = [{ - name: JS_CONTENT.name, - contents: JS_CONTENT.contents + '\n' + JS_CONTENT_MAP.toComment() - }]; - const {decorationAnalyses, renderer, privateDeclarationsAnalyses} = - createTestRenderer('test-package', sourceFiles); - const [sourceFile, mapFile] = - renderer.renderProgram(decorationAnalyses, privateDeclarationsAnalyses); - expect(sourceFile.path).toEqual(_('/node_modules/test-package/src/file.js')); - expect(sourceFile.contents).toContain(RENDERED_CONTENTS); - expect(mapHelpers.fromSource(sourceFile.contents)!.toObject()) - .toEqual(MERGED_OUTPUT_PROGRAM_MAP.toObject()); - expect(mapFile).toBeUndefined(); - }); - - it('should merge any external source map from the original file and write the output to an external source map', - () => { - const sourceFiles: TestFile[] = [{ - name: JS_CONTENT.name, - contents: JS_CONTENT.contents + '\n//# sourceMappingURL=file.js.map' - }]; - const mappingFiles: TestFile[] = - [{name: _(JS_CONTENT.name + '.map'), contents: JS_CONTENT_MAP.toJSON()}]; - const {decorationAnalyses, renderer, privateDeclarationsAnalyses} = - createTestRenderer('test-package', sourceFiles, undefined, mappingFiles); - const [sourceFile, mapFile] = - renderer.renderProgram(decorationAnalyses, privateDeclarationsAnalyses); - expect(sourceFile.path).toEqual(_('/node_modules/test-package/src/file.js')); - expect(sourceFile.contents) - .toEqual( - RENDERED_CONTENTS + '\n' + mapHelpers.generateMapFileComment('file.js.map')); - expect(mapFile.path).toEqual(_('/node_modules/test-package/src/file.js.map')); - expect(JSON.parse(mapFile.contents) as any) - .toEqual(MERGED_OUTPUT_PROGRAM_MAP.toObject()); - }); - - - it('should render an internal source map for files whose original file does not have a source map', - () => { - const sourceFiles: TestFile[] = [JS_CONTENT]; - const {decorationAnalyses, renderer, privateDeclarationsAnalyses} = - createTestRenderer('test-package', sourceFiles, undefined); - const [sourceFile, mapFile] = - renderer.renderProgram(decorationAnalyses, privateDeclarationsAnalyses); - expect(sourceFile.path).toEqual(_('/node_modules/test-package/src/file.js')); - expect(sourceFile.contents) - .toEqual(RENDERED_CONTENTS + '\n' + OUTPUT_PROGRAM_MAP.toComment()); - expect(mapFile).toBeUndefined(); - }); - }); - - describe('@angular/core support', () => { - it('should render relative imports in ESM bundles', () => { - const CORE_FILE: TestFile = { - name: _('/node_modules/test-package/src/core.js'), - contents: - `import { NgModule } from './ng_module';\nexport class MyModule {}\nMyModule.decorators = [\n { type: NgModule, args: [] }\n];\n` - }; - const R3_SYMBOLS_FILE: TestFile = { - // r3_symbols in the file name indicates that this is the path to rewrite core imports - // to - name: _('/node_modules/test-package/src/r3_symbols.js'), - contents: `export const NgModule = () => null;` - }; - // The package name of `@angular/core` indicates that we are compiling the core library. - const {decorationAnalyses, renderer, privateDeclarationsAnalyses, testFormatter} = - createTestRenderer('@angular/core', [CORE_FILE, R3_SYMBOLS_FILE]); - renderer.renderProgram(decorationAnalyses, privateDeclarationsAnalyses); - const addAdjacentStatementsSpy = testFormatter.addAdjacentStatements as jasmine.Spy; - expect(addAdjacentStatementsSpy.calls.first().args[2]) - .toContain( - `function () { (typeof ngDevMode === "undefined" || ngDevMode) && ɵngcc0.setClassMetadata(`); - const addImportsSpy = testFormatter.addImports as jasmine.Spy; - expect(addImportsSpy.calls.first().args[1]).toEqual([ - {specifier: './r3_symbols', qualifier: jasmine.objectContaining({text: 'ɵngcc0'})} - ]); - }); - - it('should render no imports in FESM bundles', () => { - const CORE_FILE: TestFile = { - name: _('/node_modules/test-package/src/core.js'), - contents: `export const NgModule = () => null; - export class MyModule {}\nMyModule.decorators = [\n { type: NgModule, args: [] }\n];\n` - }; - - const {decorationAnalyses, renderer, privateDeclarationsAnalyses, testFormatter} = - createTestRenderer('@angular/core', [CORE_FILE]); - renderer.renderProgram(decorationAnalyses, privateDeclarationsAnalyses); - const addAdjacentStatementsSpy = testFormatter.addAdjacentStatements as jasmine.Spy; - expect(addAdjacentStatementsSpy.calls.first().args[2]) - .toContain( - `function () { (typeof ngDevMode === "undefined" || ngDevMode) && setClassMetadata(`); - const addImportsSpy = testFormatter.addImports as jasmine.Spy; - expect(addImportsSpy.calls.first().args[1]).toEqual([]); - }); - }); - }); - }); -}); diff --git a/packages/compiler-cli/ngcc/test/rendering/umd_rendering_formatter_spec.ts b/packages/compiler-cli/ngcc/test/rendering/umd_rendering_formatter_spec.ts deleted file mode 100644 index 9765f4ca583..00000000000 --- a/packages/compiler-cli/ngcc/test/rendering/umd_rendering_formatter_spec.ts +++ /dev/null @@ -1,755 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import {DeclareVarStmt, LiteralExpr, StmtModifier} from '@angular/compiler'; -import MagicString from 'magic-string'; -import ts from 'typescript'; - -import {absoluteFrom, absoluteFromSourceFile, getFileSystem, getSourceFileOrError} from '../../../src/ngtsc/file_system'; -import {runInEachFileSystem, TestFile} from '../../../src/ngtsc/file_system/testing'; -import {NoopImportRewriter} from '../../../src/ngtsc/imports'; -import {MockLogger} from '../../../src/ngtsc/logging/testing'; -import {getDeclaration, loadTestFiles} from '../../../src/ngtsc/testing'; -import {ImportManager} from '../../../src/ngtsc/translator'; -import {DecorationAnalyzer} from '../../src/analysis/decoration_analyzer'; -import {NgccReferencesRegistry} from '../../src/analysis/ngcc_references_registry'; -import {UmdReflectionHost} from '../../src/host/umd_host'; -import {UmdRenderingFormatter} from '../../src/rendering/umd_rendering_formatter'; -import {AdditionalFormatOptions, testForEachUmdFormat, WrapperFunctionFormat} from '../helpers/umd_utils'; -import {makeTestEntryPointBundle} from '../helpers/utils'; - -interface TestFileSpec extends Omit { - name: string; - contents: { - preamble?: string; moduleName: string; dependencies: string[]; factoryBody: string; - additionalOptions?: AdditionalFormatOptions; - }; -} - -function setup(file: TestFile) { - loadTestFiles([file]); - const fs = getFileSystem(); - const logger = new MockLogger(); - const bundle = makeTestEntryPointBundle('test-package', 'esm5', false, [file.name]); - const src = bundle.src; - const host = new UmdReflectionHost(logger, false, src); - const referencesRegistry = new NgccReferencesRegistry(host); - const decorationAnalyses = - new DecorationAnalyzer(fs, bundle, host, referencesRegistry).analyzeProgram(); - const renderer = new UmdRenderingFormatter(fs, host, false); - const importManager = new ImportManager(new NoopImportRewriter(), 'i'); - return { - decorationAnalyses, - host, - importManager, - program: src.program, - renderer, - sourceFile: src.file, - }; -} - -runInEachFileSystem(() => { - describe( - 'UmdRenderingFormatter', testForEachUmdFormat(({createUmdModule, wrapperFunctionFormat}) => { - let _: typeof absoluteFrom; - let PROGRAM: TestFile; - let PROGRAM_DECORATE_HELPER: TestFile; - let PROGRAM_WITH_GLOBAL_INITIALIZER: TestFile; - - const formatFactory = (spec: TestFileSpec): TestFile => ({ - ...spec, - name: _(spec.name), - contents: `${spec.contents.preamble ?? ''}\n` + - createUmdModule( - spec.contents.moduleName, spec.contents.dependencies, - spec.contents.factoryBody, spec.contents.additionalOptions), - }); - - beforeEach(() => { - _ = absoluteFrom; - - PROGRAM = formatFactory({ - name: '/node_modules/test-package/some/file.js', - contents: { - preamble: '/* A copyright notice */', - moduleName: 'file', - dependencies: ['some-side-effect', '/local-dep', '@angular/core'], - factoryBody: ` -var A = (function() { - function A() {} - A.decorators = [ - { type: core.Directive, args: [{ selector: '[a]' }] }, - { type: OtherA } - ]; - A.prototype.ngDoCheck = function() { - // - }; - return A; -}()); - -var B = (function() { - function B() {} - B.decorators = [ - { type: OtherB }, - { type: core.Directive, args: [{ selector: '[b]' }] } - ]; - return B; -}()); - -var C = (function() { - function C() {} - C.decorators = [ - { type: core.Directive, args: [{ selector: '[c]' }] }, - ]; - return C; -}()); - -function NoIife() {} - -var BadIife = (function() { - function BadIife() {} - BadIife.decorators = [ - { type: core.Directive, args: [{ selector: '[c]' }] }, - ]; -}()); - -// Some other content -exports.A = A; -exports.B = B; -exports.C = C; -exports.NoIife = NoIife; -exports.BadIife = BadIife; -`, - }, - }); - PROGRAM_DECORATE_HELPER = formatFactory({ - name: '/node_modules/test-package/some/file.js', - contents: { - preamble: '/* A copyright notice */', - moduleName: 'file', - dependencies: ['/tslib', '@angular/core'], - factoryBody: ` - var OtherA = function () { return function (node) { }; }; - var OtherB = function () { return function (node) { }; }; - var A = /** @class */ (function () { - function A() { - } - A = tslib.__decorate([ - core.Directive({ selector: '[a]' }), - OtherA() - ], A); - return A; - }()); - exports.A = A; - var B = /** @class */ (function () { - function B() { - } - B = tslib.__decorate([ - OtherB(), - core.Directive({ selector: '[b]' }) - ], B); - return B; - }()); - exports.B = B; - var C = /** @class */ (function () { - function C() { - } - C = tslib.__decorate([ - core.Directive({ selector: '[c]' }) - ], C); - return C; - }()); - exports.C = C; - var D = /** @class */ (function () { - function D() { - } - D_1 = D; - var D_1; - D = D_1 = tslib.__decorate([ - core.Directive({ selector: '[d]', providers: [D_1] }) - ], D); - return D; - }()); - exports.D = D; - // Some other content -`, - }, - }); - PROGRAM_WITH_GLOBAL_INITIALIZER = formatFactory({ - name: '/node_modules/test-package/some/file.js', - contents: { - moduleName: 'file', - dependencies: ['some-side-effect', '/local-dep', '@angular/core'], - factoryBody: '', - additionalOptions: {hasGlobalInitializer: true}, - }, - }); - }); - - describe('addImports', () => { - it('should append the given imports into the CommonJS2 factory call', () => { - const {renderer, program} = setup(PROGRAM); - const file = - getSourceFileOrError(program, _('/node_modules/test-package/some/file.js')); - const output = new MagicString(PROGRAM.contents); - renderer.addImports( - output, - [ - {specifier: '@angular/core', qualifier: ts.factory.createIdentifier('i0')}, - {specifier: '@angular/common', qualifier: ts.factory.createIdentifier('i1')} - ], - file); - expect(output.toString()) - .toContain( - wrapperFunctionFormat === WrapperFunctionFormat.Rollup ? - `typeof exports === 'object' && typeof module !== 'undefined' ?\n` + - ` factory(require('@angular/core'),require('@angular/common'),exports, require('some-side-effect'), require('/local-dep'), require('@angular/core')) :` : - `if (typeof exports === 'object' && typeof module === 'object')\n` + - ` module.exports = factory(require('@angular/core'),require('@angular/common'),require('some-side-effect'), require('/local-dep'), require('@angular/core'));`); - }); - - if (wrapperFunctionFormat === WrapperFunctionFormat.Webpack) { - // The CommonJS (vs CommonJS2) format only applies to the Webpack format. - it('should append the given imports into the CommonJS factory call', () => { - const {renderer, program} = setup(PROGRAM); - const file = - getSourceFileOrError(program, _('/node_modules/test-package/some/file.js')); - const output = new MagicString(PROGRAM.contents); - renderer.addImports( - output, - [ - {specifier: '@angular/core', qualifier: ts.factory.createIdentifier('i0')}, - {specifier: '@angular/common', qualifier: ts.factory.createIdentifier('i1')} - ], - file); - expect(output.toString()) - .toContain( - `if (typeof exports === 'object')\n` + - ` exports['file'] = factory(require('@angular/core'),require('@angular/common'),require('some-side-effect'), require('/local-dep'), require('@angular/core'));`); - }); - } - - it('should append the given imports into the AMD initialization', () => { - const {renderer, program} = setup(PROGRAM); - const file = - getSourceFileOrError(program, _('/node_modules/test-package/some/file.js')); - const output = new MagicString(PROGRAM.contents); - renderer.addImports( - output, - [ - {specifier: '@angular/core', qualifier: ts.factory.createIdentifier('i0')}, - {specifier: '@angular/common', qualifier: ts.factory.createIdentifier('i1')} - ], - file); - expect(output.toString()) - .toContain( - wrapperFunctionFormat === WrapperFunctionFormat.Rollup ? - `typeof define === 'function' && define.amd ?\n` + - ` define('file', ['@angular/core','@angular/common','exports', 'some-side-effect', '/local-dep', '@angular/core'], factory) :` : - `if (typeof define === 'function' && define.amd)\n` + - ` define(['@angular/core','@angular/common','some-side-effect', '/local-dep', '@angular/core'], factory);`); - }); - - it('should append the given imports into the global initialization', () => { - const {renderer, program} = setup(PROGRAM); - const file = - getSourceFileOrError(program, _('/node_modules/test-package/some/file.js')); - const output = new MagicString(PROGRAM.contents); - renderer.addImports( - output, - [ - {specifier: '@angular/core', qualifier: ts.factory.createIdentifier('i0')}, - {specifier: '@angular/common', qualifier: ts.factory.createIdentifier('i1')} - ], - file); - expect(output.toString()) - .toContain( - wrapperFunctionFormat === WrapperFunctionFormat.Rollup ? - `(factory(global.ng.core,global.ng.common,global.file, global.someSideEffect, global.localDep, global.ng.core));` : - `root['file'] = factory(global.ng.core,global.ng.common,root['someSideEffect'], root['localDep'], root['ng']['core']);`); - }); - - it('should remap import identifiers to valid global properties', () => { - const {renderer, program} = setup(PROGRAM); - const file = - getSourceFileOrError(program, _('/node_modules/test-package/some/file.js')); - const output = new MagicString(PROGRAM.contents); - renderer.addImports( - output, - [ - {specifier: '@ngrx/store', qualifier: ts.factory.createIdentifier('i0')}, { - specifier: '@angular/platform-browser-dynamic', - qualifier: ts.factory.createIdentifier('i1') - }, - { - specifier: '@angular/common/testing', - qualifier: ts.factory.createIdentifier('i2') - }, - {specifier: '@angular-foo/package', qualifier: ts.factory.createIdentifier('i3')} - ], - file); - expect(output.toString()) - .toContain( - wrapperFunctionFormat === WrapperFunctionFormat.Rollup ? - `(factory(` + - `global.ngrx.store,global.ng.platformBrowserDynamic,global.ng.common.testing,global.angularFoo.package,` + - `global.file, global.someSideEffect, global.localDep, global.ng.core));` : - `root['file'] = factory(` + - `global.ngrx.store,global.ng.platformBrowserDynamic,global.ng.common.testing,global.angularFoo.package,` + - `root['someSideEffect'], root['localDep'], root['ng']['core']);`); - }); - - if (wrapperFunctionFormat === WrapperFunctionFormat.Rollup) { - // Global initializer only applies to the Rollup format. - it('should append the given imports into the global initialization, if it has a global/self initializer', - () => { - const {renderer, program} = setup(PROGRAM_WITH_GLOBAL_INITIALIZER); - const file = - getSourceFileOrError(program, _('/node_modules/test-package/some/file.js')); - const output = new MagicString(file.text); - renderer.addImports( - output, - [ - {specifier: '@angular/core', qualifier: ts.factory.createIdentifier('i0')}, - {specifier: '@angular/common', qualifier: ts.factory.createIdentifier('i1')} - ], - file); - expect(output.toString()) - .toContain( - `(global = global || self, factory(global.ng.core,global.ng.common,global.file, global.someSideEffect, global.localDep, global.ng.core))`); - }); - } - - it('should append the given imports as parameters into the factory function definition', - () => { - const {renderer, program} = setup(PROGRAM); - const file = - getSourceFileOrError(program, _('/node_modules/test-package/some/file.js')); - const output = new MagicString(PROGRAM.contents); - renderer.addImports( - output, - [ - {specifier: '@angular/core', qualifier: ts.factory.createIdentifier('i0')}, - {specifier: '@angular/common', qualifier: ts.factory.createIdentifier('i1')} - ], - file); - expect(output.toString()) - .toContain( - wrapperFunctionFormat === WrapperFunctionFormat.Rollup ? - `(function (i0,i1,exports, someSideEffect, localDep, core) {\n 'use strict';` : - `function (i0,i1,someSideEffect, localDep, core) {\n 'use strict';`); - }); - - it('should handle the case where there were no prior imports nor exports', () => { - const PROGRAM = formatFactory({ - name: _('/node_modules/test-package/some/file.js'), - contents: { - preamble: '/* A copyright notice */', - moduleName: 'file', - dependencies: [], - factoryBody: ` - var index = ''; - return index;`, - additionalOptions: {exportsParamIndex: -1}, - }, - }); - const {renderer, program} = setup(PROGRAM); - const file = - getSourceFileOrError(program, _('/node_modules/test-package/some/file.js')); - const output = new MagicString(PROGRAM.contents); - renderer.addImports( - output, - [ - {specifier: '@angular/core', qualifier: ts.factory.createIdentifier('i0')}, - {specifier: '@angular/common', qualifier: ts.factory.createIdentifier('i1')} - ], - file); - const outputSrc = output.toString(); - - if (wrapperFunctionFormat === WrapperFunctionFormat.Rollup) { - expect(outputSrc).toContain( - `typeof exports === 'object' && typeof module !== 'undefined' ?\n` + - ` factory(require('@angular/core'),require('@angular/common')) :`); - expect(outputSrc).toContain( - `typeof define === 'function' && define.amd ?\n` + - ` define('file',['@angular/core','@angular/common'], factory) :`); - expect(outputSrc).toContain(`(factory(global.ng.core,global.ng.common));`); - expect(outputSrc).toContain(`(function (i0,i1) {\n 'use strict';`); - } else { - expect(outputSrc).toContain( - `if (typeof exports === 'object' && typeof module === 'object')\n` + - ` module.exports = factory(require('@angular/core'),require('@angular/common'));`); - expect(outputSrc).toContain( - `if (typeof define === 'function' && define.amd)\n` + - ` define(['@angular/core','@angular/common'], factory);`); - expect(outputSrc).toContain( - `if (typeof exports === 'object')\n` + - ` exports['file'] = factory(require('@angular/core'),require('@angular/common'));`); - expect(outputSrc).toContain(`factory(global.ng.core,global.ng.common);`); - expect(outputSrc).toContain(`function (i0,i1) {\n 'use strict';`); - } - }); - - it('should leave the file unchanged if there are no imports to add', () => { - const {renderer, program} = setup(PROGRAM); - const file = - getSourceFileOrError(program, _('/node_modules/test-package/some/file.js')); - const output = new MagicString(PROGRAM.contents); - const contentsBefore = output.toString(); - - renderer.addImports(output, [], file); - const contentsAfter = output.toString(); - - expect(contentsAfter).toBe(contentsBefore); - }); - - it('should handle the case where not all dependencies are used by the factory', () => { - const PROGRAM = formatFactory({ - name: _('/node_modules/test-package/some/file.js'), - contents: { - preamble: ` - /* A copyright notice */ - /* A copyright notice */`, - moduleName: 'file', - dependencies: ['/local-dep', '@angular/core', 'some-side-effect'], - factoryBody: ` - // Note that someSideEffect is not in the factory function parameter list`, - additionalOptions: {unusedDependencies: new Set(['some-side-effect'])}, - }, - }); - const {renderer, program} = setup(PROGRAM); - const file = - getSourceFileOrError(program, _('/node_modules/test-package/some/file.js')); - const output = new MagicString(PROGRAM.contents); - renderer.addImports( - output, - [ - {specifier: '@angular/core', qualifier: ts.factory.createIdentifier('i0')}, - {specifier: '@angular/common', qualifier: ts.factory.createIdentifier('i1')} - ], - file); - const outputSrc = output.toString(); - - if (wrapperFunctionFormat === WrapperFunctionFormat.Rollup) { - expect(outputSrc).toContain( - `typeof exports === 'object' && typeof module !== 'undefined' ?\n` + - ` factory(require('@angular/core'),require('@angular/common'),exports, require('/local-dep'), require('@angular/core'), require('some-side-effect')) :`); - expect(outputSrc).toContain( - `typeof define === 'function' && define.amd ?\n` + - ` define('file', ['@angular/core','@angular/common','exports', '/local-dep', '@angular/core', 'some-side-effect'], factory) :`); - expect(outputSrc).toContain( - `(factory(global.ng.core,global.ng.common,global.file, global.localDep, global.ng.core, global.someSideEffect));`); - expect(outputSrc).toContain( - `(function (i0,i1,exports, localDep, core) {\n 'use strict';`); - } else { - expect(outputSrc).toContain( - `if (typeof exports === 'object' && typeof module === 'object')\n` + - ` module.exports = factory(require('@angular/core'),require('@angular/common'),require('/local-dep'), require('@angular/core'), require('some-side-effect'));`); - expect(outputSrc).toContain( - `if (typeof define === 'function' && define.amd)\n` + - ` define(['@angular/core','@angular/common','/local-dep', '@angular/core', 'some-side-effect'], factory);`); - expect(outputSrc).toContain( - `if (typeof exports === 'object')\n` + - ` exports['file'] = factory(require('@angular/core'),require('@angular/common'),require('/local-dep'), require('@angular/core'), require('some-side-effect'));`); - expect(outputSrc).toContain( - `root['file'] = factory(global.ng.core,global.ng.common,root['localDep'], root['ng']['core'], root['someSideEffect']);`); - expect(outputSrc).toContain(`function (i0,i1,localDep, core) {\n 'use strict';`); - } - }); - }); - - describe('addExports', () => { - it('should insert the given exports at the end of the source file', () => { - const {importManager, renderer, sourceFile} = setup(PROGRAM); - const output = new MagicString(PROGRAM.contents); - const generateNamedImportSpy = - spyOn(importManager, 'generateNamedImport').and.callThrough(); - renderer.addExports( - output, PROGRAM.name.replace(/\.js$/, ''), - [ - {from: _('/node_modules/test-package/some/a.js'), identifier: 'ComponentA1'}, - {from: _('/node_modules/test-package/some/a.js'), identifier: 'ComponentA2'}, - {from: _('/node_modules/test-package/some/foo/b.js'), identifier: 'ComponentB'}, - {from: PROGRAM.name, identifier: 'TopLevelComponent'}, - ], - importManager, sourceFile); - - expect(output.toString()).toContain(` -exports.A = A; -exports.B = B; -exports.C = C; -exports.NoIife = NoIife; -exports.BadIife = BadIife; -exports.ComponentA1 = i0.ComponentA1; -exports.ComponentA2 = i0.ComponentA2; -exports.ComponentB = i1.ComponentB; -exports.TopLevelComponent = TopLevelComponent; - -})`); - - expect(generateNamedImportSpy).toHaveBeenCalledWith('./a', 'ComponentA1'); - expect(generateNamedImportSpy).toHaveBeenCalledWith('./a', 'ComponentA2'); - expect(generateNamedImportSpy).toHaveBeenCalledWith('./foo/b', 'ComponentB'); - }); - }); - - describe('addConstants', () => { - it('should insert the given constants after imports in the source file', () => { - const {renderer, program} = setup(PROGRAM); - const file = - getSourceFileOrError(program, _('/node_modules/test-package/some/file.js')); - const output = new MagicString(PROGRAM.contents); - renderer.addConstants(output, 'var x = 3;', file); - expect(output.toString()).toContain(`someSideEffect, localDep, core) { -${' '} -var x = 3; -'use strict'; - -var A = (function() {`); - }); - - it('should insert constants after inserted imports', - () => { - // This test (from ESM5) is not needed as constants go in the body - // of the UMD IIFE, so cannot come before imports. - }); - }); - - describe('addDefinitions', () => { - it('should insert the definitions directly before the return statement of the class IIFE', - () => { - const {renderer, decorationAnalyses, sourceFile} = setup(PROGRAM); - const output = new MagicString(PROGRAM.contents); - const compiledClass = - decorationAnalyses.get(sourceFile)!.compiledClasses.find(c => c.name === 'A')!; - renderer.addDefinitions(output, compiledClass, 'SOME DEFINITION TEXT'); - expect(output.toString()).toContain(` - A.prototype.ngDoCheck = function() { - // - }; -SOME DEFINITION TEXT - return A; -`); - }); - - it('should error if the compiledClass is not valid', () => { - const {renderer, sourceFile, program} = setup(PROGRAM); - const output = new MagicString(PROGRAM.contents); - - const noIifeDeclaration = getDeclaration( - program, absoluteFromSourceFile(sourceFile), 'NoIife', ts.isFunctionDeclaration); - const mockNoIifeClass: any = {declaration: noIifeDeclaration, name: 'NoIife'}; - expect(() => renderer.addDefinitions(output, mockNoIifeClass, 'SOME DEFINITION TEXT')) - .toThrowError( - `Compiled class "NoIife" in "${ - _('/node_modules/test-package/some/file.js')}" does not have a valid syntax.\n` + - `Expected an ES5 IIFE wrapped function. But got:\n` + - `function NoIife() {}`); - - const badIifeDeclaration = getDeclaration( - program, absoluteFromSourceFile(sourceFile), 'BadIife', ts.isVariableDeclaration); - const mockBadIifeClass: any = {declaration: badIifeDeclaration, name: 'BadIife'}; - expect(() => renderer.addDefinitions(output, mockBadIifeClass, 'SOME DEFINITION TEXT')) - .toThrowError( - `Compiled class wrapper IIFE does not have a return statement: BadIife in ${ - _('/node_modules/test-package/some/file.js')}`); - }); - }); - - describe('addAdjacentStatements', () => { - const contents: TestFileSpec['contents'] = { - moduleName: 'file', - dependencies: ['/tslib', '@angular/core'], - factoryBody: `\n` + - ` var SomeDirective = /** @class **/ (function () {\n` + - ` function SomeDirective(zone, cons) {}\n` + - ` SomeDirective.prototype.method = function() {}\n` + - ` SomeDirective.decorators = [\n` + - ` { type: core.Directive, args: [{ selector: '[a]' }] },\n` + - ` { type: OtherA }\n` + - ` ];\n` + - ` SomeDirective.ctorParameters = function() { return [\n` + - ` { type: core.NgZone },\n` + - ` { type: core.Console }\n` + - ` ]; };\n` + - ` return SomeDirective;\n` + - ` }());\n` + - ` exports.SomeDirective = SomeDirective;\n`, - }; - - it('should insert the statements after all the static methods of the class', () => { - const program = - formatFactory({name: _('/node_modules/test-package/some/file.js'), contents}); - const {renderer, decorationAnalyses, sourceFile} = setup(program); - const output = new MagicString(program.contents); - const compiledClass = decorationAnalyses.get(sourceFile)!.compiledClasses.find( - c => c.name === 'SomeDirective')!; - renderer.addAdjacentStatements(output, compiledClass, 'SOME STATEMENTS'); - expect(output.toString()) - .toContain( - ` SomeDirective.ctorParameters = function() { return [\n` + - ` { type: core.NgZone },\n` + - ` { type: core.Console }\n` + - ` ]; };\n` + - `SOME STATEMENTS\n` + - ` return SomeDirective;\n`); - }); - - it('should insert the statements after any definitions', () => { - const program = - formatFactory({name: _('/node_modules/test-package/some/file.js'), contents}); - const {renderer, decorationAnalyses, sourceFile} = setup(program); - const output = new MagicString(program.contents); - const compiledClass = decorationAnalyses.get(sourceFile)!.compiledClasses.find( - c => c.name === 'SomeDirective')!; - renderer.addDefinitions(output, compiledClass, 'SOME DEFINITIONS'); - renderer.addAdjacentStatements(output, compiledClass, 'SOME STATEMENTS'); - const definitionsPosition = output.toString().indexOf('SOME DEFINITIONS'); - const statementsPosition = output.toString().indexOf('SOME STATEMENTS'); - expect(definitionsPosition).not.toEqual(-1, 'definitions should exist'); - expect(statementsPosition).not.toEqual(-1, 'statements should exist'); - expect(statementsPosition).toBeGreaterThan(definitionsPosition); - }); - }); - - describe('removeDecorators', () => { - it('should delete the decorator (and following comma) that was matched in the analysis', - () => { - const {renderer, decorationAnalyses, sourceFile} = setup(PROGRAM); - const output = new MagicString(PROGRAM.contents); - const compiledClass = - decorationAnalyses.get(sourceFile)!.compiledClasses.find(c => c.name === 'A')!; - const decorator = compiledClass.decorators![0]; - const decoratorsToRemove = new Map(); - decoratorsToRemove.set(decorator.node!.parent!, [decorator.node!]); - renderer.removeDecorators(output, decoratorsToRemove); - expect(output.toString()) - .not.toContain(`{ type: core.Directive, args: [{ selector: '[a]' }] },`); - expect(output.toString()).toContain(`{ type: OtherA }`); - expect(output.toString()) - .toContain(`{ type: core.Directive, args: [{ selector: '[b]' }] }`); - expect(output.toString()).toContain(`{ type: OtherB }`); - expect(output.toString()) - .toContain(`{ type: core.Directive, args: [{ selector: '[c]' }] }`); - }); - - - it('should delete the decorator (but cope with no trailing comma) that was matched in the analysis', - () => { - const {renderer, decorationAnalyses, sourceFile} = setup(PROGRAM); - const output = new MagicString(PROGRAM.contents); - const compiledClass = - decorationAnalyses.get(sourceFile)!.compiledClasses.find(c => c.name === 'B')!; - const decorator = compiledClass.decorators![0]; - const decoratorsToRemove = new Map(); - decoratorsToRemove.set(decorator.node!.parent!, [decorator.node!]); - renderer.removeDecorators(output, decoratorsToRemove); - expect(output.toString()) - .toContain(`{ type: core.Directive, args: [{ selector: '[a]' }] },`); - expect(output.toString()).toContain(`{ type: OtherA }`); - expect(output.toString()) - .not.toContain(`{ type: core.Directive, args: [{ selector: '[b]' }] }`); - expect(output.toString()).toContain(`{ type: OtherB }`); - expect(output.toString()) - .toContain(`{ type: core.Directive, args: [{ selector: '[c]' }] }`); - }); - - - it('should delete the decorator (and its container if there are not other decorators left) that was matched in the analysis', - () => { - const {renderer, decorationAnalyses, sourceFile} = setup(PROGRAM); - const output = new MagicString(PROGRAM.contents); - const compiledClass = - decorationAnalyses.get(sourceFile)!.compiledClasses.find(c => c.name === 'C')!; - const decorator = compiledClass.decorators![0]; - const decoratorsToRemove = new Map(); - decoratorsToRemove.set(decorator.node!.parent!, [decorator.node!]); - renderer.removeDecorators(output, decoratorsToRemove); - renderer.addDefinitions(output, compiledClass, 'SOME DEFINITION TEXT'); - expect(output.toString()) - .toContain(`{ type: core.Directive, args: [{ selector: '[a]' }] },`); - expect(output.toString()).toContain(`{ type: OtherA }`); - expect(output.toString()) - .toContain(`{ type: core.Directive, args: [{ selector: '[b]' }] }`); - expect(output.toString()).toContain(`{ type: OtherB }`); - expect(output.toString()).not.toContain(`C.decorators`); - }); - }); - - describe('[__decorate declarations]', () => { - it('should delete the decorator (and following comma) that was matched in the analysis', - () => { - const {renderer, decorationAnalyses, sourceFile} = setup(PROGRAM_DECORATE_HELPER); - const output = new MagicString(PROGRAM_DECORATE_HELPER.contents); - const compiledClass = - decorationAnalyses.get(sourceFile)!.compiledClasses.find(c => c.name === 'A')!; - const decorator = compiledClass.decorators!.find(d => d.name === 'Directive')!; - const decoratorsToRemove = new Map(); - decoratorsToRemove.set(decorator.node!.parent!, [decorator.node!]); - renderer.removeDecorators(output, decoratorsToRemove); - expect(output.toString()).not.toContain(`core.Directive({ selector: '[a]' }),`); - expect(output.toString()).toContain(`OtherA()`); - expect(output.toString()).toContain(`core.Directive({ selector: '[b]' })`); - expect(output.toString()).toContain(`OtherB()`); - expect(output.toString()).toContain(`core.Directive({ selector: '[c]' })`); - }); - - it('should delete the decorator (but cope with no trailing comma) that was matched in the analysis', - () => { - const {renderer, decorationAnalyses, sourceFile} = setup(PROGRAM_DECORATE_HELPER); - const output = new MagicString(PROGRAM_DECORATE_HELPER.contents); - const compiledClass = - decorationAnalyses.get(sourceFile)!.compiledClasses.find(c => c.name === 'B')!; - const decorator = compiledClass.decorators!.find(d => d.name === 'Directive')!; - const decoratorsToRemove = new Map(); - decoratorsToRemove.set(decorator.node!.parent!, [decorator.node!]); - renderer.removeDecorators(output, decoratorsToRemove); - expect(output.toString()).toContain(`core.Directive({ selector: '[a]' }),`); - expect(output.toString()).toContain(`OtherA()`); - expect(output.toString()).not.toContain(`core.Directive({ selector: '[b]' })`); - expect(output.toString()).toContain(`OtherB()`); - expect(output.toString()).toContain(`core.Directive({ selector: '[c]' })`); - }); - - - it('should delete the decorator (and its container if there are no other decorators left) that was matched in the analysis', - () => { - const {renderer, decorationAnalyses, sourceFile} = setup(PROGRAM_DECORATE_HELPER); - const output = new MagicString(PROGRAM_DECORATE_HELPER.contents); - const compiledClass = - decorationAnalyses.get(sourceFile)!.compiledClasses.find(c => c.name === 'C')!; - const decorator = compiledClass.decorators!.find(d => d.name === 'Directive')!; - const decoratorsToRemove = new Map(); - decoratorsToRemove.set(decorator.node!.parent!, [decorator.node!]); - renderer.removeDecorators(output, decoratorsToRemove); - expect(output.toString()).toContain(`core.Directive({ selector: '[a]' }),`); - expect(output.toString()).toContain(`OtherA()`); - expect(output.toString()).toContain(`core.Directive({ selector: '[b]' })`); - expect(output.toString()).toContain(`OtherB()`); - expect(output.toString()).not.toContain(`core.Directive({ selector: '[c]' })`); - expect(output.toString()).not.toContain(`C = tslib_1.__decorate([`); - expect(output.toString()).toContain(`function C() {\n }\n return C;`); - }); - }); - - describe('printStatement', () => { - it('should transpile code to ES5', () => { - const {renderer, sourceFile, importManager} = setup(PROGRAM); - - const stmt1 = new DeclareVarStmt('foo', new LiteralExpr(42), null, StmtModifier.Static); - const stmt2 = new DeclareVarStmt('bar', new LiteralExpr(true)); - const stmt3 = new DeclareVarStmt('baz', new LiteralExpr('qux')); - - expect(renderer.printStatement(stmt1, sourceFile, importManager)).toBe('var foo = 42;'); - expect(renderer.printStatement(stmt2, sourceFile, importManager)) - .toBe('var bar = true;'); - expect(renderer.printStatement(stmt3, sourceFile, importManager)) - .toBe('var baz = "qux";'); - }); - }); - })); -}); diff --git a/packages/compiler-cli/ngcc/test/utils_spec.ts b/packages/compiler-cli/ngcc/test/utils_spec.ts deleted file mode 100644 index 932a15cff14..00000000000 --- a/packages/compiler-cli/ngcc/test/utils_spec.ts +++ /dev/null @@ -1,389 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ - -import ts from 'typescript'; - -import {absoluteFrom as _abs, FileSystem, getFileSystem} from '../../src/ngtsc/file_system'; -import {runInEachFileSystem} from '../../src/ngtsc/file_system/testing'; -import {KnownDeclaration} from '../../src/ngtsc/reflection'; -import {loadTestFiles} from '../../src/ngtsc/testing'; -import {FactoryMap, getTsHelperFnFromDeclaration, getTsHelperFnFromIdentifier, isRelativePath, loadJson, loadSecondaryEntryPointInfoForApfV14, stripExtension} from '../src/utils'; - -describe('FactoryMap', () => { - it('should return an existing value', () => { - const factoryFnSpy = jasmine.createSpy('factory'); - const factoryMap = new FactoryMap(factoryFnSpy, [['k1', 'v1'], ['k2', 'v2']]); - - expect(factoryMap.get('k1')).toBe('v1'); - expect(factoryMap.get('k2')).toBe('v2'); - expect(factoryFnSpy).not.toHaveBeenCalled(); - }); - - it('should not treat falsy values as missing', () => { - const factoryFnSpy = jasmine.createSpy('factory').and.returnValue('never gonna happen'); - const factoryMap = new FactoryMap(factoryFnSpy, [ - ['k1', ''], - ['k2', 0], - ['k3', false], - ['k4', null], - ['k5', undefined], - ]); - - expect(factoryMap.get('k1')).toBe(''); - expect(factoryMap.get('k2')).toBe(0); - expect(factoryMap.get('k3')).toBe(false); - expect(factoryMap.get('k4')).toBe(null); - expect(factoryMap.get('k5')).toBe(undefined); - expect(factoryFnSpy).not.toHaveBeenCalled(); - }); - - it('should create, store and return the value if it does not exist', () => { - const factoryFnSpy = jasmine.createSpy('factory').and.returnValues('v3', 'never gonna happen'); - const factoryMap = new FactoryMap(factoryFnSpy, [['k1', 'v1'], ['k2', 'v2']]); - - expect(factoryMap.get('k3')).toBe('v3'); - expect(factoryFnSpy).toHaveBeenCalledTimes(1); - - factoryFnSpy.calls.reset(); - - expect(factoryMap.get('k3')).toBe('v3'); - expect(factoryFnSpy).not.toHaveBeenCalled(); - }); -}); - -describe('getTsHelperFnFromDeclaration()', () => { - const createFunctionDeclaration = (fnName?: string) => ts.factory.createFunctionDeclaration( - undefined, undefined, fnName, undefined, [], undefined, undefined); - const createVariableDeclaration = (varName: string) => - ts.factory.createVariableDeclaration(varName, undefined, undefined, undefined); - - it('should recognize the `__assign` helper as function declaration', () => { - const decl1 = createFunctionDeclaration('__assign'); - const decl2 = createFunctionDeclaration('__assign$42'); - - expect(getTsHelperFnFromDeclaration(decl1)).toBe(KnownDeclaration.TsHelperAssign); - expect(getTsHelperFnFromDeclaration(decl2)).toBe(KnownDeclaration.TsHelperAssign); - }); - - it('should recognize the `__assign` helper as variable declaration', () => { - const decl1 = createVariableDeclaration('__assign'); - const decl2 = createVariableDeclaration('__assign$42'); - - expect(getTsHelperFnFromDeclaration(decl1)).toBe(KnownDeclaration.TsHelperAssign); - expect(getTsHelperFnFromDeclaration(decl2)).toBe(KnownDeclaration.TsHelperAssign); - }); - - it('should recognize the `__spread` helper as function declaration', () => { - const decl1 = createFunctionDeclaration('__spread'); - const decl2 = createFunctionDeclaration('__spread$42'); - - expect(getTsHelperFnFromDeclaration(decl1)).toBe(KnownDeclaration.TsHelperSpread); - expect(getTsHelperFnFromDeclaration(decl2)).toBe(KnownDeclaration.TsHelperSpread); - }); - - it('should recognize the `__spread` helper as variable declaration', () => { - const decl1 = createVariableDeclaration('__spread'); - const decl2 = createVariableDeclaration('__spread$42'); - - expect(getTsHelperFnFromDeclaration(decl1)).toBe(KnownDeclaration.TsHelperSpread); - expect(getTsHelperFnFromDeclaration(decl2)).toBe(KnownDeclaration.TsHelperSpread); - }); - - it('should recognize the `__spreadArrays` helper as function declaration', () => { - const decl1 = createFunctionDeclaration('__spreadArrays'); - const decl2 = createFunctionDeclaration('__spreadArrays$42'); - - expect(getTsHelperFnFromDeclaration(decl1)).toBe(KnownDeclaration.TsHelperSpreadArrays); - expect(getTsHelperFnFromDeclaration(decl2)).toBe(KnownDeclaration.TsHelperSpreadArrays); - }); - - it('should recognize the `__spreadArrays` helper as variable declaration', () => { - const decl1 = createVariableDeclaration('__spreadArrays'); - const decl2 = createVariableDeclaration('__spreadArrays$42'); - - expect(getTsHelperFnFromDeclaration(decl1)).toBe(KnownDeclaration.TsHelperSpreadArrays); - expect(getTsHelperFnFromDeclaration(decl2)).toBe(KnownDeclaration.TsHelperSpreadArrays); - }); - - it('should recognize the `__spreadArray` helper as function declaration', () => { - const decl1 = createFunctionDeclaration('__spreadArray'); - const decl2 = createFunctionDeclaration('__spreadArray$42'); - - expect(getTsHelperFnFromDeclaration(decl1)).toBe(KnownDeclaration.TsHelperSpreadArray); - expect(getTsHelperFnFromDeclaration(decl2)).toBe(KnownDeclaration.TsHelperSpreadArray); - }); - - it('should recognize the `__spreadArray` helper as variable declaration', () => { - const decl1 = createVariableDeclaration('__spreadArray'); - const decl2 = createVariableDeclaration('__spreadArray$42'); - - expect(getTsHelperFnFromDeclaration(decl1)).toBe(KnownDeclaration.TsHelperSpreadArray); - expect(getTsHelperFnFromDeclaration(decl2)).toBe(KnownDeclaration.TsHelperSpreadArray); - }); - - it('should return null for unrecognized helpers', () => { - const decl1 = createFunctionDeclaration('__foo'); - const decl2 = createVariableDeclaration('spread'); - const decl3 = createFunctionDeclaration('spread$42'); - - expect(getTsHelperFnFromDeclaration(decl1)).toBe(null); - expect(getTsHelperFnFromDeclaration(decl2)).toBe(null); - expect(getTsHelperFnFromDeclaration(decl3)).toBe(null); - }); - - it('should return null for unnamed declarations', () => { - const unnamledDecl = createFunctionDeclaration(undefined); - - expect(getTsHelperFnFromDeclaration(unnamledDecl)).toBe(null); - }); - - it('should return null for non-function/variable declarations', () => { - const classDecl = ts.factory.createClassDeclaration( - undefined, undefined, '__assign', undefined, undefined, []); - - expect(classDecl.name!.text).toBe('__assign'); - expect(getTsHelperFnFromDeclaration(classDecl)).toBe(null); - }); -}); - -describe('getTsHelperFnFromIdentifier()', () => { - it('should recognize the `__assign` helper', () => { - const id1 = ts.factory.createIdentifier('__assign'); - const id2 = ts.factory.createIdentifier('__assign$42'); - - expect(getTsHelperFnFromIdentifier(id1)).toBe(KnownDeclaration.TsHelperAssign); - expect(getTsHelperFnFromIdentifier(id2)).toBe(KnownDeclaration.TsHelperAssign); - }); - - it('should recognize the `__spread` helper', () => { - const id1 = ts.factory.createIdentifier('__spread'); - const id2 = ts.factory.createIdentifier('__spread$42'); - - expect(getTsHelperFnFromIdentifier(id1)).toBe(KnownDeclaration.TsHelperSpread); - expect(getTsHelperFnFromIdentifier(id2)).toBe(KnownDeclaration.TsHelperSpread); - }); - - it('should recognize the `__spreadArrays` helper', () => { - const id1 = ts.factory.createIdentifier('__spreadArrays'); - const id2 = ts.factory.createIdentifier('__spreadArrays$42'); - - expect(getTsHelperFnFromIdentifier(id1)).toBe(KnownDeclaration.TsHelperSpreadArrays); - expect(getTsHelperFnFromIdentifier(id2)).toBe(KnownDeclaration.TsHelperSpreadArrays); - }); - - it('should recognize the `__spreadArray` helper', () => { - const id1 = ts.factory.createIdentifier('__spreadArray'); - const id2 = ts.factory.createIdentifier('__spreadArray$42'); - - expect(getTsHelperFnFromIdentifier(id1)).toBe(KnownDeclaration.TsHelperSpreadArray); - expect(getTsHelperFnFromIdentifier(id2)).toBe(KnownDeclaration.TsHelperSpreadArray); - }); - - it('should return null for unrecognized helpers', () => { - const id1 = ts.factory.createIdentifier('__foo'); - const id2 = ts.factory.createIdentifier('spread'); - const id3 = ts.factory.createIdentifier('spread$42'); - - expect(getTsHelperFnFromIdentifier(id1)).toBe(null); - expect(getTsHelperFnFromIdentifier(id2)).toBe(null); - expect(getTsHelperFnFromIdentifier(id3)).toBe(null); - }); -}); - -runInEachFileSystem(() => { - describe('isRelativePath()', () => { - it('should return true for relative paths', () => { - expect(isRelativePath('.')).toBe(true); - expect(isRelativePath('..')).toBe(true); - expect(isRelativePath('./')).toBe(true); - expect(isRelativePath('.\\')).toBe(true); - expect(isRelativePath('../')).toBe(true); - expect(isRelativePath('..\\')).toBe(true); - expect(isRelativePath('./abc/xyz')).toBe(true); - expect(isRelativePath('.\\abc\\xyz')).toBe(true); - expect(isRelativePath('../abc/xyz')).toBe(true); - expect(isRelativePath('..\\abc\\xyz')).toBe(true); - }); - - it('should return true for absolute paths', () => { - expect(isRelativePath(_abs('/'))).toBe(true); - expect(isRelativePath(_abs('/abc/xyz'))).toBe(true); - }); - - it('should return false for other paths', () => { - expect(isRelativePath('abc')).toBe(false); - expect(isRelativePath('abc/xyz')).toBe(false); - expect(isRelativePath('.abc')).toBe(false); - expect(isRelativePath('..abc')).toBe(false); - expect(isRelativePath('@abc')).toBe(false); - expect(isRelativePath('.abc/xyz')).toBe(false); - expect(isRelativePath('..abc/xyz')).toBe(false); - expect(isRelativePath('@abc/xyz')).toBe(false); - }); - }); -}); - -describe('stripExtension()', () => { - it('should strip the extension from a file name', () => { - expect(stripExtension('foo.ts')).toBe('foo'); - expect(stripExtension('/foo/bar.ts')).toBe('/foo/bar'); - expect(stripExtension('/foo/bar.d.ts')).toBe('/foo/bar'); - }); - - it('should do nothing if there is no extension in a file name', () => { - expect(stripExtension('foo')).toBe('foo'); - expect(stripExtension('/foo/bar')).toBe('/foo/bar'); - expect(stripExtension('/fo-o/b_ar')).toBe('/fo-o/b_ar'); - }); -}); - -runInEachFileSystem(() => { - let fs: FileSystem; - - beforeEach(() => fs = getFileSystem()); - - describe('loadJson()', () => { - it('should load a `.json` file', () => { - const jsonData = {foo: 'yes', bar: 'sure'}; - loadTestFiles([ - { - name: _abs('/foo/bar.json'), - contents: `${JSON.stringify(jsonData)}\n`, - }, - ]); - - expect(loadJson(fs, _abs('/foo/bar.json'))).toEqual(jsonData); - }); - - it('should return `null` if it fails to read the `.json` file', () => { - expect(loadJson(fs, _abs('/does/not/exist.json'))).toBeNull(); - }); - - it('should return `null` if it fails to parse the `.json` file', () => { - loadTestFiles([ - { - name: _abs('/foo/bar.txt'), - contents: '{This is not valid JSON.}', - }, - ]); - - expect(loadJson(fs, _abs('/foo/bar.json'))).toBeNull(); - }); - }); -}); - -runInEachFileSystem(() => { - let fs: FileSystem; - - beforeEach(() => fs = getFileSystem()); - - describe('loadSecondaryEntryPointInfoForApfV14()', () => { - it('should return `null` if the primary `package.json` failed to be loaded', () => { - expect(loadSecondaryEntryPointInfoForApfV14(fs, null, _abs('/foo'), _abs('/foo/bar'))) - .toBe(null); - }); - - it('should return `null` if the primary `package.json` has no `exports` property', () => { - const primaryPackageJson = { - name: 'some-package', - version: '1.33.7', - main: './index.js', - }; - - expect(loadSecondaryEntryPointInfoForApfV14( - fs, primaryPackageJson, _abs('/foo'), _abs('/foo/bar'))) - .toBe(null); - }); - - it('should return `null` if the primary `package.json`\'s `exports` property is a string', - () => { - const primaryPackageJson = { - name: 'some-package', - exports: './index.js', - }; - - expect(loadSecondaryEntryPointInfoForApfV14( - fs, primaryPackageJson, _abs('/foo'), _abs('/foo/bar'))) - .toBe(null); - }); - - it('should return `null` if the primary `package.json`\'s `exports` property is a string array', - () => { - const primaryPackageJson = { - name: 'some-package', - exports: [ - './foo.js', - './bar.js', - ], - }; - - expect(loadSecondaryEntryPointInfoForApfV14( - fs, primaryPackageJson, _abs('/foo'), _abs('/foo/bar'))) - .toBe(null); - }); - - it('should return `null` if there is no info for the specified entry-point', () => { - const primaryPackageJson = { - name: 'some-package', - exports: { - './baz': { - main: './baz/index.js', - }, - }, - }; - - expect(loadSecondaryEntryPointInfoForApfV14( - fs, primaryPackageJson, _abs('/foo'), _abs('/foo/bar'))) - .toBe(null); - }); - - it('should return `null` if the entry-point info is a string', () => { - const primaryPackageJson = { - name: 'some-package', - exports: { - './bar': './bar/index.js', - }, - }; - - expect(loadSecondaryEntryPointInfoForApfV14( - fs, primaryPackageJson, _abs('/foo'), _abs('/foo/bar'))) - .toBe(null); - }); - - it('should return `null` if the entry-point info is a string array', () => { - const primaryPackageJson = { - name: 'some-package', - exports: { - './bar': [ - './bar/a.js', - './bar/b.js', - ], - }, - }; - - expect(loadSecondaryEntryPointInfoForApfV14( - fs, primaryPackageJson, _abs('/foo'), _abs('/foo/bar'))) - .toBe(null); - }); - - it('should return the entry-point info if it exists and is an object', () => { - const primaryPackageJson = { - name: 'some-package', - exports: { - './bar': { - main: './bar/index.js', - }, - }, - }; - - expect(loadSecondaryEntryPointInfoForApfV14( - fs, primaryPackageJson, _abs('/foo'), _abs('/foo/bar'))) - .toEqual({main: './bar/index.js'}); - }); - }); -}); diff --git a/packages/compiler-cli/ngcc/test/writing/cleaning/cleaning_strategies_spec.ts b/packages/compiler-cli/ngcc/test/writing/cleaning/cleaning_strategies_spec.ts deleted file mode 100644 index 8964593af58..00000000000 --- a/packages/compiler-cli/ngcc/test/writing/cleaning/cleaning_strategies_spec.ts +++ /dev/null @@ -1,239 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import {absoluteFrom, AbsoluteFsPath, FileSystem, getFileSystem, PathSegment} from '../../../../src/ngtsc/file_system'; -import {runInEachFileSystem} from '../../../../src/ngtsc/file_system/testing'; -import {EntryPointPackageJson} from '../../../src/packages/entry_point'; -import {BackupFileCleaner, NgccDirectoryCleaner, PackageJsonCleaner} from '../../../src/writing/cleaning/cleaning_strategies'; - -runInEachFileSystem(() => { - describe('cleaning strategies', () => { - let fs: FileSystem; - let _abs: typeof absoluteFrom; - - beforeEach(() => { - fs = getFileSystem(); - _abs = absoluteFrom; - }); - - describe('PackageJsonCleaner', () => { - let packageJsonPath: AbsoluteFsPath; - beforeEach(() => { - packageJsonPath = _abs('/node_modules/pkg/package.json'); - }); - - describe('canClean()', () => { - it('should return true if the basename is package.json', () => { - const strategy = new PackageJsonCleaner(fs); - expect(strategy.canClean(packageJsonPath, fs.basename(packageJsonPath))).toBe(true); - }); - - it('should return false if the basename is not package.json', () => { - const filePath = _abs('/node_modules/pkg/index.js'); - const fileName = fs.basename(filePath); - const strategy = new PackageJsonCleaner(fs); - expect(strategy.canClean(filePath, fileName)).toBe(false); - }); - }); - - describe('clean()', () => { - it('should not touch the file if there is no build marker', () => { - const strategy = new PackageJsonCleaner(fs); - const packageJson: EntryPointPackageJson = {name: 'test-package'}; - fs.ensureDir(fs.dirname(packageJsonPath)); - fs.writeFile(packageJsonPath, JSON.stringify(packageJson)); - strategy.clean(packageJsonPath, fs.basename(packageJsonPath)); - const newPackageJson = JSON.parse(fs.readFile(packageJsonPath)) as EntryPointPackageJson; - expect(newPackageJson).toEqual({name: 'test-package'}); - }); - - it('should remove the processed marker', () => { - const strategy = new PackageJsonCleaner(fs); - const packageJson: EntryPointPackageJson = { - name: 'test-package', - __processed_by_ivy_ngcc__: {'fesm2015': '8.0.0'} - }; - fs.ensureDir(fs.dirname(packageJsonPath)); - fs.writeFile(packageJsonPath, JSON.stringify(packageJson)); - strategy.clean(packageJsonPath, fs.basename(packageJsonPath)); - const newPackageJson = JSON.parse(fs.readFile(packageJsonPath)) as EntryPointPackageJson; - expect(newPackageJson).toEqual({name: 'test-package'}); - }); - - it('should remove the new entry points', () => { - const strategy = new PackageJsonCleaner(fs); - const packageJson: EntryPointPackageJson = { - name: 'test-package', - __processed_by_ivy_ngcc__: {'fesm2015': '8.0.0'} - }; - fs.ensureDir(fs.dirname(packageJsonPath)); - fs.writeFile(packageJsonPath, JSON.stringify(packageJson)); - strategy.clean(packageJsonPath, fs.basename(packageJsonPath)); - const newPackageJson = JSON.parse(fs.readFile(packageJsonPath)) as EntryPointPackageJson; - expect(newPackageJson).toEqual({name: 'test-package'}); - }); - - it('should remove the prepublish script if there was a processed marker', () => { - const strategy = new PackageJsonCleaner(fs); - const packageJson: EntryPointPackageJson = { - name: 'test-package', - __processed_by_ivy_ngcc__: {'fesm2015': '8.0.0'}, - scripts: {prepublishOnly: 'added by ngcc', test: 'do testing'}, - }; - fs.ensureDir(fs.dirname(packageJsonPath)); - fs.writeFile(packageJsonPath, JSON.stringify(packageJson)); - strategy.clean(packageJsonPath, fs.basename(packageJsonPath)); - const newPackageJson = JSON.parse(fs.readFile(packageJsonPath)) as EntryPointPackageJson; - expect(newPackageJson).toEqual({ - name: 'test-package', - scripts: {test: 'do testing'}, - }); - }); - - it('should revert and remove the backup for the prepublish script if there was a processed marker', - () => { - const strategy = new PackageJsonCleaner(fs); - const packageJson: EntryPointPackageJson = { - name: 'test-package', - __processed_by_ivy_ngcc__: {'fesm2015': '8.0.0'}, - scripts: { - prepublishOnly: 'added by ngcc', - prepublishOnly__ivy_ngcc_bak: 'original', - test: 'do testing' - }, - }; - fs.ensureDir(fs.dirname(packageJsonPath)); - fs.writeFile(packageJsonPath, JSON.stringify(packageJson)); - strategy.clean(packageJsonPath, fs.basename(packageJsonPath)); - const newPackageJson = - JSON.parse(fs.readFile(packageJsonPath)) as EntryPointPackageJson; - expect(newPackageJson).toEqual({ - name: 'test-package', - scripts: {prepublishOnly: 'original', test: 'do testing'}, - }); - }); - - it('should not touch the scripts if there was not processed marker', () => { - const strategy = new PackageJsonCleaner(fs); - const packageJson: EntryPointPackageJson = { - name: 'test-package', - scripts: { - prepublishOnly: 'added by ngcc', - prepublishOnly__ivy_ngcc_bak: 'original', - test: 'do testing' - }, - }; - fs.ensureDir(fs.dirname(packageJsonPath)); - fs.writeFile(packageJsonPath, JSON.stringify(packageJson)); - strategy.clean(packageJsonPath, fs.basename(packageJsonPath)); - const newPackageJson = JSON.parse(fs.readFile(packageJsonPath)) as EntryPointPackageJson; - expect(newPackageJson).toEqual({ - name: 'test-package', - scripts: { - prepublishOnly: 'added by ngcc', - prepublishOnly__ivy_ngcc_bak: 'original', - test: 'do testing' - } - }); - }); - }); - }); - - describe('BackupFileCleaner', () => { - let filePath: AbsoluteFsPath; - let backupFilePath: AbsoluteFsPath; - beforeEach(() => { - filePath = _abs('/node_modules/pkg/index.js'); - backupFilePath = _abs('/node_modules/pkg/index.js.__ivy_ngcc_bak'); - }); - - describe('canClean()', () => { - it('should return true if the file name ends in .__ivy_ngcc_bak and the processed file exists', - () => { - const strategy = new BackupFileCleaner(fs); - fs.ensureDir(fs.dirname(filePath)); - fs.writeFile(filePath, 'processed file'); - fs.writeFile(backupFilePath, 'original file'); - expect(strategy.canClean(backupFilePath, fs.basename(backupFilePath))).toBe(true); - }); - - it('should return false if the file does not end in .__ivy_ngcc_bak', () => { - const strategy = new BackupFileCleaner(fs); - fs.ensureDir(fs.dirname(filePath)); - fs.writeFile(filePath, 'processed file'); - fs.writeFile(backupFilePath, 'original file'); - expect(strategy.canClean(filePath, fs.basename(filePath))).toBe(false); - }); - - it('should return false if the file ends in .__ivy_ngcc_bak but the processed file does not exist', - () => { - const strategy = new BackupFileCleaner(fs); - fs.ensureDir(fs.dirname(filePath)); - fs.writeFile(backupFilePath, 'original file'); - expect(strategy.canClean(backupFilePath, fs.basename(backupFilePath))).toBe(false); - }); - }); - - describe('clean()', () => { - it('should move the backup file back to its original file path', () => { - const strategy = new BackupFileCleaner(fs); - fs.ensureDir(fs.dirname(filePath)); - fs.writeFile(filePath, 'processed file'); - fs.writeFile(backupFilePath, 'original file'); - strategy.clean(backupFilePath, fs.basename(backupFilePath)); - expect(fs.exists(backupFilePath)).toBe(false); - expect(fs.readFile(filePath)).toEqual('original file'); - }); - }); - }); - - describe('NgccDirectoryCleaner', () => { - let ivyDirectory: AbsoluteFsPath; - beforeEach(() => { - ivyDirectory = _abs('/node_modules/pkg/__ivy_ngcc__'); - }); - - describe('canClean()', () => { - it('should return true if the path is a directory and is called __ivy_ngcc__', () => { - const strategy = new NgccDirectoryCleaner(fs); - fs.ensureDir(ivyDirectory); - expect(strategy.canClean(ivyDirectory, fs.basename(ivyDirectory))).toBe(true); - }); - - it('should return false if the path is a directory and not called __ivy_ngcc__', () => { - const strategy = new NgccDirectoryCleaner(fs); - const filePath = _abs('/node_modules/pkg/other'); - fs.ensureDir(ivyDirectory); - expect(strategy.canClean(filePath, fs.basename(filePath))).toBe(false); - }); - - it('should return false if the path is called __ivy_ngcc__ but does not exist', () => { - const strategy = new NgccDirectoryCleaner(fs); - expect(strategy.canClean(ivyDirectory, fs.basename(ivyDirectory))).toBe(false); - }); - - it('should return false if the path is called __ivy_ngcc__ but is not a directory', () => { - const strategy = new NgccDirectoryCleaner(fs); - fs.ensureDir(fs.dirname(ivyDirectory)); - fs.writeFile(ivyDirectory, 'some contents'); - expect(strategy.canClean(ivyDirectory, fs.basename(ivyDirectory))).toBe(false); - }); - }); - - describe('clean()', () => { - it('should remove the __ivy_ngcc__ directory', () => { - const strategy = new NgccDirectoryCleaner(fs); - fs.ensureDir(ivyDirectory); - fs.ensureDir(fs.resolve(ivyDirectory, 'subfolder')); - fs.writeFile(fs.resolve(ivyDirectory, 'subfolder', 'file.txt'), 'file contents'); - strategy.clean(ivyDirectory, fs.basename(ivyDirectory)); - expect(fs.exists(ivyDirectory)).toBe(false); - }); - }); - }); - }); -}); diff --git a/packages/compiler-cli/ngcc/test/writing/cleaning/package_cleaner_spec.ts b/packages/compiler-cli/ngcc/test/writing/cleaning/package_cleaner_spec.ts deleted file mode 100644 index 8c64aee814a..00000000000 --- a/packages/compiler-cli/ngcc/test/writing/cleaning/package_cleaner_spec.ts +++ /dev/null @@ -1,86 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import {absoluteFrom, AbsoluteFsPath, FileSystem, getFileSystem, PathSegment} from '@angular/compiler-cli/src/ngtsc/file_system'; - -import {runInEachFileSystem} from '../../../../src/ngtsc/file_system/testing'; -import {CleaningStrategy} from '../../../src/writing/cleaning/cleaning_strategies'; -import {PackageCleaner} from '../../../src/writing/cleaning/package_cleaner'; - -runInEachFileSystem(() => { - describe('PackageCleaner', () => { - let fs: FileSystem; - let _: typeof absoluteFrom; - beforeEach(() => { - fs = getFileSystem(); - _ = absoluteFrom; - }); - - describe('clean()', () => { - it('should call `canClean()` on each cleaner for each directory and file below the given one', - () => { - const log: string[] = []; - fs.ensureDir(_('/a/b/c')); - fs.writeFile(_('/a/b/d.txt'), 'd contents'); - fs.writeFile(_('/a/b/c/e.txt'), 'e contents'); - const a = new MockCleaningStrategy(log, 'a', false); - const b = new MockCleaningStrategy(log, 'b', false); - const c = new MockCleaningStrategy(log, 'c', false); - const cleaner = new PackageCleaner(fs, [a, b, c]); - cleaner.clean(_('/a/b')); - expect(log).toEqual([ - `a:canClean('${_('/a/b/c')}', 'c')`, - `b:canClean('${_('/a/b/c')}', 'c')`, - `c:canClean('${_('/a/b/c')}', 'c')`, - `a:canClean('${_('/a/b/c/e.txt')}', 'e.txt')`, - `b:canClean('${_('/a/b/c/e.txt')}', 'e.txt')`, - `c:canClean('${_('/a/b/c/e.txt')}', 'e.txt')`, - `a:canClean('${_('/a/b/d.txt')}', 'd.txt')`, - `b:canClean('${_('/a/b/d.txt')}', 'd.txt')`, - `c:canClean('${_('/a/b/d.txt')}', 'd.txt')`, - ]); - }); - - it('should call `clean()` for the first cleaner that returns true for `canClean()`', () => { - const log: string[] = []; - fs.ensureDir(_('/a/b/c')); - fs.writeFile(_('/a/b/d.txt'), 'd contents'); - fs.writeFile(_('/a/b/c/e.txt'), 'e contents'); - const a = new MockCleaningStrategy(log, 'a', false); - const b = new MockCleaningStrategy(log, 'b', true); - const c = new MockCleaningStrategy(log, 'c', false); - const cleaner = new PackageCleaner(fs, [a, b, c]); - cleaner.clean(_('/a/b')); - expect(log).toEqual([ - `a:canClean('${_('/a/b/c')}', 'c')`, - `b:canClean('${_('/a/b/c')}', 'c')`, - `b:clean('${_('/a/b/c')}', 'c')`, - `a:canClean('${_('/a/b/c/e.txt')}', 'e.txt')`, - `b:canClean('${_('/a/b/c/e.txt')}', 'e.txt')`, - `b:clean('${_('/a/b/c/e.txt')}', 'e.txt')`, - `a:canClean('${_('/a/b/d.txt')}', 'd.txt')`, - `b:canClean('${_('/a/b/d.txt')}', 'd.txt')`, - `b:clean('${_('/a/b/d.txt')}', 'd.txt')`, - ]); - }); - }); - }); -}); - - -class MockCleaningStrategy implements CleaningStrategy { - constructor(private log: string[], private label: string, private _canClean: boolean) {} - - canClean(path: AbsoluteFsPath, basename: PathSegment) { - this.log.push(`${this.label}:canClean('${path}', '${basename}')`); - return this._canClean; - } - - clean(path: AbsoluteFsPath, basename: PathSegment): void { - this.log.push(`${this.label}:clean('${path}', '${basename}')`); - } -} diff --git a/packages/compiler-cli/ngcc/test/writing/in_place_file_writer_spec.ts b/packages/compiler-cli/ngcc/test/writing/in_place_file_writer_spec.ts deleted file mode 100644 index f75c9c4aed2..00000000000 --- a/packages/compiler-cli/ngcc/test/writing/in_place_file_writer_spec.ts +++ /dev/null @@ -1,177 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import {absoluteFrom, getFileSystem} from '../../../src/ngtsc/file_system'; -import {runInEachFileSystem} from '../../../src/ngtsc/file_system/testing'; -import {MockLogger} from '../../../src/ngtsc/logging/testing'; -import {loadTestFiles} from '../../../src/ngtsc/testing'; -import {EntryPoint} from '../../src/packages/entry_point'; -import {EntryPointBundle} from '../../src/packages/entry_point_bundle'; -import {InPlaceFileWriter, NGCC_BACKUP_EXTENSION} from '../../src/writing/in_place_file_writer'; - -runInEachFileSystem(() => { - describe('InPlaceFileWriter', () => { - let _: typeof absoluteFrom; - - beforeEach(() => { - _ = absoluteFrom; - loadTestFiles([ - {name: _('/package/path/top-level.js'), contents: 'ORIGINAL TOP LEVEL'}, - {name: _('/package/path/folder-1/file-1.js'), contents: 'ORIGINAL FILE 1'}, - {name: _('/package/path/folder-1/file-2.js'), contents: 'ORIGINAL FILE 2'}, - {name: _('/package/path/folder-2/file-3.js'), contents: 'ORIGINAL FILE 3'}, - {name: _('/package/path/folder-2/file-4.js'), contents: 'ORIGINAL FILE 4'}, - {name: _('/package/path/already-backed-up.js'), contents: 'ORIGINAL ALREADY BACKED UP'}, - {name: _('/package/path/already-backed-up.js.__ivy_ngcc_bak'), contents: 'BACKED UP'}, - ]); - }); - - describe('writeBundle()', () => { - it('should write all the FileInfo to the disk', () => { - const fs = getFileSystem(); - const logger = new MockLogger(); - const fileWriter = new InPlaceFileWriter(fs, logger, /* errorOnFailedEntryPoint */ true); - fileWriter.writeBundle({} as EntryPointBundle, [ - {path: _('/package/path/top-level.js'), contents: 'MODIFIED TOP LEVEL'}, - {path: _('/package/path/folder-1/file-1.js'), contents: 'MODIFIED FILE 1'}, - {path: _('/package/path/folder-2/file-4.js'), contents: 'MODIFIED FILE 4'}, - {path: _('/package/path/folder-3/file-5.js'), contents: 'NEW FILE 5'}, - ]); - expect(fs.readFile(_('/package/path/top-level.js'))).toEqual('MODIFIED TOP LEVEL'); - expect(fs.readFile(_('/package/path/folder-1/file-1.js'))).toEqual('MODIFIED FILE 1'); - expect(fs.readFile(_('/package/path/folder-1/file-2.js'))).toEqual('ORIGINAL FILE 2'); - expect(fs.readFile(_('/package/path/folder-2/file-3.js'))).toEqual('ORIGINAL FILE 3'); - expect(fs.readFile(_('/package/path/folder-2/file-4.js'))).toEqual('MODIFIED FILE 4'); - expect(fs.readFile(_('/package/path/folder-3/file-5.js'))).toEqual('NEW FILE 5'); - }); - - it('should create backups of all files that previously existed', () => { - const fs = getFileSystem(); - const logger = new MockLogger(); - const fileWriter = new InPlaceFileWriter(fs, logger, /* errorOnFailedEntryPoint */ true); - fileWriter.writeBundle({} as EntryPointBundle, [ - {path: _('/package/path/top-level.js'), contents: 'MODIFIED TOP LEVEL'}, - {path: _('/package/path/folder-1/file-1.js'), contents: 'MODIFIED FILE 1'}, - {path: _('/package/path/folder-2/file-4.js'), contents: 'MODIFIED FILE 4'}, - {path: _('/package/path/folder-3/file-5.js'), contents: 'NEW FILE 5'}, - ]); - expect(fs.readFile(_('/package/path/top-level.js.__ivy_ngcc_bak'))) - .toEqual('ORIGINAL TOP LEVEL'); - expect(fs.readFile(_('/package/path/folder-1/file-1.js.__ivy_ngcc_bak'))) - .toEqual('ORIGINAL FILE 1'); - expect(fs.exists(_('/package/path/folder-1/file-2.js.__ivy_ngcc_bak'))).toBe(false); - expect(fs.exists(_('/package/path/folder-2/file-3.js.__ivy_ngcc_bak'))).toBe(false); - expect(fs.readFile(_('/package/path/folder-2/file-4.js.__ivy_ngcc_bak'))) - .toEqual('ORIGINAL FILE 4'); - expect(fs.exists(_('/package/path/folder-3/file-5.js.__ivy_ngcc_bak'))).toBe(false); - }); - - it('should throw an error if the backup file already exists and errorOnFailedEntryPoint is true', - () => { - const fs = getFileSystem(); - const logger = new MockLogger(); - const fileWriter = new InPlaceFileWriter(fs, logger, /* errorOnFailedEntryPoint */ true); - const absoluteBackupPath = _('/package/path/already-backed-up.js'); - expect( - () => fileWriter.writeBundle( - {} as EntryPointBundle, - [{path: absoluteBackupPath, contents: 'MODIFIED BACKED UP'}])) - .toThrowError(`Tried to overwrite ${ - absoluteBackupPath}.__ivy_ngcc_bak with an ngcc back up file, which is disallowed.`); - }); - - it('should log an error, and skip writing the file, if the backup file already exists and errorOnFailedEntryPoint is false', - () => { - const fs = getFileSystem(); - const logger = new MockLogger(); - const fileWriter = - new InPlaceFileWriter(fs, logger, /* errorOnFailedEntryPoint */ false); - const absoluteBackupPath = _('/package/path/already-backed-up.js'); - fileWriter.writeBundle( - {} as EntryPointBundle, - [{path: absoluteBackupPath, contents: 'MODIFIED BACKED UP'}]); - // Should not have written the new file nor overwritten the backup file. - expect(fs.readFile(absoluteBackupPath)).toEqual('ORIGINAL ALREADY BACKED UP'); - expect(fs.readFile(_(absoluteBackupPath + '.__ivy_ngcc_bak'))).toEqual('BACKED UP'); - expect(logger.logs.error).toEqual([[ - `Tried to write ${ - absoluteBackupPath}.__ivy_ngcc_bak with an ngcc back up file but it already exists so not writing, nor backing up, ${ - absoluteBackupPath}.\n` + - `This error may be caused by one of the following:\n` + - `* two or more entry-points overlap and ngcc has been asked to process some files more than once.\n` + - ` In this case, you should check other entry-points in this package\n` + - ` and set up a config to ignore any that you are not using.\n` + - `* a previous run of ngcc was killed in the middle of processing, in a way that cannot be recovered.\n` + - ` In this case, you should try cleaning the node_modules directory and any dist directories that contain local libraries. Then try again.` - ]]); - }); - }); - - describe('revertBundle()', () => { - it('should revert the written files (and their backups)', () => { - const fs = getFileSystem(); - const logger = new MockLogger(); - const fileWriter = new InPlaceFileWriter(fs, logger, /* errorOnFailedEntryPoint */ true); - - const filePath1 = _('/package/path/folder-1/file-1.js'); - const filePath2 = _('/package/path/folder-1/file-2.js'); - const fileBackupPath1 = _(`/package/path/folder-1/file-1.js${NGCC_BACKUP_EXTENSION}`); - const fileBackupPath2 = _(`/package/path/folder-1/file-2.js${NGCC_BACKUP_EXTENSION}`); - - fileWriter.writeBundle({} as EntryPointBundle, [ - {path: filePath1, contents: 'MODIFIED FILE 1'}, - {path: filePath2, contents: 'MODIFIED FILE 2'}, - ]); - expect(fs.readFile(filePath1)).toBe('MODIFIED FILE 1'); - expect(fs.readFile(filePath2)).toBe('MODIFIED FILE 2'); - expect(fs.readFile(fileBackupPath1)).toBe('ORIGINAL FILE 1'); - expect(fs.readFile(fileBackupPath2)).toBe('ORIGINAL FILE 2'); - - fileWriter.revertBundle({} as EntryPoint, [filePath1, filePath2], []); - expect(fs.readFile(filePath1)).toBe('ORIGINAL FILE 1'); - expect(fs.readFile(filePath2)).toBe('ORIGINAL FILE 2'); - expect(fs.exists(fileBackupPath1)).toBeFalse(); - expect(fs.exists(fileBackupPath2)).toBeFalse(); - }); - - it('should just remove the written files if there is no backup', () => { - const fs = getFileSystem(); - const logger = new MockLogger(); - const fileWriter = new InPlaceFileWriter(fs, logger, /* errorOnFailedEntryPoint */ true); - - const filePath = _('/package/path/folder-1/file-1.js'); - const fileBackupPath = _(`/package/path/folder-1/file-1.js${NGCC_BACKUP_EXTENSION}`); - - fileWriter.writeBundle({} as EntryPointBundle, [ - {path: filePath, contents: 'MODIFIED FILE 1'}, - ]); - fs.removeFile(fileBackupPath); - expect(fs.readFile(filePath)).toBe('MODIFIED FILE 1'); - expect(fs.exists(fileBackupPath)).toBeFalse(); - - fileWriter.revertBundle({} as EntryPoint, [filePath], []); - expect(fs.exists(filePath)).toBeFalse(); - expect(fs.exists(fileBackupPath)).toBeFalse(); - }); - - it('should do nothing if the file does not exist', () => { - const fs = getFileSystem(); - const logger = new MockLogger(); - const fileWriter = new InPlaceFileWriter(fs, logger, /* errorOnFailedEntryPoint */ true); - - const filePath = _('/package/path/non-existent.js'); - const fileBackupPath = _(`/package/path/non-existent.js${NGCC_BACKUP_EXTENSION}`); - - fs.writeFile(fileBackupPath, 'BACKUP WITHOUT FILE'); - fileWriter.revertBundle({} as EntryPoint, [filePath], []); - - expect(fs.exists(filePath)).toBeFalse(); - expect(fs.readFile(fileBackupPath)).toBe('BACKUP WITHOUT FILE'); - }); - }); - }); -}); diff --git a/packages/compiler-cli/ngcc/test/writing/new_entry_point_file_writer_spec.ts b/packages/compiler-cli/ngcc/test/writing/new_entry_point_file_writer_spec.ts deleted file mode 100644 index 758f108d3f7..00000000000 --- a/packages/compiler-cli/ngcc/test/writing/new_entry_point_file_writer_spec.ts +++ /dev/null @@ -1,737 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import {absoluteFrom, FileSystem, getFileSystem} from '../../../src/ngtsc/file_system'; -import {runInEachFileSystem} from '../../../src/ngtsc/file_system/testing'; -import {MockLogger} from '../../../src/ngtsc/logging/testing'; -import {RawSourceMap} from '../../../src/ngtsc/sourcemaps'; -import {loadTestFiles} from '../../../src/ngtsc/testing'; -import {DtsProcessing} from '../../src/execution/tasks/api'; -import {NgccConfiguration} from '../../src/packages/configuration'; -import {EntryPoint, EntryPointFormat, EntryPointJsonProperty, getEntryPointInfo, isEntryPoint} from '../../src/packages/entry_point'; -import {EntryPointBundle, makeEntryPointBundle} from '../../src/packages/entry_point_bundle'; -import {NewEntryPointPropertiesMap} from '../../src/packages/entry_point_manifest'; -import {createModuleResolutionCache, SharedFileCache} from '../../src/packages/source_file_cache'; -import {FileWriter} from '../../src/writing/file_writer'; -import {NewEntryPointFileWriter} from '../../src/writing/new_entry_point_file_writer'; -import {DirectPackageJsonUpdater} from '../../src/writing/package_json_updater'; -import {loadPackageJson} from '../packages/entry_point_spec'; - -runInEachFileSystem(() => { - describe('NewEntryPointFileWriter', () => { - let _: typeof absoluteFrom; - let fs: FileSystem; - let fileWriter: FileWriter; - let logger: MockLogger; - let entryPoint: EntryPoint; - let esm5bundle: EntryPointBundle; - let esm2015bundle: EntryPointBundle; - - beforeEach(() => { - _ = absoluteFrom; - fs = getFileSystem(); - logger = new MockLogger(); - loadTestFiles([ - { - name: _('/node_modules/test/package.json'), - contents: ` - { - "module": "./esm5.js", - "fesm2015": "./es2015/index.js", - "fesm5": "./esm5.js", - "es2015": "./es2015/index.js", - "typings": "./index.d.ts" - } - `, - }, - {name: _('/node_modules/test/index.d.ts'), contents: 'export declare class FooTop {}'}, - {name: _('/node_modules/test/index.d.ts.map'), contents: 'ORIGINAL MAPPING DATA'}, - {name: _('/node_modules/test/index.metadata.json'), contents: '...'}, - {name: _('/node_modules/test/esm5.js'), contents: 'export function FooTop() {}'}, - {name: _('/node_modules/test/esm5.js.map'), contents: 'ORIGINAL MAPPING DATA'}, - {name: _('/node_modules/test/es2015/index.js'), contents: 'export {FooTop} from "./foo";'}, - { - name: _('/node_modules/test/es2015/index.js.map'), - contents: - '{"version":3,"file":"index.js","sources":["../src/index.ts"],"mappings":"AAAA"}' - }, - {name: _('/node_modules/test/src/index.ts'), contents: 'export {FooTop} from "./foo";'}, - {name: _('/node_modules/test/es2015/foo.js'), contents: 'export class FooTop {}'}, - { - name: _('/node_modules/test/a/package.json'), - contents: ` - { - "module": "./esm5.js", - "fesm2015": "./es2015/index.js", - "fesm5": "./esm5.js", - "es2015": "./es2015/index.js", - "typings": "./index.d.ts" - } - `, - }, - {name: _('/node_modules/test/a/index.d.ts'), contents: 'export declare class FooA {}'}, - {name: _('/node_modules/test/a/index.metadata.json'), contents: '...'}, - {name: _('/node_modules/test/a/esm5.js'), contents: 'export function FooA() {}'}, - {name: _('/node_modules/test/a/es2015/index.js'), contents: 'export {FooA} from "./foo";'}, - {name: _('/node_modules/test/a/es2015/foo.js'), contents: 'export class FooA {}'}, - { - name: _('/node_modules/test/b/package.json'), - // This entry-point points to files outside its folder - contents: - `{"module": "../lib/esm5.js", "es2015": "../lib/es2015/index.js", "typings": "../typings/index.d.ts"}` - }, - {name: _('/node_modules/test/lib/esm5.js'), contents: 'export function FooB() {}'}, - { - name: _('/node_modules/test/lib/es2015/index.js'), - contents: 'export {FooB} from "./foo"; import * from "other";' - }, - { - name: _('/node_modules/test/lib/es2015/foo.js'), - contents: 'import {FooA} from "test/a"; import "events"; export class FooB {}' - }, - { - name: _('/node_modules/test/typings/index.d.ts'), - contents: 'export declare class FooB {}' - }, - {name: _('/node_modules/test/typings/index.metadata.json'), contents: '...'}, - { - name: _('/node_modules/other/package.json'), - contents: '{"module": "./esm5.js", "typings": "./index.d.ts"}' - }, - {name: _('/node_modules/other/index.d.ts'), contents: 'export declare class OtherClass {}'}, - {name: _('/node_modules/other/esm5.js'), contents: 'export class OtherClass {}'}, - {name: _('/node_modules/events/package.json'), contents: '{"main": "./events.js"}'}, - {name: _('/node_modules/events/events.js'), contents: 'export class OtherClass {}'}, - ]); - }); - - describe('writeBundle() [primary entry-point]', () => { - beforeEach(() => { - fileWriter = new NewEntryPointFileWriter( - fs, logger, /* errorOnFailedEntryPoint */ true, new DirectPackageJsonUpdater(fs)); - const config = new NgccConfiguration(fs, _('/')); - const result = getEntryPointInfo( - fs, config, logger, _('/node_modules/test'), _('/node_modules/test'))!; - if (!isEntryPoint(result)) { - return fail(`Expected an entry point but got ${result}`); - } - entryPoint = result; - esm5bundle = makeTestBundle(fs, entryPoint, 'module', 'esm5'); - esm2015bundle = makeTestBundle(fs, entryPoint, 'es2015', 'esm2015'); - }); - - it('should write the modified files to a new folder', () => { - fileWriter.writeBundle( - esm5bundle, - [ - { - path: _('/node_modules/test/esm5.js'), - contents: 'export function FooTop() {} // MODIFIED' - }, - {path: _('/node_modules/test/esm5.js.map'), contents: 'MODIFIED MAPPING DATA'}, - ], - ['module']); - expect(fs.readFile(_('/node_modules/test/__ivy_ngcc__/esm5.js'))) - .toEqual('export function FooTop() {} // MODIFIED'); - expect(fs.readFile(_('/node_modules/test/esm5.js'))).toEqual('export function FooTop() {}'); - expect(fs.readFile(_('/node_modules/test/__ivy_ngcc__/esm5.js.map'))) - .toEqual('MODIFIED MAPPING DATA'); - expect(fs.readFile(_('/node_modules/test/esm5.js.map'))).toEqual('ORIGINAL MAPPING DATA'); - }); - - it('should also copy unmodified files in the program', () => { - fileWriter.writeBundle( - esm2015bundle, - [ - { - path: _('/node_modules/test/es2015/foo.js'), - contents: 'export class FooTop {} // MODIFIED' - }, - ], - ['es2015']); - expect(fs.readFile(_('/node_modules/test/__ivy_ngcc__/es2015/foo.js'))) - .toEqual('export class FooTop {} // MODIFIED'); - expect(fs.readFile(_('/node_modules/test/es2015/foo.js'))) - .toEqual('export class FooTop {}'); - expect(fs.readFile(_('/node_modules/test/__ivy_ngcc__/es2015/index.js'))) - .toEqual('export {FooTop} from "./foo";'); - expect(fs.readFile(_('/node_modules/test/es2015/index.js'))) - .toEqual('export {FooTop} from "./foo";'); - }); - - it('should copy any source-map for unmodified files in the program (adding missing sourceRoot)', - () => { - // Ensure source-mapping for a non-processed source file `index.js`. - const sourceMap = { - version: 3, - file: 'index.js', - sources: ['../src/index.ts'], - mappings: 'AAAA', - }; - loadTestFiles([ - { - name: _('/node_modules/test/es2015/index.js.map'), - contents: JSON.stringify(sourceMap) - }, - {name: _('/node_modules/test/src/index.ts'), contents: 'export {FooTop} from "./foo";'} - ]); - - // Simulate that only the `foo.js` file was modified - const modifiedFiles = [{ - path: _('/node_modules/test/es2015/foo.js'), - contents: 'export class FooTop {} // MODIFIED' - }]; - fileWriter.writeBundle(esm2015bundle, modifiedFiles, ['es2015']); - - expect( - JSON.parse(fs.readFile(_('/node_modules/test/__ivy_ngcc__/es2015/index.js.map'))) as - Partial) - .toEqual({...sourceMap, sourceRoot: '../../es2015'}); - }); - - it('should copy any source-map for unmodified files in the program (updating sourceRoot)', - () => { - // Ensure source-mapping for a non-processed source file `index.js`. - const sourceMap = { - version: 3, - file: 'index.js', - sourceRoot: '../src', - sources: ['index.ts'], - mappings: 'AAAA', - }; - loadTestFiles([ - { - name: _('/node_modules/test/es2015/index.js.map'), - contents: JSON.stringify(sourceMap) - }, - {name: _('/node_modules/test/src/index.ts'), contents: 'export {FooTop} from "./foo";'} - ]); - - // Simulate that only the `foo.js` file was modified - const modifiedFiles = [{ - path: _('/node_modules/test/es2015/foo.js'), - contents: 'export class FooTop {} // MODIFIED' - }]; - fileWriter.writeBundle(esm2015bundle, modifiedFiles, ['es2015']); - - expect( - JSON.parse(fs.readFile(_('/node_modules/test/__ivy_ngcc__/es2015/index.js.map'))) as - Partial) - .toEqual({...sourceMap, sourceRoot: '../../src'}); - }); - - it('should ignore (with a warning) any invalid source-map for unmodified files in the program', - () => { - // Ensure source-mapping for a non-processed source file `index.js`. - loadTestFiles([ - {name: _('/node_modules/test/es2015/index.js.map'), contents: 'INVALID JSON STRING'}, - {name: _('/node_modules/test/src/index.ts'), contents: 'export {FooTop} from "./foo";'} - ]); - - // Simulate that only the `foo.js` file was modified - const modifiedFiles = [{ - path: _('/node_modules/test/es2015/foo.js'), - contents: 'export class FooTop {} // MODIFIED' - }]; - fileWriter.writeBundle(esm2015bundle, modifiedFiles, ['es2015']); - - expect(fs.exists(_('/node_modules/test/__ivy_ngcc__/es2015/index.js.map'))).toBe(false); - expect(logger.logs.warn).toEqual([ - [`Failed to process source-map at ${_('/node_modules/test/es2015/index.js.map')}`], - ['Unexpected token I in JSON at position 0'], - ]); - }); - - it('should update the package.json properties', () => { - fileWriter.writeBundle( - esm5bundle, - [ - { - path: _('/node_modules/test/esm5.js'), - contents: 'export function FooTop() {} // MODIFIED' - }, - ], - ['module']); - expect(loadPackageJson(fs, '/node_modules/test')).toEqual(jasmine.objectContaining({ - module_ivy_ngcc: '__ivy_ngcc__/esm5.js', - })); - - fileWriter.writeBundle( - esm2015bundle, - [ - { - path: _('/node_modules/test/es2015/foo.js'), - contents: 'export class FooTop {} // MODIFIED' - }, - ], - ['es2015']); - expect(loadPackageJson(fs, '/node_modules/test')).toEqual(jasmine.objectContaining({ - module_ivy_ngcc: '__ivy_ngcc__/esm5.js', - es2015_ivy_ngcc: '__ivy_ngcc__/es2015/index.js', - })); - }); - - it('should be able to update multiple package.json properties at once', () => { - fileWriter.writeBundle( - esm5bundle, - [ - { - path: _('/node_modules/test/esm5.js'), - contents: 'export function FooTop() {} // MODIFIED' - }, - ], - ['module', 'fesm5']); - expect(loadPackageJson(fs, '/node_modules/test')).toEqual(jasmine.objectContaining({ - module_ivy_ngcc: '__ivy_ngcc__/esm5.js', - fesm5_ivy_ngcc: '__ivy_ngcc__/esm5.js', - })); - - fileWriter.writeBundle( - esm2015bundle, - [ - { - path: _('/node_modules/test/es2015/foo.js'), - contents: 'export class FooTop {} // MODIFIED' - }, - ], - ['es2015', 'fesm2015']); - expect(loadPackageJson(fs, '/node_modules/test')).toEqual(jasmine.objectContaining({ - module_ivy_ngcc: '__ivy_ngcc__/esm5.js', - fesm5_ivy_ngcc: '__ivy_ngcc__/esm5.js', - es2015_ivy_ngcc: '__ivy_ngcc__/es2015/index.js', - fesm2015_ivy_ngcc: '__ivy_ngcc__/es2015/index.js', - })); - }); - - it('should overwrite and backup typings files', () => { - fileWriter.writeBundle( - esm2015bundle, - [ - { - path: _('/node_modules/test/index.d.ts'), - contents: 'export declare class FooTop {} // MODIFIED' - }, - {path: _('/node_modules/test/index.d.ts.map'), contents: 'MODIFIED MAPPING DATA'}, - ], - ['es2015']); - expect(fs.readFile(_('/node_modules/test/index.d.ts'))) - .toEqual('export declare class FooTop {} // MODIFIED'); - expect(fs.readFile(_('/node_modules/test/index.d.ts.__ivy_ngcc_bak'))) - .toEqual('export declare class FooTop {}'); - expect(fs.exists(_('/node_modules/test/__ivy_ngcc__/index.d.ts'))).toBe(false); - - expect(fs.readFile(_('/node_modules/test/index.d.ts.map'))) - .toEqual('MODIFIED MAPPING DATA'); - expect(fs.readFile(_('/node_modules/test/index.d.ts.map.__ivy_ngcc_bak'))) - .toEqual('ORIGINAL MAPPING DATA'); - expect(fs.exists(_('/node_modules/test/__ivy_ngcc__/index.d.ts.map'))).toBe(false); - }); - }); - - describe('writeBundle() [secondary entry-point]', () => { - beforeEach(() => { - fileWriter = new NewEntryPointFileWriter( - fs, logger, /* errorOnFailedEntryPoint */ true, new DirectPackageJsonUpdater(fs)); - const config = new NgccConfiguration(fs, _('/')); - const result = getEntryPointInfo( - fs, config, logger, _('/node_modules/test'), _('/node_modules/test/a'))!; - if (!isEntryPoint(result)) { - return fail(`Expected an entry point but got ${result}`); - } - entryPoint = result; - esm5bundle = makeTestBundle(fs, entryPoint, 'module', 'esm5'); - esm2015bundle = makeTestBundle(fs, entryPoint, 'es2015', 'esm2015'); - }); - - it('should write the modified file to a new folder', () => { - fileWriter.writeBundle( - esm5bundle, - [ - { - path: _('/node_modules/test/a/esm5.js'), - contents: 'export function FooA() {} // MODIFIED' - }, - ], - ['module']); - expect(fs.readFile(_('/node_modules/test/__ivy_ngcc__/a/esm5.js'))) - .toEqual('export function FooA() {} // MODIFIED'); - expect(fs.readFile(_('/node_modules/test/a/esm5.js'))).toEqual('export function FooA() {}'); - }); - - it('should also copy unmodified files in the program', () => { - fileWriter.writeBundle( - esm2015bundle, - [ - { - path: _('/node_modules/test/a/es2015/foo.js'), - contents: 'export class FooA {} // MODIFIED' - }, - ], - ['es2015']); - expect(fs.readFile(_('/node_modules/test/__ivy_ngcc__/a/es2015/foo.js'))) - .toEqual('export class FooA {} // MODIFIED'); - expect(fs.readFile(_('/node_modules/test/a/es2015/foo.js'))) - .toEqual('export class FooA {}'); - expect(fs.readFile(_('/node_modules/test/__ivy_ngcc__/a/es2015/index.js'))) - .toEqual('export {FooA} from "./foo";'); - expect(fs.readFile(_('/node_modules/test/a/es2015/index.js'))) - .toEqual('export {FooA} from "./foo";'); - }); - - it('should update the package.json properties', () => { - fileWriter.writeBundle( - esm5bundle, - [ - { - path: _('/node_modules/test/a/esm5.js'), - contents: 'export function FooA() {} // MODIFIED' - }, - ], - ['module']); - expect(loadPackageJson(fs, '/node_modules/test/a')).toEqual(jasmine.objectContaining({ - module_ivy_ngcc: '../__ivy_ngcc__/a/esm5.js', - })); - - fileWriter.writeBundle( - esm2015bundle, - [ - { - path: _('/node_modules/test/a/es2015/foo.js'), - contents: 'export class FooA {} // MODIFIED' - }, - ], - ['es2015']); - expect(loadPackageJson(fs, '/node_modules/test/a')).toEqual(jasmine.objectContaining({ - module_ivy_ngcc: '../__ivy_ngcc__/a/esm5.js', - es2015_ivy_ngcc: '../__ivy_ngcc__/a/es2015/index.js', - })); - }); - - it('should be able to update multiple package.json properties at once', () => { - fileWriter.writeBundle( - esm5bundle, - [ - { - path: _('/node_modules/test/a/esm5.js'), - contents: 'export function FooA() {} // MODIFIED' - }, - ], - ['module', 'fesm5']); - expect(loadPackageJson(fs, '/node_modules/test/a')).toEqual(jasmine.objectContaining({ - module_ivy_ngcc: '../__ivy_ngcc__/a/esm5.js', - fesm5_ivy_ngcc: '../__ivy_ngcc__/a/esm5.js', - })); - - fileWriter.writeBundle( - esm2015bundle, - [ - { - path: _('/node_modules/test/a/es2015/foo.js'), - contents: 'export class FooA {} // MODIFIED' - }, - ], - ['es2015', 'fesm2015']); - expect(loadPackageJson(fs, '/node_modules/test/a')).toEqual(jasmine.objectContaining({ - module_ivy_ngcc: '../__ivy_ngcc__/a/esm5.js', - fesm5_ivy_ngcc: '../__ivy_ngcc__/a/esm5.js', - es2015_ivy_ngcc: '../__ivy_ngcc__/a/es2015/index.js', - fesm2015_ivy_ngcc: '../__ivy_ngcc__/a/es2015/index.js', - })); - }); - - it('should overwrite and backup typings files', () => { - fileWriter.writeBundle( - esm2015bundle, - [ - { - path: _('/node_modules/test/a/index.d.ts'), - contents: 'export declare class FooA {} // MODIFIED' - }, - ], - ['es2015']); - expect(fs.readFile(_('/node_modules/test/a/index.d.ts'))) - .toEqual('export declare class FooA {} // MODIFIED'); - expect(fs.readFile(_('/node_modules/test/a/index.d.ts.__ivy_ngcc_bak'))) - .toEqual('export declare class FooA {}'); - expect(fs.exists(_('/node_modules/test/__ivy_ngcc__/a/index.d.ts'))).toBe(false); - }); - }); - - describe('writeBundle() [entry-point (with files placed outside entry-point folder)]', () => { - beforeEach(() => { - fileWriter = new NewEntryPointFileWriter( - fs, logger, /* errorOnFailedEntryPoint */ true, new DirectPackageJsonUpdater(fs)); - const config = new NgccConfiguration(fs, _('/')); - const result = getEntryPointInfo( - fs, config, new MockLogger(), _('/node_modules/test'), _('/node_modules/test/b'))!; - if (!isEntryPoint(result)) { - return fail(`Expected an entry point but got ${result}`); - } - entryPoint = result; - esm5bundle = makeTestBundle(fs, entryPoint, 'module', 'esm5'); - esm2015bundle = makeTestBundle(fs, entryPoint, 'es2015', 'esm2015'); - }); - - it('should write the modified file to a new folder', () => { - fileWriter.writeBundle( - esm5bundle, - [ - { - path: _('/node_modules/test/lib/esm5.js'), - contents: 'export function FooB() {} // MODIFIED' - }, - ], - ['module']); - expect(fs.readFile(_('/node_modules/test/__ivy_ngcc__/lib/esm5.js'))) - .toEqual('export function FooB() {} // MODIFIED'); - expect(fs.readFile(_('/node_modules/test/lib/esm5.js'))) - .toEqual('export function FooB() {}'); - }); - - it('should also copy unmodified files in the program', () => { - fileWriter.writeBundle( - esm2015bundle, - [ - { - path: _('/node_modules/test/lib/es2015/foo.js'), - contents: 'export class FooB {} // MODIFIED' - }, - ], - ['es2015']); - expect(fs.readFile(_('/node_modules/test/__ivy_ngcc__/lib/es2015/foo.js'))) - .toEqual('export class FooB {} // MODIFIED'); - expect(fs.readFile(_('/node_modules/test/lib/es2015/foo.js'))) - .toEqual('import {FooA} from "test/a"; import "events"; export class FooB {}'); - expect(fs.readFile(_('/node_modules/test/__ivy_ngcc__/lib/es2015/index.js'))) - .toEqual('export {FooB} from "./foo"; import * from "other";'); - expect(fs.readFile(_('/node_modules/test/lib/es2015/index.js'))) - .toEqual('export {FooB} from "./foo"; import * from "other";'); - }); - - it('should not copy typings files within the package (i.e. from a different entry-point)', - () => { - fileWriter.writeBundle( - esm2015bundle, - [ - { - path: _('/node_modules/test/lib/es2015/foo.js'), - contents: 'export class FooB {} // MODIFIED' - }, - ], - ['es2015']); - expect(fs.exists(_('/node_modules/test/__ivy_ngcc__/a/index.d.ts'))).toEqual(false); - }); - - it('should not copy files outside of the package', () => { - fileWriter.writeBundle( - esm2015bundle, - [ - { - path: _('/node_modules/test/lib/es2015/foo.js'), - contents: 'export class FooB {} // MODIFIED' - }, - ], - ['es2015']); - expect(fs.exists(_('/node_modules/test/other/index.d.ts'))).toEqual(false); - expect(fs.exists(_('/node_modules/test/events/events.js'))).toEqual(false); - }); - - it('should update the package.json properties', () => { - fileWriter.writeBundle( - esm5bundle, - [ - { - path: _('/node_modules/test/lib/esm5.js'), - contents: 'export function FooB() {} // MODIFIED' - }, - ], - ['module']); - expect(loadPackageJson(fs, '/node_modules/test/b')).toEqual(jasmine.objectContaining({ - module_ivy_ngcc: '../__ivy_ngcc__/lib/esm5.js', - })); - - fileWriter.writeBundle( - esm2015bundle, - [ - { - path: _('/node_modules/test/lib/es2015/foo.js'), - contents: 'export class FooB {} // MODIFIED' - }, - ], - ['es2015']); - expect(loadPackageJson(fs, '/node_modules/test/b')).toEqual(jasmine.objectContaining({ - module_ivy_ngcc: '../__ivy_ngcc__/lib/esm5.js', - es2015_ivy_ngcc: '../__ivy_ngcc__/lib/es2015/index.js', - })); - }); - - it('should overwrite and backup typings files', () => { - fileWriter.writeBundle( - esm2015bundle, - [ - { - path: _('/node_modules/test/typings/index.d.ts'), - contents: 'export declare class FooB {} // MODIFIED' - }, - ], - ['es2015']); - expect(fs.readFile(_('/node_modules/test/typings/index.d.ts'))) - .toEqual('export declare class FooB {} // MODIFIED'); - expect(fs.readFile(_('/node_modules/test/typings/index.d.ts.__ivy_ngcc_bak'))) - .toEqual('export declare class FooB {}'); - expect(fs.exists(_('/node_modules/test/__ivy_ngcc__/typings/index.d.ts'))).toBe(false); - }); - }); - - describe('revertFile()', () => { - beforeEach(() => { - fileWriter = new NewEntryPointFileWriter( - fs, logger, /* errorOnFailedEntryPoint */ true, new DirectPackageJsonUpdater(fs)); - const config = new NgccConfiguration(fs, _('/')); - const result = getEntryPointInfo( - fs, config, logger, _('/node_modules/test'), _('/node_modules/test'))!; - if (!isEntryPoint(result)) { - return fail(`Expected an entry point but got ${result}`); - } - entryPoint = result; - esm5bundle = makeTestBundle(fs, entryPoint, 'module', 'esm5'); - }); - - it('should remove non-typings files', () => { - fileWriter.writeBundle( - esm5bundle, - [ - { - path: _('/node_modules/test/esm5.js'), - contents: 'export function FooTop() {} // MODIFIED', - }, - { - path: _('/node_modules/test/esm5.js.map'), - contents: 'MODIFIED MAPPING DATA', - }, - // Normally there will be no backup file. Write one here to ensure it is not removed. - { - path: _('/node_modules/test/esm5.js.__ivy_ngcc_bak'), - contents: 'NOT AN ACTUAL BACKUP', - }, - ], - ['module']); - - expect(fs.exists(_('/node_modules/test/__ivy_ngcc__/esm5.js'))).toBeTrue(); - expect(fs.exists(_('/node_modules/test/__ivy_ngcc__/esm5.js.map'))).toBeTrue(); - expect(fs.exists(_('/node_modules/test/__ivy_ngcc__/esm5.js.__ivy_ngcc_bak'))).toBeTrue(); - - fileWriter.revertBundle( - esm5bundle.entryPoint, - [_('/node_modules/test/esm5.js'), _('/node_modules/test/esm5.js.map')], []); - - expect(fs.exists(_('/node_modules/test/__ivy_ngcc__/esm5.js'))).toBeFalse(); - expect(fs.exists(_('/node_modules/test/__ivy_ngcc__/esm5.js.map'))).toBeFalse(); - expect(fs.exists(_('/node_modules/test/__ivy_ngcc__/esm5.js.__ivy_ngcc_bak'))).toBeTrue(); - }); - - it('should revert written typings files (and their backups)', () => { - fileWriter.writeBundle( - esm5bundle, - [ - { - path: _('/node_modules/test/index.d.ts'), - contents: 'export declare class FooTop {} // MODIFIED' - }, - { - path: _('/node_modules/test/index.d.ts.map'), - contents: 'MODIFIED MAPPING DATA', - }, - ], - ['module']); - - expect(fs.readFile(_('/node_modules/test/index.d.ts'))) - .toBe('export declare class FooTop {} // MODIFIED'); - expect(fs.readFile(_('/node_modules/test/index.d.ts.__ivy_ngcc_bak'))) - .toBe('export declare class FooTop {}'); - expect(fs.readFile(_('/node_modules/test/index.d.ts.map'))).toBe('MODIFIED MAPPING DATA'); - expect(fs.readFile(_('/node_modules/test/index.d.ts.map.__ivy_ngcc_bak'))) - .toBe('ORIGINAL MAPPING DATA'); - - fileWriter.revertBundle( - esm5bundle.entryPoint, - [_('/node_modules/test/index.d.ts'), _('/node_modules/test/index.d.ts.map')], []); - - expect(fs.readFile(_('/node_modules/test/index.d.ts'))) - .toBe('export declare class FooTop {}'); - expect(fs.exists(_('/node_modules/test/index.d.ts.__ivy_ngcc_bak'))).toBeFalse(); - expect(fs.readFile(_('/node_modules/test/index.d.ts.map'))).toBe('ORIGINAL MAPPING DATA'); - expect(fs.exists(_('/node_modules/test/index.d.ts.map.__ivy_ngcc_bak'))).toBeFalse(); - }); - - it('should revert changes to `package.json`', () => { - const entryPoint = esm5bundle.entryPoint; - const packageJsonPath = fs.join(entryPoint.packagePath, 'package.json'); - - fileWriter.writeBundle( - esm5bundle, - [ - { - path: _('/node_modules/test/index.d.ts'), - contents: 'export declare class FooTop {} // MODIFIED' - }, - { - path: _('/node_modules/test/index.d.ts.map'), - contents: 'MODIFIED MAPPING DATA', - }, - ], - ['fesm5', 'module']); - const packageJsonFromFile1 = - JSON.parse(fs.readFile(packageJsonPath)) as NewEntryPointPropertiesMap; - - expect(entryPoint.packageJson).toEqual(jasmine.objectContaining({ - fesm5_ivy_ngcc: '__ivy_ngcc__/esm5.js', - fesm5: './esm5.js', - module_ivy_ngcc: '__ivy_ngcc__/esm5.js', - module: './esm5.js', - })); - - expect(packageJsonFromFile1).toEqual(jasmine.objectContaining({ - fesm5_ivy_ngcc: '__ivy_ngcc__/esm5.js', - fesm5: './esm5.js', - module_ivy_ngcc: '__ivy_ngcc__/esm5.js', - module: './esm5.js', - })); - - fileWriter.revertBundle( - esm5bundle.entryPoint, - [_('/node_modules/test/index.d.ts'), _('/node_modules/test/index.d.ts.map')], - ['fesm5', 'module']); - const packageJsonFromFile2 = - JSON.parse(fs.readFile(packageJsonPath)) as NewEntryPointPropertiesMap; - - expect(entryPoint.packageJson).toEqual(jasmine.objectContaining({ - fesm5: './esm5.js', - module: './esm5.js', - })); - expect(entryPoint.packageJson.fesm5_ivy_ngcc).toBeUndefined(); - expect(entryPoint.packageJson.module_ivy_ngcc).toBeUndefined(); - - expect(packageJsonFromFile2).toEqual(jasmine.objectContaining({ - fesm5: './esm5.js', - module: './esm5.js', - })); - expect(packageJsonFromFile2.fesm5_ivy_ngcc).toBeUndefined(); - expect(packageJsonFromFile2.module_ivy_ngcc).toBeUndefined(); - }); - }); - }); - - function makeTestBundle( - fs: FileSystem, entryPoint: EntryPoint, formatProperty: EntryPointJsonProperty, - format: EntryPointFormat): EntryPointBundle { - const moduleResolutionCache = createModuleResolutionCache(fs); - return makeEntryPointBundle( - fs, entryPoint, new SharedFileCache(fs), moduleResolutionCache, - entryPoint.packageJson[formatProperty]!, false, format, DtsProcessing.Yes); - } -}); diff --git a/packages/compiler-cli/ngcc/test/writing/package_json_updater_spec.ts b/packages/compiler-cli/ngcc/test/writing/package_json_updater_spec.ts deleted file mode 100644 index 058efde8144..00000000000 --- a/packages/compiler-cli/ngcc/test/writing/package_json_updater_spec.ts +++ /dev/null @@ -1,296 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import {absoluteFrom, AbsoluteFsPath, FileSystem, getFileSystem} from '../../../src/ngtsc/file_system'; -import {runInEachFileSystem} from '../../../src/ngtsc/file_system/testing'; -import {loadTestFiles} from '../../../src/ngtsc/testing'; -import {JsonObject} from '../../src/utils'; -import {DirectPackageJsonUpdater, PackageJsonUpdater} from '../../src/writing/package_json_updater'; - -runInEachFileSystem(() => { - describe('DirectPackageJsonUpdater', () => { - let _: typeof absoluteFrom; - let fs: FileSystem; - let updater: PackageJsonUpdater; - - // Helpers - const readJson = (path: AbsoluteFsPath) => JSON.parse(fs.readFile(path)) as T; - - beforeEach(() => { - _ = absoluteFrom; - fs = getFileSystem(); - updater = new DirectPackageJsonUpdater(fs); - }); - - it('should update a `package.json` file on disk', () => { - const jsonPath = _('/foo/package.json'); - loadTestFiles([ - {name: jsonPath, contents: '{"foo": true, "bar": {"baz": "OK"}}'}, - ]); - - const update = updater.createUpdate().addChange(['foo'], false).addChange(['bar', 'baz'], 42); - - // Not updated yet. - expect(readJson(jsonPath)).toEqual({ - foo: true, - bar: {baz: 'OK'}, - }); - - update.writeChanges(jsonPath); - - // Updated now. - expect(readJson(jsonPath)).toEqual({ - foo: false, - bar: {baz: 42}, - }); - }); - - it('should update an in-memory representation (if provided)', () => { - const jsonPath = _('/foo/package.json'); - loadTestFiles([ - {name: jsonPath, contents: '{"foo": true, "bar": {"baz": "OK"}}'}, - ]); - - const pkg = readJson<{foo: boolean, bar: {baz: string | number}}>(jsonPath); - const update = updater.createUpdate().addChange(['foo'], false).addChange(['bar', 'baz'], 42); - - // Not updated yet. - expect(pkg).toEqual({ - foo: true, - bar: {baz: 'OK'}, - }); - - update.writeChanges(jsonPath, pkg); - - // Updated now. - expect(pkg).toEqual({ - foo: false, - bar: {baz: 42}, - }); - }); - - it('should create the `package.json` file, if it does not exist', () => { - const jsonPath = _('/foo/package.json'); - expect(fs.exists(jsonPath)).toBe(false); - - updater.createUpdate() - .addChange(['foo'], false) - .addChange(['bar', 'baz'], 42) - .writeChanges(jsonPath); - - expect(fs.exists(jsonPath)).toBe(true); - expect(readJson(jsonPath)).toEqual({ - foo: false, - bar: {baz: 42}, - }); - }); - - it('should create any missing ancestor objects', () => { - const jsonPath = _('/foo/package.json'); - loadTestFiles([ - {name: jsonPath, contents: '{"foo": {}}'}, - ]); - - const pkg = readJson<{foo: {bar: {baz: {qux: string}}}}>(jsonPath); - updater.createUpdate() - .addChange(['foo', 'bar', 'baz', 'qux'], 'updated') - .writeChanges(jsonPath, pkg); - - expect(readJson(jsonPath)).toEqual(pkg); - expect(pkg).toEqual({ - foo: { - bar: { - baz: { - qux: 'updated', - }, - }, - }, - }); - }); - - it('should throw, if no changes have been recorded', () => { - const jsonPath = _('/foo/package.json'); - - expect(() => updater.createUpdate().writeChanges(jsonPath)) - .toThrowError(`No changes to write to '${jsonPath}'.`); - }); - - it('should throw, if a property-path is empty', () => { - const jsonPath = _('/foo/package.json'); - - expect(() => updater.createUpdate().addChange([], 'missing').writeChanges(jsonPath)) - .toThrowError(`Missing property path for writing value to '${jsonPath}'.`); - }); - - it('should throw, if a property-path points to a non-object intermediate value', () => { - const jsonPath = _('/foo/package.json'); - loadTestFiles([ - {name: jsonPath, contents: '{"foo": null, "bar": 42, "baz": {"qux": []}}'}, - ]); - - const writeToProp = (propPath: string[]) => - updater.createUpdate().addChange(propPath, 'updated').writeChanges(jsonPath); - - expect(() => writeToProp(['foo', 'child'])) - .toThrowError('Property path \'foo.child\' does not point to an object.'); - expect(() => writeToProp(['bar', 'child'])) - .toThrowError('Property path \'bar.child\' does not point to an object.'); - expect(() => writeToProp(['baz', 'qux', 'child'])) - .toThrowError('Property path \'baz.qux.child\' does not point to an object.'); - }); - - it('should throw, if trying to re-apply an already applied update', () => { - const update = updater.createUpdate().addChange(['foo'], 'updated'); - - expect(() => update.writeChanges(_('/foo/package.json'))).not.toThrow(); - expect(() => update.writeChanges(_('/foo/package.json'))) - .toThrowError('Trying to apply a `PackageJsonUpdate` that has already been applied.'); - expect(() => update.writeChanges(_('/bar/package.json'))) - .toThrowError('Trying to apply a `PackageJsonUpdate` that has already been applied.'); - }); - - it('should throw, if trying to update a synthesized `package.json` file', () => { - const update = updater.createUpdate().addChange(['foo'], 'updated'); - - expect(() => update.writeChanges(_('/foo/package.json'), { - synthesized: true - })).toThrowError('Trying to update a non-existent (synthesized) `package.json` file.'); - }); - - describe('(property positioning)', () => { - // Helpers - const createJsonFile = (jsonObj: JsonObject) => { - const jsonPath = _('/foo/package.json'); - loadTestFiles([{name: jsonPath, contents: JSON.stringify(jsonObj)}]); - return jsonPath; - }; - const expectJsonEquals = (jsonFilePath: AbsoluteFsPath, jsonObj: JsonObject) => - expect(fs.readFile(jsonFilePath).trim()).toBe(JSON.stringify(jsonObj, null, 2)); - - it('should not change property positioning by default', () => { - const jsonPath = createJsonFile({ - p2: '2', - p1: {p12: '1.2', p11: '1.1'}, - }); - - updater.createUpdate() - .addChange(['p1', 'p11'], '1.1-updated') - .addChange(['p1', 'p10'], '1.0-added') - .addChange(['p2'], '2-updated') - .addChange(['p0'], '0-added') - .writeChanges(jsonPath); - - expectJsonEquals(jsonPath, { - p2: '2-updated', - p1: {p12: '1.2', p11: '1.1-updated', p10: '1.0-added'}, - p0: '0-added', - }); - }); - - it('should not change property positioning with `positioning: unimportant`', () => { - const jsonPath = createJsonFile({ - p2: '2', - p1: {p12: '1.2', p11: '1.1'}, - }); - - updater.createUpdate() - .addChange(['p1', 'p11'], '1.1-updated', 'unimportant') - .addChange(['p1', 'p10'], '1.0-added', 'unimportant') - .addChange(['p2'], '2-updated', 'unimportant') - .addChange(['p0'], '0-added', 'unimportant') - .writeChanges(jsonPath); - - expectJsonEquals(jsonPath, { - p2: '2-updated', - p1: {p12: '1.2', p11: '1.1-updated', p10: '1.0-added'}, - p0: '0-added', - }); - }); - - it('should position added/updated properties alphabetically with `positioning: alphabetic`', - () => { - const jsonPath = createJsonFile({ - p2: '2', - p1: {p12: '1.2', p11: '1.1'}, - }); - - updater.createUpdate() - .addChange(['p1', 'p11'], '1.1-updated', 'alphabetic') - .addChange(['p1', 'p10'], '1.0-added', 'alphabetic') - .addChange(['p0'], '0-added', 'alphabetic') - .addChange(['p3'], '3-added', 'alphabetic') - .writeChanges(jsonPath); - - expectJsonEquals(jsonPath, { - p0: '0-added', - p2: '2', - p1: {p10: '1.0-added', p11: '1.1-updated', p12: '1.2'}, - p3: '3-added', - }); - }); - - it('should position added/updated properties correctly with `positioning: {before: ...}`', - () => { - const jsonPath = createJsonFile({ - p2: '2', - p1: {p12: '1.2', p11: '1.1'}, - }); - - updater.createUpdate() - .addChange(['p0'], '0-added', {before: 'p1'}) - .addChange(['p1', 'p10'], '1.0-added', {before: 'p11'}) - .addChange(['p1', 'p12'], '1.2-updated', {before: 'p11'}) - .writeChanges(jsonPath); - - expectJsonEquals(jsonPath, { - p2: '2', - p0: '0-added', - p1: {p10: '1.0-added', p12: '1.2-updated', p11: '1.1'}, - }); - - // Verify that trying to add before non-existent property, puts updated property at the - // end. - updater.createUpdate() - .addChange(['p3'], '3-added', {before: 'non-existent'}) - .addChange(['p1', 'p10'], '1.0-updated', {before: 'non-existent'}) - .writeChanges(jsonPath); - - expectJsonEquals(jsonPath, { - p2: '2', - p0: '0-added', - p1: {p12: '1.2-updated', p11: '1.1', p10: '1.0-updated'}, - p3: '3-added', - }); - }); - - it('should ignore positioning when updating an in-memory representation', () => { - const jsonObj = { - p20: '20', - p10: {p102: '10.2', p101: '10.1'}, - }; - const jsonPath = createJsonFile(jsonObj); - - updater.createUpdate() - .addChange(['p0'], '0-added', 'alphabetic') - .addChange(['p1'], '1-added', 'unimportant') - .addChange(['p2'], '2-added') - .addChange(['p20'], '20-updated', 'alphabetic') - .addChange(['p10', 'p103'], '10.3-added', {before: 'p102'}) - .addChange(['p10', 'p102'], '10.2-updated', {before: 'p103'}) - .writeChanges(jsonPath, jsonObj); - - expect(JSON.stringify(jsonObj)).toBe(JSON.stringify({ - p20: '20-updated', - p10: {p102: '10.2-updated', p101: '10.1', p103: '10.3-added'}, - p0: '0-added', - p1: '1-added', - p2: '2-added', - })); - }); - }); - }); -}); diff --git a/packages/compiler-cli/package.json b/packages/compiler-cli/package.json index d3355bda262..9a05eba0098 100644 --- a/packages/compiler-cli/package.json +++ b/packages/compiler-cli/package.json @@ -4,7 +4,6 @@ "description": "Angular - the compiler CLI for Node.js", "typings": "index.d.ts", "bin": { - "ngcc": "./bundles/ngcc/main-ngcc.js", "ngc": "./bundles/src/bin/ngc.js", "ng-xi18n": "./bundles/src/bin/ng_xi18n.js" }, @@ -17,10 +16,6 @@ "./package.json": { "default": "./package.json" }, - "./ngcc": { - "types": "./ngcc/index.d.ts", - "default": "./bundles/ngcc/index.js" - }, "./linker": { "types": "./linker/index.d.ts", "default": "./bundles/linker/index.js" @@ -44,12 +39,6 @@ "./private/tooling": { "types": "./private/tooling.d.ts", "default": "./bundles/private/tooling.js" - }, - "./ngcc/src/execution/cluster/ngcc_cluster_worker": { - "default": "./bundles/ngcc/src/execution/cluster/ngcc_cluster_worker.js" - }, - "./ngcc/src/locking/lock_file_with_child_process/ngcc_lock_unlocker": { - "default": "./bundles/ngcc/src/locking/lock_file_with_child_process/ngcc_lock_unlocker.js" } }, "dependencies": { @@ -58,8 +47,6 @@ "reflect-metadata": "^0.1.2", "chokidar": "^3.0.0", "convert-source-map": "^1.5.1", - "dependency-graph": "^0.11.0", - "magic-string": "^0.27.0", "semver": "^7.0.0", "tslib": "^2.3.0", "yargs": "^17.2.1" diff --git a/packages/core/BUILD.bazel b/packages/core/BUILD.bazel index 25c572ceac6..266dc0f48d0 100644 --- a/packages/core/BUILD.bazel +++ b/packages/core/BUILD.bazel @@ -66,7 +66,6 @@ ng_package( "//integration:__subpackages__", "//packages/bazel/test/ng_package:__pkg__", "//packages/compiler-cli/integrationtest:__pkg__", - "//packages/compiler-cli/ngcc/test:__pkg__", "//packages/compiler-cli/test:__pkg__", "//packages/compiler-cli/test/diagnostics:__pkg__", "//packages/compiler-cli/test/transformers:__pkg__", diff --git a/renovate.json b/renovate.json index 8dcef0ca272..ad7fc6c7540 100644 --- a/renovate.json +++ b/renovate.json @@ -17,14 +17,6 @@ "labels": ["target: patch", "area: build & ci", "action: review"], "ignorePaths": ["aio/content/demos/first-app/package.json"], "ignoreDeps": [ - "@angular/animations-12", - "@angular/common-12", - "@angular/core-12", - "@angular/forms-12", - "@angular/platform-browser-12", - "@angular/platform-browser-dynamic-12", - "@angular/platform-server-12", - "@angular/router-12", "@types/node", "@types/selenium-webdriver", "angular-1.5", diff --git a/yarn.lock b/yarn.lock index 2de0db39c2b..a5a6eb28f12 100644 --- a/yarn.lock +++ b/yarn.lock @@ -26,6 +26,13 @@ "@angular-devkit/core" "15.2.0-next.4" rxjs "6.6.7" +"@angular-devkit/architect@github:angular/angular-devkit-architect-builds#c8ac660d8": + version "0.1600.0-next.0" + resolved "https://codeload.github.com/angular/angular-devkit-architect-builds/tar.gz/29524724b52b968d806ecc8ba247ca4b11554635" + dependencies: + "@angular-devkit/core" "github:angular/angular-devkit-core-builds#c8ac660d8" + rxjs "6.6.7" + "@angular-devkit/build-angular@15.2.0-next.3": version "15.2.0-next.3" resolved "https://registry.yarnpkg.com/@angular-devkit/build-angular/-/build-angular-15.2.0-next.3.tgz#a74d77afdb8750f86c53969fa3fb64d09c48eb8b" @@ -93,18 +100,18 @@ optionalDependencies: esbuild "0.17.5" -"@angular-devkit/build-angular@15.2.0-next.4": - version "15.2.0-next.4" - resolved "https://registry.yarnpkg.com/@angular-devkit/build-angular/-/build-angular-15.2.0-next.4.tgz#e3e700c8447b721f630d5f415294dd574d32616a" - integrity sha512-F7iWNWV6gHushoxYfqLNHtjkcyCs7lFhG5Wq/ZyD3jdSbsEon1Yuhl3O9751R8NyCPAhEAEVhSStfHwsF1Nu+g== +"@angular-devkit/build-angular@https://github.com/angular/angular-devkit-build-angular-builds.git#5e54a29f44b099e58e63af002edc7975440aee8f": + version "16.0.0-next.0" + resolved "https://github.com/angular/angular-devkit-build-angular-builds.git#5e54a29f44b099e58e63af002edc7975440aee8f" dependencies: "@ampproject/remapping" "2.2.0" - "@angular-devkit/architect" "0.1502.0-next.4" - "@angular-devkit/build-webpack" "0.1502.0-next.4" - "@angular-devkit/core" "15.2.0-next.4" + "@angular-devkit/architect" "github:angular/angular-devkit-architect-builds#c8ac660d8" + "@angular-devkit/build-webpack" "github:angular/angular-devkit-build-webpack-builds#c8ac660d8" + "@angular-devkit/core" "github:angular/angular-devkit-core-builds#c8ac660d8" "@babel/core" "7.20.12" "@babel/generator" "7.20.14" "@babel/helper-annotate-as-pure" "7.18.6" + "@babel/helper-split-export-declaration" "7.18.6" "@babel/plugin-proposal-async-generator-functions" "7.20.7" "@babel/plugin-transform-async-to-generator" "7.20.7" "@babel/plugin-transform-runtime" "7.19.6" @@ -112,7 +119,7 @@ "@babel/runtime" "7.20.13" "@babel/template" "7.20.7" "@discoveryjs/json-ext" "0.5.7" - "@ngtools/webpack" "15.2.0-next.4" + "@ngtools/webpack" "github:angular/ngtools-webpack-builds#c8ac660d8" ansi-colors "4.1.3" autoprefixer "10.4.13" babel-loader "9.1.2" @@ -123,7 +130,7 @@ copy-webpack-plugin "11.0.0" critters "0.0.16" css-loader "6.7.3" - esbuild-wasm "0.17.6" + esbuild-wasm "0.17.8" glob "8.1.0" https-proxy-agent "5.0.1" inquirer "8.2.4" @@ -133,17 +140,17 @@ less-loader "11.1.0" license-webpack-plugin "4.0.2" loader-utils "3.2.1" - magic-string "0.27.0" + magic-string "0.29.0" mini-css-extract-plugin "2.7.2" open "8.4.1" ora "5.4.1" - parse5-html-rewriting-stream "6.0.1" + parse5-html-rewriting-stream "7.0.0" piscina "3.2.0" postcss "8.4.21" postcss-loader "7.0.2" resolve-url-loader "5.0.0" rxjs "6.6.7" - sass "1.58.0" + sass "1.58.1" sass-loader "13.2.0" semver "7.3.8" source-map-loader "4.0.1" @@ -158,7 +165,7 @@ webpack-merge "5.8.0" webpack-subresource-integrity "5.1.0" optionalDependencies: - esbuild "0.17.6" + esbuild "0.17.8" "@angular-devkit/build-optimizer@0.14.0-beta.5": version "0.14.0-beta.5" @@ -178,12 +185,11 @@ "@angular-devkit/architect" "0.1502.0-next.3" rxjs "6.6.7" -"@angular-devkit/build-webpack@0.1502.0-next.4": - version "0.1502.0-next.4" - resolved "https://registry.yarnpkg.com/@angular-devkit/build-webpack/-/build-webpack-0.1502.0-next.4.tgz#8e4f68627dca010cd6d0aa5b5b3dae93088abe3a" - integrity sha512-JY/b/sLmDD9tRUq24qRpEq0YVnlnOraHV6I+jFqilUS/WeMIORSGC4XlLTPaoqXGLYoWZIEK5c+a4wgtRehNag== +"@angular-devkit/build-webpack@github:angular/angular-devkit-build-webpack-builds#c8ac660d8": + version "0.1600.0-next.0" + resolved "https://codeload.github.com/angular/angular-devkit-build-webpack-builds/tar.gz/ca58571eb53359906662759cdc57d04a3ab31c9c" dependencies: - "@angular-devkit/architect" "0.1502.0-next.4" + "@angular-devkit/architect" "github:angular/angular-devkit-architect-builds#c8ac660d8" rxjs "6.6.7" "@angular-devkit/core@15.2.0-next.3": @@ -208,6 +214,16 @@ rxjs "6.6.7" source-map "0.7.4" +"@angular-devkit/core@github:angular/angular-devkit-core-builds#c8ac660d8": + version "16.0.0-next.0" + resolved "https://codeload.github.com/angular/angular-devkit-core-builds/tar.gz/211da8439134666a58f6ba5a6c607288d099a8bf" + dependencies: + ajv "8.12.0" + ajv-formats "2.1.1" + jsonc-parser "3.2.0" + rxjs "6.6.7" + source-map "0.7.4" + "@angular-devkit/schematics@15.2.0-next.4": version "15.2.0-next.4" resolved "https://registry.yarnpkg.com/@angular-devkit/schematics/-/schematics-15.2.0-next.4.tgz#a48510038ed7a5a3ec3bf6bdf1160edab5daab35" @@ -219,13 +235,6 @@ ora "5.4.1" rxjs "6.6.7" -"@angular/animations-12@npm:@angular/animations@12.2.13": - version "12.2.13" - resolved "https://registry.yarnpkg.com/@angular/animations/-/animations-12.2.13.tgz#909b79c56a85bb31c0605ed419850340b5580be9" - integrity sha512-qpdmvu+nxsKnimJ3Hc1joNuzK7xXYyE+VtNMk4K77Ao/9+5C/juGMce85DhqZCcu1xraZ3YYIrzYmL/GgdUK4g== - dependencies: - tslib "^2.2.0" - "@angular/benchpress@0.3.0": version "0.3.0" resolved "https://registry.yarnpkg.com/@angular/benchpress/-/benchpress-0.3.0.tgz#0adf14156ff31dfee3ef607c0088e9ee6fb08777" @@ -236,7 +245,6 @@ "@angular/build-tooling@https://github.com/angular/dev-infra-private-build-tooling-builds.git#8b49b0b38e67191b52367d8d0a6317d00b7d5100": version "0.0.0-accc3c96df0d40ff639070132acb0699a58bfcf8" - uid "8b49b0b38e67191b52367d8d0a6317d00b7d5100" resolved "https://github.com/angular/dev-infra-private-build-tooling-builds.git#8b49b0b38e67191b52367d8d0a6317d00b7d5100" dependencies: "@angular-devkit/build-angular" "15.2.0-next.3" @@ -307,20 +315,6 @@ symbol-observable "4.0.0" yargs "17.6.2" -"@angular/common-12@npm:@angular/common@12.2.13": - version "12.2.13" - resolved "https://registry.yarnpkg.com/@angular/common/-/common-12.2.13.tgz#fe52ffae8668c1dec41b26cdc3a749101399e1fe" - integrity sha512-I1t/jl9ojCwTJKT7PKHnk23D+vMHTHS/9qZ2nndCjzGusMK8029nn8l3tHAkwefvxZWSaLAk1MgsVcP+rHQNsQ== - dependencies: - tslib "^2.2.0" - -"@angular/core-12@npm:@angular/core@12.2.13": - version "12.2.13" - resolved "https://registry.yarnpkg.com/@angular/core/-/core-12.2.13.tgz#fc40acbdcee5c5878bd249ddc0d643be724076c3" - integrity sha512-tZ5nAnmOi418JDaJIFiiP9z2JrluMJZvUvXO4QDUs52BXaL2yuP7MJ2LczWOVJXrBLZXeZGfjDjZmaFPA24grg== - dependencies: - tslib "^2.2.0" - "@angular/core@^13.0.0 || ^14.0.0-0": version "14.0.5" resolved "https://registry.yarnpkg.com/@angular/core/-/core-14.0.5.tgz#135db22c6cc2ea51fc8f504f1400a5453f73eec3" @@ -328,13 +322,6 @@ dependencies: tslib "^2.3.0" -"@angular/forms-12@npm:@angular/forms@12.2.13": - version "12.2.13" - resolved "https://registry.yarnpkg.com/@angular/forms/-/forms-12.2.13.tgz#4de90e88991c274b5d5e3ab0300267e161ba78f2" - integrity sha512-wa7I5R8sck2q+VWNL262GJTVxtpEHMys8Bt6oE+lyHB5zlZAgOXwAb8GE4XVwuc+BZU1Gvrocn21P/8KvDY1uw== - dependencies: - tslib "^2.2.0" - "@angular/material@15.2.0-next.4": version "15.2.0-next.4" resolved "https://registry.yarnpkg.com/@angular/material/-/material-15.2.0-next.4.tgz#d6212e5172cd3bafbcac980cdf15791903010f17" @@ -391,42 +378,11 @@ "@angular/ng-dev@https://github.com/angular/dev-infra-private-ng-dev-builds.git#045097fc5c47d0241b157d3b904df316c737d654": version "0.0.0-accc3c96df0d40ff639070132acb0699a58bfcf8" - uid "045097fc5c47d0241b157d3b904df316c737d654" resolved "https://github.com/angular/dev-infra-private-ng-dev-builds.git#045097fc5c47d0241b157d3b904df316c737d654" dependencies: "@yarnpkg/lockfile" "^1.1.0" typescript "~4.9.0" -"@angular/platform-browser-12@npm:@angular/platform-browser@12.2.13": - version "12.2.13" - resolved "https://registry.yarnpkg.com/@angular/platform-browser/-/platform-browser-12.2.13.tgz#769fb148b2128663d6b405d2a41023f2cdaa1c95" - integrity sha512-Ua8m2GtG2msqz/Mr/MK7s8RXud554x8cup2THVAwetyTaY5th/1LOZX0hhDIhfsxBCLHnC53LRhMbSsI0cikOg== - dependencies: - tslib "^2.2.0" - -"@angular/platform-browser-dynamic-12@npm:@angular/platform-browser-dynamic@12.2.13": - version "12.2.13" - resolved "https://registry.yarnpkg.com/@angular/platform-browser-dynamic/-/platform-browser-dynamic-12.2.13.tgz#9ee6309f511da419d5d80a1a8279adc806e92f04" - integrity sha512-a7y7R3vOXhMAk9uQWK5/23DefahuF0UYJSFM/wKeVo5zR+qOCVCTfafkJMcWbuZWTrSDbVafJ1xbcWnu3+TkCg== - dependencies: - tslib "^2.2.0" - -"@angular/platform-server-12@npm:@angular/platform-server@12.2.13": - version "12.2.13" - resolved "https://registry.yarnpkg.com/@angular/platform-server/-/platform-server-12.2.13.tgz#a293433a7db8da97b208c5641eb9c95c6ca0bd12" - integrity sha512-QHz6k0CUYE2MncaBIL7+GLAJ7z6YADgoGNzeO0kDMnMc+vo/C1gf/LtjNNaPOEHqzyYtBwlaCB8ldFaS6FpBMg== - dependencies: - domino "^2.1.2" - tslib "^2.2.0" - xhr2 "^0.2.0" - -"@angular/router-12@npm:@angular/router@12.2.13": - version "12.2.13" - resolved "https://registry.yarnpkg.com/@angular/router/-/router-12.2.13.tgz#e5b939a9675f9d4834fee3e75eb8d040ef63b3b5" - integrity sha512-HZL/Pzp6I7fQiMLrzfEzhnqhgNcGcFjBgMMOoLp5IA1IV26rp1NU6zYJzPrXtopBx7XLl8BECehAwFqrJsu/PQ== - dependencies: - tslib "^2.2.0" - "@apidevtools/json-schema-ref-parser@^9.0.3": version "9.0.9" resolved "https://registry.yarnpkg.com/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-9.0.9.tgz#d720f9256e3609621280584f2b47ae165359268b" @@ -982,7 +938,7 @@ dependencies: "@babel/types" "^7.18.9" -"@babel/helper-split-export-declaration@^7.18.6": +"@babel/helper-split-export-declaration@7.18.6", "@babel/helper-split-export-declaration@^7.18.6": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz#7367949bc75b20c6d5a5d4a97bba2824ae8ef075" integrity sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA== @@ -2308,220 +2264,220 @@ resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.17.5.tgz#a145f43018e639bed94ed637369e2dcdd6bf9ea2" integrity sha512-KHWkDqYAMmKZjY4RAN1PR96q6UOtfkWlTS8uEwWxdLtkRt/0F/csUhXIrVfaSIFxnscIBMPynGfhsMwQDRIBQw== -"@esbuild/android-arm64@0.17.6": - version "0.17.6" - resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.17.6.tgz#b11bd4e4d031bb320c93c83c137797b2be5b403b" - integrity sha512-YnYSCceN/dUzUr5kdtUzB+wZprCafuD89Hs0Aqv9QSdwhYQybhXTaSTcrl6X/aWThn1a/j0eEpUBGOE7269REg== +"@esbuild/android-arm64@0.17.8": + version "0.17.8" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.17.8.tgz#b3d5b65a3b2e073a6c7ee36b1f3c30c8f000315b" + integrity sha512-oa/N5j6v1svZQs7EIRPqR8f+Bf8g6HBDjD/xHC02radE/NjKHK7oQmtmLxPs1iVwYyvE+Kolo6lbpfEQ9xnhxQ== "@esbuild/android-arm@0.17.5": version "0.17.5" resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.17.5.tgz#9fa2deff7fc5d180bb4ecff70beea3a95ac44251" integrity sha512-crmPUzgCmF+qZXfl1YkiFoUta2XAfixR1tEnr/gXIixE+WL8Z0BGqfydP5oox0EUOgQMMRgtATtakyAcClQVqQ== -"@esbuild/android-arm@0.17.6": - version "0.17.6" - resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.17.6.tgz#ac6b5674da2149997f6306b3314dae59bbe0ac26" - integrity sha512-bSC9YVUjADDy1gae8RrioINU6e1lCkg3VGVwm0QQ2E1CWcC4gnMce9+B6RpxuSsrsXsk1yojn7sp1fnG8erE2g== +"@esbuild/android-arm@0.17.8": + version "0.17.8" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.17.8.tgz#c41e496af541e175369d48164d0cf01a5f656cf6" + integrity sha512-0/rb91GYKhrtbeglJXOhAv9RuYimgI8h623TplY2X+vA4EXnk3Zj1fXZreJ0J3OJJu1bwmb0W7g+2cT/d8/l/w== "@esbuild/android-x64@0.17.5": version "0.17.5" resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.17.5.tgz#145fc61f810400e65a56b275280d1422a102c2ef" integrity sha512-8fI/AnIdmWz/+1iza2WrCw8kwXK9wZp/yZY/iS8ioC+U37yJCeppi9EHY05ewJKN64ASoBIseufZROtcFnX5GA== -"@esbuild/android-x64@0.17.6": - version "0.17.6" - resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.17.6.tgz#18c48bf949046638fc209409ff684c6bb35a5462" - integrity sha512-MVcYcgSO7pfu/x34uX9u2QIZHmXAB7dEiLQC5bBl5Ryqtpj9lT2sg3gNDEsrPEmimSJW2FXIaxqSQ501YLDsZQ== +"@esbuild/android-x64@0.17.8": + version "0.17.8" + resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.17.8.tgz#080fa67c29be77f5a3ca5ee4cc78d5bf927e3a3b" + integrity sha512-bTliMLqD7pTOoPg4zZkXqCDuzIUguEWLpeqkNfC41ODBHwoUgZ2w5JBeYimv4oP6TDVocoYmEhZrCLQTrH89bg== "@esbuild/darwin-arm64@0.17.5": version "0.17.5" resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.17.5.tgz#61fb0546aa4bae0850817d6e0d008b1cb3f64b49" integrity sha512-EAvaoyIySV6Iif3NQCglUNpnMfHSUgC5ugt2efl3+QDntucJe5spn0udNZjTgNi6tKVqSceOw9tQ32liNZc1Xw== -"@esbuild/darwin-arm64@0.17.6": - version "0.17.6" - resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.17.6.tgz#b3fe19af1e4afc849a07c06318124e9c041e0646" - integrity sha512-bsDRvlbKMQMt6Wl08nHtFz++yoZHsyTOxnjfB2Q95gato+Yi4WnRl13oC2/PJJA9yLCoRv9gqT/EYX0/zDsyMA== +"@esbuild/darwin-arm64@0.17.8": + version "0.17.8" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.17.8.tgz#053622bf9a82f43d5c075b7818e02618f7b4a397" + integrity sha512-ghAbV3ia2zybEefXRRm7+lx8J/rnupZT0gp9CaGy/3iolEXkJ6LYRq4IpQVI9zR97ID80KJVoUlo3LSeA/sMAg== "@esbuild/darwin-x64@0.17.5": version "0.17.5" resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.17.5.tgz#54b770f0c49f524ae9ba24c85d6dea8b521f610d" integrity sha512-ha7QCJh1fuSwwCgoegfdaljowwWozwTDjBgjD3++WAy/qwee5uUi1gvOg2WENJC6EUyHBOkcd3YmLDYSZ2TPPA== -"@esbuild/darwin-x64@0.17.6": - version "0.17.6" - resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.17.6.tgz#f4dacd1ab21e17b355635c2bba6a31eba26ba569" - integrity sha512-xh2A5oPrYRfMFz74QXIQTQo8uA+hYzGWJFoeTE8EvoZGHb+idyV4ATaukaUvnnxJiauhs/fPx3vYhU4wiGfosg== +"@esbuild/darwin-x64@0.17.8": + version "0.17.8" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.17.8.tgz#8a1aadb358d537d8efad817bb1a5bff91b84734b" + integrity sha512-n5WOpyvZ9TIdv2V1K3/iIkkJeKmUpKaCTdun9buhGRWfH//osmUjlv4Z5mmWdPWind/VGcVxTHtLfLCOohsOXw== "@esbuild/freebsd-arm64@0.17.5": version "0.17.5" resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.17.5.tgz#be1dd18b7b9411f10bdc362ba8bff16386175367" integrity sha512-VbdXJkn2aI2pQ/wxNEjEcnEDwPpxt3CWWMFYmO7CcdFBoOsABRy2W8F3kjbF9F/pecEUDcI3b5i2w+By4VQFPg== -"@esbuild/freebsd-arm64@0.17.6": - version "0.17.6" - resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.17.6.tgz#ea4531aeda70b17cbe0e77b0c5c36298053855b4" - integrity sha512-EnUwjRc1inT4ccZh4pB3v1cIhohE2S4YXlt1OvI7sw/+pD+dIE4smwekZlEPIwY6PhU6oDWwITrQQm5S2/iZgg== +"@esbuild/freebsd-arm64@0.17.8": + version "0.17.8" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.17.8.tgz#e6738d0081ba0721a5c6c674e84c6e7fcea61989" + integrity sha512-a/SATTaOhPIPFWvHZDoZYgxaZRVHn0/LX1fHLGfZ6C13JqFUZ3K6SMD6/HCtwOQ8HnsNaEeokdiDSFLuizqv5A== "@esbuild/freebsd-x64@0.17.5": version "0.17.5" resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.17.5.tgz#c9c1960fa3e1eada4e5d4be2a11a2f04ce14198f" integrity sha512-olgGYND1/XnnWxwhjtY3/ryjOG/M4WfcA6XH8dBTH1cxMeBemMODXSFhkw71Kf4TeZFFTN25YOomaNh0vq2iXg== -"@esbuild/freebsd-x64@0.17.6": - version "0.17.6" - resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.17.6.tgz#1896170b3c9f63c5e08efdc1f8abc8b1ed7af29f" - integrity sha512-Uh3HLWGzH6FwpviUcLMKPCbZUAFzv67Wj5MTwK6jn89b576SR2IbEp+tqUHTr8DIl0iDmBAf51MVaP7pw6PY5Q== +"@esbuild/freebsd-x64@0.17.8": + version "0.17.8" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.17.8.tgz#1855e562f2b730f4483f6e94086e9e2597feb4c3" + integrity sha512-xpFJb08dfXr5+rZc4E+ooZmayBW6R3q59daCpKZ/cDU96/kvDM+vkYzNeTJCGd8rtO6fHWMq5Rcv/1cY6p6/0Q== "@esbuild/linux-arm64@0.17.5": version "0.17.5" resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.17.5.tgz#34d96d11c6899017ecae42fb97de8e0c3282902f" integrity sha512-8a0bqSwu3OlLCfu2FBbDNgQyBYdPJh1B9PvNX7jMaKGC9/KopgHs37t+pQqeMLzcyRqG6z55IGNQAMSlCpBuqg== -"@esbuild/linux-arm64@0.17.6": - version "0.17.6" - resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.17.6.tgz#967dfb951c6b2de6f2af82e96e25d63747f75079" - integrity sha512-bUR58IFOMJX523aDVozswnlp5yry7+0cRLCXDsxnUeQYJik1DukMY+apBsLOZJblpH+K7ox7YrKrHmJoWqVR9w== +"@esbuild/linux-arm64@0.17.8": + version "0.17.8" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.17.8.tgz#481da38952721a3fdb77c17a36ceaacc4270b5c5" + integrity sha512-v3iwDQuDljLTxpsqQDl3fl/yihjPAyOguxuloON9kFHYwopeJEf1BkDXODzYyXEI19gisEsQlG1bM65YqKSIww== "@esbuild/linux-arm@0.17.5": version "0.17.5" resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.17.5.tgz#86332e6293fd713a54ab299a5e2ed7c60c9e1c07" integrity sha512-YBdCyQwA3OQupi6W2/WO4FnI+NWFWe79cZEtlbqSESOHEg7a73htBIRiE6uHPQe7Yp5E4aALv+JxkRLGEUL7tw== -"@esbuild/linux-arm@0.17.6": - version "0.17.6" - resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.17.6.tgz#097a0ee2be39fed3f37ea0e587052961e3bcc110" - integrity sha512-7YdGiurNt7lqO0Bf/U9/arrPWPqdPqcV6JCZda4LZgEn+PTQ5SMEI4MGR52Bfn3+d6bNEGcWFzlIxiQdS48YUw== +"@esbuild/linux-arm@0.17.8": + version "0.17.8" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.17.8.tgz#18127072b270bb6321c6d11be20bfd30e0d6ad17" + integrity sha512-6Ij8gfuGszcEwZpi5jQIJCVIACLS8Tz2chnEBfYjlmMzVsfqBP1iGmHQPp7JSnZg5xxK9tjCc+pJ2WtAmPRFVA== "@esbuild/linux-ia32@0.17.5": version "0.17.5" resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.17.5.tgz#7bd9185c844e7dfce6a01dfdec584e115602a8c4" integrity sha512-uCwm1r/+NdP7vndctgq3PoZrnmhmnecWAr114GWMRwg2QMFFX+kIWnp7IO220/JLgnXK/jP7VKAFBGmeOYBQYQ== -"@esbuild/linux-ia32@0.17.6": - version "0.17.6" - resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.17.6.tgz#a38a789d0ed157495a6b5b4469ec7868b59e5278" - integrity sha512-ujp8uoQCM9FRcbDfkqECoARsLnLfCUhKARTP56TFPog8ie9JG83D5GVKjQ6yVrEVdMie1djH86fm98eY3quQkQ== +"@esbuild/linux-ia32@0.17.8": + version "0.17.8" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.17.8.tgz#ee400af7b3bc69e8ca2e593ca35156ffb9abd54f" + integrity sha512-8svILYKhE5XetuFk/B6raFYIyIqydQi+GngEXJgdPdI7OMKUbSd7uzR02wSY4kb53xBrClLkhH4Xs8P61Q2BaA== "@esbuild/linux-loong64@0.17.5": version "0.17.5" resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.17.5.tgz#2907d4120c7b3642b96be6014f77e7624c378eea" integrity sha512-3YxhSBl5Sb6TtBjJu+HP93poBruFzgXmf3PVfIe4xOXMj1XpxboYZyw3W8BhoX/uwxzZz4K1I99jTE/5cgDT1g== -"@esbuild/linux-loong64@0.17.6": - version "0.17.6" - resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.17.6.tgz#ae3983d0fb4057883c8246f57d2518c2af7cf2ad" - integrity sha512-y2NX1+X/Nt+izj9bLoiaYB9YXT/LoaQFYvCkVD77G/4F+/yuVXYCWz4SE9yr5CBMbOxOfBcy/xFL4LlOeNlzYQ== +"@esbuild/linux-loong64@0.17.8": + version "0.17.8" + resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.17.8.tgz#8c509d8a454693d39824b83b3f66c400872fce82" + integrity sha512-B6FyMeRJeV0NpyEOYlm5qtQfxbdlgmiGdD+QsipzKfFky0K5HW5Td6dyK3L3ypu1eY4kOmo7wW0o94SBqlqBSA== "@esbuild/linux-mips64el@0.17.5": version "0.17.5" resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.17.5.tgz#fc98be741e8080ecd13b404d5fca5302d3835bf4" integrity sha512-Hy5Z0YVWyYHdtQ5mfmfp8LdhVwGbwVuq8mHzLqrG16BaMgEmit2xKO+iDakHs+OetEx0EN/2mUzDdfdktI+Nmg== -"@esbuild/linux-mips64el@0.17.6": - version "0.17.6" - resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.17.6.tgz#15fbbe04648d944ec660ee5797febdf09a9bd6af" - integrity sha512-09AXKB1HDOzXD+j3FdXCiL/MWmZP0Ex9eR8DLMBVcHorrWJxWmY8Nms2Nm41iRM64WVx7bA/JVHMv081iP2kUA== +"@esbuild/linux-mips64el@0.17.8": + version "0.17.8" + resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.17.8.tgz#f2b0d36e63fb26bc3f95b203b6a80638292101ca" + integrity sha512-CCb67RKahNobjm/eeEqeD/oJfJlrWyw29fgiyB6vcgyq97YAf3gCOuP6qMShYSPXgnlZe/i4a8WFHBw6N8bYAA== "@esbuild/linux-ppc64@0.17.5": version "0.17.5" resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.17.5.tgz#ea12e8f6b290a613ac4903c9e00835c69ced065c" integrity sha512-5dbQvBLbU/Y3Q4ABc9gi23hww1mQcM7KZ9KBqabB7qhJswYMf8WrDDOSw3gdf3p+ffmijMd28mfVMvFucuECyg== -"@esbuild/linux-ppc64@0.17.6": - version "0.17.6" - resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.17.6.tgz#38210094e8e1a971f2d1fd8e48462cc65f15ef19" - integrity sha512-AmLhMzkM8JuqTIOhxnX4ubh0XWJIznEynRnZAVdA2mMKE6FAfwT2TWKTwdqMG+qEaeyDPtfNoZRpJbD4ZBv0Tg== +"@esbuild/linux-ppc64@0.17.8": + version "0.17.8" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.17.8.tgz#1e628be003e036e90423716028cc884fe5ba25bd" + integrity sha512-bytLJOi55y55+mGSdgwZ5qBm0K9WOCh0rx+vavVPx+gqLLhxtSFU0XbeYy/dsAAD6xECGEv4IQeFILaSS2auXw== "@esbuild/linux-riscv64@0.17.5": version "0.17.5" resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.17.5.tgz#ce47b15fd4227eeb0590826e41bdc430c5bfd06c" integrity sha512-fp/KUB/ZPzEWGTEUgz9wIAKCqu7CjH1GqXUO2WJdik1UNBQ7Xzw7myIajpxztE4Csb9504ERiFMxZg5KZ6HlZQ== -"@esbuild/linux-riscv64@0.17.6": - version "0.17.6" - resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.17.6.tgz#bc3c66d5578c3b9951a6ed68763f2a6856827e4a" - integrity sha512-Y4Ri62PfavhLQhFbqucysHOmRamlTVK10zPWlqjNbj2XMea+BOs4w6ASKwQwAiqf9ZqcY9Ab7NOU4wIgpxwoSQ== +"@esbuild/linux-riscv64@0.17.8": + version "0.17.8" + resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.17.8.tgz#419a815cb4c3fb9f1b78ef5295f5b48b8bf6427a" + integrity sha512-2YpRyQJmKVBEHSBLa8kBAtbhucaclb6ex4wchfY0Tj3Kg39kpjeJ9vhRU7x4mUpq8ISLXRXH1L0dBYjAeqzZAw== "@esbuild/linux-s390x@0.17.5": version "0.17.5" resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.17.5.tgz#962fa540d7498967270eb1d4b9ac6c4a4f339735" integrity sha512-kRV3yw19YDqHTp8SfHXfObUFXlaiiw4o2lvT1XjsPZ++22GqZwSsYWJLjMi1Sl7j9qDlDUduWDze/nQx0d6Lzw== -"@esbuild/linux-s390x@0.17.6": - version "0.17.6" - resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.17.6.tgz#d7ba7af59285f63cfce6e5b7f82a946f3e6d67fc" - integrity sha512-SPUiz4fDbnNEm3JSdUW8pBJ/vkop3M1YwZAVwvdwlFLoJwKEZ9L98l3tzeyMzq27CyepDQ3Qgoba44StgbiN5Q== +"@esbuild/linux-s390x@0.17.8": + version "0.17.8" + resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.17.8.tgz#291c49ae5c3d11d226352755c0835911fe1a9e5c" + integrity sha512-QgbNY/V3IFXvNf11SS6exkpVcX0LJcob+0RWCgV9OiDAmVElnxciHIisoSix9uzYzScPmS6dJFbZULdSAEkQVw== "@esbuild/linux-x64@0.17.5": version "0.17.5" resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.17.5.tgz#9fa52884c3d876593a522aa1d4df43b717907050" integrity sha512-vnxuhh9e4pbtABNLbT2ANW4uwQ/zvcHRCm1JxaYkzSehugoFd5iXyC4ci1nhXU13mxEwCnrnTIiiSGwa/uAF1g== -"@esbuild/linux-x64@0.17.6": - version "0.17.6" - resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.17.6.tgz#ba51f8760a9b9370a2530f98964be5f09d90fed0" - integrity sha512-a3yHLmOodHrzuNgdpB7peFGPx1iJ2x6m+uDvhP2CKdr2CwOaqEFMeSqYAHU7hG+RjCq8r2NFujcd/YsEsFgTGw== +"@esbuild/linux-x64@0.17.8": + version "0.17.8" + resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.17.8.tgz#03199d91c76faf80bd54104f5cbf0a489bc39f6a" + integrity sha512-mM/9S0SbAFDBc4OPoyP6SEOo5324LpUxdpeIUUSrSTOfhHU9hEfqRngmKgqILqwx/0DVJBzeNW7HmLEWp9vcOA== "@esbuild/netbsd-x64@0.17.5": version "0.17.5" resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.17.5.tgz#47bb187b86aad9622051cb80c27e439b7d9e3a9a" integrity sha512-cigBpdiSx/vPy7doUyImsQQBnBjV5f1M99ZUlaJckDAJjgXWl6y9W17FIfJTy8TxosEF6MXq+fpLsitMGts2nA== -"@esbuild/netbsd-x64@0.17.6": - version "0.17.6" - resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.17.6.tgz#e84d6b6fdde0261602c1e56edbb9e2cb07c211b9" - integrity sha512-EanJqcU/4uZIBreTrnbnre2DXgXSa+Gjap7ifRfllpmyAU7YMvaXmljdArptTHmjrkkKm9BK6GH5D5Yo+p6y5A== +"@esbuild/netbsd-x64@0.17.8": + version "0.17.8" + resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.17.8.tgz#b436d767e1b21852f9ed212e2bb57f77203b0ae2" + integrity sha512-eKUYcWaWTaYr9zbj8GertdVtlt1DTS1gNBWov+iQfWuWyuu59YN6gSEJvFzC5ESJ4kMcKR0uqWThKUn5o8We6Q== "@esbuild/openbsd-x64@0.17.5": version "0.17.5" resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.17.5.tgz#abc55c35a1ed2bc3c5ede2ef50a3b2f87395009a" integrity sha512-VdqRqPVIjjZfkf40LrqOaVuhw9EQiAZ/GNCSM2UplDkaIzYVsSnycxcFfAnHdWI8Gyt6dO15KHikbpxwx+xHbw== -"@esbuild/openbsd-x64@0.17.6": - version "0.17.6" - resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.17.6.tgz#cf4b9fb80ce6d280a673d54a731d9c661f88b083" - integrity sha512-xaxeSunhQRsTNGFanoOkkLtnmMn5QbA0qBhNet/XLVsc+OVkpIWPHcr3zTW2gxVU5YOHFbIHR9ODuaUdNza2Vw== +"@esbuild/openbsd-x64@0.17.8": + version "0.17.8" + resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.17.8.tgz#d1481d8539e21d4729cd04a0450a26c2c8789e89" + integrity sha512-Vc9J4dXOboDyMXKD0eCeW0SIeEzr8K9oTHJU+Ci1mZc5njPfhKAqkRt3B/fUNU7dP+mRyralPu8QUkiaQn7iIg== "@esbuild/sunos-x64@0.17.5": version "0.17.5" resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.17.5.tgz#b83c080a2147662599a5d18b2ff47f07c93e03a0" integrity sha512-ItxPaJ3MBLtI4nK+mALLEoUs6amxsx+J1ibnfcYMkqaCqHST1AkF4aENpBehty3czqw64r/XqL+W9WqU6kc2Qw== -"@esbuild/sunos-x64@0.17.6": - version "0.17.6" - resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.17.6.tgz#a6838e246079b24d962b9dcb8d208a3785210a73" - integrity sha512-gnMnMPg5pfMkZvhHee21KbKdc6W3GR8/JuE0Da1kjwpK6oiFU3nqfHuVPgUX2rsOx9N2SadSQTIYV1CIjYG+xw== +"@esbuild/sunos-x64@0.17.8": + version "0.17.8" + resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.17.8.tgz#2cfb8126e079b2c00fd1bf095541e9f5c47877e4" + integrity sha512-0xvOTNuPXI7ft1LYUgiaXtpCEjp90RuBBYovdd2lqAFxje4sEucurg30M1WIm03+3jxByd3mfo+VUmPtRSVuOw== "@esbuild/win32-arm64@0.17.5": version "0.17.5" resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.17.5.tgz#2a4c41f427d9cf25b75f9d61493711a482106850" integrity sha512-4u2Q6qsJTYNFdS9zHoAi80spzf78C16m2wla4eJPh4kSbRv+BpXIfl6TmBSWupD8e47B1NrTfrOlEuco7mYQtg== -"@esbuild/win32-arm64@0.17.6": - version "0.17.6" - resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.17.6.tgz#ace0186e904d109ea4123317a3ba35befe83ac21" - integrity sha512-G95n7vP1UnGJPsVdKXllAJPtqjMvFYbN20e8RK8LVLhlTiSOH1sd7+Gt7rm70xiG+I5tM58nYgwWrLs6I1jHqg== +"@esbuild/win32-arm64@0.17.8": + version "0.17.8" + resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.17.8.tgz#7c6ecfd097ca23b82119753bf7072bbaefe51e3a" + integrity sha512-G0JQwUI5WdEFEnYNKzklxtBheCPkuDdu1YrtRrjuQv30WsYbkkoixKxLLv8qhJmNI+ATEWquZe/N0d0rpr55Mg== "@esbuild/win32-ia32@0.17.5": version "0.17.5" resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.17.5.tgz#7c14e3250725d0e2c21f89c98eb6abb520cba0e0" integrity sha512-KYlm+Xu9TXsfTWAcocLuISRtqxKp/Y9ZBVg6CEEj0O5J9mn7YvBKzAszo2j1ndyzUPk+op+Tie2PJeN+BnXGqQ== -"@esbuild/win32-ia32@0.17.6": - version "0.17.6" - resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.17.6.tgz#7fb3f6d4143e283a7f7dffc98a6baf31bb365c7e" - integrity sha512-96yEFzLhq5bv9jJo5JhTs1gI+1cKQ83cUpyxHuGqXVwQtY5Eq54ZEsKs8veKtiKwlrNimtckHEkj4mRh4pPjsg== +"@esbuild/win32-ia32@0.17.8": + version "0.17.8" + resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.17.8.tgz#cffec63c3cb0ef8563a04df4e09fa71056171d00" + integrity sha512-Fqy63515xl20OHGFykjJsMnoIWS+38fqfg88ClvPXyDbLtgXal2DTlhb1TfTX34qWi3u4I7Cq563QcHpqgLx8w== "@esbuild/win32-x64@0.17.5": version "0.17.5" resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.17.5.tgz#a8f3d26d8afc5186eccda265ceb1820b8e8830be" integrity sha512-XgA9qWRqby7xdYXuF6KALsn37QGBMHsdhmnpjfZtYxKxbTOwfnDM6MYi2WuUku5poNaX2n9XGVr20zgT/2QwCw== -"@esbuild/win32-x64@0.17.6": - version "0.17.6" - resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.17.6.tgz#563ff4277f1230a006472664fa9278a83dd124da" - integrity sha512-n6d8MOyUrNp6G4VSpRcgjs5xj4A91svJSaiwLIDWVWEsZtpN5FA9NlBbZHDmAJc2e8e6SF4tkBD3HAvPF+7igA== +"@esbuild/win32-x64@0.17.8": + version "0.17.8" + resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.17.8.tgz#200a0965cf654ac28b971358ecdca9cc5b44c335" + integrity sha512-1iuezdyDNngPnz8rLRDO2C/ZZ/emJLb72OsZeqQ6gL6Avko/XCXZw+NuxBSNhBAP13Hie418V7VMt9et1FMvpg== "@gar/promisify@^1.1.3": version "1.1.3" @@ -3478,10 +3434,9 @@ resolved "https://registry.yarnpkg.com/@ngtools/webpack/-/webpack-15.2.0-next.3.tgz#3fc8b605d1be5ad6a66b908b89847681319ee95e" integrity sha512-Je7rwxLPcpmkHIvOzzdhWr9xqK6FMi1lGh5rEW49E9O9E1ESfh5OMnC3pgC8gOEzrhcnOuXgV7JZ5AjJwCgmgQ== -"@ngtools/webpack@15.2.0-next.4": - version "15.2.0-next.4" - resolved "https://registry.yarnpkg.com/@ngtools/webpack/-/webpack-15.2.0-next.4.tgz#354d6096329a7f33e787d218385dbb610ad42bee" - integrity sha512-Ftqqo9AHVVbDLKS5q5AzN+InimGwCZQTCPfnA5WoDOPHjEDoA0Ekg+7IaRjRWpOd7lPYr9fh62KQLXVc2vIydg== +"@ngtools/webpack@github:angular/ngtools-webpack-builds#c8ac660d8": + version "16.0.0-next.0" + resolved "https://codeload.github.com/angular/ngtools-webpack-builds/tar.gz/c784933671856a6529d7dd560ba1026890898f57" "@nicolo-ribaudo/chokidar-2@2.1.8-no-fsevents.3": version "2.1.8-no-fsevents.3" @@ -7618,11 +7573,6 @@ depd@^1.1.2, depd@~1.1.2: resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9" integrity sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ== -dependency-graph@^0.11.0: - version "0.11.0" - resolved "https://registry.yarnpkg.com/dependency-graph/-/dependency-graph-0.11.0.tgz#ac0ce7ed68a54da22165a85e97a01d53f5eb2e27" - integrity sha512-JeMq7fEshyepOWDfcfHK06N3MhyPhz++vtqWhMT5O9A3K42rdsEDpfdVqjaqaAhsw6a+ZqeDvQVtD0hFHQWrzg== - dependency-tree@^9.0.0: version "9.0.0" resolved "https://registry.yarnpkg.com/dependency-tree/-/dependency-tree-9.0.0.tgz#9288dd6daf35f6510c1ea30d9894b75369aa50a2" @@ -7907,7 +7857,7 @@ domhandler@^4.2.0, domhandler@^4.3.1: dependencies: domelementtype "^2.2.0" -domino@^2.1.2, domino@~2.1.6: +domino@~2.1.6: version "2.1.6" resolved "https://registry.yarnpkg.com/domino/-/domino-2.1.6.tgz#fe4ace4310526e5e7b9d12c7de01b7f485a57ffe" integrity sha512-3VdM/SXBZX2omc9JF9nOPCtDaYQ67BGp5CoLpIQlO2KCAPETs8TcDHacF26jXadGbvUteZzRTeos2fhID5+ucQ== @@ -8149,7 +8099,7 @@ entities@^2.0.0: resolved "https://registry.yarnpkg.com/entities/-/entities-2.2.0.tgz#098dc90ebb83d8dffa089d55256b351d34c4da55" integrity sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A== -entities@^4.4.0: +entities@^4.3.0, entities@^4.4.0: version "4.4.0" resolved "https://registry.yarnpkg.com/entities/-/entities-4.4.0.tgz#97bdaba170339446495e653cfd2db78962900174" integrity sha512-oYp7156SP8LkeGD0GF85ad1X9Ai79WtRsZ2gxJqtBuzH+98YUV6jkHEKlZkMbcrjJjIVJNIDP/3WL9wQkoPbWA== @@ -8236,10 +8186,10 @@ esbuild-wasm@0.17.5: resolved "https://registry.yarnpkg.com/esbuild-wasm/-/esbuild-wasm-0.17.5.tgz#324d21d41033eaf58aa447feb186f0dab9f21819" integrity sha512-Sm34YFT8ENLbOLJeMWdbAwSXpMuYivp8KfJR/b+x74034XNkFAJPwgzUMVwu9wLzGd4APadwVUfXCLukmJwC9g== -esbuild-wasm@0.17.6: - version "0.17.6" - resolved "https://registry.yarnpkg.com/esbuild-wasm/-/esbuild-wasm-0.17.6.tgz#f5935d90c1104a1c04a3fbe5daaf7f79beaeb2fc" - integrity sha512-9Ldow2+kulEnGtOTbngHyiFIneIi+g7pJOz8cZQhW1KWKqsu9nCYDba2JlwsH/PJtAGNSTCrKBmaKYf8rJYvgQ== +esbuild-wasm@0.17.8: + version "0.17.8" + resolved "https://registry.yarnpkg.com/esbuild-wasm/-/esbuild-wasm-0.17.8.tgz#c2348306430c27613ee3cc9a955cdd54df29137a" + integrity sha512-zCmpxv95E0FuCmvdw1K836UHnj4EdiQnFfjTby35y3LAjRPtXMj3sbHDRHjbD8Mqg5lTwq3knacr/1qIFU51CQ== esbuild@0.17.5: version "0.17.5" @@ -8269,33 +8219,33 @@ esbuild@0.17.5: "@esbuild/win32-ia32" "0.17.5" "@esbuild/win32-x64" "0.17.5" -esbuild@0.17.6: - version "0.17.6" - resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.17.6.tgz#bbccd4433629deb6e0a83860b3b61da120ba4e01" - integrity sha512-TKFRp9TxrJDdRWfSsSERKEovm6v30iHnrjlcGhLBOtReE28Yp1VSBRfO3GTaOFMoxsNerx4TjrhzSuma9ha83Q== +esbuild@0.17.8: + version "0.17.8" + resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.17.8.tgz#f7f799abc7cdce3f0f2e3e0c01f120d4d55193b4" + integrity sha512-g24ybC3fWhZddZK6R3uD2iF/RIPnRpwJAqLov6ouX3hMbY4+tKolP0VMF3zuIYCaXun+yHwS5IPQ91N2BT191g== optionalDependencies: - "@esbuild/android-arm" "0.17.6" - "@esbuild/android-arm64" "0.17.6" - "@esbuild/android-x64" "0.17.6" - "@esbuild/darwin-arm64" "0.17.6" - "@esbuild/darwin-x64" "0.17.6" - "@esbuild/freebsd-arm64" "0.17.6" - "@esbuild/freebsd-x64" "0.17.6" - "@esbuild/linux-arm" "0.17.6" - "@esbuild/linux-arm64" "0.17.6" - "@esbuild/linux-ia32" "0.17.6" - "@esbuild/linux-loong64" "0.17.6" - "@esbuild/linux-mips64el" "0.17.6" - "@esbuild/linux-ppc64" "0.17.6" - "@esbuild/linux-riscv64" "0.17.6" - "@esbuild/linux-s390x" "0.17.6" - "@esbuild/linux-x64" "0.17.6" - "@esbuild/netbsd-x64" "0.17.6" - "@esbuild/openbsd-x64" "0.17.6" - "@esbuild/sunos-x64" "0.17.6" - "@esbuild/win32-arm64" "0.17.6" - "@esbuild/win32-ia32" "0.17.6" - "@esbuild/win32-x64" "0.17.6" + "@esbuild/android-arm" "0.17.8" + "@esbuild/android-arm64" "0.17.8" + "@esbuild/android-x64" "0.17.8" + "@esbuild/darwin-arm64" "0.17.8" + "@esbuild/darwin-x64" "0.17.8" + "@esbuild/freebsd-arm64" "0.17.8" + "@esbuild/freebsd-x64" "0.17.8" + "@esbuild/linux-arm" "0.17.8" + "@esbuild/linux-arm64" "0.17.8" + "@esbuild/linux-ia32" "0.17.8" + "@esbuild/linux-loong64" "0.17.8" + "@esbuild/linux-mips64el" "0.17.8" + "@esbuild/linux-ppc64" "0.17.8" + "@esbuild/linux-riscv64" "0.17.8" + "@esbuild/linux-s390x" "0.17.8" + "@esbuild/linux-x64" "0.17.8" + "@esbuild/netbsd-x64" "0.17.8" + "@esbuild/openbsd-x64" "0.17.8" + "@esbuild/sunos-x64" "0.17.8" + "@esbuild/win32-arm64" "0.17.8" + "@esbuild/win32-ia32" "0.17.8" + "@esbuild/win32-x64" "0.17.8" escalade@^3.1.1: version "3.1.1" @@ -11939,6 +11889,13 @@ magic-string@0.27.0, magic-string@^0.27.0: dependencies: "@jridgewell/sourcemap-codec" "^1.4.13" +magic-string@0.29.0: + version "0.29.0" + resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.29.0.tgz#f034f79f8c43dba4ae1730ffb5e8c4e084b16cf3" + integrity sha512-WcfidHrDjMY+eLjlU+8OvwREqHwpgCeKVBUpQ3OhYYuvfaYCUgcbuBzappNzZvg/v8onU3oQj+BYpkOJe9Iw4Q== + dependencies: + "@jridgewell/sourcemap-codec" "^1.4.13" + magic-string@^0.25.7: version "0.25.9" resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.25.9.tgz#de7f9faf91ef8a1c91d02c2e5314c8277dbcdd1c" @@ -13301,6 +13258,15 @@ parse5-html-rewriting-stream@6.0.1: parse5 "^6.0.1" parse5-sax-parser "^6.0.1" +parse5-html-rewriting-stream@7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/parse5-html-rewriting-stream/-/parse5-html-rewriting-stream-7.0.0.tgz#e376d3e762d2950ccbb6bb59823fc1d7e9fdac36" + integrity sha512-mazCyGWkmCRWDI15Zp+UiCqMp/0dgEmkZRvhlsqqKYr4SsVm/TvnSpD9fCvqCA2zoWJcfRym846ejWBBHRiYEg== + dependencies: + entities "^4.3.0" + parse5 "^7.0.0" + parse5-sax-parser "^7.0.0" + parse5-htmlparser2-tree-adapter@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-6.0.1.tgz#2cdf9ad823321140370d4dbf5d3e92c7c8ddc6e6" @@ -13315,12 +13281,19 @@ parse5-sax-parser@^6.0.1: dependencies: parse5 "^6.0.1" +parse5-sax-parser@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/parse5-sax-parser/-/parse5-sax-parser-7.0.0.tgz#4c05064254f0488676aca75fb39ca069ec96dee5" + integrity sha512-5A+v2SNsq8T6/mG3ahcz8ZtQ0OUFTatxPbeidoMB7tkJSGDY3tdfl4MHovtLQHkEn5CGxijNWRQHhRQ6IRpXKg== + dependencies: + parse5 "^7.0.0" + parse5@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/parse5/-/parse5-6.0.1.tgz#e1a1c085c569b3dc08321184f19a39cc27f7c30b" integrity sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw== -parse5@^7.1.2: +parse5@^7.0.0, parse5@^7.1.2: version "7.1.2" resolved "https://registry.yarnpkg.com/parse5/-/parse5-7.1.2.tgz#0736bebbfd77793823240a23b7fc5e010b7f8e32" integrity sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw== @@ -14743,10 +14716,10 @@ sass@1.57.1: immutable "^4.0.0" source-map-js ">=0.6.2 <2.0.0" -sass@1.58.0: - version "1.58.0" - resolved "https://registry.yarnpkg.com/sass/-/sass-1.58.0.tgz#ee8aea3ad5ea5c485c26b3096e2df6087d0bb1cc" - integrity sha512-PiMJcP33DdKtZ/1jSjjqVIKihoDc6yWmYr9K/4r3fVVIEDAluD0q7XZiRKrNJcPK3qkLRF/79DND1H5q1LBjgg== +sass@1.58.1: + version "1.58.1" + resolved "https://registry.yarnpkg.com/sass/-/sass-1.58.1.tgz#17ab0390076a50578ed0733f1cc45429e03405f6" + integrity sha512-bnINi6nPXbP1XNRaranMFEBZWUfdW/AF16Ql5+ypRxfTvCRTTKrLsMIakyDcayUt2t/RZotmL4kgJwNH5xO+bg== dependencies: chokidar ">=3.0.0 <4.0.0" immutable "^4.0.0" @@ -16211,7 +16184,7 @@ tslib@^1.13.0, tslib@^1.8.1, tslib@^1.9.0: resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== -tslib@^2.0.0, tslib@^2.0.1, tslib@^2.0.3, tslib@^2.1.0, tslib@^2.2.0, tslib@^2.3.0, tslib@^2.3.1: +tslib@^2.0.0, tslib@^2.0.1, tslib@^2.0.3, tslib@^2.1.0, tslib@^2.3.0, tslib@^2.3.1: version "2.4.0" resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.4.0.tgz#7cecaa7f073ce680a05847aa77be941098f36dc3" integrity sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ== @@ -17304,7 +17277,7 @@ xdg-basedir@^4.0.0: resolved "https://registry.yarnpkg.com/xdg-basedir/-/xdg-basedir-4.0.0.tgz#4bc8d9984403696225ef83a1573cbbcb4e79db13" integrity sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q== -xhr2@0.2.1, xhr2@^0.2.0: +xhr2@0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/xhr2/-/xhr2-0.2.1.tgz#4e73adc4f9cfec9cbd2157f73efdce3a5f108a93" integrity sha512-sID0rrVCqkVNUn8t6xuv9+6FViXjUVXq8H5rWOH2rz9fDNQEd4g0EA2XlcEdJXRz5BMEn4O1pJFdT+z4YHhoWw==