From 8c7807497b52680ca47a6f0580d39d7bd1141a69 Mon Sep 17 00:00:00 2001 From: Laura Harker Date: Fri, 10 May 2024 16:46:09 -0700 Subject: [PATCH] Internal change PiperOrigin-RevId: 632636968 --- README.md | 207 +--- demo/package.json | 2 +- package.json | 4 +- src/annotator_host.ts | 5 +- src/cli_support.ts | 15 +- src/closure_externs.js | 4 +- src/clutz.ts | 247 ++-- src/decorator_downlevel_transformer.ts | 313 +++-- src/decorators.ts | 238 ++-- src/enum_transformer.ts | 136 ++- src/externs.ts | 383 ++++-- src/fileoverview_comment_transformer.ts | 214 ++-- src/googmodule.ts | 885 ++++++++------ src/jsdoc.ts | 240 +++- src/jsdoc_transformer.ts | 1039 +++++++++++------ src/module_type_translator.ts | 412 ++++--- src/ns_transformer.ts | 305 +++-- src/path.ts | 13 +- src/summary.ts | 12 +- src/transformer_util.ts | 241 ++-- src/ts_migration_exports_shim.ts | 365 +++--- src/tsickle.ts | 226 ++-- src/tsickle_declaration_marker.ts | 38 - src/type_translator.ts | 451 ++++--- test/closure.ts | 13 +- test/decorator_downlevel_transformer_test.ts | 58 +- test/e2e_closure_test.ts | 113 +- test/golden_tsickle_test.ts | 126 +- test/googmodule_test.ts | 442 ++++--- test/jsdoc_test.ts | 35 +- test/test_support.ts | 186 ++- .../goog_colon_and_clutz_ref.ts | 12 +- .../migrated_default_type.ts | 4 +- .../migrated_default_value.ts | 4 +- test/tsickle_test.ts | 288 +++-- test/type_translator_test.ts | 47 +- test_files/abstract/abstract.js | 2 - test_files/async_functions/async_functions.js | 2 - test_files/augment/externs.js | 2 - test_files/augment/user.js | 2 - test_files/basic.untyped/basic.untyped.js | 2 - test_files/cast_extends/cast_extends.js | 2 - test_files/class.untyped/class.js | 2 - test_files/class/class.js | 2 - test_files/clutz.no_externs/import_default.js | 2 - .../clutz2_output_demo8.d.ts | 18 - .../clutz_output_demo1.d.ts | 16 - .../clutz_output_demo2.d.ts | 16 - .../clutz_output_demo3.d.ts | 16 - .../clutz_output_demo4.d.ts | 13 - .../clutz_output_demo5.d.ts | 17 - .../clutz_output_demo6.d.ts | 18 - .../clutz_output_demo7.d.ts | 11 - .../user_code.d.ts | 53 - .../user_code.ts | 69 -- .../clutz_type_value.no_externs/user.js | 2 - test_files/comments/comments.js | 2 - test_files/comments/freestanding_jsdoc.js | 25 + test_files/comments/freestanding_jsdoc.ts | 15 + test_files/comments/trailing_no_semicolon.js | 20 + test_files/comments/trailing_no_semicolon.ts | 17 + .../conditional_rest_tuple_type.js | 2 - test_files/ctors/ctors.js | 2 - test_files/debugger/user.js | 2 - test_files/decl_merge/imported_inner_decl.js | 2 - test_files/decl_merge/inner_class.js | 2 - test_files/decl_merge/inner_enum.js | 2 - test_files/decl_merge/inner_interface.js | 18 +- test_files/decl_merge/inner_interface.ts | 2 +- test_files/decl_merge/inner_typedef.js | 2 - test_files/decl_merge/outer_enum.js | 29 + test_files/decl_merge/outer_enum.ts | 20 + test_files/decl_merge/rejected_ns.js | 44 +- test_files/decl_merge/rejected_ns.ts | 15 +- test_files/declare_export/declare_export.js | 8 +- test_files/declare_export_dts/user.js | 2 - .../declare_import/declare_import_in_ts.js | 2 - test_files/declare_var_and_ns/externs.js | 2 - test_files/decorator/decorator.js | 2 - test_files/decorator/default_export.js | 2 - test_files/decorator/export_const.js | 2 - test_files/decorator/only_types.js | 2 - test_files/doc_params/doc_params.js | 2 - .../docs_on_ctor_param_properties.js | 2 - test_files/enum.no_nstransform/enum.js | 40 + test_files/enum.no_nstransform/enum.ts | 27 + test_files/enum.puretransform/enum.js | 21 + test_files/enum.puretransform/enum.ts | 17 + test_files/enum/enum.js | 5 +- test_files/enum/enum.ts | 1 + test_files/enum/enum_user.js | 2 - test_files/enum_ref_import/enum_ref_import.js | 2 - .../enum_value_literal_type.js | 8 +- test_files/eventmap/eventmap.js | 2 - test_files/export/export.js | 2 + test_files/export/export_helper.js | 4 +- test_files/export/export_helper_2.js | 2 - test_files/export/export_helper_3.js | 2 - test_files/export/export_star_imported.js | 4 +- test_files/export_declare_namespace/user.js | 2 - .../export_destructuring.js | 28 + .../export_destructuring.ts | 11 + .../export_local_type/export_local_type.js | 2 - test_files/export_merged/main.js | 2 - test_files/export_multi/export_multi.js | 2 - test_files/export_star_as_ns/star_as_ns.js | 2 - test_files/exporting_decorator/exporting.js | 2 - .../extend_and_implement.js | 2 - test_files/fields/fields.js | 2 - test_files/fields_no_ctor/fields_no_ctor.js | 2 - test_files/file_comment/before_import.js | 2 - .../file_comment/comment_before_class.js | 2 - .../comment_before_elided_import.js | 2 - test_files/file_comment/comment_before_var.js | 2 - test_files/file_comment/comment_no_tag.js | 4 +- test_files/file_comment/comment_with_text.js | 2 - test_files/file_comment/export_star.js | 2 - ...iew_comment_add_suppress_before_license.js | 2 - .../fileoverview_comment_merge_suppress.js | 6 +- .../fileoverview_in_comment_text.js | 2 - test_files/file_comment/latecomment_front.js | 4 +- test_files/file_comment/multiple_comments.js | 10 +- test_files/file_comment/side_effect_import.js | 2 - test_files/functions/functions.js | 2 - test_files/functions/two_jsdoc_blocks.js | 2 - test_files/generic_extends/user.js | 2 - test_files/generic_in_prop_access/user.js | 2 - .../generic_local_var/generic_local_var.js | 2 - test_files/generic_nested_classes/user.js | 2 - .../ignored_ambient_external_module/user.js | 2 - .../interface.js | 2 - .../implement_reexported_interface/user.js | 2 - test_files/implements/implements.js | 2 - .../clutz_input.d.ts | 14 - .../decluser.d.ts | 14 - .../decluser.ts | 5 - .../jsprovides.js | 7 - .../conflicting_multiple.js | 2 - .../conflicting_multiple_bystar.js | 2 - .../conflicting_multiple_empty.js | 2 - .../conflicting_multiple_type.js | 2 - .../multiple_side_effect.js | 2 - test_files/import_by_path.no_externs/user.js | 2 - .../import_by_path.no_externs/user_default.js | 2 - .../using_multiple.js | 2 - .../import_equals/import_equals_type_usage.js | 2 - .../import_from_goog.js | 2 - .../import_only_types/types_and_constenum.js | 6 +- test_files/import_only_types/types_only.js | 2 - .../import_prefixed/import_prefixed_mixed.js | 4 +- .../import_prefixed/import_prefixed_types.js | 8 +- test_files/interface/implement_import.js | 2 - test_files/interface/interface.js | 2 - test_files/interface/interface_extends.js | 2 - test_files/interface/interface_merge.js | 2 - test_files/interface/interface_type_params.js | 2 - test_files/internal.declaration/internal.d.ts | 5 + .../invalid_closure_properties.js | 3 +- test_files/jsdoc/enum_tag.js | 2 - test_files/jsdoc/jsdoc.js | 18 +- test_files/jsdoc/jsdoc.ts | 12 + test_files/jsdoc_types.untyped/jsdoc_types.js | 8 +- test_files/jsdoc_types.untyped/module1.js | 2 - test_files/jsdoc_types.untyped/module2.js | 2 - test_files/jsdoc_types.untyped/nevertyped.js | 2 - test_files/jsdoc_types/initialized_unknown.js | 2 - test_files/jsdoc_types/jsdoc_types.js | 2 - test_files/jsdoc_types/module1.js | 2 - test_files/jsdoc_types/module2.js | 2 - test_files/jsdoc_types/nevertyped.js | 2 - test_files/jsx.no_externs/jsx.js | 2 - test_files/methods/methods.js | 2 - .../export_enum_in_namespace.js | 2 - .../export_namespace.js | 2 +- .../merged_namespace.js | 2 - .../namespaced.no_nstransform/reopen_ns.js | 2 - test_files/nullable/nullable.js | 2 - test_files/optional_chaining/keyed_access.js | 2 - .../optional_chaining/optional_chaining.js | 2 - test_files/optional_method/optional_method.js | 2 - .../parameter_properties.js | 2 - test_files/partial/partial.js | 2 - test_files/private_field/private_field.js | 2 - .../promiseconstructor/promiseconstructor.js | 2 - test_files/protected/protected.js | 2 - test_files/readonly/readonly.js | 2 - test_files/recursive_alias/recursive_alias.js | 2 - test_files/recursive_union/recursive_union.js | 2 - .../rest_parameters_any.js | 2 - .../rest_parameters_generic_empty.js | 2 - .../rest_parameters_tuple.js | 2 - test_files/return_this/return_this.js | 2 - test_files/scope_collision/collision.js | 2 - test_files/side_effect_import/module1.js | 2 - test_files/side_effect_import/module2.js | 2 - .../side_effect_import/side_effect_import.js | 2 - .../single_value_enum/single_value_enum.js | 2 - test_files/spread_type/spread_type.js | 49 + test_files/spread_type/spread_type.ts | 33 + test_files/static/static.js | 2 - .../uncapitalize_lowercase.js | 2 - .../structural.untyped/structural.untyped.js | 2 - test_files/super/super.js | 2 - test_files/this_type/this_type.js | 2 - .../transitive_symbol_type_only/exporter.js | 2 - .../transitive_symbol_type_only.js | 2 - .../bad.js | 2 - ...ult_shorthand_with_more_than_one_export.js | 2 - .../bad_default_shorthand_with_no_exports.js | 2 - .../bad_dln_only.js | 2 - .../correct_default_shorthand.js | 2 - .../correct_default_type.js | 2 - .../correct_default_value.js | 2 - .../correct_default_with_re_export.js | 2 - .../correct_named.js | 2 - .../correct_named_shorthand.js | 2 - .../pintomodule.js | 2 - .../emits_other_errors.js | 2 - test_files/tuple_types/tuple_functions.js | 2 - test_files/tuple_types/tuple_types.js | 2 - .../type_alias_imported/type_alias_declare.js | 2 - .../type_alias_default_exporter.js | 2 - test_files/type_and_value/module.js | 2 - .../type_args_repeated/type_args_repeated.js | 2 - test_files/type_intersection/intersection.js | 2 - test_files/type_narrowing/emit_extra_casts.js | 2 - .../type_propaccess.js | 2 - test_files/typeof_function_overloads/user.js | 19 + test_files/typeof_function_overloads/user.ts | 11 + test_files/underscore/underscore.js | 2 - .../use_closure_externs.js | 2 - test_files/visibility/public_override.js | 2 - yarn.lock | 603 ++++++++++ 233 files changed, 5746 insertions(+), 3334 deletions(-) delete mode 100644 src/tsickle_declaration_marker.ts delete mode 100644 test_files/clutz_imports.declaration.no_externs/clutz2_output_demo8.d.ts delete mode 100644 test_files/clutz_imports.declaration.no_externs/clutz_output_demo1.d.ts delete mode 100644 test_files/clutz_imports.declaration.no_externs/clutz_output_demo2.d.ts delete mode 100644 test_files/clutz_imports.declaration.no_externs/clutz_output_demo3.d.ts delete mode 100644 test_files/clutz_imports.declaration.no_externs/clutz_output_demo4.d.ts delete mode 100644 test_files/clutz_imports.declaration.no_externs/clutz_output_demo5.d.ts delete mode 100644 test_files/clutz_imports.declaration.no_externs/clutz_output_demo6.d.ts delete mode 100644 test_files/clutz_imports.declaration.no_externs/clutz_output_demo7.d.ts delete mode 100644 test_files/clutz_imports.declaration.no_externs/user_code.d.ts delete mode 100644 test_files/clutz_imports.declaration.no_externs/user_code.ts create mode 100644 test_files/comments/freestanding_jsdoc.js create mode 100644 test_files/comments/freestanding_jsdoc.ts create mode 100644 test_files/comments/trailing_no_semicolon.js create mode 100644 test_files/comments/trailing_no_semicolon.ts create mode 100644 test_files/decl_merge/outer_enum.js create mode 100644 test_files/decl_merge/outer_enum.ts create mode 100644 test_files/enum.no_nstransform/enum.js create mode 100644 test_files/enum.no_nstransform/enum.ts create mode 100644 test_files/enum.puretransform/enum.js create mode 100644 test_files/enum.puretransform/enum.ts create mode 100644 test_files/export_destructuring/export_destructuring.js create mode 100644 test_files/export_destructuring/export_destructuring.ts delete mode 100644 test_files/import_by_path.declaration.no_externs/clutz_input.d.ts delete mode 100644 test_files/import_by_path.declaration.no_externs/decluser.d.ts delete mode 100644 test_files/import_by_path.declaration.no_externs/decluser.ts delete mode 100644 test_files/import_by_path.declaration.no_externs/jsprovides.js create mode 100644 test_files/spread_type/spread_type.js create mode 100644 test_files/spread_type/spread_type.ts create mode 100644 test_files/typeof_function_overloads/user.js create mode 100644 test_files/typeof_function_overloads/user.ts create mode 100644 yarn.lock diff --git a/README.md b/README.md index d80559b47..625fa06d2 100644 --- a/README.md +++ b/README.md @@ -1,207 +1,8 @@ -# Tsickle - TypeScript to Closure Translator [![Build Status](https://github.com/angular/tsickle/actions/workflows/node.js.yml/badge.svg)](https://github.com/angular/tsickle/actions/workflows/node.js.yml) +# Tsickle - TypeScript to Closure Translator -Tsickle converts TypeScript code into a form acceptable to the [Closure -Compiler]. This allows using TypeScript to transpile your sources, and then -using Closure Compiler to bundle and optimize them, while taking advantage of -type information in Closure Compiler. +The tsickle repository is unsupported and will not be updated. It has been [officially unsupported](https://github.com/angular/tsickle/commit/3bf8d97c6bd53a920eb4f9b4d18cb584ead48e5a) since November 2022, has not seen any new commits to master since December 2023, and will be frozen starting May 2024. We generally cannot recommend using tsickle to integrate with Closure Compiler outside of the Google ecosystem due to the complexity of the toolchain. -[closure compiler]: https://github.com/google/closure-compiler/ +This repository will remain publicly available to fork. -## What conversion means +Previous version of this README can be found [here](https://github.com/angular/tsickle/blob/3bf8d97c6bd53a920eb4f9b4d18cb584ead48e5a/README.md). -A (non-exhaustive) list of the sorts of transformations Tsickle applies: - -- inserts Closure-compatible JSDoc annotations on functions/classes/etc -- converts ES6 modules into `goog.module` modules -- generates externs.js from TypeScript d.ts (and `declare`, see below) -- declares types for class member variables -- translates `export * from ...` into a form Closure accepts -- converts TypeScript enums into a form Closure accepts -- reprocesses all jsdoc to strip Closure-invalid tags - -In general the goal is that you write valid TypeScript and Tsickle handles -making it valid Closure Compiler code. - -## Warning: unsupported - -Google uses tsickle internally to minify its apps (including those using -Angular) using Closure Compiler. We have little experience using tsickle in the -other JavaScript ecosystems that are seen outside of Google, and there is -generally no support for using it from our side. - -## Usage - -Tsickle is a library, designed to be used by a larger program that interacts -with TypeScript and the Closure compiler. - -Some known clients are: - -1. Within Google we use tsickle inside the [Bazel build - system](https://bazel.build/). That code is published as - open source as part of [Bazel's nodejs/TypeScript - build rules](https://bazelbuild.github.io/rules_nodejs/). -1. [tscc](https://github.com/theseanl/tscc) wraps tsickle and - closure compiler, and interops with rollup. -1. We publish a simple demo program in the `demo/` subdirectory. - -## Design details - -### Output format - -Tsickle is designed to do whatever is necessary to make the code acceptable by -Closure compiler. We view its output as a necessary intermediate form for -communicating to the Closure compiler, and not something for humans. This means -the tsickle output may be kind of ugly to read. Its only real use is to pass it -on to the compiler. - -For one example, the syntax of types tsickle produces are specific to Closure. -The type `{!Foo}` means "Foo, excluding null" and a type alias becomes a `var` -statement that is tagged with `@typedef`. - -Tsickle emits modules using Closure's `goog.module` module system. This system -is similar to but different from ES modules, and was supported by Closure before -the ES module system was finalized. - -### Differences from TypeScript - -Closure and TypeScript are not identical. Tsickle hides most of the -differences, but users must still be aware of some differences. - -#### `declare` - -Any declaration in a `.d.ts` file, as well as any declaration tagged with -`declare ...`, is intepreted by Tsickle as a name that should be preserved -through Closure compilation (i.e. not renamed into something shorter). Use it -any time the specific string names of your fields are significant. That would -most often happen when the object either coming from outside your program, or -being passed out of the program. - -Example: - - declare interface JSONResult { - username: string; - } - let r = JSON.parse(input) as JSONResult; - console.log(r.username); - -By adding `declare` to the interface (or if it were in a `.d.ts` file), Tsickle -will inform Closure that it must use exactly the field name `.username` (and not -e.g. `.a`) in the output JS. This matters for this example because the input -JSON probably uses the string `'username'` and not whatever name Closure would -invent for it. (Note: `declare` on an interface has no additional meaning in -pure TypeScript.) - -#### Exporting decorators - -An exporting decorator is a decorator that has `@ExportDecoratedItems` in its -JSDoc. - -The names of elements that have an exporting decorator are preserved through -the Closure compilation process by applying an `@export` tag to them. - -Example: - - /** @ExportDecoratedItems */ - function myDecorator() { - // ... - } - - @myDecorator() - class DoNotRenameThisClass { ... } - -## Development - -### Dependencies - -- nodejs. Install from your operating system's package manger, by following - instructions on https://nodejs.org/en/, or by using - [NVM](https://github.com/nvm-sh/nvm) -- yarn. Install from your operating system's package manager or by following - [instructions on yarnpkg.com](https://yarnpkg.com/en/docs/install). - -### One-time setup - -Run `yarn` to install dependencies. - -### Build & Test commands - -- `yarn build` builds the code base. -- Run `tsc --watch` for an interactive, incremental, and continuous build. -- `yarn lint` checks for lint. -- `yarn test` runs unit tests, e2e tests and checks for lint (but make sure to - `yarn build` first or run tsc!). Set the `TESTBRIDGE_TEST_ONLY` environment - variable to filter what golden tests to run. - -### TypeScript AST help - -https://astexplorer.net/ and https://ts-ast-viewer.com/ are convenient tools to -visualize and inspect a TypeScript AST. - -### Debugging - -You can debug tests by passing `--node_options=--inspect` or -`--node_options=--inspect-brk` (to suspend execution directly after startup). - -For example, to debug a specific golden test: - -```shell -TESTBRIDGE_TEST_ONLY=my_golden_test node --inspect-brk=4332 ./node_modules/.bin/jasmine out/test/*.js -``` - -Then open [about:inspect] in Chrome and choose "about:inspect". Chrome will -launch a debugging session on any node process that starts with a debugger -listening on one of the listed ports. The tsickle tests and Chrome both default -to `localhost:9229`, so things should work out of the box. - -The break in specific code locations you can add `debugger;` statements in the -source code. - -### Updating Goldens - -Run `UPDATE_GOLDENS=y yarn test` to have the test suite update the goldens in -`test_files/...`. - -### Environment variables - -Set the environment variable `TESTBRIDGE_TEST_ONLY=` to limit the golden -tests (found in `test_files/...`) to only run tests with a name matching the -regex. - -### Releasing - -On a new branch, run: - -``` -# tsickle releases are all minor releases for now, see npm help version. -$ npm version minor -``` - -This will update the version in `package.json`, commit the changes, and -create a git tag. - -Push the branch, open a pull request, get it reviewed, and wait for it to be merged. - -Checkout and pull the latest version from master: - -``` -$ git checkout master && git pull -``` - -Check if the tag exists. If not, re-tag the commit and push the tag. - -``` -$ git tag -# Does this show the tag already? If not, proceed with: -$ git tag v0.32.0 && git push origin v0.32.0 # but use correct version -``` - -Once the versioned tag is pushed to GitHub the release (as found on -https://github.com/angular/tsickle/releases) will be implicitly created. - -From the master branch run: - -``` -npm config set registry https://wombat-dressing-room.appspot.com -npm login -npm publish # runs a clean build & test automatically -``` diff --git a/demo/package.json b/demo/package.json index 0bd9d3e46..ff2929688 100644 --- a/demo/package.json +++ b/demo/package.json @@ -8,7 +8,7 @@ "dependencies": { "minimist": "^1.2.3", "tsickle": "file:../", - "typescript": "5.2.2" + "typescript": "5.4.2" }, "devDependencies": { "@types/minimist": "1.2.0", diff --git a/package.json b/package.json index b2f9930b5..e5423f429 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,7 @@ "out/src/*" ], "peerDependencies": { - "typescript": "~5.1.5" + "typescript": "~5.4.2" }, "devDependencies": { "@types/diff-match-patch": "^1.0.32", @@ -28,7 +28,7 @@ "source-map-support": "^0.5.19", "tslib": "^2.2.0", "tslint": "^6.1.3", - "typescript": "5.2.2" + "typescript": "5.4.2" }, "scripts": { "build": "tsc", diff --git a/src/annotator_host.ts b/src/annotator_host.ts index aa5b18e84..2d15575ed 100644 --- a/src/annotator_host.ts +++ b/src/annotator_host.ts @@ -53,6 +53,9 @@ export interface AnnotatorHost { * prefix to scope symbols in externs file (see externs.ts). */ export function moduleNameAsIdentifier( - host: AnnotatorHost, fileName: string, context = ''): string { + host: AnnotatorHost, + fileName: string, + context = '', +): string { return host.pathToModuleName(context, fileName).replace(/\./g, '$'); } diff --git a/src/cli_support.ts b/src/cli_support.ts index 5326bea23..0eb445a4e 100644 --- a/src/cli_support.ts +++ b/src/cli_support.ts @@ -25,7 +25,10 @@ export function assertAbsolute(fileName: string) { * import and generates a googmodule module name for the imported module. */ export function pathToModuleName( - rootModulePath: string, context: string, fileName: string): string { + rootModulePath: string, + context: string, + fileName: string, +): string { fileName = fileName.replace(/(\.d)?\.[tj]s$/, ''); if (fileName[0] === '.') { @@ -37,7 +40,9 @@ export function pathToModuleName( // TODO(evanm): various tests assume they can import relative paths like // 'foo/bar' and have them interpreted as root-relative; preserve that here. // Fix this by removing the next line. - if (!path.isAbsolute(fileName)) fileName = path.join(rootModulePath, fileName); + if (!path.isAbsolute(fileName)) { + fileName = path.join(rootModulePath, fileName); + } // TODO(evanm): various tests assume they can pass in a 'fileName' like // 'goog:foo.bar' and have this function do something reasonable. @@ -50,8 +55,10 @@ export function pathToModuleName( } // Replace characters not supported by goog.module. - const moduleName = - fileName.replace(/\/|\\/g, '.').replace(/^[^a-zA-Z_$]/, '_').replace(/[^a-zA-Z0-9._$]/g, '_'); + const moduleName = fileName + .replace(/\/|\\/g, '.') + .replace(/^[^a-zA-Z_$]/, '_') + .replace(/[^a-zA-Z0-9._$]/g, '_'); return moduleName; } diff --git a/src/closure_externs.js b/src/closure_externs.js index 52d97158f..ce0486687 100644 --- a/src/closure_externs.js +++ b/src/closure_externs.js @@ -60,7 +60,7 @@ var ReadonlySet; * @template T * @extends {IThenable} */ -function PromiseLike() {}; +function PromiseLike() {} /** @typedef {function(new:Promise)} */ var PromiseConstructor; @@ -94,7 +94,7 @@ var TemplateStringsArray; var RegExpMatchArray; /** @record */ -function ImportMeta() {}; +function ImportMeta() {} // Representations for TS' EventMap objects. // These are types that contain a mapping from event names to event object diff --git a/src/clutz.ts b/src/clutz.ts index 2e1a818e8..e5ae5f054 100644 --- a/src/clutz.ts +++ b/src/clutz.ts @@ -21,15 +21,20 @@ import * as googmodule from './googmodule'; import * as path from './path'; import {isDeclaredInClutzDts} from './type_translator'; +interface ClutzHost { + /** See compiler_host.ts */ + rootDirsRelative(fileName: string): string; +} + /** * Constructs a ts.CustomTransformerFactory that postprocesses the .d.ts * that are generated by ordinary TypeScript compilations to add some * Clutz-specific logic. See generateClutzAliases. */ export function makeDeclarationTransformerFactory( - typeChecker: ts.TypeChecker, - googmoduleHost: googmodule.GoogModuleProcessorHost): - ts.CustomTransformerFactory { + typeChecker: ts.TypeChecker, + host: ClutzHost & googmodule.GoogModuleProcessorHost, +): ts.CustomTransformerFactory { return (context: ts.TransformationContext): ts.CustomTransformer => { return { transformBundle(): ts.Bundle { @@ -49,16 +54,15 @@ export function makeDeclarationTransformerFactory( // import 'path/to/the/js_file'; // so to for that import to resolve, you need to first import the clutz // d.ts that defines that declared module. - const imports = - gatherNecessaryClutzImports(googmoduleHost, typeChecker, file); - let importStmts: ts.Statement[]|undefined; + const imports = gatherNecessaryClutzImports(host, typeChecker, file); + let importStmts: ts.Statement[] | undefined; if (imports.length > 0) { - importStmts = imports.map(fileName => { + importStmts = imports.map((fileName) => { fileName = path.relative(options.rootDir!, fileName); return ts.factory.createImportDeclaration( - /* modifiers */ undefined, - /* importClause */ undefined, - /* moduleSpecifier */ ts.factory.createStringLiteral(fileName), + /* modifiers */ undefined, + /* importClause */ undefined, + /* moduleSpecifier */ ts.factory.createStringLiteral(fileName), ); }); } @@ -66,22 +70,69 @@ export function makeDeclarationTransformerFactory( // Construct `declare global {}` in the Clutz namespace for symbols // Clutz might use. const globalBlock = generateClutzAliases( - file, googmoduleHost.pathToModuleName('', file.fileName), - typeChecker, options); + file, + host.pathToModuleName('', file.fileName), + typeChecker, + options, + ); // Only need to transform file if we needed one of the above additions. if (!importStmts && !globalBlock) return file; - return ts.factory.updateSourceFile(file, [ - ...(importStmts ?? []), - ...file.statements, - ...(globalBlock ? [globalBlock] : []), - ]); - } + return ts.factory.updateSourceFile( + file, + ts.setTextRange( + ts.factory.createNodeArray([ + ...(importStmts ?? []), + ...file.statements, + ...(globalBlock ? [globalBlock] : []), + ]), + file.statements, + ), + file.isDeclarationFile, + file.referencedFiles.map((f) => + fixRelativeReference(f, file, options, host), + ), + // /// directives are ignored under bazel. + /*typeReferences=*/ [], + ); + }, }; }; } +/** + * Fixes a relative reference from an output file with respect to multiple + * rootDirs. See https://github.com/Microsoft/TypeScript/issues/8245 for + * details. + */ +function fixRelativeReference( + reference: ts.FileReference, + origin: ts.SourceFile, + options: ts.CompilerOptions, + host: ClutzHost, +): ts.FileReference { + if (!options.outDir || !options.rootDir) { + return reference; + } + const originDir = path.dirname(origin.fileName); + // Where TypeScript expects the output to be. + const expectedOutDir = path.join( + options.outDir, + path.relative(options.rootDir, originDir), + ); + const referencedFile = path.join(expectedOutDir, reference.fileName); + // Where the output is actually emitted. + const actualOutDir = path.join( + options.outDir, + host.rootDirsRelative(originDir), + ); + const fixedReference = path.relative(actualOutDir, referencedFile); + + reference.fileName = fixedReference; + return reference; +} + /** Compares two strings and returns a number suitable for use in sort(). */ function stringCompare(a: string, b: string): number { if (a < b) return -1; @@ -96,11 +147,14 @@ function stringCompare(a: string, b: string): number { * symbols in the Clutz naming convention. */ function generateClutzAliases( - sourceFile: ts.SourceFile, moduleName: string, typeChecker: ts.TypeChecker, - options: ts.CompilerOptions): ts.Statement|undefined { + sourceFile: ts.SourceFile, + moduleName: string, + typeChecker: ts.TypeChecker, + options: ts.CompilerOptions, +): ts.Statement | undefined { const moduleSymbol = typeChecker.getSymbolAtLocation(sourceFile); const moduleExports = - moduleSymbol && typeChecker.getExportsOfModule(moduleSymbol); + moduleSymbol && typeChecker.getExportsOfModule(moduleSymbol); if (!moduleExports) return undefined; // .d.ts files can be transformed, too, so we need to compare the original @@ -125,7 +179,7 @@ function generateClutzAliases( // // TODO(radokirov): attempt to add appropriate imports for 2) so that // currently finding out local appears even harder than fixing exports. - const localExports = moduleExports.filter(e => { + const localExports = moduleExports.filter((e) => { // If there are no declarations, be conservative and don't emit the aliases. // I don't know how can this happen, we have no tests that excercise it. if (!e.declarations) return false; @@ -158,8 +212,10 @@ function generateClutzAliases( // isInternalDeclaration API expects to be provided the grandparent // VariableStatement. const node = ts.isVariableDeclaration(d) ? d.parent.parent : d; - if (options.stripInternal && - isInternalDeclaration(node, origSourceFile)) { + if ( + options.stripInternal && + isInternalDeclaration(node, origSourceFile) + ) { return false; } @@ -210,10 +266,14 @@ function generateClutzAliases( const nestedExports: ts.ExportSpecifier[] = []; for (const symbol of localExports) { let localName = symbol.name; - const declaration = - symbol.declarations?.find(d => d.getSourceFile() === origSourceFile); - if (declaration && ts.isExportSpecifier(declaration) && - declaration.propertyName) { + const declaration = symbol.declarations?.find( + (d) => d.getSourceFile() === origSourceFile, + ); + if ( + declaration && + ts.isExportSpecifier(declaration) && + declaration.propertyName + ) { // If declared in an "export {X as Y};" export specifier, then X (stored // in propertyName) is the local name that resolves within the module, // whereas Y is only available on the exports, i.e. the name used to @@ -224,13 +284,20 @@ function generateClutzAliases( const mangledName = `module$contents$${clutzModuleName}_${symbol.name}`; // These ExportSpecifiers are the `foo as bar` bits as found in a larger // `export {foo as bar}` statement, which is constructed after this loop. - globalExports.push(ts.factory.createExportSpecifier( - /* isTypeOnly */ false, ts.factory.createIdentifier(localName), - ts.factory.createIdentifier(mangledName))); - nestedExports.push(ts.factory.createExportSpecifier( + globalExports.push( + ts.factory.createExportSpecifier( + /* isTypeOnly */ false, + ts.factory.createIdentifier(localName), + ts.factory.createIdentifier(mangledName), + ), + ); + nestedExports.push( + ts.factory.createExportSpecifier( /* isTypeOnly */ false, localName === symbol.name ? undefined : localName, - ts.factory.createIdentifier(symbol.name))); + ts.factory.createIdentifier(symbol.name), + ), + ); } // Create two export statements that will be used to contribute to the @@ -239,43 +306,49 @@ function generateClutzAliases( // 1) For globalExports, // export {...}; ts.factory.createExportDeclaration( - /* modifiers */ undefined, - /* isTypeOnly */ false, ts.factory.createNamedExports(globalExports)), + /* modifiers */ undefined, + /* isTypeOnly */ false, + ts.factory.createNamedExports(globalExports), + ), // 2) For nestedExports // namespace module$exports$module$name$here { // export {...}; // } ts.factory.createModuleDeclaration( - /* modifiers */[ts.factory.createModifier(ts.SyntaxKind.ExportKeyword)], - ts.factory.createIdentifier(`module$exports$${clutzModuleName}`), - ts.factory.createModuleBlock([ - ts.factory.createExportDeclaration( - /* modifiers */ undefined, - /* isTypeOnly */ false, - ts.factory.createNamedExports(nestedExports)), - ]), - ts.NodeFlags.Namespace), + /* modifiers */ [ts.factory.createModifier(ts.SyntaxKind.ExportKeyword)], + ts.factory.createIdentifier(`module$exports$${clutzModuleName}`), + ts.factory.createModuleBlock([ + ts.factory.createExportDeclaration( + /* modifiers */ undefined, + /* isTypeOnly */ false, + ts.factory.createNamedExports(nestedExports), + ), + ]), + ts.NodeFlags.Namespace, + ), ]; - // Wrap a `declare global { namespace ಠ_ಠ.clutz { ... } }` around // the statements in globalDeclarations. return ts.factory.createModuleDeclaration( - /* modifiers */[ts.factory.createModifier(ts.SyntaxKind.DeclareKeyword)], - ts.factory.createIdentifier('global'), ts.factory.createModuleBlock([ - ts.factory.createModuleDeclaration( - /* modifiers */ undefined, - // Note: it's not exactly right to use a '.' within an identifier - // like I am doing here, but I could not figure out how to construct - // an AST that has a dotted name here -- the types require a - // ModuleDeclaration, but nesting another ModuleDeclaration in here - // always created a new {} block, despite trying the - // 'NestedNamespace' flag. - ts.factory.createIdentifier('ಠ_ಠ.clutz'), - ts.factory.createModuleBlock(globalDeclarations), - ts.NodeFlags.Namespace | ts.NodeFlags.NestedNamespace), - ]), - ts.NodeFlags.GlobalAugmentation); + /* modifiers */ [ts.factory.createModifier(ts.SyntaxKind.DeclareKeyword)], + ts.factory.createIdentifier('global'), + ts.factory.createModuleBlock([ + ts.factory.createModuleDeclaration( + /* modifiers */ undefined, + // Note: it's not exactly right to use a '.' within an identifier + // like I am doing here, but I could not figure out how to construct + // an AST that has a dotted name here -- the types require a + // ModuleDeclaration, but nesting another ModuleDeclaration in here + // always created a new {} block, despite trying the + // 'NestedNamespace' flag. + ts.factory.createIdentifier('ಠ_ಠ.clutz'), + ts.factory.createModuleBlock(globalDeclarations), + ts.NodeFlags.Namespace | ts.NodeFlags.NestedNamespace, + ), + ]), + ts.NodeFlags.GlobalAugmentation, + ); } /** @@ -285,23 +358,31 @@ function generateClutzAliases( * __clutz_actual_namespace field). */ function ambientModuleSymbolFromClutz( - googmoduleHost: googmodule.GoogModuleProcessorHost, - typeChecker: ts.TypeChecker, stmt: ts.Statement): ts.Symbol|undefined { + googmoduleHost: googmodule.GoogModuleProcessorHost, + typeChecker: ts.TypeChecker, + stmt: ts.Statement, +): ts.Symbol | undefined { if (!ts.isImportDeclaration(stmt) && !ts.isExportDeclaration(stmt)) { return undefined; } if (!stmt.moduleSpecifier) { - return undefined; // can be absent on 'export' statements. + return undefined; // can be absent on 'export' statements. } const moduleSymbol = typeChecker.getSymbolAtLocation(stmt.moduleSpecifier); - if (moduleSymbol?.valueDeclaration && - ts.isSourceFile(moduleSymbol.valueDeclaration)) { + if ( + moduleSymbol?.valueDeclaration && + ts.isSourceFile(moduleSymbol.valueDeclaration) + ) { return undefined; } const ignoredDiagnostics: ts.Diagnostic[] = []; const namespace = googmodule.jsPathToNamespace( - googmoduleHost, stmt, ignoredDiagnostics, - (stmt.moduleSpecifier as ts.StringLiteral).text, () => moduleSymbol); + googmoduleHost, + stmt, + ignoredDiagnostics, + (stmt.moduleSpecifier as ts.StringLiteral).text, + () => moduleSymbol, + ); if (namespace === null) return undefined; return moduleSymbol; } @@ -315,7 +396,9 @@ function ambientModuleSymbolFromClutz( * because sometimes TS generates AST nodes that don't have a parent. */ function clutzSymbolFromQualifiedName( - typeChecker: ts.TypeChecker, name: ts.EntityName): ts.Symbol|undefined { + typeChecker: ts.TypeChecker, + name: ts.EntityName, +): ts.Symbol | undefined { const node = ts.isQualifiedName(name) ? name.right : name; let sym = typeChecker.getSymbolAtLocation(node); if (!sym) { @@ -328,8 +411,12 @@ function clutzSymbolFromQualifiedName( sym = (node as any)['symbol'] as ts.Symbol | undefined; } - if (!sym || !sym.declarations || sym.declarations.length === 0 || - !isDeclaredInClutzDts(sym.declarations[0])) { + if ( + !sym || + !sym.declarations || + sym.declarations.length === 0 || + !isDeclaredInClutzDts(sym.declarations[0]) + ) { return undefined; } @@ -341,7 +428,9 @@ function clutzSymbolFromQualifiedName( * symbol, and if so return the underlying ts.Symbol. */ function clutzSymbolFromNode( - typeChecker: ts.TypeChecker, node: ts.Node): ts.Symbol|undefined { + typeChecker: ts.TypeChecker, + node: ts.Node, +): ts.Symbol | undefined { if (ts.isTypeReferenceNode(node)) { // Reference in type position. return clutzSymbolFromQualifiedName(typeChecker, node.typeName); @@ -360,7 +449,7 @@ function clutzSymbolFromNode( * This is a path to an underlying d.ts file that defines that symbol, without a * file extension. */ -function importPathForSymbol(sym: ts.Symbol): string|undefined { +function importPathForSymbol(sym: ts.Symbol): string | undefined { if (!sym.declarations || sym.declarations.length === 0) { // This can happen if an import or symbol somehow references a nonexistent // type, for example in a case where type checking failed or via 'any'. @@ -385,23 +474,27 @@ function importPathForSymbol(sym: ts.Symbol): string|undefined { * import paths of the underlying files that define them. */ function gatherNecessaryClutzImports( - googmoduleHost: googmodule.GoogModuleProcessorHost, - typeChecker: ts.TypeChecker, sf: ts.SourceFile): string[] { + googmoduleHost: googmodule.GoogModuleProcessorHost, + typeChecker: ts.TypeChecker, + sf: ts.SourceFile, +): string[] { const imports = new Set(); for (const stmt of sf.statements) { // Recurse to find all non-imported accesses to symbols. ts.forEachChild(stmt, visit); // Then handle explicit import/export statements. - const moduleSymbol = - ambientModuleSymbolFromClutz(googmoduleHost, typeChecker, stmt); + const moduleSymbol = ambientModuleSymbolFromClutz( + googmoduleHost, + typeChecker, + stmt, + ); if (!moduleSymbol) continue; const importPath = importPathForSymbol(moduleSymbol); if (importPath) imports.add(importPath); } return Array.from(imports); - /** * Recursively searches a node for references to symbols declared in Clutz * .d.ts files and adds any referenced source files to the `imports` set. diff --git a/src/decorator_downlevel_transformer.ts b/src/decorator_downlevel_transformer.ts index 1b2f6f546..f9a5a5962 100644 --- a/src/decorator_downlevel_transformer.ts +++ b/src/decorator_downlevel_transformer.ts @@ -31,7 +31,11 @@ import * as ts from 'typescript'; import {getDecoratorDeclarations} from './decorators'; import * as jsdoc from './jsdoc'; -import {getAllLeadingComments, symbolIsValue, visitEachChild} from './transformer_util'; +import { + getAllLeadingComments, + symbolIsValue, + visitEachChild, +} from './transformer_util'; /** * Returns true if the given decorator should be downleveled. @@ -70,14 +74,16 @@ function shouldLower(decorator: ts.Decorator, typeChecker: ts.TypeChecker) { } const DECORATOR_INVOCATION_JSDOC_TYPE = - '!Array<{type: !Function, args: (undefined|!Array)}>'; + '!Array<{type: !Function, args: (undefined|!Array)}>'; function addJSDocTypeAnnotation(node: ts.Node, jsdocType: string): void { ts.setSyntheticLeadingComments(node, [ - jsdoc.toSynthesizedComment([{ - tagName: 'type', - type: jsdocType, - }]), + jsdoc.toSynthesizedComment([ + { + tagName: 'type', + type: jsdocType, + }, + ]), ]); } @@ -89,29 +95,35 @@ function addJSDocTypeAnnotation(node: ts.Node, jsdocType: string): void { * { type: decorator, args: [arg1, arg2] } */ function extractMetadataFromSingleDecorator( - decorator: ts.Decorator, diagnostics: ts.Diagnostic[]): ts.ObjectLiteralExpression { + decorator: ts.Decorator, + diagnostics: ts.Diagnostic[], +): ts.ObjectLiteralExpression { const metadataProperties: ts.ObjectLiteralElementLike[] = []; const expr = decorator.expression; switch (expr.kind) { case ts.SyntaxKind.Identifier: // The decorator was a plain @Foo. metadataProperties.push( - ts.factory.createPropertyAssignment('type', expr)); + ts.factory.createPropertyAssignment('type', expr), + ); break; case ts.SyntaxKind.CallExpression: // The decorator was a call, like @Foo(bar). const call = expr as ts.CallExpression; metadataProperties.push( - ts.factory.createPropertyAssignment('type', call.expression)); + ts.factory.createPropertyAssignment('type', call.expression), + ); if (call.arguments.length) { const args: ts.Expression[] = []; for (const arg of call.arguments) { args.push(arg); } const argsArrayLiteral = ts.factory.createArrayLiteralExpression( - ts.factory.createNodeArray(args, /* hasTrailingComma */ true)); + ts.factory.createNodeArray(args, /* hasTrailingComma */ true), + ); metadataProperties.push( - ts.factory.createPropertyAssignment('args', argsArrayLiteral)); + ts.factory.createPropertyAssignment('args', argsArrayLiteral), + ); } break; default: @@ -119,8 +131,9 @@ function extractMetadataFromSingleDecorator( file: decorator.getSourceFile(), start: decorator.getStart(), length: decorator.getEnd() - decorator.getStart(), - messageText: - `${ts.SyntaxKind[decorator.kind]} not implemented in gathering decorator metadata`, + messageText: `${ + ts.SyntaxKind[decorator.kind] + } not implemented in gathering decorator metadata`, category: ts.DiagnosticCategory.Error, code: 0, }); @@ -133,13 +146,21 @@ function extractMetadataFromSingleDecorator( * Takes a list of decorator metadata object ASTs and produces an AST for a * static class property of an array of those metadata objects. */ -function createDecoratorClassProperty(decoratorList: ts.ObjectLiteralExpression[]) { +function createDecoratorClassProperty( + decoratorList: ts.ObjectLiteralExpression[], +) { const modifier = ts.factory.createToken(ts.SyntaxKind.StaticKeyword); const initializer = ts.factory.createArrayLiteralExpression( - ts.factory.createNodeArray(decoratorList, /* hasTrailingComma */ true), - true); + ts.factory.createNodeArray(decoratorList, /* hasTrailingComma */ true), + true, + ); const prop = ts.factory.createPropertyDeclaration( - [modifier], 'decorators', undefined, undefined, initializer); + [modifier], + 'decorators', + undefined, + undefined, + initializer, + ); addJSDocTypeAnnotation(prop, DECORATOR_INVOCATION_JSDOC_TYPE); // NB: the .decorators property does not get a @nocollapse property. There is @@ -165,9 +186,10 @@ function createDecoratorClassProperty(decoratorList: ts.ObjectLiteralExpression[ * }]; */ function createCtorParametersClassProperty( - diagnostics: ts.Diagnostic[], - entityNameToExpression: (n: ts.EntityName) => ts.Expression | undefined, - ctorParameters: ParameterDecorationInfo[]): ts.PropertyDeclaration { + diagnostics: ts.Diagnostic[], + entityNameToExpression: (n: ts.EntityName) => ts.Expression | undefined, + ctorParameters: ParameterDecorationInfo[], +): ts.PropertyDeclaration { const params: ts.Expression[] = []; for (const ctorParam of ctorParameters) { @@ -176,40 +198,58 @@ function createCtorParametersClassProperty( continue; } - const paramType = ctorParam.type ? - typeReferenceToExpression(entityNameToExpression, ctorParam.type) : - undefined; - const members = - [ts.factory.createPropertyAssignment('type', paramType || ts.factory.createIdentifier('undefined'))]; + const paramType = ctorParam.type + ? typeReferenceToExpression(entityNameToExpression, ctorParam.type) + : undefined; + const members = [ + ts.factory.createPropertyAssignment( + 'type', + paramType || ts.factory.createIdentifier('undefined'), + ), + ]; const decorators: ts.ObjectLiteralExpression[] = []; for (const deco of ctorParam.decorators) { decorators.push(extractMetadataFromSingleDecorator(deco, diagnostics)); } if (decorators.length) { - members.push(ts.factory.createPropertyAssignment('decorators', ts.factory.createArrayLiteralExpression(decorators))); + members.push( + ts.factory.createPropertyAssignment( + 'decorators', + ts.factory.createArrayLiteralExpression(decorators), + ), + ); } params.push(ts.factory.createObjectLiteralExpression(members)); } const initializer = ts.factory.createArrowFunction( - undefined, undefined, [], undefined, ts.factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken), - ts.factory.createArrayLiteralExpression(params, true)); + undefined, + undefined, + [], + undefined, + ts.factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken), + ts.factory.createArrayLiteralExpression(params, true), + ); const ctorProp = ts.factory.createPropertyDeclaration( - [ts.factory.createToken(ts.SyntaxKind.StaticKeyword)], 'ctorParameters', - undefined, undefined, initializer); + [ts.factory.createToken(ts.SyntaxKind.StaticKeyword)], + 'ctorParameters', + undefined, + undefined, + initializer, + ); ts.setSyntheticLeadingComments(ctorProp, [ jsdoc.toSynthesizedComment([ { tagName: 'type', type: lines( - `function(): !Array<(null|{`, - ` type: ?,`, - ` decorators: (undefined|${DECORATOR_INVOCATION_JSDOC_TYPE}),`, - `})>`, - ), + `function(): !Array<(null|{`, + ` type: ?,`, + ` decorators: (undefined|${DECORATOR_INVOCATION_JSDOC_TYPE}),`, + `})>`, + ), }, - {tagName: 'nocollapse'} + {tagName: 'nocollapse'}, ]), ]); @@ -226,21 +266,35 @@ function createCtorParametersClassProperty( * }; */ function createPropDecoratorsClassProperty( - diagnostics: ts.Diagnostic[], properties: Map): ts.PropertyDeclaration { + diagnostics: ts.Diagnostic[], + properties: Map, +): ts.PropertyDeclaration { // `static propDecorators: {[key: string]: ` + {type: Function, args?: any[]}[] + `} = {\n`); const entries: ts.ObjectLiteralElementLike[] = []; for (const [name, decorators] of properties.entries()) { - entries.push(ts.factory.createPropertyAssignment( + entries.push( + ts.factory.createPropertyAssignment( name, ts.factory.createArrayLiteralExpression( - decorators.map(deco => extractMetadataFromSingleDecorator(deco, diagnostics))))); + decorators.map((deco) => + extractMetadataFromSingleDecorator(deco, diagnostics), + ), + ), + ), + ); } const initializer = ts.factory.createObjectLiteralExpression(entries, true); const prop = ts.factory.createPropertyDeclaration( - [ts.factory.createToken(ts.SyntaxKind.StaticKeyword)], 'propDecorators', - undefined, undefined, initializer); + [ts.factory.createToken(ts.SyntaxKind.StaticKeyword)], + 'propDecorators', + undefined, + undefined, + initializer, + ); addJSDocTypeAnnotation( - prop, `!Object`); + prop, + `!Object`, + ); return prop; } @@ -253,8 +307,9 @@ function createPropDecoratorsClassProperty( * metadata. */ function typeReferenceToExpression( - entityNameToExpression: (n: ts.EntityName) => ts.Expression | undefined, - node: ts.TypeNode): ts.Expression|undefined { + entityNameToExpression: (n: ts.EntityName) => ts.Expression | undefined, + node: ts.TypeNode, +): ts.Expression | undefined { let kind = node.kind; if (ts.isLiteralTypeNode(node)) { // Treat literal types like their base type (boolean, string, number). @@ -295,7 +350,7 @@ interface ParameterDecorationInfo { * The type declaration for the parameter. Only set if the type is a value (e.g. a class, not an * interface). */ - type: ts.TypeNode|null; + type: ts.TypeNode | null; /** The list of decorators found on the parameter, null if none. */ decorators: ts.Decorator[]; } @@ -304,8 +359,9 @@ interface ParameterDecorationInfo { * Transformer factory for the decorator downlevel transformer. See fileoverview for details. */ export function decoratorDownlevelTransformer( - typeChecker: ts.TypeChecker, diagnostics: ts.Diagnostic[]): - (context: ts.TransformationContext) => ts.Transformer { + typeChecker: ts.TypeChecker, + diagnostics: ts.Diagnostic[], +): (context: ts.TransformationContext) => ts.Transformer { return (context: ts.TransformationContext) => { /** A map from symbols to the identifier of an import, reset per SourceFile. */ let importNamesBySymbol = new Map(); @@ -325,7 +381,9 @@ export function decoratorDownlevelTransformer( * identifier is not marked as stemming from a "type only" expression, causing it to be emitted * and causing the import to be retained. */ - function entityNameToExpression(name: ts.EntityName): ts.Expression|undefined { + function entityNameToExpression( + name: ts.EntityName, + ): ts.Expression | undefined { const sym = typeChecker.getSymbolAtLocation(name); if (!sym) return undefined; // Check if the entity name references a symbol that is an actual value. If it is not, it @@ -350,9 +408,12 @@ export function decoratorDownlevelTransformer( * element, or the element has an exotic name. */ function transformClassElement( - element: ts.PropertyDeclaration|ts.GetAccessorDeclaration| - ts.SetAccessorDeclaration|ts.MethodDeclaration): - [string|undefined, ts.ClassElement, ts.Decorator[]] { + element: + | ts.PropertyDeclaration + | ts.GetAccessorDeclaration + | ts.SetAccessorDeclaration + | ts.MethodDeclaration, + ): [string | undefined, ts.ClassElement, ts.Decorator[]] { element = ts.visitEachChild(element, visitor, context); const modifiersToKeep: ts.ModifierLike[] = []; const toLower: ts.Decorator[] = []; @@ -383,33 +444,54 @@ export function decoratorDownlevelTransformer( const name = element.name.text; let newNode: ts.ClassElement; - const modifiers = modifiersToKeep.length ? - ts.setTextRange( - ts.factory.createNodeArray(modifiersToKeep), - ts.factory.createNodeArray(element.modifiers ?? [])) : - undefined; + const modifiers = modifiersToKeep.length + ? ts.setTextRange( + ts.factory.createNodeArray(modifiersToKeep), + ts.factory.createNodeArray(element.modifiers ?? []), + ) + : undefined; switch (element.kind) { case ts.SyntaxKind.PropertyDeclaration: newNode = ts.factory.updatePropertyDeclaration( - element, modifiers, element.name, - element.questionToken ?? element.exclamationToken, element.type, - element.initializer); + element, + modifiers, + element.name, + element.questionToken ?? element.exclamationToken, + element.type, + element.initializer, + ); break; case ts.SyntaxKind.GetAccessor: newNode = ts.factory.updateGetAccessorDeclaration( - element, modifiers, element.name, element.parameters, - element.type, element.body); + element, + modifiers, + element.name, + element.parameters, + element.type, + element.body, + ); break; case ts.SyntaxKind.SetAccessor: newNode = ts.factory.updateSetAccessorDeclaration( - element, modifiers, element.name, element.parameters, - element.body); + element, + modifiers, + element.name, + element.parameters, + element.body, + ); break; case ts.SyntaxKind.MethodDeclaration: newNode = ts.factory.updateMethodDeclaration( - element, modifiers, element.asteriskToken, element.name, - element.questionToken, element.typeParameters, element.parameters, - element.type, element.body); + element, + modifiers, + element.asteriskToken, + element.name, + element.questionToken, + element.typeParameters, + element.parameters, + element.type, + element.body, + ); break; default: throw new Error(`unexpected element: ${element}`); @@ -421,12 +503,17 @@ export function decoratorDownlevelTransformer( * Transforms a constructor. Returns the transformed constructor and the list of parameter * information collected, consisting of decorators and optional type. */ - function transformConstructor(ctor: ts.ConstructorDeclaration): - [ts.ConstructorDeclaration, ParameterDecorationInfo[]] { + function transformConstructor( + ctor: ts.ConstructorDeclaration, + ): [ts.ConstructorDeclaration, ParameterDecorationInfo[]] { ctor = ts.visitEachChild(ctor, visitor, context); const newParameters: ts.ParameterDeclaration[] = []; - const oldParameters = ts.visitParameterList(ctor.parameters, visitor, context); + const oldParameters = ts.visitParameterList( + ctor.parameters, + visitor, + context, + ); const parametersInfo: ParameterDecorationInfo[] = []; for (const param of oldParameters) { const modifiersToKeep: ts.ModifierLike[] = []; @@ -450,16 +537,24 @@ export function decoratorDownlevelTransformer( } parametersInfo.push(paramInfo); const newParam = ts.factory.updateParameterDeclaration( - param, // Must pass 'undefined' to avoid emitting decorator - // metadata. - modifiersToKeep, param.dotDotDotToken, param.name, - param.questionToken, param.type, param.initializer); + param, // Must pass 'undefined' to avoid emitting decorator + // metadata. + modifiersToKeep, + param.dotDotDotToken, + param.name, + param.questionToken, + param.type, + param.initializer, + ); newParameters.push(newParam); } const updated = ts.factory.updateConstructorDeclaration( - ctor, ctor.modifiers, newParameters, - ts.visitFunctionBody(ctor.body, visitor, context)); + ctor, + ctor.modifiers, + newParameters, + ts.visitFunctionBody(ctor.body, visitor, context), + ); return [updated, parametersInfo]; } @@ -470,10 +565,12 @@ export function decoratorDownlevelTransformer( * - creates a ctorParameters property * - creates a propDecorators property */ - function transformClassDeclaration(classDecl: ts.ClassDeclaration): ts.ClassDeclaration { + function transformClassDeclaration( + classDecl: ts.ClassDeclaration, + ): ts.ClassDeclaration { const newMembers: ts.ClassElement[] = []; const decoratedProperties = new Map(); - let classParameters: ParameterDecorationInfo[]|null = null; + let classParameters: ParameterDecorationInfo[] | null = null; for (const member of classDecl.members) { switch (member.kind) { @@ -482,8 +579,12 @@ export function decoratorDownlevelTransformer( case ts.SyntaxKind.SetAccessor: case ts.SyntaxKind.MethodDeclaration: { const [name, newMember, decorators] = transformClassElement( - member as ts.PropertyDeclaration | ts.GetAccessorDeclaration | - ts.SetAccessorDeclaration | ts.MethodDeclaration); + member as + | ts.PropertyDeclaration + | ts.GetAccessorDeclaration + | ts.SetAccessorDeclaration + | ts.MethodDeclaration, + ); newMembers.push(newMember); if (name) decoratedProperties.set(name, decorators); continue; @@ -491,8 +592,9 @@ export function decoratorDownlevelTransformer( case ts.SyntaxKind.Constructor: { const ctor = member as ts.ConstructorDeclaration; if (!ctor.body) break; - const [newMember, parametersInfo] = - transformConstructor(member as ts.ConstructorDeclaration); + const [newMember, parametersInfo] = transformConstructor( + member as ts.ConstructorDeclaration, + ); classParameters = parametersInfo; newMembers.push(newMember); continue; @@ -509,7 +611,8 @@ export function decoratorDownlevelTransformer( if (ts.isDecorator(modifier)) { if (shouldLower(modifier, typeChecker)) { decoratorsToLower.push( - extractMetadataFromSingleDecorator(modifier, diagnostics)); + extractMetadataFromSingleDecorator(modifier, diagnostics), + ); continue; } } @@ -520,23 +623,40 @@ export function decoratorDownlevelTransformer( newMembers.push(createDecoratorClassProperty(decoratorsToLower)); } if (classParameters) { - if ((decoratorsToLower.length) || classParameters.some(p => !!p.decorators.length)) { + if ( + decoratorsToLower.length || + classParameters.some((p) => !!p.decorators.length) + ) { // emit ctorParameters if the class was decoratored at all, or if any of its ctors // were classParameters - newMembers.push(createCtorParametersClassProperty( - diagnostics, entityNameToExpression, classParameters)); + newMembers.push( + createCtorParametersClassProperty( + diagnostics, + entityNameToExpression, + classParameters, + ), + ); } } if (decoratedProperties.size) { - newMembers.push(createPropDecoratorsClassProperty(diagnostics, decoratedProperties)); + newMembers.push( + createPropDecoratorsClassProperty(diagnostics, decoratedProperties), + ); } return ts.factory.updateClassDeclaration( - classDecl, modifiersToKeep.length ? modifiersToKeep : undefined, - classDecl.name, classDecl.typeParameters, classDecl.heritageClauses, - ts.setTextRange( - ts.factory.createNodeArray( - newMembers, classDecl.members.hasTrailingComma), - classDecl.members)); + classDecl, + modifiersToKeep.length ? modifiersToKeep : undefined, + classDecl.name, + classDecl.typeParameters, + classDecl.heritageClauses, + ts.setTextRange( + ts.factory.createNodeArray( + newMembers, + classDecl.members.hasTrailingComma, + ), + classDecl.members, + ), + ); } function visitor(node: ts.Node): ts.Node { @@ -553,10 +673,13 @@ export function decoratorDownlevelTransformer( if (importClause.name) { names.push(importClause.name); } - if (importClause.namedBindings && - importClause.namedBindings.kind === ts.SyntaxKind.NamedImports) { + if ( + importClause.namedBindings && + importClause.namedBindings.kind === ts.SyntaxKind.NamedImports + ) { names.push( - ...importClause.namedBindings.elements.map(e => e.name)); + ...importClause.namedBindings.elements.map((e) => e.name), + ); } for (const name of names) { const sym = typeChecker.getSymbolAtLocation(name)!; diff --git a/src/decorators.ts b/src/decorators.ts index fb8523ad1..b85ec9d68 100644 --- a/src/decorators.ts +++ b/src/decorators.ts @@ -15,11 +15,16 @@ import {getAllLeadingComments, reportDiagnostic} from './transformer_util'; * Returns the declarations for the given decorator. */ export function getDecoratorDeclarations( - decorator: ts.Decorator, typeChecker: ts.TypeChecker): ts.Declaration[] { + decorator: ts.Decorator, + typeChecker: ts.TypeChecker, +): ts.Declaration[] { // Walk down the expression to find the identifier of the decorator function. let node: ts.Node = decorator; while (node.kind !== ts.SyntaxKind.Identifier) { - if (node.kind === ts.SyntaxKind.Decorator || node.kind === ts.SyntaxKind.CallExpression) { + if ( + node.kind === ts.SyntaxKind.Decorator || + node.kind === ts.SyntaxKind.CallExpression + ) { node = (node as ts.Decorator | ts.CallExpression).expression; } else { // We do not know how to handle this type of decorator. @@ -39,30 +44,39 @@ export function getDecoratorDeclarations( * Returns true if node has an exporting decorator (i.e., a decorator with @ExportDecoratedItems * in its JSDoc). */ -export function hasExportingDecorator(node: ts.Node, typeChecker: ts.TypeChecker) { +export function hasExportingDecorator( + node: ts.Node, + typeChecker: ts.TypeChecker, +) { const decorators = ts.canHaveDecorators(node) ? ts.getDecorators(node) : []; - return decorators && - decorators.some( - decorator => isExportingDecorator(decorator, typeChecker)); + return ( + decorators && + decorators.some((decorator) => isExportingDecorator(decorator, typeChecker)) + ); } /** * Returns true if the given decorator has an @ExportDecoratedItems directive in its JSDoc. */ -function isExportingDecorator(decorator: ts.Decorator, typeChecker: ts.TypeChecker) { - return getDecoratorDeclarations(decorator, typeChecker).some(declaration => { - const range = getAllLeadingComments(declaration); - if (!range) { - return false; - } - for (const {text} of range) { - if (/@ExportDecoratedItems\b/.test(text)) { - return true; +function isExportingDecorator( + decorator: ts.Decorator, + typeChecker: ts.TypeChecker, +) { + return getDecoratorDeclarations(decorator, typeChecker).some( + (declaration) => { + const range = getAllLeadingComments(declaration); + if (!range) { + return false; } - } - return false; - }); + for (const {text} of range) { + if (/@ExportDecoratedItems\b/.test(text)) { + return true; + } + } + return false; + }, + ); } /** @@ -94,11 +108,15 @@ function isExportingDecorator(decorator: ts.Decorator, typeChecker: ts.TypeCheck * ], Foo.prototype, * __googReflect.objectProperty("prop", Foo.prototype), void 0); */ -export function transformDecoratorsOutputForClosurePropertyRenaming(diagnostics: ts.Diagnostic[]) { +export function transformDecoratorsOutputForClosurePropertyRenaming( + diagnostics: ts.Diagnostic[], +) { return (context: ts.TransformationContext) => { - const result: ts.Transformer = (sourceFile: ts.SourceFile) => { - let nodeNeedingGoogReflect: undefined|ts.Node = undefined; - const visitor: ts.Visitor = (node) => { + const result: ts.Transformer = ( + sourceFile: ts.SourceFile, + ) => { + let nodeNeedingGoogReflect: undefined | ts.Node = undefined; + const visitor = (node: ts.Node) => { const replacementNode = rewriteDecorator(node); if (replacementNode) { nodeNeedingGoogReflect = node; @@ -106,47 +124,65 @@ export function transformDecoratorsOutputForClosurePropertyRenaming(diagnostics: } return ts.visitEachChild(node, visitor, context); }; - let updatedSourceFile = - // TODO: go/ts50upgrade - Remove after upgrade. - // tslint:disable-next-line:no-unnecessary-type-assertion - ts.visitNode(sourceFile, visitor, ts.isSourceFile)!; + let updatedSourceFile = ts.visitNode( + sourceFile, + visitor, + ts.isSourceFile, + ); if (nodeNeedingGoogReflect !== undefined) { const statements = [...updatedSourceFile.statements]; const googModuleIndex = statements.findIndex(isGoogModuleStatement); if (googModuleIndex === -1) { reportDiagnostic( - diagnostics, nodeNeedingGoogReflect, - 'Internal tsickle error: could not find goog.module statement to import __tsickle_googReflect for decorator compilation.'); + diagnostics, + nodeNeedingGoogReflect, + 'Internal tsickle error: could not find goog.module statement to import __tsickle_googReflect for decorator compilation.', + ); return sourceFile; } const googRequireReflectObjectProperty = - ts.factory.createVariableStatement( - undefined, - ts.factory.createVariableDeclarationList( - [ts.factory.createVariableDeclaration( - '__tsickle_googReflect', - /* exclamationToken */ undefined, /* type */ undefined, - ts.factory.createCallExpression( - ts.factory.createPropertyAccessExpression( - ts.factory.createIdentifier('goog'), 'require'), - undefined, - [ts.factory.createStringLiteral('goog.reflect')]))], - ts.NodeFlags.Const)); + ts.factory.createVariableStatement( + undefined, + ts.factory.createVariableDeclarationList( + [ + ts.factory.createVariableDeclaration( + '__tsickle_googReflect', + /* exclamationToken */ undefined, + /* type */ undefined, + ts.factory.createCallExpression( + ts.factory.createPropertyAccessExpression( + ts.factory.createIdentifier('goog'), + 'require', + ), + undefined, + [ts.factory.createStringLiteral('goog.reflect')], + ), + ), + ], + ts.NodeFlags.Const, + ), + ); // The boilerplate we produce has a goog.module line, then two related // lines dealing with the `module` variable. Insert our goog.require // after that to avoid visually breaking up the module info, and to be // with the rest of the goog.require statements. - statements.splice(googModuleIndex + 3, 0, googRequireReflectObjectProperty); + statements.splice( + googModuleIndex + 3, + 0, + googRequireReflectObjectProperty, + ); updatedSourceFile = ts.factory.updateSourceFile( - updatedSourceFile, - ts.setTextRange( - ts.factory.createNodeArray(statements), - updatedSourceFile.statements), - updatedSourceFile.isDeclarationFile, - updatedSourceFile.referencedFiles, - updatedSourceFile.typeReferenceDirectives, - updatedSourceFile.hasNoDefaultLib, - updatedSourceFile.libReferenceDirectives); + updatedSourceFile, + ts.setTextRange( + ts.factory.createNodeArray(statements), + updatedSourceFile.statements, + ), + updatedSourceFile.isDeclarationFile, + updatedSourceFile.referencedFiles, + updatedSourceFile.typeReferenceDirectives, + updatedSourceFile.hasNoDefaultLib, + updatedSourceFile.libReferenceDirectives, + ); } return updatedSourceFile; }; @@ -161,7 +197,7 @@ export function transformDecoratorsOutputForClosurePropertyRenaming(diagnostics: * * Returns undefined if no modification is necessary. */ -function rewriteDecorator(node: ts.Node): ts.Node|undefined { +function rewriteDecorator(node: ts.Node): ts.Node | undefined { if (!ts.isCallExpression(node)) { return; } @@ -189,13 +225,19 @@ function rewriteDecorator(node: ts.Node): ts.Node|undefined { } const fieldNameLiteral = untypedFieldNameLiteral; args[2] = ts.factory.createCallExpression( - ts.factory.createPropertyAccessExpression( - ts.factory.createIdentifier('__tsickle_googReflect'), - 'objectProperty'), - undefined, - [ts.factory.createStringLiteral(fieldNameLiteral.text), args[1]]); + ts.factory.createPropertyAccessExpression( + ts.factory.createIdentifier('__tsickle_googReflect'), + 'objectProperty', + ), + undefined, + [ts.factory.createStringLiteral(fieldNameLiteral.text), args[1]], + ); return ts.factory.updateCallExpression( - node, node.expression, node.typeArguments, args); + node, + node.expression, + node.typeArguments, + args, + ); } function isGoogModuleStatement(statement: ts.Node) { @@ -221,14 +263,16 @@ const TAGS_CONFLICTING_WITH_DECORATE = new Set(['template', 'abstract']); /** * Removes problematic annotations from JsDoc comments. */ -function sanitizeDecorateComments(comments: ts.SynthesizedComment[]): - ts.SynthesizedComment[] { +function sanitizeDecorateComments( + comments: ts.SynthesizedComment[], +): ts.SynthesizedComment[] { const sanitized: ts.SynthesizedComment[] = []; for (const comment of comments) { - const parsedComment: jsdoc.ParsedJSDocComment|null = jsdoc.parse(comment); + const parsedComment: jsdoc.ParsedJSDocComment | null = jsdoc.parse(comment); if (parsedComment && parsedComment.tags.length !== 0) { const filteredTags = parsedComment.tags.filter( - t => !(TAGS_CONFLICTING_WITH_DECORATE.has(t.tagName))); + (t) => !TAGS_CONFLICTING_WITH_DECORATE.has(t.tagName), + ); if (filteredTags.length !== 0) { sanitized.push(jsdoc.toSynthesizedComment(filteredTags)); } @@ -247,41 +291,47 @@ function sanitizeDecorateComments(comments: ts.SynthesizedComment[]): * `__decorate()` calls and may contain annotations that are not allowed in this * context and result in JSCompiler errors. */ -export function transformDecoratorJsdoc(): - ts.TransformerFactory { +export function transformDecoratorJsdoc(): ts.TransformerFactory { return () => { - const transformer: ts.Transformer = - (sourceFile: ts.SourceFile) => { - for (const stmt of sourceFile.statements) { - // Only need to iterate over top-level statements in the source - // file. - if (!ts.isExpressionStatement(stmt)) continue; - const expr = stmt.expression; - if (!ts.isBinaryExpression(expr)) continue; - if (expr.operatorToken.kind !== ts.SyntaxKind.EqualsToken) continue; - const rhs = expr.right; - if (!ts.isCallExpression(rhs)) continue; - if (ts.isIdentifier(rhs.expression) && - (rhs.expression.text === '__decorate')) { - const comments = ts.getSyntheticLeadingComments(stmt); - if (!comments || comments.length === 0) { - // Suppress visibility check for legacy decorators, otherwise - // any decorated final class causes errors. - ts.addSyntheticLeadingComment( - stmt, ts.SyntaxKind.MultiLineCommentTrivia, - '* @suppress {visibility} ', - /* trailing newline */ true); - } else { - // TODO(b/277272562): Remove this code path after TS5.1 is - // released, as it no longer duplicates the original comments to - // `ident = tslib_1.__decorate(...)` statements. - ts.setSyntheticLeadingComments( - stmt, sanitizeDecorateComments(comments)); - } - } + const transformer: ts.Transformer = ( + sourceFile: ts.SourceFile, + ) => { + for (const stmt of sourceFile.statements) { + // Only need to iterate over top-level statements in the source + // file. + if (!ts.isExpressionStatement(stmt)) continue; + const expr = stmt.expression; + if (!ts.isBinaryExpression(expr)) continue; + if (expr.operatorToken.kind !== ts.SyntaxKind.EqualsToken) continue; + const rhs = expr.right; + if (!ts.isCallExpression(rhs)) continue; + if ( + ts.isIdentifier(rhs.expression) && + rhs.expression.text === '__decorate' + ) { + const comments = ts.getSyntheticLeadingComments(stmt); + if (!comments || comments.length === 0) { + // Suppress visibility check for legacy decorators, otherwise + // any decorated final class causes errors. + ts.addSyntheticLeadingComment( + stmt, + ts.SyntaxKind.MultiLineCommentTrivia, + '* @suppress {visibility} ', + /* trailing newline */ true, + ); + } else { + // TODO(b/277272562): Remove this code path after TS5.1 is + // released, as it no longer duplicates the original comments to + // `ident = tslib_1.__decorate(...)` statements. + ts.setSyntheticLeadingComments( + stmt, + sanitizeDecorateComments(comments), + ); } - return sourceFile; - }; + } + } + return sourceFile; + }; return transformer; }; } diff --git a/src/enum_transformer.ts b/src/enum_transformer.ts index 812b4fff0..0ada3f483 100644 --- a/src/enum_transformer.ts +++ b/src/enum_transformer.ts @@ -19,10 +19,17 @@ * type resolve ("@type {Foo}"). */ +import {TsickleHost} from 'tsickle'; import * as ts from 'typescript'; import * as jsdoc from './jsdoc'; -import {createSingleQuoteStringLiteral, getIdentifierText, hasModifierFlag, isAmbient, isMergedDeclaration} from './transformer_util'; +import { + createSingleQuoteStringLiteral, + getIdentifierText, + hasModifierFlag, + isAmbient, + isMergedDeclaration, +} from './transformer_util'; /** * isInUnsupportedNamespace returns true if any of node's ancestors is a @@ -45,7 +52,10 @@ function isInUnsupportedNamespace(node: ts.Node) { /** * getEnumMemberType computes the type of an enum member by inspecting its initializer expression. */ -function getEnumMemberType(typeChecker: ts.TypeChecker, member: ts.EnumMember): 'number'|'string' { +function getEnumMemberType( + typeChecker: ts.TypeChecker, + member: ts.EnumMember, +): 'number' | 'string' { // Enum members without initialization have type 'number' if (!member.initializer) { return 'number'; @@ -68,8 +78,10 @@ function getEnumMemberType(typeChecker: ts.TypeChecker, member: ts.EnumMember): * getEnumType computes the Closure type of an enum, by iterating through the members and gathering * their types. */ -export function getEnumType(typeChecker: ts.TypeChecker, enumDecl: ts.EnumDeclaration): 'number'| - 'string'|'?' { +export function getEnumType( + typeChecker: ts.TypeChecker, + enumDecl: ts.EnumDeclaration, +): 'number' | 'string' | '?' { let hasNumber = false; let hasString = false; for (const member of enumDecl.members) { @@ -81,7 +93,7 @@ export function getEnumType(typeChecker: ts.TypeChecker, enumDecl: ts.EnumDeclar } } if (hasNumber && hasString) { - return '?'; // Closure's new type inference doesn't support enums of unions. + return '?'; // Closure's new type inference doesn't support enums of unions. } else if (hasNumber) { return 'number'; } else if (hasString) { @@ -95,11 +107,15 @@ export function getEnumType(typeChecker: ts.TypeChecker, enumDecl: ts.EnumDeclar /** * Transformer factory for the enum transformer. See fileoverview for details. */ -export function enumTransformer(typeChecker: ts.TypeChecker): - (context: ts.TransformationContext) => ts.Transformer { +export function enumTransformer( + host: TsickleHost, + typeChecker: ts.TypeChecker, +): (context: ts.TransformationContext) => ts.Transformer { return (context: ts.TransformationContext) => { - function visitor(node: T): T|ts.Node[] { - if (!ts.isEnumDeclaration(node)) return ts.visitEachChild(node, visitor, context); + function visitor(node: T): T | ts.Node[] { + if (!ts.isEnumDeclaration(node)) { + return ts.visitEachChild(node, visitor, context); + } // TODO(martinprobst): The enum transformer does not work for enums embedded in namespaces, // because TS does not support splitting export and declaration ("export {Foo};") in @@ -126,8 +142,9 @@ export function enumTransformer(typeChecker: ts.TypeChecker): enumIndex = enumConstValue + 1; if (enumConstValue < 0) { enumValue = ts.factory.createPrefixUnaryExpression( - ts.SyntaxKind.MinusToken, - ts.factory.createNumericLiteral(-enumConstValue)); + ts.SyntaxKind.MinusToken, + ts.factory.createNumericLiteral(-enumConstValue), + ); } else { enumValue = ts.factory.createNumericLiteral(enumConstValue); } @@ -161,28 +178,46 @@ export function enumTransformer(typeChecker: ts.TypeChecker): enumValue = ts.factory.createNumericLiteral(enumIndex); enumIndex++; } - values.push(ts.setOriginalNode( + values.push( + ts.setOriginalNode( ts.setTextRange( - ts.factory.createPropertyAssignment(member.name, enumValue), - member), - member)); + ts.factory.createPropertyAssignment(member.name, enumValue), + member, + ), + member, + ), + ); } const varDecl = ts.factory.createVariableDeclaration( - node.name, /* exclamationToken */ undefined, /* type */ undefined, - ts.factory.createObjectLiteralExpression( - ts.setTextRange( - ts.factory.createNodeArray(values, true), node.members), - true)); - const varDeclStmt = ts.setOriginalNode( + node.name, + /* exclamationToken */ undefined, + /* type */ undefined, + ts.factory.createObjectLiteralExpression( ts.setTextRange( - ts.factory.createVariableStatement( - /* modifiers */ undefined, - ts.factory.createVariableDeclarationList( - [varDecl], - /* create a const var */ ts.NodeFlags.Const)), - node), - node); + ts.factory.createNodeArray(values, true), + node.members, + ), + true, + ), + ); + const varDeclStmt = ts.setOriginalNode( + ts.setTextRange( + ts.factory.createVariableStatement( + /* modifiers */ undefined, + ts.factory.createVariableDeclarationList( + [varDecl], + /* When using unoptimized namespaces, create a var + declaration, otherwise create a const var. See b/157460535 */ + host.useDeclarationMergingTransformation + ? ts.NodeFlags.Const + : undefined, + ), + ), + node, + ), + node, + ); const tags = jsdoc.getJSDocTags(ts.getOriginalNode(node)); tags.push({tagName: 'enum', type: enumType}); @@ -194,11 +229,19 @@ export function enumTransformer(typeChecker: ts.TypeChecker): if (isExported) { // Create a separate export {...} statement, so that the enum name can be used in local // type annotations within the file. - resultNodes.push(ts.factory.createExportDeclaration( + resultNodes.push( + ts.factory.createExportDeclaration( /* modifiers */ undefined, /* isTypeOnly */ false, - ts.factory.createNamedExports([ts.factory.createExportSpecifier( - /* isTypeOnly */ false, undefined, name)]))); + ts.factory.createNamedExports([ + ts.factory.createExportSpecifier( + /* isTypeOnly */ false, + undefined, + name, + ), + ]), + ), + ); } if (hasModifierFlag(node, ts.ModifierFlags.Const)) { @@ -227,21 +270,34 @@ export function enumTransformer(typeChecker: ts.TypeChecker): // Foo[Foo.ABC] = "ABC"; nameExpr = createSingleQuoteStringLiteral(memberName.text); // Make sure to create a clean, new identifier, so comments do not get emitted twice. - const ident = - ts.factory.createIdentifier(getIdentifierText(memberName)); + const ident = ts.factory.createIdentifier( + getIdentifierText(memberName), + ); memberAccess = ts.factory.createPropertyAccessExpression( - ts.factory.createIdentifier(name), ident); + ts.factory.createIdentifier(name), + ident, + ); } else { // Foo[Foo["A B C"]] = "A B C"; or Foo[Foo[expression]] = expression; - nameExpr = ts.isComputedPropertyName(memberName) ? memberName.expression : memberName; + nameExpr = ts.isComputedPropertyName(memberName) + ? memberName.expression + : memberName; memberAccess = ts.factory.createElementAccessExpression( - ts.factory.createIdentifier(name), nameExpr); + ts.factory.createIdentifier(name), + nameExpr, + ); } resultNodes.push( - ts.factory.createExpressionStatement(ts.factory.createAssignment( - ts.factory.createElementAccessExpression( - ts.factory.createIdentifier(name), memberAccess), - nameExpr))); + ts.factory.createExpressionStatement( + ts.factory.createAssignment( + ts.factory.createElementAccessExpression( + ts.factory.createIdentifier(name), + memberAccess, + ), + nameExpr, + ), + ), + ); } return resultNodes; } diff --git a/src/externs.ts b/src/externs.ts index a30f6caf0..f29a579a6 100644 --- a/src/externs.ts +++ b/src/externs.ts @@ -71,10 +71,21 @@ import {AnnotatorHost, moduleNameAsIdentifier} from './annotator_host'; import {getEnumType} from './enum_transformer'; import {GoogModuleProcessorHost, jsPathToNamespace} from './googmodule'; import * as jsdoc from './jsdoc'; -import {escapeForComment, maybeAddHeritageClauses, maybeAddTemplateClause} from './jsdoc_transformer'; +import { + escapeForComment, + maybeAddHeritageClauses, + maybeAddTemplateClause, +} from './jsdoc_transformer'; import {ModuleTypeTranslator} from './module_type_translator'; import * as path from './path'; -import {getEntityNameText, getIdentifierText, hasModifierFlag, isAmbient, isDtsFileName, reportDiagnostic} from './transformer_util'; +import { + getEntityNameText, + getIdentifierText, + hasModifierFlag, + isAmbient, + isDtsFileName, + reportDiagnostic, +} from './transformer_util'; import {isValidClosurePropertyName} from './type_translator'; /** @@ -97,7 +108,6 @@ const PREDECLARED_CLOSURE_EXTERNS_LIST: ReadonlyArray = [ 'WorkerGlobalScope', ]; - /** * The header to be used in generated externs. This is not included in the * output of generateExterns() because generateExterns() works one file at a @@ -129,8 +139,9 @@ const EXTERNS_HEADER = `/** * to this root. */ export function getGeneratedExterns( - externs: {[fileName: string]: {output: string, moduleNamespace: string}}, - rootDir: string): string { + externs: {[fileName: string]: {output: string; moduleNamespace: string}}, + rootDir: string, +): string { let allExterns = EXTERNS_HEADER; for (const fileName of Object.keys(externs)) { const srcPath = path.relative(rootDir, fileName); @@ -148,7 +159,9 @@ function isInGlobalAugmentation(declaration: ts.Declaration): boolean { // declare global { ... } creates a ModuleDeclaration containing a ModuleBlock containing the // declaration, with the ModuleDeclaration having the GlobalAugmentation flag set. if (!declaration.parent || !declaration.parent.parent) return false; - return (declaration.parent.parent.flags & ts.NodeFlags.GlobalAugmentation) !== 0; + return ( + (declaration.parent.parent.flags & ts.NodeFlags.GlobalAugmentation) !== 0 + ); } /** @@ -157,9 +170,10 @@ function isInGlobalAugmentation(declaration: ts.Declaration): boolean { * comment with \@fileoverview and #externs (see above for that). */ export function generateExterns( - typeChecker: ts.TypeChecker, sourceFile: ts.SourceFile, - host: AnnotatorHost&GoogModuleProcessorHost): - {output: string, diagnostics: ts.Diagnostic[], moduleNamespace: string} { + typeChecker: ts.TypeChecker, + sourceFile: ts.SourceFile, + host: AnnotatorHost & GoogModuleProcessorHost, +): {output: string; diagnostics: ts.Diagnostic[]; moduleNamespace: string} { let output = ''; const diagnostics: ts.Diagnostic[] = []; const isDts = isDtsFileName(sourceFile.fileName); @@ -204,14 +218,25 @@ export function generateExterns( } const mtt = new ModuleTypeTranslator( - sourceFile, typeChecker, host, diagnostics, /*isForExterns*/ true, - /*useInternalNamespaceForExterns=*/ hasExportEquals); + sourceFile, + typeChecker, + host, + diagnostics, + /*isForExterns*/ true, + /*useInternalNamespaceForExterns=*/ hasExportEquals, + ); for (const stmt of sourceFile.statements) { // Always collect alises for imported symbols. importsVisitor(stmt); - if (!isDts && !hasModifierFlag(stmt as ts.DeclarationStatement, ts.ModifierFlags.Ambient)) { + if ( + !isDts && + !hasModifierFlag( + stmt as ts.DeclarationStatement, + ts.ModifierFlags.Ambient, + ) + ) { continue; } visitor(stmt, []); @@ -224,7 +249,9 @@ export function generateExterns( * to it with the mangled module namespace as it is emitted in the global namespace. Similarly, if * the symbol is declared in a non-module context, it must not be mangled. */ - function qualifiedNameToMangledIdentifier(name: ts.Identifier|ts.QualifiedName) { + function qualifiedNameToMangledIdentifier( + name: ts.Identifier | ts.QualifiedName, + ) { const entityName = getEntityNameText(name); let symbol = typeChecker.getSymbolAtLocation(name); if (symbol) { @@ -234,13 +261,16 @@ export function generateExterns( } const alias = mtt.symbolsToAliasedNames.get(symbol); if (alias) return alias; - const isGlobalSymbol = symbol && symbol.declarations && symbol.declarations.some(d => { - if (isInGlobalAugmentation(d)) return true; - // If the declaration's source file is not a module, it must be global. - // If it is a module, the identifier must be local to this file, or handled above via the - // alias. - return !ts.isExternalModule(d.getSourceFile()); - }); + const isGlobalSymbol = + symbol && + symbol.declarations && + symbol.declarations.some((d) => { + if (isInGlobalAugmentation(d)) return true; + // If the declaration's source file is not a module, it must be global. + // If it is a module, the identifier must be local to this file, or handled above via the + // alias. + return !ts.isExternalModule(d.getSourceFile()); + }); if (isGlobalSymbol) return entityName; } return rootNamespace + '.' + entityName; @@ -253,21 +283,30 @@ export function generateExterns( let exportedNamespace = rootNamespace; if (exportAssignment && hasExportEquals) { - if (ts.isIdentifier(exportAssignment.expression) || - ts.isQualifiedName(exportAssignment.expression)) { + if ( + ts.isIdentifier(exportAssignment.expression) || + ts.isQualifiedName(exportAssignment.expression) + ) { // E.g. export = someName; // If someName is "declare global { namespace someName {...} }", tsickle must not qualify // access to it with module namespace as it is emitted in the global namespace. - exportedNamespace = qualifiedNameToMangledIdentifier(exportAssignment.expression); + exportedNamespace = qualifiedNameToMangledIdentifier( + exportAssignment.expression, + ); } else { reportDiagnostic( - diagnostics, exportAssignment.expression, - `export = expression must be a qualified name, got ${ - ts.SyntaxKind[exportAssignment.expression.kind]}.`); + diagnostics, + exportAssignment.expression, + `export = expression must be a qualified name, got ${ + ts.SyntaxKind[exportAssignment.expression.kind] + }.`, + ); } // Assign the actually exported namespace object (which lives somewhere under rootNamespace) // into the module's namespace. - emit(`/**\n * export = ${exportAssignment.expression.getText()}\n * @const\n */\n`); + emit( + `/**\n * export = ${exportAssignment.expression.getText()}\n * @const\n */\n`, + ); emit(`var ${moduleNamespace} = ${exportedNamespace};\n`); } @@ -275,7 +314,9 @@ export function generateExterns( // In a non-shimmed module, create a global namespace. This exists purely for backwards // compatiblity, in the medium term all code using tsickle should always use `goog.module`s, // so global names should not be neccessary. - for (const nsExport of sourceFile.statements.filter(ts.isNamespaceExportDeclaration)) { + for (const nsExport of sourceFile.statements.filter( + ts.isNamespaceExportDeclaration, + )) { const namespaceName = getIdentifierText(nsExport.name); emit(`// export as namespace ${namespaceName}\n`); writeVariableStatement(namespaceName, [], exportedNamespace); @@ -295,28 +336,31 @@ export function generateExterns( * interface Foo { x: number; } * interface Foo { y: number; } * we only want to emit the "\@record" for Foo on the first one. - * - * The exception are variable declarations, which - in externs - do not assign a value: - * /.. \@type {...} ./ - * var someVariable; - * /.. \@type {...} ./ - * someNamespace.someVariable; - * If a later declaration wants to add additional properties on someVariable, tsickle must still - * emit an assignment into the object, as it's otherwise absent. */ function isFirstValueDeclaration(decl: ts.DeclarationStatement): boolean { if (!decl.name) return true; const sym = typeChecker.getSymbolAtLocation(decl.name)!; if (!sym.declarations || sym.declarations.length < 2) return true; - const earlierDecls = sym.declarations.slice(0, sym.declarations.indexOf(decl)); - // Either there are no earlier declarations, or all of them are variables (see above). tsickle - // emits a value for all other declaration kinds (function for functions, classes, interfaces, - // {} object for namespaces). - return earlierDecls.length === 0 || earlierDecls.every(ts.isVariableDeclaration); + const earlierDecls = sym.declarations.slice( + 0, + sym.declarations.indexOf(decl), + ); + return ( + earlierDecls.length === 0 || + earlierDecls.every( + (d) => + ts.isVariableDeclaration(d) && + d.getSourceFile() !== decl.getSourceFile(), + ) + ); } /** Writes the actual variable statement of a Closure variable declaration. */ - function writeVariableStatement(name: string, namespace: ReadonlyArray, value?: string) { + function writeVariableStatement( + name: string, + namespace: ReadonlyArray, + value?: string, + ) { const qualifiedName = namespace.concat([name]).join('.'); if (namespace.length === 0) emit(`var `); emit(qualifiedName); @@ -329,7 +373,9 @@ export function generateExterns( * comment making it a declaration. */ function writeVariableDeclaration( - decl: ts.VariableDeclaration, namespace: ReadonlyArray) { + decl: ts.VariableDeclaration, + namespace: ReadonlyArray, + ) { if (decl.name.kind === ts.SyntaxKind.Identifier) { const name = getIdentifierText(decl.name); if (PREDECLARED_CLOSURE_EXTERNS_LIST.indexOf(name) >= 0) return; @@ -345,31 +391,45 @@ export function generateExterns( * Emits a JSDoc declaration that merges the signatures of the given function declaration (for * overloads), and returns the parameter names chosen. */ - function emitFunctionType(decls: ts.FunctionLikeDeclaration[], extraTags: jsdoc.Tag[] = []) { + function emitFunctionType( + decls: ts.FunctionLikeDeclaration[], + extraTags: jsdoc.Tag[] = [], + ) { const {tags, parameterNames} = mtt.getFunctionTypeJSDoc(decls, extraTags); emit('\n'); emit(jsdoc.toString(tags)); return parameterNames; } - function writeFunction(name: ts.Node, params: string[], namespace: ReadonlyArray) { + function writeFunction( + name: ts.Node, + params: string[], + namespace: ReadonlyArray, + ) { const paramsStr = params.join(', '); if (namespace.length > 0) { let fqn = namespace.join('.'); if (name.kind === ts.SyntaxKind.Identifier) { - fqn += '.'; // computed names include [ ] in their getText() representation. + fqn += '.'; // computed names include [ ] in their getText() representation. } fqn += name.getText(); emit(`${fqn} = function(${paramsStr}) {};\n`); } else { if (name.kind !== ts.SyntaxKind.Identifier) { - reportDiagnostic(diagnostics, name, 'Non-namespaced computed name in externs'); + reportDiagnostic( + diagnostics, + name, + 'Non-namespaced computed name in externs', + ); } emit(`function ${name.getText()}(${paramsStr}) {}\n`); } } - function writeEnum(decl: ts.EnumDeclaration, namespace: ReadonlyArray) { + function writeEnum( + decl: ts.EnumDeclaration, + namespace: ReadonlyArray, + ) { // E.g. /** @enum {number} */ var COUNTRY = {US: 1, CA: 1}; const name = getIdentifierText(decl.name); let members = ''; @@ -378,7 +438,7 @@ export function generateExterns( // matter in externs. const initializer = enumType === 'string' ? `''` : 1; for (const member of decl.members) { - let memberName: string|undefined; + let memberName: string | undefined; switch (member.name.kind) { case ts.SyntaxKind.Identifier: memberName = getIdentifierText(member.name); @@ -391,8 +451,9 @@ export function generateExterns( break; } if (!memberName) { - members += ` /* TODO: ${ts.SyntaxKind[member.name.kind]}: ${ - escapeForComment(member.name.getText())} */\n`; + members += ` /* TODO: ${ + ts.SyntaxKind[member.name.kind] + }: ${escapeForComment(member.name.getText())} */\n`; continue; } members += ` ${memberName}: ${initializer},\n`; @@ -410,8 +471,10 @@ export function generateExterns( * alias for them. */ function handleLostProperties( - decl: ts.TypeAliasDeclaration, namespace: readonly string[]) { - let propNames: Set|undefined = undefined; + decl: ts.TypeAliasDeclaration, + namespace: readonly string[], + ) { + let propNames: Set | undefined = undefined; function collectPropertyNames(node: ts.Node) { if (ts.isTypeLiteralNode(node)) { @@ -436,14 +499,20 @@ export function generateExterns( ts.forEachChild(decl, findTypeIntersection); if (propNames) { const helperName = - getIdentifierText(decl.name) + '_preventPropRenaming_doNotUse'; - emit(`\n/** @typedef {{${ - [...propNames].map(p => `${p}: ?`).join(', ')}}} */\n`); + getIdentifierText(decl.name) + '_preventPropRenaming_doNotUse'; + emit( + `\n/** @typedef {{${[...propNames] + .map((p) => `${p}: ?`) + .join(', ')}}} */\n`, + ); writeVariableStatement(helperName, namespace); } } - function writeTypeAlias(decl: ts.TypeAliasDeclaration, namespace: ReadonlyArray) { + function writeTypeAlias( + decl: ts.TypeAliasDeclaration, + namespace: ReadonlyArray, + ) { const typeStr = mtt.typeToClosure(decl, undefined); emit(`\n/** @typedef {${typeStr}} */\n`); writeVariableStatement(getIdentifierText(decl.name), namespace); @@ -451,7 +520,9 @@ export function generateExterns( } function writeType( - decl: ts.InterfaceDeclaration|ts.ClassDeclaration, namespace: ReadonlyArray) { + decl: ts.InterfaceDeclaration | ts.ClassDeclaration, + namespace: ReadonlyArray, + ) { const name = decl.name; if (!name) { reportDiagnostic(diagnostics, decl, 'anonymous type in externs'); @@ -461,9 +532,11 @@ export function generateExterns( // gbigint, as defined in // google3/third_party/java_src/clutz/src/resources/closure.lib.d.ts, is // defined separately in TypeScript and JavaScript. - if (name.escapedText === 'gbigint' - // Just the terminal filename so we can test this. - && decl.getSourceFile().fileName.endsWith('closure.lib.d.ts')) { + if ( + name.escapedText === 'gbigint' && + // Just the terminal filename so we can test this. + decl.getSourceFile().fileName.endsWith('closure.lib.d.ts') + ) { return; } @@ -512,8 +585,9 @@ export function generateExterns( type = '?|undefined'; } const isReadonly = hasModifierFlag(prop, ts.ModifierFlags.Readonly); - emit(jsdoc.toString( - [{tagName: isReadonly ? 'const' : 'type', type}])); + emit( + jsdoc.toString([{tagName: isReadonly ? 'const' : 'type', type}]), + ); if (hasModifierFlag(prop, ts.ModifierFlags.Static)) { emit(`\n${typeName}.${prop.name.getText()};\n`); } else { @@ -534,8 +608,10 @@ export function generateExterns( // of the extern property, if a getter exists. Both the setter and // getter should give the same type when we query the compiler, // but prefer the getter to ensure consistency. - if (!accessors.has(name) || - accessor.kind === ts.SyntaxKind.GetAccessor) { + if ( + !accessors.has(name) || + accessor.kind === ts.SyntaxKind.GetAccessor + ) { accessors.set(name, accessor); } continue; @@ -545,7 +621,9 @@ export function generateExterns( case ts.SyntaxKind.MethodDeclaration: const method = member as ts.MethodDeclaration; const isStatic = hasModifierFlag(method, ts.ModifierFlags.Static); - const methodSignature = `${method.name.getText()}$$$${isStatic ? 'static' : 'instance'}`; + const methodSignature = `${method.name.getText()}$$$${ + isStatic ? 'static' : 'instance' + }`; if (methods.has(methodSignature)) { methods.get(methodSignature)!.push(method); @@ -554,7 +632,7 @@ export function generateExterns( } continue; case ts.SyntaxKind.Constructor: - continue; // Handled above. + continue; // Handled above. default: // Members can include things like index signatures, for e.g. // interface Foo { [key: string]: number; } @@ -566,7 +644,11 @@ export function generateExterns( if (member.name) { memberName = memberName.concat([member.name.getText()]); } - emit(`\n/* TODO: ${ts.SyntaxKind[member.kind]}: ${memberName.join('.')} */\n`); + emit( + `\n/* TODO: ${ts.SyntaxKind[member.kind]}: ${memberName.join( + '.', + )} */\n`, + ); } // Handle accessors (get/set) separately so that we only emit one property @@ -603,16 +685,26 @@ export function generateExterns( } function writeExportDeclaration( - exportDeclaration: ts.ExportDeclaration, namespace: ReadonlyArray) { + exportDeclaration: ts.ExportDeclaration, + namespace: ReadonlyArray, + ) { if (!exportDeclaration.exportClause) { - emit(`\n// TODO(tsickle): export * declaration in ${ - debugLocationStr(exportDeclaration, namespace)}\n`); + emit( + `\n// TODO(tsickle): export * declaration in ${debugLocationStr( + exportDeclaration, + namespace, + )}\n`, + ); return; } if (ts.isNamespaceExport(exportDeclaration.exportClause)) { // TODO(#1135): Support generating externs using this syntax. - emit(`\n// TODO(tsickle): export * as declaration in ${ - debugLocationStr(exportDeclaration, namespace)}\n`); + emit( + `\n// TODO(tsickle): export * as declaration in ${debugLocationStr( + exportDeclaration, + namespace, + )}\n`, + ); return; } for (const exportSpecifier of exportDeclaration.exportClause.elements) { @@ -620,8 +712,10 @@ export function generateExterns( if (!exportSpecifier.propertyName) continue; emit('/** @const */\n'); writeVariableStatement( - exportSpecifier.name.text, namespace, - namespace.join('.') + '.' + exportSpecifier.propertyName.text); + exportSpecifier.name.text, + namespace, + namespace.join('.') + '.' + exportSpecifier.propertyName.text, + ); } } @@ -632,19 +726,19 @@ export function generateExterns( */ function getCtors(decl: ts.ClassDeclaration): ts.ConstructorDeclaration[] { // Get ctors from current class - const currentCtors = - decl.members.filter((m) => m.kind === ts.SyntaxKind.Constructor); + const currentCtors = decl.members.filter( + (m) => m.kind === ts.SyntaxKind.Constructor, + ); if (currentCtors.length) { return currentCtors as ts.ConstructorDeclaration[]; } // Or look at base classes if (decl.heritageClauses) { - const baseSymbols = - decl.heritageClauses - .filter((h) => h.token === ts.SyntaxKind.ExtendsKeyword) - .flatMap((h) => h.types) - .filter((t) => t.expression.kind === ts.SyntaxKind.Identifier); + const baseSymbols = decl.heritageClauses + .filter((h) => h.token === ts.SyntaxKind.ExtendsKeyword) + .flatMap((h) => h.types) + .filter((t) => t.expression.kind === ts.SyntaxKind.Identifier); for (const base of baseSymbols) { const sym = typeChecker.getSymbolAtLocation(base.expression); if (!sym || !sym.declarations) return []; @@ -669,8 +763,9 @@ export function generateExterns( * imported module URI and produce `path.to.module.Symbol` as an alias, and use that when * referencing the type. */ - function addImportAliases(decl: ts.ImportDeclaration| - ts.ImportEqualsDeclaration) { + function addImportAliases( + decl: ts.ImportDeclaration | ts.ImportEqualsDeclaration, + ) { // Side effect import, like "import 'somepath';" declares no local aliases. if (ts.isImportDeclaration(decl) && !decl.importClause) return; @@ -693,21 +788,36 @@ export function generateExterns( const moduleSymbol = typeChecker.getSymbolAtLocation(moduleUri); if (!moduleSymbol) { reportDiagnostic( - importDiagnostics, moduleUri, `imported module has no symbol`); + importDiagnostics, + moduleUri, + `imported module has no symbol`, + ); return; } const googNamespace = jsPathToNamespace( - host, moduleUri, importDiagnostics, moduleUri.text, () => moduleSymbol); + host, + moduleUri, + importDiagnostics, + moduleUri.text, + () => moduleSymbol, + ); const isDefaultImport = - ts.isImportDeclaration(decl) && !!decl.importClause?.name; + ts.isImportDeclaration(decl) && !!decl.importClause?.name; if (googNamespace) { mtt.registerImportSymbolAliases( - googNamespace, isDefaultImport, moduleSymbol, () => googNamespace); + googNamespace, + isDefaultImport, + moduleSymbol, + () => googNamespace, + ); } else { mtt.registerImportSymbolAliases( - /* googNamespace= */ undefined, isDefaultImport, moduleSymbol, - getAliasPrefixForEsModule(moduleUri)); + /* googNamespace= */ undefined, + isDefaultImport, + moduleSymbol, + getAliasPrefixForEsModule(moduleUri), + ); } } @@ -720,10 +830,15 @@ export function generateExterns( // Calls to moduleNameAsIdentifier and host.pathToModuleName can incur // file system accesses, which are slow. Make sure they are only called // once and if/when needed. - const ambientModulePrefix = - moduleNameAsIdentifier(host, moduleUri.text, sourceFile.fileName); - const defaultPrefix = - host.pathToModuleName(sourceFile.fileName, moduleUri.text); + const ambientModulePrefix = moduleNameAsIdentifier( + host, + moduleUri.text, + sourceFile.fileName, + ); + const defaultPrefix = host.pathToModuleName( + sourceFile.fileName, + moduleUri.text, + ); return (exportedSymbol: ts.Symbol) => { // While type_translator does add the mangled prefix for ambient // declarations, it only does so for non-aliased (i.e. not imported) @@ -733,9 +848,11 @@ export function generateExterns( // already contains the correct module name, which means the mangled // module name in case of imports symbols. This only applies to // non-Closure ('goog:') imports. - const isAmbientModuleDeclaration = exportedSymbol.declarations && - exportedSymbol.declarations.some( - d => isAmbient(d) || d.getSourceFile().isDeclarationFile); + const isAmbientModuleDeclaration = + exportedSymbol.declarations && + exportedSymbol.declarations.some( + (d) => isAmbient(d) || d.getSourceFile().isDeclarationFile, + ); return isAmbientModuleDeclaration ? ambientModulePrefix : defaultPrefix; }; } @@ -746,7 +863,11 @@ export function generateExterns( * covered. */ function errorUnimplementedKind(node: ts.Node, where: string) { - reportDiagnostic(diagnostics, node, `${ts.SyntaxKind[node.kind]} not implemented in ${where}`); + reportDiagnostic( + diagnostics, + node, + `${ts.SyntaxKind[node.kind]} not implemented in ${where}`, + ); } /** @@ -773,7 +894,9 @@ export function generateExterns( * without the namespace wrapper. */ function getNamespaceForTopLevelDeclaration( - declaration: ts.Declaration, namespace: ReadonlyArray): ReadonlyArray { + declaration: ts.Declaration, + namespace: ReadonlyArray, + ): ReadonlyArray { // Only use rootNamespace for top level symbols, any other namespacing (global names, nested // namespaces) is always kept. if (namespace.length !== 0) return namespace; @@ -781,7 +904,9 @@ export function generateExterns( // namespace prefixed. if (isDts && isExternalModule) return [rootNamespace]; // Same for exported declarations in regular .ts files. - if (hasModifierFlag(declaration, ts.ModifierFlags.Export)) return [rootNamespace]; + if (hasModifierFlag(declaration, ts.ModifierFlags.Export)) { + return [rootNamespace]; + } // But local declarations in .ts files or .d.ts files (1b, 2b) are global, too. return []; } @@ -797,15 +922,20 @@ export function generateExterns( function debugLocationStr(node: ts.Node, namespace: ReadonlyArray) { // Use a regex to grab the filename without a path, to make the output stable // under bazel where sandboxes use different paths. - return namespace.join('.') || node.getSourceFile().fileName.replace(/.*[/\\]/, ''); + return ( + namespace.join('.') || + node.getSourceFile().fileName.replace(/.*[/\\]/, '') + ); } function importsVisitor(node: ts.Node) { switch (node.kind) { case ts.SyntaxKind.ImportEqualsDeclaration: const importEquals = node as ts.ImportEqualsDeclaration; - if (importEquals.moduleReference.kind === - ts.SyntaxKind.ExternalModuleReference) { + if ( + importEquals.moduleReference.kind === + ts.SyntaxKind.ExternalModuleReference + ) { addImportAliases(importEquals); } break; @@ -820,7 +950,10 @@ export function generateExterns( function visitor(node: ts.Node, namespace: ReadonlyArray) { if (node.parent === sourceFile) { - namespace = getNamespaceForTopLevelDeclaration(node as ts.DeclarationStatement, namespace); + namespace = getNamespaceForTopLevelDeclaration( + node as ts.DeclarationStatement, + namespace, + ); } switch (node.kind) { @@ -850,8 +983,11 @@ export function generateExterns( // file, so effectively this augments any existing module. const importName = decl.name.text; - const mangled = - moduleNameAsIdentifier(host, importName, sourceFile.fileName); + const mangled = moduleNameAsIdentifier( + host, + importName, + sourceFile.fileName, + ); emit(`// Derived from: declare module "${importName}"\n`); namespace = [mangled]; @@ -864,7 +1000,10 @@ export function generateExterns( if (decl.body) visitor(decl.body, [mangled]); break; default: - errorUnimplementedKind(decl.name, 'externs generation of namespace'); + errorUnimplementedKind( + decl.name, + 'externs generation of namespace', + ); break; } break; @@ -876,25 +1015,37 @@ export function generateExterns( break; case ts.SyntaxKind.ImportEqualsDeclaration: const importEquals = node as ts.ImportEqualsDeclaration; - if (importEquals.moduleReference.kind === ts.SyntaxKind.ExternalModuleReference) { + if ( + importEquals.moduleReference.kind === + ts.SyntaxKind.ExternalModuleReference + ) { // Handled in `importsVisitor`. break; } const localName = getIdentifierText(importEquals.name); - const qn = qualifiedNameToMangledIdentifier(importEquals.moduleReference); + const qn = qualifiedNameToMangledIdentifier( + importEquals.moduleReference, + ); // @const so that Closure Compiler understands this is an alias. emit('/** @const */\n'); writeVariableStatement(localName, namespace, qn); break; case ts.SyntaxKind.ClassDeclaration: case ts.SyntaxKind.InterfaceDeclaration: - writeType(node as ts.InterfaceDeclaration | ts.ClassDeclaration, namespace); + writeType( + node as ts.InterfaceDeclaration | ts.ClassDeclaration, + namespace, + ); break; case ts.SyntaxKind.FunctionDeclaration: const fnDecl = node as ts.FunctionDeclaration; const name = fnDecl.name; if (!name) { - reportDiagnostic(diagnostics, fnDecl, 'anonymous function in externs'); + reportDiagnostic( + diagnostics, + fnDecl, + 'anonymous function in externs', + ); break; } // Gather up all overloads of this function. @@ -906,7 +1057,8 @@ export function generateExterns( writeFunction(name, params, namespace); break; case ts.SyntaxKind.VariableStatement: - for (const decl of (node as ts.VariableStatement).declarationList.declarations) { + for (const decl of (node as ts.VariableStatement).declarationList + .declarations) { writeVariableDeclaration(decl, namespace); } break; @@ -928,8 +1080,11 @@ export function generateExterns( writeExportDeclaration(exportDeclaration, namespace); break; default: - emit(`\n// TODO(tsickle): ${ts.SyntaxKind[node.kind]} in ${ - debugLocationStr(node, namespace)}\n`); + emit( + `\n// TODO(tsickle): ${ + ts.SyntaxKind[node.kind] + } in ${debugLocationStr(node, namespace)}\n`, + ); break; } } diff --git a/src/fileoverview_comment_transformer.ts b/src/fileoverview_comment_transformer.ts index 59563102b..6513710f4 100644 --- a/src/fileoverview_comment_transformer.ts +++ b/src/fileoverview_comment_transformer.ts @@ -10,15 +10,24 @@ import * as ts from 'typescript'; import * as jsdoc from './jsdoc'; import * as path from './path'; -import {createNotEmittedStatement, reportDiagnostic, synthesizeCommentRanges, updateSourceFileNode} from './transformer_util'; +import { + reportDiagnostic, + synthesizeCommentRanges, + updateSourceFileNode, +} from './transformer_util'; /** * A set of JSDoc tags that mark a comment as a fileoverview comment. These are * recognized by other pieces of infrastructure (Closure Compiler, module * system, ...). */ -const FILEOVERVIEW_COMMENT_MARKERS: ReadonlySet = - new Set(['fileoverview', 'externs', 'modName', 'mods', 'pintomodule']); +const FILEOVERVIEW_COMMENT_MARKERS: ReadonlySet = new Set([ + 'fileoverview', + 'externs', + 'modName', + 'mods', + 'pintomodule', +]); /** * Given a parsed \@fileoverview comment, ensures it has all the attributes we @@ -29,60 +38,69 @@ const FILEOVERVIEW_COMMENT_MARKERS: ReadonlySet = * @param tags Comment as parsed list of tags; modified in-place. */ function augmentFileoverviewComments( - options: ts.CompilerOptions, source: ts.SourceFile, tags: jsdoc.Tag[], - generateExtraSuppressions: boolean) { + options: ts.CompilerOptions, + source: ts.SourceFile, + tags: jsdoc.Tag[], + generateExtraSuppressions: boolean, +) { // Ensure we start with a @fileoverview. - let fileOverview = tags.find(t => t.tagName === 'fileoverview'); + let fileOverview = tags.find((t) => t.tagName === 'fileoverview'); if (!fileOverview) { fileOverview = {tagName: 'fileoverview', text: 'added by tsickle'}; tags.splice(0, 0, fileOverview); } if (options.rootDir != null) { - const GENERATED_FROM_COMMENT_TEXT = `\n${ - jsdoc.createGeneratedFromComment( - path.relative(options.rootDir, source.fileName))}`; + const GENERATED_FROM_COMMENT_TEXT = `\n${jsdoc.createGeneratedFromComment( + path.relative(options.rootDir, source.fileName), + )}`; - fileOverview.text = fileOverview.text ? - fileOverview.text + GENERATED_FROM_COMMENT_TEXT : - GENERATED_FROM_COMMENT_TEXT; + fileOverview.text = fileOverview.text + ? fileOverview.text + GENERATED_FROM_COMMENT_TEXT + : GENERATED_FROM_COMMENT_TEXT; } if (generateExtraSuppressions) { const suppressions = [ - // Ensure our suppressions are included in the @suppress tag: - // * Suppress checkTypes. We believe the code has already been type-checked - // by TypeScript, and we cannot model all the TypeScript type decisions in - // Closure syntax. + // Ensure our suppressions are included in the @suppress tag: + // * Suppress checkTypes. We believe the code has already been type-checked + // by TypeScript, and we cannot model all the TypeScript type decisions in + // Closure syntax. 'checkTypes', - // * Suppress extraRequire. We remove extra requires at the TypeScript - // level, so any require that gets to the JS level is a load-bearing - // require. + // * Suppress extraRequire. We remove extra requires at the TypeScript + // level, so any require that gets to the JS level is a load-bearing + // require. 'extraRequire', - // * Types references are propagated between files even when they are not - // directly imported. While these are violations of the "missing require" - // rules they are believed to be safe. + // * Types references are propagated between files even when they are not + // directly imported. While these are violations of the "missing require" + // rules they are believed to be safe. 'missingRequire', - // * Suppress uselessCode. We emit an "if (false)" around type - // declarations, which is flagged as unused code unless we suppress it. + // * Suppress uselessCode. We emit an "if (false)" around type + // declarations, which is flagged as unused code unless we suppress it. 'uselessCode', - // * Suppress some checks for user errors that TS already checks. + // suspiciousCode errors flag patterns that are suspicious if human-written + // but not inherently wrong. See also b/323580655. + 'suspiciousCode', + // * Suppress some checks for user errors that TS already checks. 'missingReturn', 'unusedPrivateMembers', - // * Suppress checking for @override, because TS doesn't model it. + // * Suppress checking for @override, because TS doesn't model it. 'missingOverride', - // * Suppress const JSCompiler errors in TS file. - // a) TypeScript already checks for "const" and - // b) there are various JSCompiler false positives + // * Suppress const JSCompiler errors in TS file. + // a) TypeScript already checks for "const" and + // b) there are various JSCompiler false positives 'const', ]; - const suppressTags = suppressions.map( - s => ({tagName: 'suppress', text: 'added by tsickle', type: s})); + const suppressTags = suppressions.map((s) => ({ + tagName: 'suppress', + text: 'added by tsickle', + type: s, + })); // Special case the @license tag because all text following this tag is // treated by the compiler as part of the license, so we need to place the // new @suppress tags before @license. - const licenseTagIndex = tags.findIndex(t => t.tagName === 'license'); + const licenseTagIndex = tags.findIndex((t) => t.tagName === 'license'); if (licenseTagIndex !== -1) { tags.splice(licenseTagIndex, 0, ...suppressTags); } else { @@ -98,21 +116,31 @@ function augmentFileoverviewComments( * comment. */ export function transformFileoverviewCommentFactory( - options: ts.CompilerOptions, diagnostics: ts.Diagnostic[], - generateExtraSuppressions: boolean) { - return (): (sourceFile: ts.SourceFile) => ts.SourceFile => { + options: ts.CompilerOptions, + diagnostics: ts.Diagnostic[], + generateExtraSuppressions: boolean, +) { + return (): ((sourceFile: ts.SourceFile) => ts.SourceFile) => { function checkNoFileoverviewComments( - context: ts.Node, comments: jsdoc.SynthesizedCommentWithOriginal[], - message: string) { + context: ts.Node, + comments: jsdoc.SynthesizedCommentWithOriginal[], + message: string, + ) { for (const comment of comments) { const parse = jsdoc.parse(comment); - if (parse !== null && - parse.tags.some(t => FILEOVERVIEW_COMMENT_MARKERS.has(t.tagName))) { + if ( + parse !== null && + parse.tags.some((t) => FILEOVERVIEW_COMMENT_MARKERS.has(t.tagName)) + ) { // Report a warning; this should not break compilation in third party // code. reportDiagnostic( - diagnostics, context, message, comment.originalRange, - ts.DiagnosticCategory.Warning); + diagnostics, + context, + message, + comment.originalRange, + ts.DiagnosticCategory.Warning, + ); } } } @@ -128,7 +156,7 @@ export function transformFileoverviewCommentFactory( let fileComments: ts.SynthesizedComment[] = []; const firstStatement = - sourceFile.statements.length && sourceFile.statements[0] || null; + (sourceFile.statements.length && sourceFile.statements[0]) || null; const originalComments = ts.getLeadingCommentRanges(text, 0) || []; if (!firstStatement) { @@ -142,8 +170,10 @@ export function transformFileoverviewCommentFactory( // fileoverview comment. for (let i = originalComments.length - 1; i >= 0; i--) { const end = originalComments[i].end; - if (!text.substring(end).startsWith('\n\n') && - !text.substring(end).startsWith('\r\n\r\n')) { + if ( + !text.substring(end).startsWith('\n\n') && + !text.substring(end).startsWith('\r\n\r\n') + ) { continue; } // This comment is separated from the source file with a double break, @@ -151,36 +181,38 @@ export function transformFileoverviewCommentFactory( // Split them off and attach them onto a NotEmittedStatement, so that // they do not get lost later on. const synthesizedComments = - jsdoc.synthesizeLeadingComments(firstStatement); - const notEmitted = ts.factory.createNotEmittedStatement(sourceFile); + jsdoc.synthesizeLeadingComments(firstStatement); // Modify the comments on the firstStatement in place by removing the // file-level comments. fileComments = synthesizedComments.splice(0, i + 1); - // Move the fileComments onto notEmitted. - ts.setSyntheticLeadingComments(notEmitted, fileComments); - sourceFile = - updateSourceFileNode(sourceFile, ts.factory.createNodeArray([ - notEmitted, firstStatement, ...sourceFile.statements.slice(1) - ])); break; } + } + // Move the fileComments onto notEmitted. + const notEmitted = ts.factory.createNotEmittedStatement(sourceFile); + ts.setSyntheticLeadingComments(notEmitted, fileComments); + sourceFile = updateSourceFileNode( + sourceFile, + ts.factory.createNodeArray([notEmitted, ...sourceFile.statements]), + ); - // Now walk every top level statement and escape/drop any @fileoverview - // comments found. Closure ignores all @fileoverview comments but the - // last, so tsickle must make sure not to emit duplicated ones. - for (let i = 0; i < sourceFile.statements.length; i++) { - const stmt = sourceFile.statements[i]; - // Accept the NotEmittedStatement inserted above. - if (i === 0 && stmt.kind === ts.SyntaxKind.NotEmittedStatement) { - continue; - } - const comments = jsdoc.synthesizeLeadingComments(stmt); - checkNoFileoverviewComments( - stmt, comments, - `file comments must be at the top of the file, ` + - `separated from the file body by an empty line.`); + // Now walk every top level statement and escape/drop any @fileoverview + // comments found. Closure ignores all @fileoverview comments but the + // last, so tsickle must make sure not to emit duplicated ones. + for (let i = 0; i < sourceFile.statements.length; i++) { + const stmt = sourceFile.statements[i]; + // Accept the NotEmittedStatement inserted above. + if (i === 0 && stmt.kind === ts.SyntaxKind.NotEmittedStatement) { + continue; } + const comments = jsdoc.synthesizeLeadingComments(stmt); + checkNoFileoverviewComments( + stmt, + comments, + `file comments must be at the top of the file, ` + + `separated from the file body by an empty line.`, + ); } // Closure Compiler considers the *last* comment with @fileoverview (or @@ -192,44 +224,40 @@ export function transformFileoverviewCommentFactory( let fileoverviewIdx = -1; let tags: jsdoc.Tag[] = []; for (let i = fileComments.length - 1; i >= 0; i--) { - const parse = jsdoc.parseContents(fileComments[i].text); - if (parse !== null && - parse.tags.some(t => FILEOVERVIEW_COMMENT_MARKERS.has(t.tagName))) { + const parsed = jsdoc.parse(fileComments[i]); + if ( + parsed !== null && + parsed.tags.some((t) => FILEOVERVIEW_COMMENT_MARKERS.has(t.tagName)) + ) { fileoverviewIdx = i; - tags = parse.tags; + tags = parsed.tags; break; } } + const mutableJsDoc = new jsdoc.MutableJSDoc( + notEmitted, + fileComments, + fileoverviewIdx, + tags, + ); if (fileoverviewIdx !== -1) { checkNoFileoverviewComments( - firstStatement || sourceFile, - fileComments.slice(0, fileoverviewIdx), - `duplicate file level comment`); + firstStatement || sourceFile, + fileComments.slice(0, fileoverviewIdx), + `duplicate file level comment`, + ); } - augmentFileoverviewComments(options, sourceFile, tags, generateExtraSuppressions); - const commentText = jsdoc.toStringWithoutStartEnd(tags); + augmentFileoverviewComments( + options, + sourceFile, + mutableJsDoc.tags, + generateExtraSuppressions, + ); + mutableJsDoc.updateComment(); - if (fileoverviewIdx < 0) { - // No existing comment to merge with, just emit a new one. - return addNewFileoverviewComment(sourceFile, commentText); - } - - fileComments[fileoverviewIdx].text = commentText; - // sf does not need to be updated, synthesized comments are mutable. return sourceFile; }; }; } - -function addNewFileoverviewComment( - sf: ts.SourceFile, commentText: string): ts.SourceFile { - let syntheticFirstStatement = createNotEmittedStatement(sf); - syntheticFirstStatement = ts.addSyntheticTrailingComment( - syntheticFirstStatement, ts.SyntaxKind.MultiLineCommentTrivia, - commentText, true); - return updateSourceFileNode( - sf, - ts.factory.createNodeArray([syntheticFirstStatement, ...sf.statements])); -} diff --git a/src/googmodule.ts b/src/googmodule.ts index bf6e6d91b..01cc3625c 100644 --- a/src/googmodule.ts +++ b/src/googmodule.ts @@ -9,7 +9,13 @@ import * as ts from 'typescript'; import {ModulesManifest} from './modules_manifest'; -import {createGoogCall, createGoogLoadedModulesRegistration, createNotEmittedStatementWithComments, createSingleQuoteStringLiteral, reportDiagnostic} from './transformer_util'; +import { + createGoogCall, + createGoogLoadedModulesRegistration, + createNotEmittedStatementWithComments, + createSingleQuoteStringLiteral, + reportDiagnostic, +} from './transformer_util'; /** * Provides dependencies for and configures the goog namespace resolution @@ -29,11 +35,10 @@ export interface GoogModuleProcessorHost { * Takes the import URL of an ES6 import and returns the googmodule module * name for the imported module, iff the module is an original closure * JavaScript file. - * - * Warning: If this function is present, GoogModule won't produce diagnostics - * for multiple provides. */ - jsPathToModuleName?(importPath: string): string|undefined; + jsPathToModuleName?( + importPath: string, + ): {name: string; multipleProvides: boolean} | undefined; /** * Takes the import URL of an ES6 import and returns the property name that * should be stripped from the usage. @@ -57,7 +62,7 @@ export interface GoogModuleProcessorHost { * const bar_1 = goog.require('lib.Bar'); * console.log(bar_1); */ - jsPathToStripProperty?(importPath: string): string|undefined; + jsPathToStripProperty?(importPath: string): string | undefined; /** * If we do googmodule processing, we polyfill module.id, since that's * part of ES6 modules. This function determines what the module.id will be @@ -75,7 +80,7 @@ export interface GoogModuleProcessorHost { * If 'closure', it's transformed to goog.requireDynamic(). * If 'nodejs', it's the default behaviour, which is nodejs require. */ - transformDynamicImport: 'nodejs'|'closure'; + transformDynamicImport: 'nodejs' | 'closure'; } /** @@ -86,16 +91,28 @@ export interface GoogModuleProcessorHost { * marker symbols in Clutz .d.ts files. */ export function jsPathToNamespace( - host: GoogModuleProcessorHost, context: ts.Node, - diagnostics: ts.Diagnostic[], importPath: string, - getModuleSymbol: () => ts.Symbol | undefined): string|undefined { - const namespace = localJsPathToNamespace(host, importPath); + host: GoogModuleProcessorHost, + context: ts.Node, + diagnostics: ts.Diagnostic[], + importPath: string, + getModuleSymbol: () => ts.Symbol | undefined, +): string | undefined { + const namespace = localJsPathToNamespace( + host, + context, + diagnostics, + importPath, + ); if (namespace) return namespace; const moduleSymbol = getModuleSymbol(); if (!moduleSymbol) return; return getGoogNamespaceFromClutzComments( - context, diagnostics, importPath, moduleSymbol); + context, + diagnostics, + importPath, + moduleSymbol, + ); } /** @@ -105,7 +122,11 @@ export function jsPathToNamespace( * Forwards to `jsPathToModuleName` on the host if present. */ export function localJsPathToNamespace( - host: GoogModuleProcessorHost, importPath: string): string|undefined { + host: GoogModuleProcessorHost, + context: ts.Node | undefined, + diagnostics: ts.Diagnostic[], + importPath: string, +): string | undefined { if (importPath.match(/^goog:/)) { // This is a namespace import, of the form "goog:foo.bar". // Fix it to just "foo.bar". @@ -113,7 +134,12 @@ export function localJsPathToNamespace( } if (host.jsPathToModuleName) { - return host.jsPathToModuleName(importPath); + const module = host.jsPathToModuleName(importPath); + if (!module) return undefined; + if (module.multipleProvides) { + reportMultipleProvidesError(context, diagnostics, importPath); + } + return module.name; } return undefined; @@ -127,16 +153,20 @@ export function localJsPathToNamespace( * marker symbols in Clutz .d.ts files. */ export function jsPathToStripProperty( - host: GoogModuleProcessorHost, importPath: string, - getModuleSymbol: () => ts.Symbol | undefined): string|undefined { + host: GoogModuleProcessorHost, + importPath: string, + getModuleSymbol: () => ts.Symbol | undefined, +): string | undefined { if (host.jsPathToStripProperty) { return host.jsPathToStripProperty(importPath); } const moduleSymbol = getModuleSymbol(); if (!moduleSymbol) return; - const stripDefaultNameSymbol = - findLocalInDeclarations(moduleSymbol, '__clutz_strip_property'); + const stripDefaultNameSymbol = findLocalInDeclarations( + moduleSymbol, + '__clutz_strip_property', + ); if (!stripDefaultNameSymbol) return; return literalTypeOfSymbol(stripDefaultNameSymbol) as string; } @@ -146,10 +176,16 @@ export function jsPathToStripProperty( * `parent`. */ function isPropertyAccess( - node: ts.Node, parent: string, child: string): boolean { + node: ts.Node, + parent: string, + child: string, +): boolean { if (!ts.isPropertyAccessExpression(node)) return false; - return ts.isIdentifier(node.expression) && - node.expression.escapedText === parent && node.name.escapedText === child; + return ( + ts.isIdentifier(node.expression) && + node.expression.escapedText === parent && + node.name.escapedText === child + ); } /** isUseStrict returns true if node is a "use strict"; statement. */ @@ -274,7 +310,7 @@ function checkExportsVoid0Assignment(expr: ts.Expression): boolean { * Returns the string argument if call is of the form * require('foo') */ -function extractRequire(call: ts.CallExpression): ts.StringLiteral|null { +function extractRequire(call: ts.CallExpression): ts.StringLiteral | null { // Verify that the call is a call to require(...). if (call.expression.kind !== ts.SyntaxKind.Identifier) return null; const ident = call.expression as ts.Identifier; @@ -292,10 +328,14 @@ function extractRequire(call: ts.CallExpression): ts.StringLiteral|null { * given module symbol. It returns undefined if the symbol wasn't found. */ export function extractModuleMarker( - symbol: ts.Symbol, - name: '__clutz_actual_namespace'|'__clutz_multiple_provides'| - '__clutz_actual_path'|'__clutz_strip_property'| - '__clutz2_actual_path'): string|boolean|undefined { + symbol: ts.Symbol, + name: + | '__clutz_actual_namespace' + | '__clutz_multiple_provides' + | '__clutz_actual_path' + | '__clutz_strip_property' + | '__clutz2_actual_path', +): string | boolean | undefined { const localSymbol = findLocalInDeclarations(symbol, name); if (!localSymbol) return undefined; return literalTypeOfSymbol(localSymbol); @@ -311,8 +351,10 @@ declare interface InternalTsDeclaration { * declarations of the given symbol. Note that not all declarations are * containers that can have local symbols. */ -function findLocalInDeclarations(symbol: ts.Symbol, name: string): ts.Symbol| - undefined { +function findLocalInDeclarations( + symbol: ts.Symbol, + name: string, +): ts.Symbol | undefined { if (!symbol.declarations) { return undefined; } @@ -334,7 +376,7 @@ function findLocalInDeclarations(symbol: ts.Symbol, name: string): ts.Symbol| * literalTypeOfSymbol returns the literal type of symbol if it is * declared in a variable declaration that has a literal type. */ -function literalTypeOfSymbol(symbol: ts.Symbol): string|boolean|undefined { +function literalTypeOfSymbol(symbol: ts.Symbol): string | boolean | undefined { if (!symbol.declarations || symbol.declarations.length === 0) { return undefined; } @@ -352,10 +394,12 @@ function literalTypeOfSymbol(symbol: ts.Symbol): string|boolean|undefined { * Returns the name of the goog.module, from which the given source file has * been generated. */ -export function getOriginalGoogModuleFromComment(sf: ts.SourceFile): string| - undefined { - const leadingComments = - sf.getFullText().substring(sf.getFullStart(), sf.getLeadingTriviaWidth()); +export function getOriginalGoogModuleFromComment( + sf: ts.SourceFile, +): string | undefined { + const leadingComments = sf + .getFullText() + .substring(sf.getFullStart(), sf.getLeadingTriviaWidth()); const match = /^\/\/ Original goog.module name: (.*)$/m.exec(leadingComments); if (match) { return match[1]; @@ -381,46 +425,75 @@ export function getOriginalGoogModuleFromComment(sf: ts.SourceFile): string| * happens before it. */ function getGoogNamespaceFromClutzComments( - context: ts.Node, tsickleDiagnostics: ts.Diagnostic[], tsImport: string, - moduleSymbol: ts.Symbol): string|undefined { - if (moduleSymbol.valueDeclaration && - ts.isSourceFile(moduleSymbol.valueDeclaration)) { + context: ts.Node, + tsickleDiagnostics: ts.Diagnostic[], + tsImport: string, + moduleSymbol: ts.Symbol, +): string | undefined { + if ( + moduleSymbol.valueDeclaration && + ts.isSourceFile(moduleSymbol.valueDeclaration) + ) { return getOriginalGoogModuleFromComment(moduleSymbol.valueDeclaration); } - const actualNamespaceSymbol = - findLocalInDeclarations(moduleSymbol, '__clutz_actual_namespace'); + const actualNamespaceSymbol = findLocalInDeclarations( + moduleSymbol, + '__clutz_actual_namespace', + ); if (!actualNamespaceSymbol) return; - const hasMultipleProvides = - findLocalInDeclarations(moduleSymbol, '__clutz_multiple_provides'); + const hasMultipleProvides = findLocalInDeclarations( + moduleSymbol, + '__clutz_multiple_provides', + ); if (hasMultipleProvides) { // Report an error... - reportDiagnostic( - tsickleDiagnostics, context, - `referenced JavaScript module ${ - tsImport} provides multiple namespaces and cannot be imported by path.`); + reportMultipleProvidesError(context, tsickleDiagnostics, tsImport); // ... but continue producing an emit that effectively references the first // provided symbol (to continue finding any additional errors). } const actualNamespace = literalTypeOfSymbol(actualNamespaceSymbol); if (actualNamespace === undefined || typeof actualNamespace !== 'string') { reportDiagnostic( - tsickleDiagnostics, context, - `referenced module's __clutz_actual_namespace not a variable with a string literal type`); + tsickleDiagnostics, + context, + `referenced module's __clutz_actual_namespace not a variable with a string literal type`, + ); return; } return actualNamespace; } +function reportMultipleProvidesError( + context: ts.Node | undefined, + diagnostics: ts.Diagnostic[], + importPath: string, +) { + reportDiagnostic( + diagnostics, + context, + `referenced JavaScript module ${importPath} provides multiple namespaces and cannot be imported by path.`, + ); +} + /** * Converts a TS/ES module './import/path' into a goog.module compatible * namespace, handling regular imports and `goog:` namespace imports. */ function importPathToGoogNamespace( - host: GoogModuleProcessorHost, context: ts.Node, - diagnostics: ts.Diagnostic[], file: ts.SourceFile, tsImport: string, - getModuleSymbol: () => ts.Symbol | undefined): string { - const nsImport = - jsPathToNamespace(host, context, diagnostics, tsImport, getModuleSymbol); + host: GoogModuleProcessorHost, + context: ts.Node, + diagnostics: ts.Diagnostic[], + file: ts.SourceFile, + tsImport: string, + getModuleSymbol: () => ts.Symbol | undefined, +): string { + const nsImport = jsPathToNamespace( + host, + context, + diagnostics, + tsImport, + getModuleSymbol, + ); if (nsImport != null) { return nsImport; } @@ -439,32 +512,17 @@ function rewriteModuleExportsAssignment(expr: ts.ExpressionStatement) { } if (!isPropertyAccess(expr.expression.left, 'module', 'exports')) return null; return ts.setOriginalNode( - ts.setTextRange( - ts.factory.createExpressionStatement(ts.factory.createAssignment( - ts.factory.createIdentifier('exports'), expr.expression.right)), - expr), - expr); -} - -/** - * Checks whether expr is of the form `exports.abc = identifier` and if so, - * returns the string abc, otherwise returns null. - */ -function isExportsAssignment(expr: ts.Expression): string|null { - // Verify this looks something like `exports.abc = ...`. - if (!ts.isBinaryExpression(expr)) return null; - if (expr.operatorToken.kind !== ts.SyntaxKind.EqualsToken) return null; - - // Verify the left side of the expression is an access on `exports`. - if (!ts.isPropertyAccessExpression(expr.left)) return null; - if (!ts.isIdentifier(expr.left.expression)) return null; - if (expr.left.expression.escapedText !== 'exports') return null; - - // Check whether right side of assignment is an identifier. - if (!ts.isIdentifier(expr.right)) return null; - - // Return the property name as string. - return expr.left.name.escapedText.toString(); + ts.setTextRange( + ts.factory.createExpressionStatement( + ts.factory.createAssignment( + ts.factory.createIdentifier('exports'), + expr.expression.right, + ), + ), + expr, + ), + expr, + ); } /** @@ -478,17 +536,18 @@ function isExportsAssignment(expr: ts.Expression): string|null { * * @return An array of statements if it converted, or null otherwise. */ -function rewriteCommaExpressions(expr: ts.Expression): ts.Statement[]|null { +function rewriteCommaExpressions(expr: ts.Expression): ts.Statement[] | null { // There are two representation for comma expressions: // 1) a tree of "binary expressions" whose contents are comma operators - const isBinaryCommaExpression = - (expr: ts.Expression): expr is ts.BinaryExpression => - ts.isBinaryExpression(expr) && - expr.operatorToken.kind === ts.SyntaxKind.CommaToken; + const isBinaryCommaExpression = ( + expr: ts.Expression, + ): expr is ts.BinaryExpression => + ts.isBinaryExpression(expr) && + expr.operatorToken.kind === ts.SyntaxKind.CommaToken; // or, // 2) a "comma list" expression, where the subexpressions are in one array const isCommaList = (expr: ts.Expression): expr is ts.CommaListExpression => - expr.kind === ts.SyntaxKind.CommaListExpression; + expr.kind === ts.SyntaxKind.CommaListExpression; if (!isBinaryCommaExpression(expr) && !isCommaList(expr)) { return null; @@ -506,8 +565,9 @@ function rewriteCommaExpressions(expr: ts.Expression): ts.Statement[]|null { // TODO(blickly): Simplify using flatMap once node 11 available return ([] as ts.Statement[]).concat(...expr.elements.map(visit)); } - return [ts.setOriginalNode( - ts.factory.createExpressionStatement(expr), expr)]; + return [ + ts.setOriginalNode(ts.factory.createExpressionStatement(expr), expr), + ]; } } @@ -518,7 +578,9 @@ function rewriteCommaExpressions(expr: ts.Expression): ts.Statement[]|null { * separately. */ export function getAmbientModuleSymbol( - typeChecker: ts.TypeChecker, moduleUrl: ts.StringLiteral) { + typeChecker: ts.TypeChecker, + moduleUrl: ts.StringLiteral, +) { let moduleSymbol = typeChecker.getSymbolAtLocation(moduleUrl); if (!moduleSymbol) { // Angular compiler creates import statements that do not retain the @@ -532,14 +594,14 @@ export function getAmbientModuleSymbol( // specific to ambient modules. const t = moduleUrl.text; moduleSymbol = - // tslint:disable-next-line:no-any see above. - (typeChecker as any).tryFindAmbientModuleWithoutAugmentations(t); + // tslint:disable-next-line:no-any see above. + (typeChecker as any).tryFindAmbientModuleWithoutAugmentations(t); } return moduleSymbol; } interface ExportedDeclaration { - declarationSymbol: ts.Symbol&{ + declarationSymbol: ts.Symbol & { valueDeclaration: ts.Declaration; }; exportName: string; @@ -550,22 +612,25 @@ interface ExportedDeclaration { * same file. Does not include re-exports. */ function getExportedDeclarations( - sourceFile: ts.SourceFile, - typeChecker: ts.TypeChecker): ExportedDeclaration[] { + sourceFile: ts.SourceFile, + typeChecker: ts.TypeChecker, +): ExportedDeclaration[] { const moduleSymbol = typeChecker.getSymbolAtLocation(sourceFile); if (!moduleSymbol) return []; const exportSymbols = typeChecker.getExportsOfModule(moduleSymbol); const result: ExportedDeclaration[] = []; for (const exportSymbol of exportSymbols) { - const declarationSymbol = exportSymbol.flags & ts.SymbolFlags.Alias ? - typeChecker.getAliasedSymbol(exportSymbol) : - exportSymbol; + const declarationSymbol = + exportSymbol.flags & ts.SymbolFlags.Alias + ? typeChecker.getAliasedSymbol(exportSymbol) + : exportSymbol; const declarationFile = declarationSymbol.valueDeclaration?.getSourceFile(); if (declarationFile?.fileName !== sourceFile.fileName) continue; result.push({ - declarationSymbol: - declarationSymbol as ts.Symbol & {valueDeclaration: ts.Declaration}, + declarationSymbol: declarationSymbol as ts.Symbol & { + valueDeclaration: ts.Declaration; + }, exportName: exportSymbol.name, }); } @@ -581,14 +646,16 @@ function isClassDecorated(node: ts.ClassDeclaration): boolean { if (hasDecorator(node)) return true; const ctor = getFirstConstructorWithBody(node); if (!ctor) return false; - return ctor.parameters.some(p => hasDecorator(p)); + return ctor.parameters.some((p) => hasDecorator(p)); } -function getFirstConstructorWithBody(node: ts.ClassLikeDeclaration): - ts.ConstructorDeclaration|undefined { +function getFirstConstructorWithBody( + node: ts.ClassLikeDeclaration, +): ts.ConstructorDeclaration | undefined { return node.members.find( - (member): member is ts.ConstructorDeclaration => - ts.isConstructorDeclaration(member) && !!member.body); + (member): member is ts.ConstructorDeclaration => + ts.isConstructorDeclaration(member) && !!member.body, + ); } function hasDecorator(node: ts.HasDecorators): boolean { @@ -602,9 +669,10 @@ function hasDecorator(node: ts.HasDecorators): boolean { * and goog.require statements. */ export function commonJsToGoogmoduleTransformer( - host: GoogModuleProcessorHost, modulesManifest: ModulesManifest, - typeChecker: ts.TypeChecker): (context: ts.TransformationContext) => - ts.Transformer { + host: GoogModuleProcessorHost, + modulesManifest: ModulesManifest, + typeChecker: ts.TypeChecker, +): (context: ts.TransformationContext) => ts.Transformer { return (context: ts.TransformationContext): ts.Transformer => { // TS' CommonJS processing uses onSubstituteNode to, at the very end of // processing, substitute `modulename.someProperty` property accesses and @@ -621,7 +689,7 @@ export function commonJsToGoogmoduleTransformer( // This may be the original ImportDeclaration, if the identifier was // transformed from it. const orig = ts.getOriginalNode(node.expression); - let importExportDecl: ts.ImportDeclaration|ts.ExportDeclaration; + let importExportDecl: ts.ImportDeclaration | ts.ExportDeclaration; if (ts.isImportDeclaration(orig) || ts.isExportDeclaration(orig)) { importExportDecl = orig; } else { @@ -634,8 +702,11 @@ export function commonJsToGoogmoduleTransformer( const decls = sym.getDeclarations(); if (!decls || !decls.length) return node; const decl = decls[0]; - if (decl.parent && decl.parent.parent && - ts.isImportDeclaration(decl.parent.parent)) { + if ( + decl.parent && + decl.parent.parent && + ts.isImportDeclaration(decl.parent.parent) + ) { importExportDecl = decl.parent.parent; } else { return node; @@ -650,14 +721,16 @@ export function commonJsToGoogmoduleTransformer( // "goog.module" or "goog.provide" as if it was an ES6 default export. const isDefaultAccess = node.name.text === 'default'; const moduleSpecifier = - importExportDecl.moduleSpecifier as ts.StringLiteral; + importExportDecl.moduleSpecifier as ts.StringLiteral; if (isDefaultAccess && moduleSpecifier.text.startsWith('goog:')) { // Substitute "foo.default" with just "foo". return node.expression; } const stripPropertyName = jsPathToStripProperty( - host, moduleSpecifier.text, - () => getAmbientModuleSymbol(typeChecker, moduleSpecifier)); + host, + moduleSpecifier.text, + () => getAmbientModuleSymbol(typeChecker, moduleSpecifier), + ); if (!stripPropertyName) return node; // In this case, emit `modulename` instead of `modulename.property` if and // only if the accessed name matches the declared name. @@ -702,8 +775,10 @@ export function commonJsToGoogmoduleTransformer( * to, or undefined if no assignment is needed. */ function maybeCreateGoogRequire( - original: ts.Statement, call: ts.CallExpression, - newIdent: ts.Identifier|undefined): ts.Statement|null { + original: ts.Statement, + call: ts.CallExpression, + newIdent: ts.Identifier | undefined, + ): ts.Statement | null { const importedUrl = extractRequire(call); if (!importedUrl) return null; // if importPathToGoogNamespace reports an error, it has already been @@ -714,16 +789,23 @@ export function commonJsToGoogmoduleTransformer( // side-effect imports is working as intended. const ignoredDiagnostics: ts.Diagnostic[] = []; const imp = importPathToGoogNamespace( - host, importedUrl, ignoredDiagnostics, sf, importedUrl.text, - () => getAmbientModuleSymbol(typeChecker, importedUrl)); + host, + importedUrl, + ignoredDiagnostics, + sf, + importedUrl.text, + () => getAmbientModuleSymbol(typeChecker, importedUrl), + ); modulesManifest.addReferencedModule(sf.fileName, imp); - const existingImport: ts.Identifier|undefined = - namespaceToModuleVarName.get(imp); + const existingImport: ts.Identifier | undefined = + namespaceToModuleVarName.get(imp); let initializer: ts.Expression; if (!existingImport) { if (newIdent) namespaceToModuleVarName.set(imp, newIdent); - initializer = - createGoogCall('require', createSingleQuoteStringLiteral(imp)); + initializer = createGoogCall( + 'require', + createSingleQuoteStringLiteral(imp), + ); } else { initializer = existingImport; } @@ -736,36 +818,47 @@ export function commonJsToGoogmoduleTransformer( // In a goog.module we just want to access the global `goog` value, // so we skip emitting that import as a goog.require. // We check the goog module name so that we also catch relative imports. - if (newIdent && newIdent.escapedText === 'goog' && - imp === 'google3.javascript.closure.goog') { + if ( + newIdent && + newIdent.escapedText === 'goog' && + imp === 'google3.javascript.closure.goog' + ) { return createNotEmittedStatementWithComments(sf, original); } const useConst = host.options.target !== ts.ScriptTarget.ES5; - if (newIdent) { // Create a statement like one of: // var foo = goog.require('bar'); // var foo = existingImport; const varDecl = ts.factory.createVariableDeclaration( - newIdent, /* exclamationToken */ undefined, /* type */ undefined, - initializer); + newIdent, + /* exclamationToken */ undefined, + /* type */ undefined, + initializer, + ); const newStmt = ts.factory.createVariableStatement( - /* modifiers */ undefined, - ts.factory.createVariableDeclarationList( - [varDecl], - // Use 'const' in ES6 mode so Closure properly forwards type - // aliases. - useConst ? ts.NodeFlags.Const : undefined)); + /* modifiers */ undefined, + ts.factory.createVariableDeclarationList( + [varDecl], + // Use 'const' in ES6 mode so Closure properly forwards type + // aliases. + useConst ? ts.NodeFlags.Const : undefined, + ), + ); return ts.setOriginalNode( - ts.setTextRange(newStmt, original), original); + ts.setTextRange(newStmt, original), + original, + ); } else if (!newIdent && !existingImport) { // Create a statement like: // goog.require('bar'); const newStmt = ts.factory.createExpressionStatement(initializer); return ts.setOriginalNode( - ts.setTextRange(newStmt, original), original); + ts.setTextRange(newStmt, original), + original, + ); } return createNotEmittedStatementWithComments(sf, original); } @@ -784,7 +877,9 @@ export function commonJsToGoogmoduleTransformer( * mode. */ function maybeRewriteDeclareModuleId( - original: ts.Statement, call: ts.CallExpression): ts.Statement|null { + original: ts.Statement, + call: ts.CallExpression, + ): ts.Statement | null { // Verify that the call is a call to goog.declareModuleId(...). if (!ts.isPropertyAccessExpression(call.expression)) { return null; @@ -793,8 +888,10 @@ export function commonJsToGoogmoduleTransformer( if (propAccess.name.escapedText !== 'declareModuleId') { return null; } - if (!ts.isIdentifier(propAccess.expression) || - propAccess.expression.escapedText !== 'goog') { + if ( + !ts.isIdentifier(propAccess.expression) || + propAccess.expression.escapedText !== 'goog' + ) { return null; } @@ -807,12 +904,14 @@ export function commonJsToGoogmoduleTransformer( return null; } const newStmt = createGoogLoadedModulesRegistration( - arg.text, ts.factory.createIdentifier('exports')); + arg.text, + ts.factory.createIdentifier('exports'), + ); return ts.setOriginalNode(ts.setTextRange(newStmt, original), original); } interface ExportsAssignment extends ts.ExpressionStatement { - expression: ts.BinaryExpression&{ + expression: ts.BinaryExpression & { left: ts.PropertyAccessExpression; right: ts.Identifier; }; @@ -831,35 +930,50 @@ export function commonJsToGoogmoduleTransformer( * statements `let X = Z;` and `exports.Y = X;`. */ function maybeRewriteDecoratedClassChainInitializer( - stmt: ts.VariableStatement, - decl: ts.VariableDeclaration): RewrittenExportsAssignment|null { + stmt: ts.VariableStatement, + decl: ts.VariableDeclaration, + ): RewrittenExportsAssignment | null { const originalNode = ts.getOriginalNode(stmt); - if (!originalNode || !ts.isClassDeclaration(originalNode) || - !isClassDecorated(originalNode)) { + if ( + !originalNode || + !ts.isClassDeclaration(originalNode) || + !isClassDecorated(originalNode) + ) { return null; } - if (!ts.isIdentifier(decl.name) || !decl.initializer || - !ts.isBinaryExpression(decl.initializer) || - decl.initializer.operatorToken.kind !== ts.SyntaxKind.EqualsToken || - !ts.isPropertyAccessExpression(decl.initializer.left) || - !ts.isIdentifier(decl.initializer.left.expression) || - decl.initializer.left.expression.text !== 'exports') { + if ( + !ts.isIdentifier(decl.name) || + !decl.initializer || + !ts.isBinaryExpression(decl.initializer) || + decl.initializer.operatorToken.kind !== ts.SyntaxKind.EqualsToken || + !ts.isPropertyAccessExpression(decl.initializer.left) || + !ts.isIdentifier(decl.initializer.left.expression) || + decl.initializer.left.expression.text !== 'exports' + ) { return null; } const updatedDecl = ts.factory.updateVariableDeclaration( - decl, decl.name, decl.exclamationToken, decl.type, - decl.initializer.right); + decl, + decl.name, + decl.exclamationToken, + decl.type, + decl.initializer.right, + ); const newStmt = ts.factory.updateVariableStatement( - stmt, stmt.modifiers, - ts.factory.updateVariableDeclarationList( - stmt.declarationList, [updatedDecl])); + stmt, + stmt.modifiers, + ts.factory.updateVariableDeclarationList(stmt.declarationList, [ + updatedDecl, + ]), + ); return { statement: newStmt, exports: [ - ts.factory.createExpressionStatement(ts.factory.createAssignment( - decl.initializer.left, decl.name)) as ExportsAssignment, + ts.factory.createExpressionStatement( + ts.factory.createAssignment(decl.initializer.left, decl.name), + ) as ExportsAssignment, ], }; } @@ -869,25 +983,31 @@ export function commonJsToGoogmoduleTransformer( * is a class with decorators. */ function isExportsAssignmentForDecoratedClass( - stmt: ts.ExpressionStatement): stmt is ExportsAssignment { - if (!ts.isBinaryExpression(stmt.expression) || - stmt.expression.operatorToken.kind !== ts.SyntaxKind.EqualsToken || - !ts.isPropertyAccessExpression(stmt.expression.left) || - !ts.isIdentifier(stmt.expression.left.expression) || - stmt.expression.left.expression.escapedText !== 'exports' || - !ts.isIdentifier(stmt.expression.right)) { + stmt: ts.ExpressionStatement, + ): stmt is ExportsAssignment { + if ( + !ts.isBinaryExpression(stmt.expression) || + stmt.expression.operatorToken.kind !== ts.SyntaxKind.EqualsToken || + !ts.isPropertyAccessExpression(stmt.expression.left) || + !ts.isIdentifier(stmt.expression.left.expression) || + stmt.expression.left.expression.escapedText !== 'exports' || + !ts.isIdentifier(stmt.expression.right) + ) { return false; } // Variable statements in the form of `export const Y = X;` don't count. if (ts.isVariableStatement(ts.getOriginalNode(stmt))) return false; - const nameSymbol = - typeChecker.getSymbolAtLocation(stmt.expression.right); + const nameSymbol = typeChecker.getSymbolAtLocation( + stmt.expression.right, + ); if (!nameSymbol || !nameSymbol.valueDeclaration) return false; - return ts.isClassDeclaration(nameSymbol.valueDeclaration) && - isClassDecorated(nameSymbol.valueDeclaration); + return ( + ts.isClassDeclaration(nameSymbol.valueDeclaration) && + isClassDecorated(nameSymbol.valueDeclaration) + ); } /** @@ -901,16 +1021,21 @@ export function commonJsToGoogmoduleTransformer( * `delayedDecoratedClassExports`. */ function maybeRewriteDecoratedClassDecorateCall( - stmt: ts.ExpressionStatement): ts.ExpressionStatement|null { - if (!ts.isBinaryExpression(stmt.expression) || - stmt.expression.operatorToken.kind !== ts.SyntaxKind.EqualsToken || - !ts.isIdentifier(stmt.expression.left)) { + stmt: ts.ExpressionStatement, + ): ts.ExpressionStatement | null { + if ( + !ts.isBinaryExpression(stmt.expression) || + stmt.expression.operatorToken.kind !== ts.SyntaxKind.EqualsToken || + !ts.isIdentifier(stmt.expression.left) + ) { return null; } const originalNode = ts.getOriginalNode(stmt); - if (!ts.isClassDeclaration(originalNode) || - !isClassDecorated(originalNode)) { + if ( + !ts.isClassDeclaration(originalNode) || + !isClassDecorated(originalNode) + ) { return null; } @@ -937,34 +1062,41 @@ export function commonJsToGoogmoduleTransformer( * https://github.com/microsoft/TypeScript/blob/d8585688dd1bc8d82b7b5daab9af83ae1e3de197/src/compiler/transformers/module/module.ts#L2341-L2367 */ function maybeRewriteExportsAssignmentInIifeArguments( - stmt: ts.ExpressionStatement): RewrittenExportsAssignment|null { + stmt: ts.ExpressionStatement, + ): RewrittenExportsAssignment | null { if (!ts.isCallExpression(stmt.expression)) return null; // Checks call: `(function (...) { ... })(single_argument)` const call = stmt.expression; - if (!ts.isParenthesizedExpression(call.expression) || - !ts.isFunctionExpression(call.expression.expression) || - call.arguments.length !== 1) { + if ( + !ts.isParenthesizedExpression(call.expression) || + !ts.isFunctionExpression(call.expression.expression) || + call.arguments.length !== 1 + ) { return null; } // Checks argument: `identifier || (identifier = {})` const arg = call.arguments[0]; - if (!ts.isBinaryExpression(arg) || !ts.isIdentifier(arg.left) || - arg.operatorToken.kind !== ts.SyntaxKind.BarBarToken || - !ts.isParenthesizedExpression(arg.right) || - !ts.isBinaryExpression(arg.right.expression) || - arg.right.expression.operatorToken.kind !== - ts.SyntaxKind.EqualsToken || - !ts.isIdentifier(arg.right.expression.left) || - !ts.isObjectLiteralExpression(arg.right.expression.right)) { + if ( + !ts.isBinaryExpression(arg) || + !ts.isIdentifier(arg.left) || + arg.operatorToken.kind !== ts.SyntaxKind.BarBarToken || + !ts.isParenthesizedExpression(arg.right) || + !ts.isBinaryExpression(arg.right.expression) || + arg.right.expression.operatorToken.kind !== + ts.SyntaxKind.EqualsToken || + !ts.isIdentifier(arg.right.expression.left) || + !ts.isObjectLiteralExpression(arg.right.expression.right) + ) { return null; } const name = arg.right.expression.left; const nameSymbol = typeChecker.getSymbolAtLocation(name); const matchingExports = exportedDeclarations.filter( - decl => decl.declarationSymbol === nameSymbol); + (decl) => decl.declarationSymbol === nameSymbol, + ); // Only needs modification if it's exported. Note that it may be // exported multiple times under different names, e.g.: @@ -976,25 +1108,38 @@ export function commonJsToGoogmoduleTransformer( // onSubstituteNode callback. ts.setEmitFlags(arg.right.expression, ts.EmitFlags.NoSubstitution); - // Namespaces can merge with classes and functions. TypeScript emits - // separate exports assignments for those. Don't emit extra ones here. + // Namespaces can merge with classes and functions and TypeScript emits + // separate exports assignments for those already. No need to add an + // extra one. + // The same is true for enums, but only if they have been transformed + // to closure enums. const notAlreadyExported = matchingExports.filter( - decl => !ts.isClassDeclaration( - decl.declarationSymbol.valueDeclaration) && - !ts.isFunctionDeclaration( - decl.declarationSymbol.valueDeclaration)); - - const exportNames = notAlreadyExported.map(decl => decl.exportName); + (decl) => + !ts.isClassDeclaration(decl.declarationSymbol.valueDeclaration) && + !ts.isFunctionDeclaration( + decl.declarationSymbol.valueDeclaration, + ) && + !( + host.transformTypesToClosure && + ts.isEnumDeclaration(decl.declarationSymbol.valueDeclaration) + ), + ); + + const exportNames = notAlreadyExported.map((decl) => decl.exportName); return { statement: stmt, exports: exportNames.map( - exportName => - ts.factory.createExpressionStatement( - ts.factory.createAssignment( - ts.factory.createPropertyAccessExpression( - ts.factory.createIdentifier('exports'), - ts.factory.createIdentifier(exportName)), - name)) as ExportsAssignment), + (exportName) => + ts.factory.createExpressionStatement( + ts.factory.createAssignment( + ts.factory.createPropertyAccessExpression( + ts.factory.createIdentifier('exports'), + ts.factory.createIdentifier(exportName), + ), + name, + ), + ) as ExportsAssignment, + ), }; } @@ -1010,8 +1155,9 @@ export function commonJsToGoogmoduleTransformer( * Separating the `goog.require` and `exports.ns` assignment is required * by Closure to correctly infer the type of the exported namespace. */ - function maybeRewriteExportStarAsNs(stmt: ts.Statement): ts.Statement[]| - null { + function maybeRewriteExportStarAsNs( + stmt: ts.Statement, + ): ts.Statement[] | null { // Ensure this looks something like `exports.ns = require('ns);`. if (!ts.isExpressionStatement(stmt)) return null; if (!ts.isBinaryExpression(stmt.expression)) return null; @@ -1029,24 +1175,35 @@ export function commonJsToGoogmoduleTransformer( // Grab the call to `require`, and exit early if not calling `require`. if (!ts.isCallExpression(stmt.expression.right)) return null; const ident = ts.factory.createIdentifier(nextModuleVar()); - const require = - maybeCreateGoogRequire(stmt, stmt.expression.right, ident); + const require = maybeCreateGoogRequire( + stmt, + stmt.expression.right, + ident, + ); if (!require) return null; const exportedName = stmt.expression.left.name; const exportStmt = ts.setOriginalNode( - ts.setTextRange( - ts.factory.createExpressionStatement( - ts.factory.createAssignment( - ts.factory.createPropertyAccessExpression( - ts.factory.createIdentifier('exports'), - exportedName), - ident)), - stmt), - stmt); + ts.setTextRange( + ts.factory.createExpressionStatement( + ts.factory.createAssignment( + ts.factory.createPropertyAccessExpression( + ts.factory.createIdentifier('exports'), + exportedName, + ), + ident, + ), + ), + stmt, + ), + stmt, + ); ts.addSyntheticLeadingComment( - exportStmt, ts.SyntaxKind.MultiLineCommentTrivia, '* @const ', - /* trailing newline */ true); + exportStmt, + ts.SyntaxKind.MultiLineCommentTrivia, + '* @const ', + /* trailing newline */ true, + ); return [require, exportStmt]; } @@ -1072,7 +1229,8 @@ export function commonJsToGoogmoduleTransformer( * ``` */ function rewriteObjectDefinePropertyOnExports( - stmt: ts.ExpressionStatement): ts.Statement|null { + stmt: ts.ExpressionStatement, + ): ts.Statement | null { // Verify this node is a function call. if (!ts.isCallExpression(stmt.expression)) return null; @@ -1100,15 +1258,19 @@ export function commonJsToGoogmoduleTransformer( // Returns a "finder" function to location an object property. function findPropNamed(name: string) { return (p: ts.ObjectLiteralElementLike) => { - return ts.isPropertyAssignment(p) && ts.isIdentifier(p.name) && - p.name.text === name; + return ( + ts.isPropertyAssignment(p) && + ts.isIdentifier(p.name) && + p.name.text === name + ); }; } // Verify that the export is marked as enumerable. If it isn't then this // was not generated by TypeScript. - const enumerableConfig = - objDefArg3.properties.find(findPropNamed('enumerable')); + const enumerableConfig = objDefArg3.properties.find( + findPropNamed('enumerable'), + ); if (!enumerableConfig) return null; if (!ts.isPropertyAssignment(enumerableConfig)) return null; if (enumerableConfig.initializer.kind !== ts.SyntaxKind.TrueKeyword) { @@ -1134,20 +1296,24 @@ export function commonJsToGoogmoduleTransformer( // second argument to `Object.defineProperty` with the value of the // node returned by the getter function. const exportStmt = ts.setOriginalNode( - ts.setTextRange( - ts.factory.createExpressionStatement( - ts.factory.createAssignment( - ts.factory.createPropertyAccessExpression( - ts.factory.createIdentifier('exports'), - objDefArg2.text), - realExportValue)), - stmt), - stmt); + ts.setTextRange( + ts.factory.createExpressionStatement( + ts.factory.createAssignment( + ts.factory.createPropertyAccessExpression( + ts.factory.createIdentifier('exports'), + objDefArg2.text, + ), + realExportValue, + ), + ), + stmt, + ), + stmt, + ); return exportStmt; } - const exportsSeen = new Set(); const seenNamespaceOrEnumExports = new Set(); /** @@ -1163,8 +1329,10 @@ export function commonJsToGoogmoduleTransformer( * * Solution: Emit them at the end of the source file. */ - const delayedDecoratedClassExports = - new Map(); + const delayedDecoratedClassExports = new Map< + string, + ts.ExpressionStatement + >(); /** * visitTopLevelStatement implements the main CommonJS to goog.module @@ -1184,7 +1352,10 @@ export function commonJsToGoogmoduleTransformer( * variables later on */ function visitTopLevelStatement( - stmts: ts.Statement[], sf: ts.SourceFile, node: ts.Statement): void { + stmts: ts.Statement[], + sf: ts.SourceFile, + node: ts.Statement, + ): void { // Handle each particular case by adding node to stmts, then // return. For unhandled cases, break to jump to the default handling // below. @@ -1239,65 +1410,41 @@ export function commonJsToGoogmoduleTransformer( // which is a live binding generated when re-exporting from another // module. const exportFromObjDefProp = - rewriteObjectDefinePropertyOnExports(exprStmt); + rewriteObjectDefinePropertyOnExports(exprStmt); if (exportFromObjDefProp) { stmts.push(exportFromObjDefProp); return; } - // Avoid EXPORT_REPEATED_ERROR from JSCompiler. Occurs for: - // class Foo {} - // namespace Foo { ... } - // export {Foo}; - // TypeScript emits 2 separate exports assignments. One after the - // class and one after the namespace. - // TODO(b/277272562): TypeScript 5.1 changes how exports assignments - // are emitted, making this no longer an issue. On the other hand - // this is unsafe. We really need to keep the _last_ (not the first) - // export assignment in the general case. Remove this check after - // the 5.1 upgrade. - const exportName = isExportsAssignment(exprStmt.expression); - if (exportName) { - if (exportsSeen.has(exportName)) { - stmts.push(createNotEmittedStatementWithComments(sf, exprStmt)); - return; - } - exportsSeen.add(exportName); - } - - // TODO(b/277272562): This code works in 5.1. But breaks in 5.0, - // which emits separate exports assignments for namespaces and enums - // and this code would emit duplicate exports assignments. Run this - // unconditionally after 5.1 has been released. - if ((ts.versionMajorMinor as string) !== '5.0') { - // Check for inline exports assignments as they are emitted for - // exported namespaces and enums, e.g.: - // (function (Foo) { - // })(Foo || (exports.Foo = exports.Bar = Foo = {})); - // and moves the exports assignments to a separate statement. - const exportInIifeArguments = - maybeRewriteExportsAssignmentInIifeArguments(exprStmt); - if (exportInIifeArguments) { - stmts.push(exportInIifeArguments.statement); - for (const newExport of exportInIifeArguments.exports) { - const exportName = newExport.expression.left.name.text; - // Namespaces produce multiple exports assignments when - // they're re-opened in the same file. Only emit the first one - // here. This is fine because the namespace object itself - // cannot be re-assigned later. - if (!seenNamespaceOrEnumExports.has(exportName)) { - stmts.push(newExport); - seenNamespaceOrEnumExports.add(exportName); - } + // Check for inline exports assignments as they are emitted for + // exported namespaces and enums, e.g.: + // (function (Foo) { + // })(Foo || (exports.Foo = exports.Bar = Foo = {})); + // and moves the exports assignments to a separate statement. + const exportInIifeArguments = + maybeRewriteExportsAssignmentInIifeArguments(exprStmt); + if (exportInIifeArguments) { + stmts.push(exportInIifeArguments.statement); + for (const newExport of exportInIifeArguments.exports) { + const exportName = newExport.expression.left.name.text; + // Namespaces produce multiple exports assignments when + // they're re-opened in the same file. Only emit the first one + // here. This is fine because the namespace object itself + // cannot be re-assigned later. + if (!seenNamespaceOrEnumExports.has(exportName)) { + stmts.push(newExport); + seenNamespaceOrEnumExports.add(exportName); } - return; } + return; } // Delay `exports.X = X` assignments for decorated classes. if (isExportsAssignmentForDecoratedClass(exprStmt)) { delayedDecoratedClassExports.set( - exprStmt.expression.left.name.text, exprStmt); + exprStmt.expression.left.name.text, + exprStmt, + ); return; } @@ -1317,8 +1464,10 @@ export function commonJsToGoogmoduleTransformer( let callExpr = expr; // Check for declareModuleId. - const declaredModuleId = - maybeRewriteDeclareModuleId(exprStmt, callExpr); + const declaredModuleId = maybeRewriteDeclareModuleId( + exprStmt, + callExpr, + ); if (declaredModuleId) { stmts.push(declaredModuleId); return; @@ -1329,10 +1478,11 @@ export function commonJsToGoogmoduleTransformer( // or the imported version, 'tslib.__exportStar(require(...))'. The // imported version is only substituted later on though, so appears // as a plain "__exportStar" on the top level here. - const isExportStar = ts.isIdentifier(expr.expression) && - (expr.expression.text === '__exportStar' || - expr.expression.text === '__export'); - let newIdent: ts.Identifier|undefined; + const isExportStar = + ts.isIdentifier(expr.expression) && + (expr.expression.text === '__exportStar' || + expr.expression.text === '__export'); + let newIdent: ts.Identifier | undefined; if (isExportStar) { // Extract the goog.require() from the call. (It will be verified // as a goog.require() below.) @@ -1342,8 +1492,11 @@ export function commonJsToGoogmoduleTransformer( // Check whether the call is actually a require() and translate // as appropriate. - const require = - maybeCreateGoogRequire(exprStmt, callExpr, newIdent); + const require = maybeCreateGoogRequire( + exprStmt, + callExpr, + newIdent, + ); if (!require) break; stmts.push(require); @@ -1353,9 +1506,15 @@ export function commonJsToGoogmoduleTransformer( if (isExportStar) { const args: ts.Expression[] = [newIdent!]; if (expr.arguments.length > 1) args.push(expr.arguments[1]); - stmts.push(ts.factory.createExpressionStatement( + stmts.push( + ts.factory.createExpressionStatement( ts.factory.createCallExpression( - expr.expression, undefined, args))); + expr.expression, + undefined, + args, + ), + ), + ); } return; } @@ -1371,8 +1530,11 @@ export function commonJsToGoogmoduleTransformer( // It's possibly of the form "var x = require(...);". if (decl.initializer && ts.isCallExpression(decl.initializer)) { - const require = - maybeCreateGoogRequire(varStmt, decl.initializer, decl.name); + const require = maybeCreateGoogRequire( + varStmt, + decl.initializer, + decl.name, + ); if (require) { stmts.push(require); return; @@ -1382,12 +1544,14 @@ export function commonJsToGoogmoduleTransformer( // Check if it's a statement like `let X = exports.X = class X` // where `X` has decorators. const declWithChainInitializer = - maybeRewriteDecoratedClassChainInitializer(varStmt, decl); + maybeRewriteDecoratedClassChainInitializer(varStmt, decl); if (declWithChainInitializer) { stmts.push(declWithChainInitializer.statement); for (const newExport of declWithChainInitializer.exports) { delayedDecoratedClassExports.set( - newExport.expression.left.name.text, newExport); + newExport.expression.left.name.text, + newExport, + ); } return; } @@ -1418,30 +1582,34 @@ export function commonJsToGoogmoduleTransformer( * Import assertions are not a concern, module=commonjs doesn't support * them. */ - function rewriteDynamicRequire(node: ts.Node): ts.Node|null { + function rewriteDynamicRequire(node: ts.Node): ts.Node | null { // Look for `???( ??? )` if (!ts.isCallExpression(node) || node.arguments.length !== 1) { return null; } - let importedUrl: ts.StringLiteral|null = null; + let importedUrl: ts.StringLiteral | null = null; // Look for `???(() => require(???))` - if (ts.isArrowFunction(node.arguments[0]) && - ts.isCallExpression(node.arguments[0].body)) { + if ( + ts.isArrowFunction(node.arguments[0]) && + ts.isCallExpression(node.arguments[0].body) + ) { importedUrl = extractRequire(node.arguments[0].body); } // Look for `???(function(){ return require(???); })` - if (ts.isFunctionExpression(node.arguments[0]) && - ts.isBlock(node.arguments[0].body) && - node.arguments[0].body.statements.length === 1 && - ts.isReturnStatement(node.arguments[0].body.statements[0]) && - node.arguments[0].body.statements[0].expression != null && - ts.isCallExpression( - node.arguments[0].body.statements[0].expression)) { - importedUrl = - extractRequire(node.arguments[0].body.statements[0].expression); + if ( + ts.isFunctionExpression(node.arguments[0]) && + ts.isBlock(node.arguments[0].body) && + node.arguments[0].body.statements.length === 1 && + ts.isReturnStatement(node.arguments[0].body.statements[0]) && + node.arguments[0].body.statements[0].expression != null && + ts.isCallExpression(node.arguments[0].body.statements[0].expression) + ) { + importedUrl = extractRequire( + node.arguments[0].body.statements[0].expression, + ); } if (!importedUrl) { @@ -1449,31 +1617,42 @@ export function commonJsToGoogmoduleTransformer( } const callee = node.expression; - if (!ts.isPropertyAccessExpression(callee) || - callee.name.escapedText !== 'then' || - !ts.isCallExpression(callee.expression)) { + if ( + !ts.isPropertyAccessExpression(callee) || + callee.name.escapedText !== 'then' || + !ts.isCallExpression(callee.expression) + ) { return null; } const resolveCall = callee.expression; - if (resolveCall.arguments.length !== 0 || - !ts.isPropertyAccessExpression(resolveCall.expression) || - !ts.isIdentifier(resolveCall.expression.expression) || - resolveCall.expression.expression.escapedText !== 'Promise' || - !ts.isIdentifier(resolveCall.expression.name) || - resolveCall.expression.name.escapedText !== 'resolve') { + if ( + resolveCall.arguments.length !== 0 || + !ts.isPropertyAccessExpression(resolveCall.expression) || + !ts.isIdentifier(resolveCall.expression.expression) || + resolveCall.expression.expression.escapedText !== 'Promise' || + !ts.isIdentifier(resolveCall.expression.name) || + resolveCall.expression.name.escapedText !== 'resolve' + ) { return null; } const ignoredDiagnostics: ts.Diagnostic[] = []; const imp = importPathToGoogNamespace( - host, importedUrl, ignoredDiagnostics, sf, importedUrl.text, - () => getAmbientModuleSymbol(typeChecker, importedUrl!)); + host, + importedUrl, + ignoredDiagnostics, + sf, + importedUrl.text, + () => getAmbientModuleSymbol(typeChecker, importedUrl!), + ); modulesManifest.addReferencedModule(sf.fileName, imp); return createGoogCall( - 'requireDynamic', createSingleQuoteStringLiteral(imp)); + 'requireDynamic', + createSingleQuoteStringLiteral(imp), + ); } - const visitForDynamicImport: ts.Visitor = (node) => { + const visitForDynamicImport = (node: ts.Node) => { const replacementNode = rewriteDynamicRequire(node); if (replacementNode) { return replacementNode; @@ -1482,9 +1661,7 @@ export function commonJsToGoogmoduleTransformer( }; if (host.transformDynamicImport === 'closure') { - // TODO: go/ts50upgrade - Remove after upgrade. - // tslint:disable-next-line:no-unnecessary-type-assertion - sf = ts.visitNode(sf, visitForDynamicImport, ts.isSourceFile)!; + sf = ts.visitNode(sf, visitForDynamicImport, ts.isSourceFile); } // Convert each top level statement to goog.module. @@ -1502,7 +1679,8 @@ export function commonJsToGoogmoduleTransformer( // Emit: goog.module('moduleName'); const googModule = ts.factory.createExpressionStatement( - createGoogCall('module', createSingleQuoteStringLiteral(moduleName))); + createGoogCall('module', createSingleQuoteStringLiteral(moduleName)), + ); headerStmts.push(googModule); maybeAddModuleId(host, typeChecker, sf, headerStmts); @@ -1527,8 +1705,12 @@ export function commonJsToGoogmoduleTransformer( // Only add the extra require if it hasn't already been required if (resolvedModuleNames.indexOf(tslibModuleName) === -1) { - const tslibImport = ts.factory.createExpressionStatement(createGoogCall( - 'require', createSingleQuoteStringLiteral(tslibModuleName))); + const tslibImport = ts.factory.createExpressionStatement( + createGoogCall( + 'require', + createSingleQuoteStringLiteral(tslibModuleName), + ), + ); // Place the goog.require('tslib') statement right after the // goog.module statements @@ -1538,8 +1720,9 @@ export function commonJsToGoogmoduleTransformer( // Insert goog.module() etc after any leading comments in the source file. // The comments have been converted to NotEmittedStatements by // transformer_util, which this depends on. - const insertionIdx = - stmts.findIndex(s => s.kind !== ts.SyntaxKind.NotEmittedStatement); + const insertionIdx = stmts.findIndex( + (s) => s.kind !== ts.SyntaxKind.NotEmittedStatement, + ); if (insertionIdx === -1) { stmts.push(...headerStmts); } else { @@ -1547,8 +1730,9 @@ export function commonJsToGoogmoduleTransformer( } return ts.factory.updateSourceFile( - sf, - ts.setTextRange(ts.factory.createNodeArray(stmts), sf.statements)); + sf, + ts.setTextRange(ts.factory.createNodeArray(stmts), sf.statements), + ); }; }; } @@ -1567,15 +1751,18 @@ export function commonJsToGoogmoduleTransformer( * ``` */ function maybeAddModuleId( - host: GoogModuleProcessorHost, typeChecker: ts.TypeChecker, - sourceFile: ts.SourceFile, headerStmts: ts.Statement[]): void { + host: GoogModuleProcessorHost, + typeChecker: ts.TypeChecker, + sourceFile: ts.SourceFile, + headerStmts: ts.Statement[], +): void { // See if a top-level 'module' symbol exists in the source file. - const moduleSymbol: ts.Symbol|undefined = - typeChecker.getSymbolsInScope(sourceFile, ts.SymbolFlags.ModuleMember) - ?.find(s => s.name === 'module'); + const moduleSymbol: ts.Symbol | undefined = typeChecker + .getSymbolsInScope(sourceFile, ts.SymbolFlags.ModuleMember) + ?.find((s) => s.name === 'module'); if (moduleSymbol) { const declaration = - moduleSymbol.valueDeclaration ?? moduleSymbol.declarations?.[0]; + moduleSymbol.valueDeclaration ?? moduleSymbol.declarations?.[0]; // If a top-level symbol with the name `module` exists whose value is // declared in sourceFile, don't add the `module.id` symbol. @@ -1584,15 +1771,25 @@ function maybeAddModuleId( const moduleId = host.fileNameToModuleId(sourceFile.fileName); const moduleVarInitializer = ts.factory.createBinaryExpression( - ts.factory.createIdentifier('module'), ts.SyntaxKind.BarBarToken, - ts.factory.createObjectLiteralExpression( - [ts.factory.createPropertyAssignment( - 'id', createSingleQuoteStringLiteral(moduleId))])); + ts.factory.createIdentifier('module'), + ts.SyntaxKind.BarBarToken, + ts.factory.createObjectLiteralExpression([ + ts.factory.createPropertyAssignment( + 'id', + createSingleQuoteStringLiteral(moduleId), + ), + ]), + ); const modAssign = ts.factory.createVariableStatement( - /* modifiers= */ undefined, - ts.factory.createVariableDeclarationList( - [ts.factory.createVariableDeclaration( - 'module', /* exclamationToken= */ undefined, - /* type= */ undefined, moduleVarInitializer)])); + /* modifiers= */ undefined, + ts.factory.createVariableDeclarationList([ + ts.factory.createVariableDeclaration( + 'module', + /* exclamationToken= */ undefined, + /* type= */ undefined, + moduleVarInitializer, + ), + ]), + ); headerStmts.push(modAssign); } diff --git a/src/jsdoc.ts b/src/jsdoc.ts index 063c009b6..559f70bda 100644 --- a/src/jsdoc.ts +++ b/src/jsdoc.ts @@ -143,6 +143,16 @@ const CLOSURE_ALLOWED_JSDOC_TAGS_OUTPUT = new Set([ 'wizmodule', ]); +/** + * JSDoc comments not attached to any nodes can generally not contains any tags, + * so all are banned. The exception is "license", which is supported as a + * standalone comment next to the fileoverview. + */ +const BANNED_JSDOC_TAGS_IN_FREESTANDING_COMMENTS = new Set( + CLOSURE_ALLOWED_JSDOC_TAGS_OUTPUT, +); +BANNED_JSDOC_TAGS_IN_FREESTANDING_COMMENTS.delete('license'); + /** * A list of JSDoc @tags that are never allowed in TypeScript source. These are Closure tags that * can be expressed in the TypeScript surface syntax. As tsickle's emit will mangle type names, @@ -150,9 +160,27 @@ const CLOSURE_ALLOWED_JSDOC_TAGS_OUTPUT = new Set([ * Note: 'template' is special-cased below; see where this set is queried. */ const BANNED_JSDOC_TAGS_INPUT = new Set([ - 'augments', 'class', 'constructs', 'constructor', 'enum', 'extends', 'field', - 'function', 'implements', 'interface', 'lends', 'namespace', 'private', 'protected', - 'public', 'record', 'static', 'template', 'this', 'type', 'typedef', + 'augments', + 'class', + 'constructs', + 'constructor', + 'enum', + 'extends', + 'field', + 'function', + 'implements', + 'interface', + 'lends', + 'namespace', + 'private', + 'protected', + 'public', + 'record', + 'static', + 'template', + 'this', + 'type', + 'typedef', ]); /** @@ -170,15 +198,20 @@ const JSDOC_TAGS_WITH_TYPES = new Set([ 'const', 'define', 'export', - ...TAGS_CONFLICTING_WITH_TYPE + ...TAGS_CONFLICTING_WITH_TYPE, ]); /** * Tags that, if they are the only tag, should be printed in a single line JSDoc * comment. */ -const ONE_LINER_TAGS = - new Set(['type', 'typedef', 'nocollapse', 'const', 'enum']); +const ONE_LINER_TAGS = new Set([ + 'type', + 'typedef', + 'nocollapse', + 'const', + 'enum', +]); /** * Result of parsing a JSDoc comment. Such comments essentially are built of a list of tags. @@ -198,7 +231,9 @@ export interface ParsedJSDocComment { // such as merging (below), de-duplicating certain tags (@deprecated), and special treatment for // others (e.g. @suppress). We should introduce a proper model class with a more suitable data // strucure (e.g. a Map). -export function parse(comment: ts.SynthesizedComment): ParsedJSDocComment|null { +export function parse( + comment: ts.SynthesizedComment, +): ParsedJSDocComment | null { // TODO(evanm): this is a pile of hacky regexes for now, because we // would rather use the better TypeScript implementation of JSDoc // parsing. https://github.com/Microsoft/TypeScript/issues/7393 @@ -222,7 +257,7 @@ export function normalizeLineEndings(input: string): string { * * @param commentText a comment's text content, i.e. the comment w/o /* and * /. */ -export function parseContents(commentText: string): ParsedJSDocComment|null { +function parseContents(commentText: string): ParsedJSDocComment | null { // Make sure we have proper line endings before parsing on Windows. commentText = normalizeLineEndings(commentText); // Strip all the " * " bits from the front of each line. @@ -233,18 +268,20 @@ export function parseContents(commentText: string): ParsedJSDocComment|null { for (const line of lines) { let match = line.match(/^\s*@([^\s{]+) *({?.*)/); if (match) { - let [_, tagName, text] = match; + let [, tagName, text] = match; if (tagName === 'returns') { // A synonym for 'return'. tagName = 'return'; } - let type: string|undefined; + let type: string | undefined; if (BANNED_JSDOC_TAGS_INPUT.has(tagName)) { if (tagName !== 'template') { // Tell the user to not write banned tags, because there is TS // syntax available for them. - warnings.push(`@${tagName} annotations are redundant with TypeScript equivalents`); - continue; // Drop the tag so Closure won't process it. + warnings.push( + `@${tagName} annotations are redundant with TypeScript equivalents`, + ); + continue; // Drop the tag so Closure won't process it. } else { // But @template in particular is special: it's ok for the user to // write it for documentation purposes, but we don't want the @@ -256,8 +293,9 @@ export function parseContents(commentText: string): ParsedJSDocComment|null { } else if (JSDOC_TAGS_WITH_TYPES.has(tagName)) { if (text[0] === '{') { warnings.push( - `the type annotation on @${tagName} is redundant with its TypeScript type, ` + - `remove the {...} part`); + `the type annotation on @${tagName} is redundant with its TypeScript type, ` + + `remove the {...} part`, + ); continue; } } else if (tagName === 'suppress') { @@ -268,15 +306,17 @@ export function parseContents(commentText: string): ParsedJSDocComment|null { warnings.push(`malformed @${tagName} tag: "${text}"`); } } else if (tagName === 'dict') { - warnings.push('use index signatures (`[k: string]: type`) instead of @dict'); + warnings.push( + 'use index signatures (`[k: string]: type`) instead of @dict', + ); continue; } // Grab the parameter name from @param tags. - let parameterName: string|undefined; + let parameterName: string | undefined; if (tagName === 'param') { match = text.match(/^(\S+) ?(.*)/); - if (match) [_, parameterName, text] = match; + if (match) [, parameterName, text] = match; } const tag: Tag = {tagName}; @@ -308,7 +348,10 @@ export function parseContents(commentText: string): ParsedJSDocComment|null { function tagToString(tag: Tag, escapeExtraTags = new Set()): string { let out = ''; if (tag.tagName) { - if (!CLOSURE_ALLOWED_JSDOC_TAGS_OUTPUT.has(tag.tagName) || escapeExtraTags.has(tag.tagName)) { + if ( + !CLOSURE_ALLOWED_JSDOC_TAGS_OUTPUT.has(tag.tagName) || + escapeExtraTags.has(tag.tagName) + ) { // Escape tags we don't understand. This is a subtle // compromise between multiple issues. // 1) If we pass through these non-Closure tags, the user will @@ -345,7 +388,7 @@ function tagToString(tag: Tag, escapeExtraTags = new Set()): string { return out; } -/** Tags that must only occur onces in a comment (filtered below). */ +/** Tags that must only occur once in a comment (filtered below). */ const SINGLETON_TAGS = new Set(['deprecated']); /** @@ -363,11 +406,16 @@ export interface SynthesizedCommentWithOriginal extends ts.SynthesizedComment { * to synthetic comments, and makes sure the original text comments do not get * emitted by TypeScript. */ -export function synthesizeLeadingComments(node: ts.Node): SynthesizedCommentWithOriginal[] { +export function synthesizeLeadingComments( + node: ts.Node, +): SynthesizedCommentWithOriginal[] { const existing = ts.getSyntheticLeadingComments(node); - if (existing) return existing; + if (existing && hasLeadingCommentsSuppressed(node)) return existing; const text = ts.getOriginalNode(node).getFullText(); - const synthComments = getLeadingCommentRangesSynthesized(text, node.getFullStart()); + const synthComments = getLeadingCommentRangesSynthesized( + text, + node.getFullStart(), + ); if (synthComments.length) { ts.setSyntheticLeadingComments(node, synthComments); suppressLeadingCommentsRecursively(node); @@ -375,6 +423,25 @@ export function synthesizeLeadingComments(node: ts.Node): SynthesizedCommentWith return synthComments; } +function hasLeadingCommentsSuppressed(node: ts.Node): boolean { + const internalNode = node as InternalNode; + if (!internalNode.emitNode) return false; + return ( + (internalNode.emitNode.flags & ts.EmitFlags.NoLeadingComments) === + ts.EmitFlags.NoLeadingComments + ); +} + +declare interface InternalNode extends ts.Node { + // http://google3/third_party/javascript/node_modules/typescript/stable/src/compiler/types.ts;l=954;rcl=589121220 + emitNode?: InternalEmitNode; +} + +declare interface InternalEmitNode { + // http://google3/third_party/javascript/node_modules/typescript/stable/src/compiler/types.ts;l=7982;rcl=589121220 + flags: ts.EmitFlags; +} + /** * parseLeadingCommentRangesSynthesized parses the leading comment ranges out of the given text and * converts them to SynthesizedComments. @@ -382,20 +449,23 @@ export function synthesizeLeadingComments(node: ts.Node): SynthesizedCommentWith */ // VisibleForTesting export function getLeadingCommentRangesSynthesized( - text: string, offset = 0): SynthesizedCommentWithOriginal[] { + text: string, + offset = 0, +): SynthesizedCommentWithOriginal[] { const comments = ts.getLeadingCommentRanges(text, 0) || []; return comments.map((cr): SynthesizedCommentWithOriginal => { // Confusingly, CommentRange in TypeScript includes start and end markers, but // SynthesizedComments do not. - const commentText = cr.kind === ts.SyntaxKind.SingleLineCommentTrivia ? - text.substring(cr.pos + 2, cr.end) : - text.substring(cr.pos + 2, cr.end - 2); + const commentText = + cr.kind === ts.SyntaxKind.SingleLineCommentTrivia + ? text.substring(cr.pos + 2, cr.end) + : text.substring(cr.pos + 2, cr.end - 2); return { ...cr, text: commentText, pos: -1, end: -1, - originalRange: {pos: cr.pos + offset, end: cr.end + offset} + originalRange: {pos: cr.pos + offset, end: cr.end + offset}, }; }); } @@ -423,8 +493,10 @@ export function suppressLeadingCommentsRecursively(node: ts.Node) { } export function toSynthesizedComment( - tags: Tag[], escapeExtraTags?: Set, - hasTrailingNewLine = true): ts.SynthesizedComment { + tags: Tag[], + escapeExtraTags?: Set, + hasTrailingNewLine = true, +): ts.SynthesizedComment { return { kind: ts.SyntaxKind.MultiLineCommentTrivia, text: toStringWithoutStartEnd(tags, escapeExtraTags), @@ -435,22 +507,33 @@ export function toSynthesizedComment( } /** Serializes a Comment out to a string, but does not include the start and end comment tokens. */ -export function toStringWithoutStartEnd(tags: Tag[], escapeExtraTags = new Set()): string { +function toStringWithoutStartEnd( + tags: Tag[], + escapeExtraTags = new Set(), +): string { return serialize(tags, false, escapeExtraTags); } /** Serializes a Comment out to a string usable in source code. */ -export function toString(tags: Tag[], escapeExtraTags = new Set()): string { +export function toString( + tags: Tag[], + escapeExtraTags = new Set(), +): string { return serialize(tags, true, escapeExtraTags); } function serialize( - tags: Tag[], includeStartEnd: boolean, escapeExtraTags = new Set()): string { + tags: Tag[], + includeStartEnd: boolean, + escapeExtraTags = new Set(), +): string { if (tags.length === 0) return ''; if (tags.length === 1) { const tag = tags[0]; - if (ONE_LINER_TAGS.has(tag.tagName) && - (!tag.text || !tag.text.match('\n'))) { + if ( + ONE_LINER_TAGS.has(tag.tagName) && + (!tag.text || !tag.text.match('\n')) + ) { // Special-case one-liner "type" and "nocollapse" tags to fit on one line, e.g. // /** @type {foo} */ const text = tagToString(tag, escapeExtraTags); @@ -498,11 +581,16 @@ export function merge(tags: Tag[]): Tag { } const tagName = tagNames.values().next().value; const parameterName = - parameterNames.size > 0 ? Array.from(parameterNames).join('_or_') : undefined; + parameterNames.size > 0 + ? Array.from(parameterNames).join('_or_') + : undefined; const type = types.size > 0 ? Array.from(types).join('|') : undefined; // @template uses text (not type!) to declare its type parameters, with ','-separated text. const isTemplateTag = tagName === 'template'; - const text = texts.size > 0 ? Array.from(texts).join(isTemplateTag ? ',' : ' / ') : undefined; + const text = + texts.size > 0 + ? Array.from(texts).join(isTemplateTag ? ',' : ' / ') + : undefined; const tag: Tag = {tagName, parameterName, type, text}; // Note: a param can either be optional or a rest param; if we merged an // optional and rest param together, prefer marking it as a rest param. @@ -529,22 +617,40 @@ export function createGeneratedFromComment(file: string): string { * allows code to modify (including delete) it. */ export class MutableJSDoc { + private sanitizedOtherComments = false; + constructor( - private readonly node: ts.Node, - private sourceComment: ts.SynthesizedComment|null, public tags: Tag[]) {} + private readonly node: ts.Node, + private readonly allComments: ts.SynthesizedComment[], + private sourceComment: number, + public tags: Tag[], + ) {} updateComment(escapeExtraTags?: Set) { + if (!this.sanitizedOtherComments) { + for (let i = 0; i < this.allComments.length; i++) { + if (i === this.sourceComment) continue; + const comment = this.allComments[i]; + const parsed = parse(comment); + if (!parsed) continue; + comment.text = toStringWithoutStartEnd( + parsed.tags, + BANNED_JSDOC_TAGS_IN_FREESTANDING_COMMENTS, + ); + } + + this.sanitizedOtherComments = true; + } + const text = toStringWithoutStartEnd(this.tags, escapeExtraTags); - if (this.sourceComment) { + if (this.sourceComment >= 0) { if (!text) { // Delete the (now empty) comment. - const comments = ts.getSyntheticLeadingComments(this.node)!; - const idx = comments.indexOf(this.sourceComment); - comments.splice(idx, 1); - this.sourceComment = null; + this.allComments.splice(this.sourceComment, 1); + this.sourceComment = -1; return; } - this.sourceComment.text = text; + this.allComments[this.sourceComment].text = text; return; } @@ -558,9 +664,9 @@ export class MutableJSDoc { pos: -1, end: -1, }; - const comments = ts.getSyntheticLeadingComments(this.node) || []; - comments.push(comment); - ts.setSyntheticLeadingComments(this.node, comments); + this.allComments.push(comment); + this.sourceComment = this.allComments.length - 1; + ts.setSyntheticLeadingComments(this.node, this.allComments); } } @@ -574,10 +680,12 @@ export class MutableJSDoc { * diagnostic location. */ export function getJSDocTags( - node: ts.Node, diagnostics?: ts.Diagnostic[], - sourceFile?: ts.SourceFile): Tag[] { + node: ts.Node, + diagnostics?: ts.Diagnostic[], + sourceFile?: ts.SourceFile, +): Tag[] { if (!ts.getParseTreeNode(node)) return []; - const [tags, ] = parseJSDoc(node, diagnostics, sourceFile); + const [, , tags] = parseJSDoc(node, diagnostics, sourceFile); return tags; } @@ -590,18 +698,22 @@ export function getJSDocTags( * diagnostic location. */ export function getMutableJSDoc( - node: ts.Node, diagnostics?: ts.Diagnostic[], - sourceFile?: ts.SourceFile): MutableJSDoc { - const [tags, comment] = parseJSDoc(node, diagnostics, sourceFile); - return new MutableJSDoc(node, comment, tags); + node: ts.Node, + diagnostics?: ts.Diagnostic[], + sourceFile?: ts.SourceFile, +): MutableJSDoc { + const [comments, i, tags] = parseJSDoc(node, diagnostics, sourceFile); + return new MutableJSDoc(node, comments, i, tags); } function parseJSDoc( - node: ts.Node, diagnostics?: ts.Diagnostic[], - sourceFile?: ts.SourceFile): [Tag[], ts.SynthesizedComment|null] { + node: ts.Node, + diagnostics?: ts.Diagnostic[], + sourceFile?: ts.SourceFile, +): [ts.SynthesizedComment[], number, Tag[]] { // synthesizeLeadingComments below changes text locations for node, so extract // the location here in case it is needed later to report diagnostics. - let nodeCommentRange: ts.TextRange|undefined; + let nodeCommentRange: ts.TextRange | undefined; if (diagnostics !== undefined) { const pos = node.getFullStart(); const length = node.getLeadingTriviaWidth(sourceFile); @@ -609,7 +721,7 @@ function parseJSDoc( } const comments = synthesizeLeadingComments(node); - if (!comments || comments.length === 0) return [[], null]; + if (!comments || comments.length === 0) return [[], -1, []]; for (let i = comments.length - 1; i >= 0; i--) { const comment = comments[i]; @@ -618,11 +730,15 @@ function parseJSDoc( if (diagnostics !== undefined && parsed.warnings) { const range = comment.originalRange || nodeCommentRange; reportDiagnostic( - diagnostics, node, parsed.warnings.join('\n'), range, - ts.DiagnosticCategory.Warning); + diagnostics, + node, + parsed.warnings.join('\n'), + range, + ts.DiagnosticCategory.Warning, + ); } - return [parsed.tags, comment]; + return [comments, i, parsed.tags]; } } - return [[], null]; + return [comments, -1, []]; } diff --git a/src/jsdoc_transformer.ts b/src/jsdoc_transformer.ts index 6e3e5119b..f7232013f 100644 --- a/src/jsdoc_transformer.ts +++ b/src/jsdoc_transformer.ts @@ -34,14 +34,24 @@ import {GoogModuleProcessorHost} from './googmodule'; import * as jsdoc from './jsdoc'; import {ModuleTypeTranslator} from './module_type_translator'; import * as transformerUtil from './transformer_util'; -import {getPreviousDeclaration, isMergedDeclaration, symbolIsValue} from './transformer_util'; +import { + getPreviousDeclaration, + isMergedDeclaration, + symbolIsValue, +} from './transformer_util'; import {isValidClosurePropertyName} from './type_translator'; function addCommentOn( - node: ts.Node, tags: jsdoc.Tag[], escapeExtraTags?: Set, - hasTrailingNewLine = true) { - const comment = - jsdoc.toSynthesizedComment(tags, escapeExtraTags, hasTrailingNewLine); + node: ts.Node, + tags: jsdoc.Tag[], + escapeExtraTags?: Set, + hasTrailingNewLine = true, +) { + const comment = jsdoc.toSynthesizedComment( + tags, + escapeExtraTags, + hasTrailingNewLine, + ); const comments = ts.getSyntheticLeadingComments(node) || []; comments.push(comment); ts.setSyntheticLeadingComments(node, comments); @@ -49,15 +59,23 @@ function addCommentOn( } type HasTypeParameters = - ts.InterfaceDeclaration|ts.ClassLikeDeclaration|ts.TypeAliasDeclaration|ts.SignatureDeclaration; + | ts.InterfaceDeclaration + | ts.ClassLikeDeclaration + | ts.TypeAliasDeclaration + | ts.SignatureDeclaration; /** Adds an \@template clause to docTags if decl has type parameters. */ -export function maybeAddTemplateClause(docTags: jsdoc.Tag[], decl: HasTypeParameters) { +export function maybeAddTemplateClause( + docTags: jsdoc.Tag[], + decl: HasTypeParameters, +) { if (!decl.typeParameters) return; // Closure does not support template constraints (T extends X), these are ignored below. docTags.push({ tagName: 'template', - text: decl.typeParameters.map(tp => transformerUtil.getIdentifierText(tp.name)).join(', ') + text: decl.typeParameters + .map((tp) => transformerUtil.getIdentifierText(tp.name)) + .join(', '), }); } @@ -66,11 +84,15 @@ export function maybeAddTemplateClause(docTags: jsdoc.Tag[], decl: HasTypeParame * decl. Used by jsdoc_transformer and externs generation. */ export function maybeAddHeritageClauses( - docTags: jsdoc.Tag[], mtt: ModuleTypeTranslator, - decl: ts.ClassLikeDeclaration|ts.InterfaceDeclaration) { + docTags: jsdoc.Tag[], + mtt: ModuleTypeTranslator, + decl: ts.ClassLikeDeclaration | ts.InterfaceDeclaration, +) { if (!decl.heritageClauses) return; const isClass = decl.kind === ts.SyntaxKind.ClassDeclaration; - const hasAnyExtends = decl.heritageClauses.some(c => c.token === ts.SyntaxKind.ExtendsKeyword); + const hasAnyExtends = decl.heritageClauses.some( + (c) => c.token === ts.SyntaxKind.ExtendsKeyword, + ); for (const heritage of decl.heritageClauses) { const isExtends = heritage.token === ts.SyntaxKind.ExtendsKeyword; for (const expr of heritage.types) { @@ -96,7 +118,9 @@ export function maybeAddHeritageClauses( * semantics. */ function addHeritage( - relation: 'extends'|'implements', expr: ts.ExpressionWithTypeArguments): void { + relation: 'extends' | 'implements', + expr: ts.ExpressionWithTypeArguments, + ): void { const supertype = mtt.typeChecker.getTypeAtLocation(expr); // We ultimately need to have a named type in the JSDoc, so verify that // the resolved type maps back to some specific symbol. @@ -187,8 +211,9 @@ export function maybeAddHeritageClauses( * trigger getters on a superclass. See test_files/fields/fields.ts:BaseThatThrows. */ function createMemberTypeDeclaration( - mtt: ModuleTypeTranslator, - typeDecl: ts.ClassDeclaration|ts.InterfaceDeclaration): ts.IfStatement|null { + mtt: ModuleTypeTranslator, + typeDecl: ts.ClassDeclaration | ts.InterfaceDeclaration, +): ts.IfStatement | null { // Gather parameter properties from the constructor, if it exists. const ctors: ts.ConstructorDeclaration[] = []; let paramProps: ts.ParameterDeclaration[] = []; @@ -200,25 +225,35 @@ function createMemberTypeDeclaration( if (member.kind === ts.SyntaxKind.Constructor) { ctors.push(member as ts.ConstructorDeclaration); } else if ( - ts.isPropertyDeclaration(member) || ts.isPropertySignature(member) || - (ts.isMethodDeclaration(member) && member.questionToken)) { - const isStatic = - transformerUtil.hasModifierFlag(member, ts.ModifierFlags.Static); + ts.isPropertyDeclaration(member) || + ts.isPropertySignature(member) || + (ts.isMethodDeclaration(member) && member.questionToken) + ) { + const isStatic = transformerUtil.hasModifierFlag( + member, + ts.ModifierFlags.Static, + ); if (isStatic) { staticProps.push(member); } else { nonStaticProps.push(member); } } else if ( - member.kind === ts.SyntaxKind.MethodDeclaration || - member.kind === ts.SyntaxKind.MethodSignature || - member.kind === ts.SyntaxKind.GetAccessor || - member.kind === ts.SyntaxKind.SetAccessor) { - if (transformerUtil.hasModifierFlag(member, ts.ModifierFlags.Abstract) || - ts.isInterfaceDeclaration(typeDecl)) { + member.kind === ts.SyntaxKind.MethodDeclaration || + member.kind === ts.SyntaxKind.MethodSignature || + member.kind === ts.SyntaxKind.GetAccessor || + member.kind === ts.SyntaxKind.SetAccessor + ) { + if ( + transformerUtil.hasModifierFlag(member, ts.ModifierFlags.Abstract) || + ts.isInterfaceDeclaration(typeDecl) + ) { abstractMethods.push( - member as ts.MethodDeclaration | ts.GetAccessorDeclaration | - ts.SetAccessorDeclaration); + member as + | ts.MethodDeclaration + | ts.GetAccessorDeclaration + | ts.SetAccessorDeclaration, + ); } // Non-abstract methods only exist on classes, and are handled in regular // emit. @@ -231,12 +266,20 @@ function createMemberTypeDeclaration( // Only the actual constructor implementation, which must be last in a potential sequence of // overloaded constructors, may contain parameter properties. const ctor = ctors[ctors.length - 1]; - paramProps = ctor.parameters.filter( - p => transformerUtil.hasModifierFlag(p, ts.ModifierFlags.ParameterPropertyModifier)); + paramProps = ctor.parameters.filter((p) => + transformerUtil.hasModifierFlag( + p, + ts.ModifierFlags.ParameterPropertyModifier, + ), + ); } - if (nonStaticProps.length === 0 && paramProps.length === 0 && staticProps.length === 0 && - abstractMethods.length === 0) { + if ( + nonStaticProps.length === 0 && + paramProps.length === 0 && + staticProps.length === 0 && + abstractMethods.length === 0 + ) { // There are no members so we don't need to emit any type // annotations helper. return null; @@ -249,69 +292,105 @@ function createMemberTypeDeclaration( const className = transformerUtil.getIdentifierText(typeDecl.name); const staticPropAccess = ts.factory.createIdentifier(className); - const instancePropAccess = - ts.factory.createPropertyAccessExpression(staticPropAccess, 'prototype'); + const instancePropAccess = ts.factory.createPropertyAccessExpression( + staticPropAccess, + 'prototype', + ); // Closure Compiler will report conformance errors about this being unknown type when emitting // class properties as {?|undefined}, instead of just {?}. So make sure to only emit {?|undefined} // on interfaces. const isInterface = ts.isInterfaceDeclaration(typeDecl); - const propertyDecls = staticProps.map( - p => createClosurePropertyDeclaration( - mtt, staticPropAccess, p, isInterface && !!p.questionToken)); - propertyDecls.push(...[...nonStaticProps, ...paramProps].map( - p => createClosurePropertyDeclaration( - mtt, instancePropAccess, p, isInterface && !!p.questionToken))); - propertyDecls.push(...unhandled.map( - p => transformerUtil.createMultiLineComment( - p, `Skipping unhandled member: ${escapeForComment(p.getText())}`))); + const propertyDecls = staticProps.map((p) => + createClosurePropertyDeclaration( + mtt, + staticPropAccess, + p, + isInterface && !!p.questionToken, + ), + ); + propertyDecls.push( + ...[...nonStaticProps, ...paramProps].map((p) => + createClosurePropertyDeclaration( + mtt, + instancePropAccess, + p, + isInterface && !!p.questionToken, + ), + ), + ); + propertyDecls.push( + ...unhandled.map((p) => + transformerUtil.createMultiLineComment( + p, + `Skipping unhandled member: ${escapeForComment(p.getText())}`, + ), + ), + ); for (const fnDecl of abstractMethods) { // If the function declaration is computed, its name is the computed expression; otherwise, its // name can be resolved to a string. - const name = fnDecl.name && ts.isComputedPropertyName(fnDecl.name) ? fnDecl.name.expression : - propertyName(fnDecl); + const name = + fnDecl.name && ts.isComputedPropertyName(fnDecl.name) + ? fnDecl.name.expression + : propertyName(fnDecl); if (!name) { mtt.error(fnDecl, 'anonymous abstract function'); continue; } const {tags, parameterNames} = mtt.getFunctionTypeJSDoc([fnDecl], []); - if (hasExportingDecorator(fnDecl, mtt.typeChecker)) tags.push({tagName: 'export'}); + if (hasExportingDecorator(fnDecl, mtt.typeChecker)) { + tags.push({tagName: 'export'}); + } // Use element access instead of property access for computed names. - const lhs = typeof name === 'string' ? - ts.factory.createPropertyAccessExpression(instancePropAccess, name) : - ts.factory.createElementAccessExpression(instancePropAccess, name); + const lhs = + typeof name === 'string' + ? ts.factory.createPropertyAccessExpression(instancePropAccess, name) + : ts.factory.createElementAccessExpression(instancePropAccess, name); // memberNamespace because abstract methods cannot be static in TypeScript. - const abstractFnDecl = - ts.factory.createExpressionStatement(ts.factory.createAssignment( - lhs, - ts.factory.createFunctionExpression( - /* modifiers */ undefined, - /* asterisk */ undefined, - /* name */ undefined, - /* typeParameters */ undefined, - parameterNames.map( - n => ts.factory.createParameterDeclaration( - /* modifiers */ undefined, - /* dotDotDot */ undefined, n)), - undefined, - ts.factory.createBlock([]), - ))); - ts.setSyntheticLeadingComments(abstractFnDecl, [jsdoc.toSynthesizedComment(tags)]); + const abstractFnDecl = ts.factory.createExpressionStatement( + ts.factory.createAssignment( + lhs, + ts.factory.createFunctionExpression( + /* modifiers */ undefined, + /* asterisk */ undefined, + /* name */ undefined, + /* typeParameters */ undefined, + parameterNames.map((n) => + ts.factory.createParameterDeclaration( + /* modifiers */ undefined, + /* dotDotDot */ undefined, + n, + ), + ), + undefined, + ts.factory.createBlock([]), + ), + ), + ); + ts.setSyntheticLeadingComments(abstractFnDecl, [ + jsdoc.toSynthesizedComment(tags), + ]); propertyDecls.push(ts.setSourceMapRange(abstractFnDecl, fnDecl)); } // Wrap the property declarations in an 'if (false)' block. // See test_files/fields/fields.ts:BaseThatThrows for a note on this wrapper. const ifStmt = ts.factory.createIfStatement( - ts.factory.createFalse(), ts.factory.createBlock(propertyDecls, true)); + ts.factory.createFalse(), + ts.factory.createBlock(propertyDecls, true), + ); // Also add a comment above the block to exclude it from coverage. ts.addSyntheticLeadingComment( - ifStmt, ts.SyntaxKind.MultiLineCommentTrivia, ' istanbul ignore if ', - /* trailing newline */ true); + ifStmt, + ts.SyntaxKind.MultiLineCommentTrivia, + ' istanbul ignore if ', + /* trailing newline */ true, + ); return ifStmt; } -function propertyName(prop: ts.NamedDeclaration): string|null { +function propertyName(prop: ts.NamedDeclaration): string | null { if (!prop.name) return null; switch (prop.name.kind) { @@ -338,12 +417,18 @@ export function escapeForComment(str: string): string { * declaration for. Note that this includes ts.MethodDeclarations but only for * optional methods. */ -type ClosureProperty = ts.PropertyDeclaration|ts.PropertySignature| - ts.ParameterDeclaration|ts.MethodDeclaration; +type ClosureProperty = + | ts.PropertyDeclaration + | ts.PropertySignature + | ts.ParameterDeclaration + | ts.MethodDeclaration; function createClosurePropertyDeclaration( - mtt: ModuleTypeTranslator, expr: ts.Expression, prop: ClosureProperty, - optional: boolean): ts.Statement { + mtt: ModuleTypeTranslator, + expr: ts.Expression, + prop: ClosureProperty, + optional: boolean, +): ts.Statement { const name = propertyName(prop); if (!name) { // Skip warning for private identifiers because it is expected they are skipped in the @@ -352,11 +437,18 @@ function createClosurePropertyDeclaration( // adjust this output accordingly. if (ts.isPrivateIdentifier(prop.name)) { return transformerUtil.createMultiLineComment( - prop, `Skipping private member:\n${escapeForComment(prop.getText())}`); + prop, + `Skipping private member:\n${escapeForComment(prop.getText())}`, + ); } else { - mtt.debugWarn(prop, `handle unnamed member:\n${escapeForComment(prop.getText())}`); + mtt.debugWarn( + prop, + `handle unnamed member:\n${escapeForComment(prop.getText())}`, + ); return transformerUtil.createMultiLineComment( - prop, `Skipping unnamed member:\n${escapeForComment(prop.getText())}`); + prop, + `Skipping unnamed member:\n${escapeForComment(prop.getText())}`, + ); } } @@ -365,7 +457,9 @@ function createClosurePropertyDeclaration( // funny with the TS type system, and isn't actually interested in naming a // a field 'prototype', as prototype has special meaning in JS. return transformerUtil.createMultiLineComment( - prop, `Skipping illegal member name:\n${escapeForComment(prop.getText())}`); + prop, + `Skipping illegal member name:\n${escapeForComment(prop.getText())}`, + ); } let type = mtt.typeToClosure(prop); @@ -395,8 +489,9 @@ function createClosurePropertyDeclaration( tags.push({tagName: 'protected'}); } else if (flags & ts.ModifierFlags.Private) { tags.push({tagName: 'private'}); - } else if (!tags.find( - (t) => t.tagName === 'export' || t.tagName === 'package')) { + } else if ( + !tags.find((t) => t.tagName === 'export' || t.tagName === 'package') + ) { // TODO(b/202495167): remove the 'package' check above. // TS members are implicitly public if no visibility modifier was specified. @@ -409,9 +504,11 @@ function createClosurePropertyDeclaration( } const declStmt = ts.setSourceMapRange( - ts.factory.createExpressionStatement( - ts.factory.createPropertyAccessExpression(expr, name)), - prop); + ts.factory.createExpressionStatement( + ts.factory.createPropertyAccessExpression(expr, name), + ), + prop, + ); // Avoid printing annotations that can conflict with @type // This avoids Closure's error "type annotation incompatible with other annotations" addCommentOn(declStmt, tags, jsdoc.TAGS_CONFLICTING_WITH_TYPE); @@ -442,9 +539,15 @@ export function removeTypeAssertions(): ts.TransformerFactory { switch (node.kind) { case ts.SyntaxKind.TypeAssertionExpression: case ts.SyntaxKind.AsExpression: - return ts.visitNode((node as ts.AssertionExpression).expression, visitor); + return ts.visitNode( + (node as ts.AssertionExpression).expression, + visitor, + ); case ts.SyntaxKind.NonNullExpression: - return ts.visitNode((node as ts.NonNullExpression).expression, visitor); + return ts.visitNode( + (node as ts.NonNullExpression).expression, + visitor, + ); default: break; } @@ -460,7 +563,10 @@ export function removeTypeAssertions(): ts.TransformerFactory { * Returns true if node lexically (recursively) contains an 'async' function. */ function containsAsync(node: ts.Node): boolean { - if (ts.isFunctionLike(node) && transformerUtil.hasModifierFlag(node, ts.ModifierFlags.Async)) { + if ( + ts.isFunctionLike(node) && + transformerUtil.hasModifierFlag(node, ts.ModifierFlags.Async) + ) { return true; } return ts.forEachChild(node, containsAsync) || false; @@ -469,20 +575,25 @@ function containsAsync(node: ts.Node): boolean { /** * Determines if a given expression contains an optional property chain. */ -function containsOptionalChainingOperator(node: ts.PropertyAccessExpression|ts.NonNullExpression| - ts.CallExpression): boolean { +function containsOptionalChainingOperator( + node: ts.PropertyAccessExpression | ts.NonNullExpression | ts.CallExpression, +): boolean { let maybePropertyAccessChain: ts.Expression = node; // We know this is a property access chain if each member is a // PropertyAccessExpression`, a `NonNullExpression`, a `CallExpression`, or an // `ElementAccessExpression`. Once we get to an expression that isn't, we have // traversed the chain and can see if this was an optional chain. - while (ts.isPropertyAccessExpression(maybePropertyAccessChain) || - ts.isNonNullExpression(maybePropertyAccessChain) || - ts.isCallExpression(maybePropertyAccessChain) || - ts.isElementAccessExpression(maybePropertyAccessChain)) { + while ( + ts.isPropertyAccessExpression(maybePropertyAccessChain) || + ts.isNonNullExpression(maybePropertyAccessChain) || + ts.isCallExpression(maybePropertyAccessChain) || + ts.isElementAccessExpression(maybePropertyAccessChain) + ) { // If we're at an access that used `?.`, we have found an optional property chain. - if (!ts.isNonNullExpression(maybePropertyAccessChain) && - maybePropertyAccessChain.questionDotToken != null) { + if ( + !ts.isNonNullExpression(maybePropertyAccessChain) && + maybePropertyAccessChain.questionDotToken != null + ) { return true; } @@ -497,13 +608,20 @@ function containsOptionalChainingOperator(node: ts.PropertyAccessExpression|ts.N * JSDoc annotations. */ export function jsdocTransformer( - host: AnnotatorHost&GoogModuleProcessorHost, tsOptions: ts.CompilerOptions, - typeChecker: ts.TypeChecker, diagnostics: ts.Diagnostic[]): - (context: ts.TransformationContext) => ts.Transformer { + host: AnnotatorHost & GoogModuleProcessorHost, + tsOptions: ts.CompilerOptions, + typeChecker: ts.TypeChecker, + diagnostics: ts.Diagnostic[], +): (context: ts.TransformationContext) => ts.Transformer { return (context: ts.TransformationContext): ts.Transformer => { return (sourceFile: ts.SourceFile) => { const moduleTypeTranslator = new ModuleTypeTranslator( - sourceFile, typeChecker, host, diagnostics, /*isForExterns*/ false); + sourceFile, + typeChecker, + host, + diagnostics, + /*isForExterns*/ false, + ); /** * The set of all names exported from an export * in the current module. Used to prevent * emitting duplicated exports. The first export * takes precedence in ES6. @@ -522,15 +640,19 @@ export function jsdocTransformer( * this accesses. More generally, Closure also cannot infer constraints for any other * templated types, but that might require a more general solution in Closure Compiler. */ - let contextThisType: ts.Type|null = null; + let contextThisType: ts.Type | null = null; let emitNarrowedTypes = true; - function visitClassDeclaration(classDecl: ts.ClassDeclaration): ts.Statement[] { + function visitClassDeclaration( + classDecl: ts.ClassDeclaration, + ): ts.Statement[] { const contextThisTypeBackup = contextThisType; const mjsdoc = moduleTypeTranslator.getMutableJSDoc(classDecl); - if (transformerUtil.hasModifierFlag(classDecl, ts.ModifierFlags.Abstract)) { + if ( + transformerUtil.hasModifierFlag(classDecl, ts.ModifierFlags.Abstract) + ) { mjsdoc.tags.push({tagName: 'abstract'}); } @@ -540,7 +662,10 @@ export function jsdocTransformer( } mjsdoc.updateComment(jsdoc.TAGS_CONFLICTING_WITH_TYPE); const decls: ts.Statement[] = []; - const memberDecl = createMemberTypeDeclaration(moduleTypeTranslator, classDecl); + const memberDecl = createMemberTypeDeclaration( + moduleTypeTranslator, + classDecl, + ); // WARNING: order is significant; we must create the member decl before transforming away // parameter property comments when visiting the constructor. decls.push(ts.visitEachChild(classDecl, visitor, context)); @@ -565,26 +690,40 @@ export function jsdocTransformer( * TODO(martinprobst): remove this once the Closure side issue has been resolved. */ function visitHeritageClause(heritageClause: ts.HeritageClause) { - if (heritageClause.token !== ts.SyntaxKind.ExtendsKeyword || !heritageClause.parent || - heritageClause.parent.kind === ts.SyntaxKind.InterfaceDeclaration) { + if ( + heritageClause.token !== ts.SyntaxKind.ExtendsKeyword || + !heritageClause.parent || + heritageClause.parent.kind === ts.SyntaxKind.InterfaceDeclaration + ) { return ts.visitEachChild(heritageClause, visitor, context); } if (heritageClause.types.length !== 1) { moduleTypeTranslator.error( - heritageClause, `expected exactly one type in class extension clause`); + heritageClause, + `expected exactly one type in class extension clause`, + ); } const type = heritageClause.types[0]; let expr: ts.Expression = type.expression; - while (ts.isParenthesizedExpression(expr) || ts.isNonNullExpression(expr) || - ts.isAssertionExpression(expr)) { + while ( + ts.isParenthesizedExpression(expr) || + ts.isNonNullExpression(expr) || + ts.isAssertionExpression(expr) + ) { expr = expr.expression; } - return ts.factory.updateHeritageClause( - heritageClause, [ts.factory.updateExpressionWithTypeArguments( - type, expr, type.typeArguments || [])]); + return ts.factory.updateHeritageClause(heritageClause, [ + ts.factory.updateExpressionWithTypeArguments( + type, + expr, + type.typeArguments || [], + ), + ]); } - function visitInterfaceDeclaration(iface: ts.InterfaceDeclaration): ts.Statement[] { + function visitInterfaceDeclaration( + iface: ts.InterfaceDeclaration, + ): ts.Statement[] { const sym = typeChecker.getSymbolAtLocation(iface.name); if (!sym) { moduleTypeTranslator.error(iface, 'interface with no symbol'); @@ -594,44 +733,59 @@ export function jsdocTransformer( // single namespace, unless the interface is merged with a namespace. if (symbolIsValue(typeChecker, sym) && !isMergedDeclaration(iface)) { moduleTypeTranslator.debugWarn( - iface, `type/symbol conflict for ${sym.name}, using {?} for now`); - return [transformerUtil.createSingleLineComment( - iface, 'WARNING: interface has both a type and a value, skipping emit')]; + iface, + `type/symbol conflict for ${sym.name}, using {?} for now`, + ); + return [ + transformerUtil.createSingleLineComment( + iface, + 'WARNING: interface has both a type and a value, skipping emit', + ), + ]; } - const tags = moduleTypeTranslator.getJSDoc(iface, /* reportWarnings */ true) || []; + const tags = + moduleTypeTranslator.getJSDoc(iface, /* reportWarnings */ true) || []; tags.push({tagName: 'record'}); maybeAddTemplateClause(tags, iface); if (!host.untyped) { maybeAddHeritageClauses(tags, moduleTypeTranslator, iface); } const name = transformerUtil.getIdentifierText(iface.name); - const modifiers = - transformerUtil.hasModifierFlag(iface, ts.ModifierFlags.Export) ? - [ts.factory.createToken(ts.SyntaxKind.ExportKeyword)] : - undefined; + const modifiers = transformerUtil.hasModifierFlag( + iface, + ts.ModifierFlags.Export, + ) + ? [ts.factory.createToken(ts.SyntaxKind.ExportKeyword)] + : undefined; const decl = ts.setSourceMapRange( - ts.factory.createFunctionDeclaration( - modifiers, - /* asterisk */ undefined, - name, - /* typeParameters */ undefined, - /* parameters */[], - /* type */ undefined, - /* body */ ts.factory.createBlock([]), - ), - iface); + ts.factory.createFunctionDeclaration( + modifiers, + /* asterisk */ undefined, + name, + /* typeParameters */ undefined, + /* parameters */ [], + /* type */ undefined, + /* body */ ts.factory.createBlock([]), + ), + iface, + ); addCommentOn(decl, tags, jsdoc.TAGS_CONFLICTING_WITH_TYPE); const isFirstOccurrence = getPreviousDeclaration(sym, iface) === null; const declarations: ts.Statement[] = []; if (isFirstOccurrence) declarations.push(decl); - const memberDecl = createMemberTypeDeclaration(moduleTypeTranslator, iface); + const memberDecl = createMemberTypeDeclaration( + moduleTypeTranslator, + iface, + ); if (memberDecl) declarations.push(memberDecl); return declarations; } /** Function declarations are emitted as they are, with only JSDoc added. */ - function visitFunctionLikeDeclaration(fnDecl: T): T { + function visitFunctionLikeDeclaration< + T extends ts.FunctionLikeDeclaration, + >(fnDecl: T): T { if (!fnDecl.body) { // Two cases: abstract methods and overloaded methods/functions. // Abstract methods are handled in emitTypeAnnotationsHandler. @@ -639,10 +793,12 @@ export function jsdocTransformer( return ts.visitEachChild(fnDecl, visitor, context); } const extraTags = []; - if (hasExportingDecorator(fnDecl, typeChecker)) extraTags.push({tagName: 'export'}); + if (hasExportingDecorator(fnDecl, typeChecker)) { + extraTags.push({tagName: 'export'}); + } const {tags, thisReturnType} = - moduleTypeTranslator.getFunctionTypeJSDoc([fnDecl], extraTags); + moduleTypeTranslator.getFunctionTypeJSDoc([fnDecl], extraTags); // async functions when down-leveled access `this` to pass it to // tslib.__awaiter. Closure wants to know the type of 'this' for that. @@ -654,10 +810,16 @@ export function jsdocTransformer( // suppress in that case. We do so by stuffing a @this on any function // where it might be needed; it's harmless to overapproximate. const isDownlevellingAsync = - tsOptions.target !== undefined && tsOptions.target <= ts.ScriptTarget.ES2018; + tsOptions.target !== undefined && + tsOptions.target <= ts.ScriptTarget.ES2018; const isFunction = fnDecl.kind === ts.SyntaxKind.FunctionDeclaration; - const hasExistingThisTag = tags.some(t => t.tagName === 'this'); - if (isDownlevellingAsync && isFunction && !hasExistingThisTag && containsAsync(fnDecl)) { + const hasExistingThisTag = tags.some((t) => t.tagName === 'this'); + if ( + isDownlevellingAsync && + isFunction && + !hasExistingThisTag && + containsAsync(fnDecl) + ) { tags.push({tagName: 'this', type: '*'}); } const mjsdoc = moduleTypeTranslator.getMutableJSDoc(fnDecl); @@ -687,28 +849,43 @@ export function jsdocTransformer( updatedParams.push(param); continue; } - const updatedParamName = - renameArrayBindings(param.name, bindingAliases); + const updatedParamName = renameArrayBindings( + param.name, + bindingAliases, + ); if (!updatedParamName) { updatedParams.push(param); continue; } hasUpdatedParams = true; - updatedParams.push(ts.factory.updateParameterDeclaration( - param, param.modifiers, param.dotDotDotToken, updatedParamName, - param.questionToken, param.type, param.initializer)); + updatedParams.push( + ts.factory.updateParameterDeclaration( + param, + param.modifiers, + param.dotDotDotToken, + updatedParamName, + param.questionToken, + param.type, + param.initializer, + ), + ); } if (!hasUpdatedParams || bindingAliases.length === 0) return fnDecl; let body = fnDecl.body; - const stmts: ts.Statement[] = - createArrayBindingAliases(ts.NodeFlags.Let, bindingAliases); + const stmts: ts.Statement[] = createArrayBindingAliases( + ts.NodeFlags.Let, + bindingAliases, + ); if (!ts.isBlock(body)) { - stmts.push(ts.factory.createReturnStatement( + stmts.push( + ts.factory.createReturnStatement( // Use ( parens ) to protect the return statement against // automatic semicolon insertion. - ts.factory.createParenthesizedExpression(body))); + ts.factory.createParenthesizedExpression(body), + ), + ); body = ts.factory.createBlock(stmts, true); } else { stmts.push(...body.statements); @@ -718,44 +895,80 @@ export function jsdocTransformer( switch (fnDecl.kind) { case ts.SyntaxKind.FunctionDeclaration: fnDecl = ts.factory.updateFunctionDeclaration( - fnDecl, fnDecl.modifiers, fnDecl.asteriskToken, - fnDecl.name, fnDecl.typeParameters, updatedParams, - fnDecl.type, body) as T; + fnDecl, + fnDecl.modifiers, + fnDecl.asteriskToken, + fnDecl.name, + fnDecl.typeParameters, + updatedParams, + fnDecl.type, + body, + ) as T; break; case ts.SyntaxKind.MethodDeclaration: - fnDecl = - ts.factory.updateMethodDeclaration( - fnDecl, fnDecl.modifiers, fnDecl.asteriskToken, fnDecl.name, - fnDecl.questionToken, fnDecl.typeParameters, updatedParams, - fnDecl.type, body) as T; + fnDecl = ts.factory.updateMethodDeclaration( + fnDecl, + fnDecl.modifiers, + fnDecl.asteriskToken, + fnDecl.name, + fnDecl.questionToken, + fnDecl.typeParameters, + updatedParams, + fnDecl.type, + body, + ) as T; break; case ts.SyntaxKind.SetAccessor: fnDecl = ts.factory.updateSetAccessorDeclaration( - fnDecl, fnDecl.modifiers, fnDecl.name, updatedParams, - body) as T; + fnDecl, + fnDecl.modifiers, + fnDecl.name, + updatedParams, + body, + ) as T; break; case ts.SyntaxKind.Constructor: fnDecl = ts.factory.updateConstructorDeclaration( - fnDecl, fnDecl.modifiers, updatedParams, body) as T; + fnDecl, + fnDecl.modifiers, + updatedParams, + body, + ) as T; break; case ts.SyntaxKind.FunctionExpression: fnDecl = ts.factory.updateFunctionExpression( - fnDecl, fnDecl.modifiers, fnDecl.asteriskToken, - fnDecl.name, fnDecl.typeParameters, updatedParams, - fnDecl.type, body) as T; + fnDecl, + fnDecl.modifiers, + fnDecl.asteriskToken, + fnDecl.name, + fnDecl.typeParameters, + updatedParams, + fnDecl.type, + body, + ) as T; break; case ts.SyntaxKind.ArrowFunction: fnDecl = ts.factory.updateArrowFunction( - fnDecl, fnDecl.modifiers, fnDecl.name, updatedParams, - fnDecl.type, fnDecl.equalsGreaterThanToken, body) as T; + fnDecl, + fnDecl.modifiers, + fnDecl.name, + updatedParams, + fnDecl.type, + fnDecl.equalsGreaterThanToken, + body, + ) as T; break; case ts.SyntaxKind.GetAccessor: moduleTypeTranslator.error( - fnDecl, `get accessors cannot have parameters`); + fnDecl, + `get accessors cannot have parameters`, + ); break; default: moduleTypeTranslator.error( - fnDecl, `unexpected function like declaration`); + fnDecl, + `unexpected function like declaration`, + ); break; } return fnDecl; @@ -776,22 +989,31 @@ export function jsdocTransformer( * b;`), and attaches JSDoc comments to each variable. JSDoc comments preceding the * original variable are attached to the first newly created one. */ - function visitVariableStatement(varStmt: ts.VariableStatement): ts.Statement[] { + function visitVariableStatement( + varStmt: ts.VariableStatement, + ): ts.Statement[] { const stmts: ts.Statement[] = []; // "const", "let", etc are stored in node flags on the declarationList. const flags = ts.getCombinedNodeFlags(varStmt.declarationList); - let tags: jsdoc.Tag[]|null = - moduleTypeTranslator.getJSDoc(varStmt, /* reportWarnings */ true); + let tags: jsdoc.Tag[] | null = moduleTypeTranslator.getJSDoc( + varStmt, + /* reportWarnings */ true, + ); const leading = ts.getSyntheticLeadingComments(varStmt); if (leading) { // Attach non-JSDoc comments to a not emitted statement. const commentHolder = ts.factory.createNotEmittedStatement(varStmt); - ts.setSyntheticLeadingComments(commentHolder, leading.filter(c => c.text[0] !== '*')); + ts.setSyntheticLeadingComments( + commentHolder, + leading.filter((c) => c.text[0] !== '*'), + ); stmts.push(commentHolder); } - + const isExported = varStmt.modifiers?.some( + (modifier) => modifier.kind === ts.SyntaxKind.ExportKeyword, + ); for (const decl of varStmt.declarationList.declarations) { const localTags: jsdoc.Tag[] = []; if (tags) { @@ -809,13 +1031,18 @@ export function jsdocTransformer( // TODO(martinprobst): consider doing this for all types that get emitted as ?, not just // for marked ones. const initializersMarkedAsUnknown = - !!decl.initializer && moduleTypeTranslator.isAlwaysUnknownSymbol(decl); - if (!initializersMarkedAsUnknown && - // No JSDoc needed for assigning a class expression to a var. - decl.initializer?.kind !== ts.SyntaxKind.ClassExpression) { + !!decl.initializer && + moduleTypeTranslator.isAlwaysUnknownSymbol(decl); + if ( + !initializersMarkedAsUnknown && + // No JSDoc needed for assigning a class expression to a var. + decl.initializer?.kind !== ts.SyntaxKind.ClassExpression + ) { const typeStr = moduleTypeTranslator.typeToClosure(decl); // If @define is present then add the type to it, rather than adding a normal @type. - const defineTag = localTags.find(({tagName}) => tagName === 'define'); + const defineTag = localTags.find( + ({tagName}) => tagName === 'define', + ); if (defineTag) { defineTag.type = typeStr; } else { @@ -826,34 +1053,53 @@ export function jsdocTransformer( const aliases: Array<[ts.Identifier, ts.Identifier]> = []; const updatedBinding = renameArrayBindings(decl.name, aliases); if (updatedBinding && aliases.length > 0) { - const declVisited = - // TODO: go/ts50upgrade - Remove after upgrade. - // tslint:disable-next-line:no-unnecessary-type-assertion - ts.visitNode(decl, visitor, ts.isVariableDeclaration)!; + const declVisited = ts.visitNode( + decl, + visitor, + ts.isVariableDeclaration, + )!; const newDecl = ts.factory.updateVariableDeclaration( - declVisited, updatedBinding, declVisited.exclamationToken, - declVisited.type, declVisited.initializer); + declVisited, + updatedBinding, + declVisited.exclamationToken, + declVisited.type, + declVisited.initializer, + ); const newStmt = ts.factory.createVariableStatement( - varStmt.modifiers, - ts.factory.createVariableDeclarationList([newDecl], flags)); + varStmt.modifiers?.filter( + (modifier) => modifier.kind !== ts.SyntaxKind.ExportKeyword, + ), + ts.factory.createVariableDeclarationList([newDecl], flags), + ); if (localTags.length) { addCommentOn( - newStmt, localTags, jsdoc.TAGS_CONFLICTING_WITH_TYPE); + newStmt, + localTags, + jsdoc.TAGS_CONFLICTING_WITH_TYPE, + ); } stmts.push(newStmt); - stmts.push(...createArrayBindingAliases( - varStmt.declarationList.flags, aliases)); + stmts.push( + ...createArrayBindingAliases( + varStmt.declarationList.flags, + aliases, + isExported, + ), + ); continue; } } - const newDecl = - // TODO: go/ts50upgrade - Remove after upgrade. - // tslint:disable-next-line:no-unnecessary-type-assertion - ts.visitNode(decl, visitor, ts.isVariableDeclaration)!; + const newDecl = ts.setEmitFlags( + ts.visitNode(decl, visitor, ts.isVariableDeclaration)!, + ts.EmitFlags.NoComments, + ); const newStmt = ts.factory.createVariableStatement( - varStmt.modifiers, - ts.factory.createVariableDeclarationList([newDecl], flags)); - if (localTags.length) addCommentOn(newStmt, localTags, jsdoc.TAGS_CONFLICTING_WITH_TYPE); + varStmt.modifiers, + ts.factory.createVariableDeclarationList([newDecl], flags), + ); + if (localTags.length) { + addCommentOn(newStmt, localTags, jsdoc.TAGS_CONFLICTING_WITH_TYPE); + } stmts.push(newStmt); } @@ -878,8 +1124,12 @@ export function jsdocTransformer( return tsOptions.module === ts.ModuleKind.CommonJS; } - function visitTypeAliasDeclaration(typeAlias: ts.TypeAliasDeclaration): ts.Statement[] { - const sym = moduleTypeTranslator.mustGetSymbolAtLocation(typeAlias.name); + function visitTypeAliasDeclaration( + typeAlias: ts.TypeAliasDeclaration, + ): ts.Statement[] { + const sym = moduleTypeTranslator.mustGetSymbolAtLocation( + typeAlias.name, + ); // If the type is also defined as a value, skip emitting it. Closure collapses type & value // namespaces, the two emits would conflict if tsickle emitted both. if (symbolIsValue(typeChecker, sym)) return []; @@ -889,18 +1139,27 @@ export function jsdocTransformer( // Set any type parameters as unknown, Closure does not support type aliases with type // parameters. - moduleTypeTranslator.newTypeTranslator(typeAlias).markTypeParameterAsUnknown( - moduleTypeTranslator.symbolsToAliasedNames, typeAlias.typeParameters); - const typeStr = - host.untyped ? '?' : moduleTypeTranslator.typeToClosure(typeAlias, undefined); + moduleTypeTranslator + .newTypeTranslator(typeAlias) + .markTypeParameterAsUnknown( + moduleTypeTranslator.symbolsToAliasedNames, + typeAlias.typeParameters, + ); + const typeStr = host.untyped + ? '?' + : moduleTypeTranslator.typeToClosure(typeAlias, undefined); // We want to emit a @typedef. They are a bit weird because they are 'var' statements // that have no value. - const tags = moduleTypeTranslator.getJSDoc(typeAlias, /* reportWarnings */ true); + const tags = moduleTypeTranslator.getJSDoc( + typeAlias, + /* reportWarnings */ true, + ); tags.push({tagName: 'typedef', type: typeStr}); - let propertyBase: string|null = null; - if (transformerUtil.hasModifierFlag( - typeAlias, ts.ModifierFlags.Export)) { + let propertyBase: string | null = null; + if ( + transformerUtil.hasModifierFlag(typeAlias, ts.ModifierFlags.Export) + ) { // Given: export type T = ...; // We cannot emit `export var foo;` and let TS generate from there // because TypeScript drops exports that are never assigned values, @@ -915,9 +1174,11 @@ export function jsdocTransformer( propertyBase = 'exports'; } const ns = transformerUtil.getTransformedNs(typeAlias); - if (ns !== null && - (ts.getOriginalNode(typeAlias).parent?.parent === ns) && - ts.isIdentifier(ns.name)) { + if ( + ns !== null && + ts.getOriginalNode(typeAlias).parent?.parent === ns && + ts.isIdentifier(ns.name) + ) { // If the type alias T is defined at the top level of a transformed // merged namespace, generate the type alias as a propery of the // merged namespace: ns.T @@ -926,19 +1187,24 @@ export function jsdocTransformer( let decl: ts.Statement; if (propertyBase !== null) { decl = ts.factory.createExpressionStatement( - ts.factory.createPropertyAccessExpression( - ts.factory.createIdentifier(propertyBase), - ts.factory.createIdentifier(typeName))); + ts.factory.createPropertyAccessExpression( + ts.factory.createIdentifier(propertyBase), + ts.factory.createIdentifier(typeName), + ), + ); } else { // Given: type T = ...; // We produce: var T; // Note: not const, because 'const Foo;' is illegal; // not let, because we want hoisting behavior for types. decl = ts.factory.createVariableStatement( - /* modifiers */ undefined, - ts.factory.createVariableDeclarationList( - [ts.factory.createVariableDeclaration( - ts.factory.createIdentifier(typeName))])); + /* modifiers */ undefined, + ts.factory.createVariableDeclarationList([ + ts.factory.createVariableDeclaration( + ts.factory.createIdentifier(typeName), + ), + ]), + ); } decl = ts.setSourceMapRange(decl, typeAlias); addCommentOn(decl, tags, jsdoc.TAGS_CONFLICTING_WITH_TYPE); @@ -946,19 +1212,33 @@ export function jsdocTransformer( } /** Emits a parenthesized Closure cast: `(/** \@type ... * / (expr))`. */ - function createClosureCast(context: ts.Node, expression: ts.Expression, type: ts.Type) { + function createClosureCast( + context: ts.Node, + expression: ts.Expression, + type: ts.Type, + ) { const inner = ts.factory.createParenthesizedExpression(expression); - const comment = addCommentOn( - inner, [{tagName: 'type', type: moduleTypeTranslator.typeToClosure(context, type)}]); + const comment = addCommentOn(inner, [ + { + tagName: 'type', + type: moduleTypeTranslator.typeToClosure(context, type), + }, + ]); comment.hasTrailingNewLine = false; return ts.setSourceMapRange( - ts.factory.createParenthesizedExpression(inner), context); + ts.factory.createParenthesizedExpression(inner), + context, + ); } /** Converts a TypeScript type assertion into a Closure Cast. */ function visitAssertionExpression(assertion: ts.AssertionExpression) { const type = typeChecker.getTypeAtLocation(assertion.type); - return createClosureCast(assertion, ts.visitEachChild(assertion, visitor, context), type); + return createClosureCast( + assertion, + ts.visitEachChild(assertion, visitor, context), + type, + ); } /** @@ -984,10 +1264,13 @@ export function jsdocTransformer( const type = typeChecker.getTypeAtLocation(nonNull.expression); const nonNullType = typeChecker.getNonNullableType(type); return createClosureCast( - nonNull, ts.visitEachChild(nonNull, visitor, context), nonNullType); + nonNull, + ts.visitEachChild(nonNull, visitor, context), + nonNullType, + ); } - function getNarrowedType(node: ts.Expression): ts.Type|undefined { + function getNarrowedType(node: ts.Expression): ts.Type | undefined { // Don't support narrowing of `this`, `super` and module/class/interface // declarations. JSCompiler doesn't support casts in all locations and // they are rarely used in type guards in practice. @@ -995,12 +1278,16 @@ export function jsdocTransformer( if (node.kind === ts.SyntaxKind.ThisKeyword) return undefined; const symbol = typeChecker.getSymbolAtLocation(node); - if (symbol?.declarations === undefined || - symbol.declarations.length === 0 || - symbol.declarations.some( - (decl) => ts.isClassDeclaration(decl) || - ts.isInterfaceDeclaration(decl) || - ts.isModuleDeclaration(decl))) { + if ( + symbol?.declarations === undefined || + symbol.declarations.length === 0 || + symbol.declarations.some( + (decl) => + ts.isClassDeclaration(decl) || + ts.isInterfaceDeclaration(decl) || + ts.isModuleDeclaration(decl), + ) + ) { return undefined; } @@ -1008,12 +1295,15 @@ export function jsdocTransformer( const notNullableType = typeChecker.getNonNullableType(typeAtUsage); for (const decl of symbol.declarations) { - const declaredType = - typeChecker.getTypeOfSymbolAtLocation(symbol, decl); - if (typeAtUsage !== declaredType && - notNullableType !== - typeChecker.getNonNullableType(declaredType) && - moduleTypeTranslator.typeToClosure(node, typeAtUsage) !== '?') { + const declaredType = typeChecker.getTypeOfSymbolAtLocation( + symbol, + decl, + ); + if ( + typeAtUsage !== declaredType && + notNullableType !== typeChecker.getNonNullableType(declaredType) && + moduleTypeTranslator.typeToClosure(node, typeAtUsage) !== '?' + ) { return typeAtUsage; } } @@ -1021,7 +1311,8 @@ export function jsdocTransformer( } function visitPropertyAccessExpression( - node: ts.PropertyAccessExpression) { + node: ts.PropertyAccessExpression, + ) { // Do not emit narrowing casts if it's disabled in current context (e.g. // in deletion expressions) or if the node contains `?.` (see comment in // visitNonNullExpression() why we can't emit casts there). @@ -1041,13 +1332,15 @@ export function jsdocTransformer( return ts.visitEachChild(node, visitor, context); } const propertyAccessWithCast = - ts.factory.updatePropertyAccessExpression( - node, - createClosureCast( - node.expression, - ts.visitEachChild(node.expression, visitor, context), - objType), - node.name); + ts.factory.updatePropertyAccessExpression( + node, + createClosureCast( + node.expression, + ts.visitEachChild(node.expression, visitor, context), + objType, + ), + node.name, + ); const propType = getNarrowedType(node); if (propType === undefined) { @@ -1081,12 +1374,15 @@ export function jsdocTransformer( // side-effect imports. if (!sym) return importDecl; - const importPath = - (importDecl.moduleSpecifier as ts.StringLiteral).text; + const importPath = (importDecl.moduleSpecifier as ts.StringLiteral) + .text; moduleTypeTranslator.requireType( - importDecl.moduleSpecifier, importPath, sym, - /* default import? */ !!importDecl.importClause.name); + importDecl.moduleSpecifier, + importPath, + sym, + /* default import? */ !!importDecl.importClause.name, + ); return importDecl; } @@ -1116,8 +1412,12 @@ export function jsdocTransformer( // Note: We create explicit exports of type symbols for closure in visitExportDeclaration. return false; } - if (!tsOptions.preserveConstEnums && sym.flags & ts.SymbolFlags.ConstEnum) { - return false; + if (sym.flags & ts.SymbolFlags.ConstEnum) { + if (tsOptions.preserveConstEnums) { + return !sym.valueDeclaration!.getSourceFile().isDeclarationFile; + } else { + return false; + } } return true; } @@ -1126,16 +1426,21 @@ export function jsdocTransformer( * visitExportDeclaration requireTypes exported modules and emits explicit exports for * types (which normally do not get emitted by TypeScript). */ - function visitExportDeclaration(exportDecl: ts.ExportDeclaration): ts.Node|ts.Node[] { - const importedModuleSymbol = exportDecl.moduleSpecifier && - typeChecker.getSymbolAtLocation(exportDecl.moduleSpecifier)!; + function visitExportDeclaration( + exportDecl: ts.ExportDeclaration, + ): ts.Node | ts.Node[] { + const importedModuleSymbol = + exportDecl.moduleSpecifier && + typeChecker.getSymbolAtLocation(exportDecl.moduleSpecifier)!; if (importedModuleSymbol) { // requireType all explicitly imported modules, so that symbols can be referenced and // type only modules are usable from type declarations. moduleTypeTranslator.requireType( - exportDecl.moduleSpecifier!, (exportDecl.moduleSpecifier as ts.StringLiteral).text, - importedModuleSymbol, - /* default import? */ false); + exportDecl.moduleSpecifier!, + (exportDecl.moduleSpecifier as ts.StringLiteral).text, + importedModuleSymbol, + /* default import? */ false, + ); } const typesToExport: Array<[string, ts.Symbol]> = []; @@ -1145,40 +1450,62 @@ export function jsdocTransformer( // Explicitly spelled out exports (i.e. the exports of the current module) take precedence // over implicit ones from export *. Use the current module's exports to filter. - const currentModuleSymbol = typeChecker.getSymbolAtLocation(sourceFile); - const currentModuleExports = currentModuleSymbol && currentModuleSymbol.exports; + const currentModuleSymbol = + typeChecker.getSymbolAtLocation(sourceFile); + const currentModuleExports = + currentModuleSymbol && currentModuleSymbol.exports; if (!importedModuleSymbol) { - moduleTypeTranslator.error(exportDecl, `export * without module symbol`); + moduleTypeTranslator.error( + exportDecl, + `export * without module symbol`, + ); return exportDecl; } - const exportedSymbols = typeChecker.getExportsOfModule(importedModuleSymbol); + const exportedSymbols = + typeChecker.getExportsOfModule(importedModuleSymbol); const exportSpecifiers: ts.ExportSpecifier[] = []; for (const sym of exportedSymbols) { - if (currentModuleExports && currentModuleExports.has(sym.escapedName)) continue; + if ( + currentModuleExports && + currentModuleExports.has(sym.escapedName) + ) { + continue; + } // We might have already generated an export for the given symbol. if (expandedStarImports.has(sym.name)) continue; expandedStarImports.add(sym.name); // Only create an export specifier for values that are exported. For types, the code // below creates specific export statements that match Closure's expectations. if (shouldEmitValueExportForSymbol(sym)) { - exportSpecifiers.push(ts.factory.createExportSpecifier( - /* isTypeOnly */ false, undefined, sym.name)); + exportSpecifiers.push( + ts.factory.createExportSpecifier( + /* isTypeOnly */ false, + undefined, + sym.name, + ), + ); } else { typesToExport.push([sym.name, sym]); } } const isTypeOnlyExport = false; exportDecl = ts.factory.updateExportDeclaration( - exportDecl, exportDecl.modifiers, isTypeOnlyExport, - ts.factory.createNamedExports(exportSpecifiers), - exportDecl.moduleSpecifier, exportDecl.assertClause); + exportDecl, + exportDecl.modifiers, + isTypeOnlyExport, + ts.factory.createNamedExports(exportSpecifiers), + exportDecl.moduleSpecifier, + exportDecl.attributes, + ); } else if (ts.isNamedExports(exportDecl.exportClause)) { // export {a, b, c} from 'abc'; for (const exp of exportDecl.exportClause.elements) { const exportedName = transformerUtil.getIdentifierText(exp.name); - typesToExport.push( - [exportedName, moduleTypeTranslator.mustGetSymbolAtLocation(exp.name)]); + typesToExport.push([ + exportedName, + moduleTypeTranslator.mustGetSymbolAtLocation(exp.name), + ]); } } // Do not emit typedef re-exports in untyped mode. @@ -1190,17 +1517,30 @@ export function jsdocTransformer( if (sym.flags & ts.SymbolFlags.Alias) { aliasedSymbol = typeChecker.getAliasedSymbol(sym); } - const isTypeAlias = (aliasedSymbol.flags & ts.SymbolFlags.Value) === 0 && - (aliasedSymbol.flags & (ts.SymbolFlags.TypeAlias | ts.SymbolFlags.Interface)) !== 0; - if (!isTypeAlias) continue; + const isTypeAlias = + (aliasedSymbol.flags & ts.SymbolFlags.Value) === 0 && + (aliasedSymbol.flags & + (ts.SymbolFlags.TypeAlias | ts.SymbolFlags.Interface)) !== + 0; + const isConstEnum = + (aliasedSymbol.flags & ts.SymbolFlags.ConstEnum) !== 0; + if (!isTypeAlias && !isConstEnum) continue; const typeName = - moduleTypeTranslator.symbolsToAliasedNames.get(aliasedSymbol) || aliasedSymbol.name; + moduleTypeTranslator.symbolsToAliasedNames.get(aliasedSymbol) || + aliasedSymbol.name; const stmt = ts.factory.createExpressionStatement( - ts.factory.createPropertyAccessExpression( - ts.factory.createIdentifier('exports'), exportedName)); + ts.factory.createPropertyAccessExpression( + ts.factory.createIdentifier('exports'), + exportedName, + ), + ); addCommentOn(stmt, [{tagName: 'typedef', type: '!' + typeName}]); ts.addSyntheticTrailingComment( - stmt, ts.SyntaxKind.SingleLineCommentTrivia, ' re-export typedef', true); + stmt, + ts.SyntaxKind.SingleLineCommentTrivia, + ' re-export typedef', + true, + ); result.push(stmt); } return result; @@ -1214,7 +1554,9 @@ export function jsdocTransformer( switch (node.kind) { case ts.SyntaxKind.VariableStatement: const varDecl = node as ts.VariableStatement; - return varDecl.declarationList.declarations.map((d) => getExportDeclarationNames(d)[0]); + return varDecl.declarationList.declarations.map( + (d) => getExportDeclarationNames(d)[0], + ); case ts.SyntaxKind.VariableDeclaration: case ts.SyntaxKind.FunctionDeclaration: case ts.SyntaxKind.InterfaceDeclaration: @@ -1233,7 +1575,11 @@ export function jsdocTransformer( break; } moduleTypeTranslator.error( - node, `unsupported export declaration ${ts.SyntaxKind[node.kind]}: ${node.getText()}`); + node, + `unsupported export declaration ${ + ts.SyntaxKind[node.kind] + }: ${node.getText()}`, + ); return []; } @@ -1260,28 +1606,26 @@ export function jsdocTransformer( // Ambient ModuleDeclarations are always referenced as global symbols, so they don't // need to be exported. if (node.kind === ts.SyntaxKind.ModuleDeclaration) continue; - const mangledName = moduleNameAsIdentifier(host, sourceFile.fileName); + const mangledName = moduleNameAsIdentifier( + host, + sourceFile.fileName, + ); const declName = transformerUtil.getIdentifierText(decl); const stmt = ts.factory.createExpressionStatement( - ts.factory.createPropertyAccessExpression( - ts.factory.createIdentifier('exports'), declName)); - addCommentOn(stmt, [{tagName: 'typedef', type: `!${mangledName}.${declName}`}]); + ts.factory.createPropertyAccessExpression( + ts.factory.createIdentifier('exports'), + declName, + ), + ); + addCommentOn(stmt, [ + {tagName: 'typedef', type: `!${mangledName}.${declName}`}, + ]); result.push(stmt); } } return result; } - /** - * Visits enum declarations to check for validity of JSDoc comments without transforming the - * node at all. - */ - function visitEnumDeclaration(node: ts.EnumDeclaration) { - // Calling `getJSDoc` will validate and report any errors, but this code - // doesn't really care about the return value. - moduleTypeTranslator.getJSDoc(node, /* reportWarnings */ true); - } - /** * Counter to generate (reasonably) unique alias names for array * rebindings. @@ -1294,16 +1638,16 @@ export function jsdocTransformer( * but does not support nested object patterns. */ function renameArrayBindings( - node: ts.ArrayBindingPattern, - aliases: Array<[ts.Identifier, ts.Identifier]>): - ts.ArrayBindingPattern|undefined { + node: ts.ArrayBindingPattern, + aliases: Array<[ts.Identifier, ts.Identifier]>, + ): ts.ArrayBindingPattern | undefined { const updatedElements: ts.ArrayBindingElement[] = []; for (const e of node.elements) { if (ts.isOmittedExpression(e)) { updatedElements.push(e); continue; } else if (ts.isObjectBindingPattern(e.name)) { - return undefined; // object binding patterns are unsupported + return undefined; // object binding patterns are unsupported } let updatedBindingName; if (ts.isArrayBindingPattern(e.name)) { @@ -1314,17 +1658,20 @@ export function jsdocTransformer( } else { // Plain identifier. const aliasName = ts.factory.createIdentifier( - `${e.name.text}__tsickle_destructured_${aliasCounter++}`); + `${e.name.text}__tsickle_destructured_${aliasCounter++}`, + ); aliases.push([e.name, aliasName]); updatedBindingName = aliasName; } - updatedElements.push(ts.factory.updateBindingElement( - e, e.dotDotDotToken, + updatedElements.push( + ts.factory.updateBindingElement( + e, + e.dotDotDotToken, ts.visitNode(e.propertyName, visitor, ts.isPropertyName), updatedBindingName, - // TODO: go/ts50upgrade - Remove after upgrade. - // tslint:disable-next-line:no-unnecessary-type-assertion - ts.visitNode(e.initializer, visitor) as ts.Expression)); + ts.visitNode(e.initializer, visitor) as ts.Expression, + ), + ); } return ts.factory.updateArrayBindingPattern(node, updatedElements); } @@ -1336,25 +1683,40 @@ export function jsdocTransformer( * controls const/let/var in particular. */ function createArrayBindingAliases( - flags: ts.NodeFlags, - aliases: Array<[ts.Identifier, ts.Identifier]>): ts.Statement[] { + flags: ts.NodeFlags, + aliases: Array<[ts.Identifier, ts.Identifier]>, + needsExport = false, + ): ts.Statement[] { const aliasDecls: ts.Statement[] = []; for (const [oldName, aliasName] of aliases) { - const typeStr = - moduleTypeTranslator.typeToClosure(ts.getOriginalNode(oldName)); + const typeStr = moduleTypeTranslator.typeToClosure( + ts.getOriginalNode(oldName), + ); const closureCastExpr = - ts.factory.createParenthesizedExpression(aliasName); + ts.factory.createParenthesizedExpression(aliasName); addCommentOn( - closureCastExpr, [{tagName: 'type', type: typeStr}], - /* escape tags */ undefined, - /* hasTrailingNewLine */ false); + closureCastExpr, + [{tagName: 'type', type: typeStr}], + /* escape tags */ undefined, + /* hasTrailingNewLine */ false, + ); const varDeclList = ts.factory.createVariableDeclarationList( - [ts.factory.createVariableDeclaration( - oldName, /* exclamationToken? */ undefined, - /* type? */ undefined, closureCastExpr)], - flags); + [ + ts.factory.createVariableDeclaration( + oldName, + /* exclamationToken? */ undefined, + /* type? */ undefined, + closureCastExpr, + ), + ], + flags, + ); const varStmt = ts.factory.createVariableStatement( - /*modifiers*/ undefined, varDeclList); + needsExport + ? [ts.factory.createModifier(ts.SyntaxKind.ExportKeyword)] + : undefined, + varDeclList, + ); aliasDecls.push(varStmt); } return aliasDecls; @@ -1397,38 +1759,48 @@ export function jsdocTransformer( } const updatedInitializer = ts.factory.updateVariableDeclarationList( - varDecls, [ts.factory.updateVariableDeclaration( - varDecl, updatedPattern, varDecl.exclamationToken, - varDecl.type, varDecl.initializer)]); + varDecls, + [ + ts.factory.updateVariableDeclaration( + varDecl, + updatedPattern, + varDecl.exclamationToken, + varDecl.type, + varDecl.initializer, + ), + ], + ); const aliasDecls = createArrayBindingAliases(varDecls.flags, aliases); // Convert the for/of body into a block, if needed. let updatedStatement; if (ts.isBlock(node.statement)) { updatedStatement = ts.factory.updateBlock(node.statement, [ ...aliasDecls, - // TODO: go/ts50upgrade - Remove after upgrade. - // tslint:disable-next-line:no-unnecessary-type-assertion - ...ts.visitNode(node.statement, visitor, ts.isBlock)!.statements + ...ts.visitNode(node.statement, visitor, ts.isBlock)!.statements, ]); } else { updatedStatement = ts.factory.createBlock([ ...aliasDecls, - // TODO: go/ts50upgrade - Remove after upgrade. - // tslint:disable-next-line:no-unnecessary-type-assertion - ts.visitNode(node.statement, visitor) as ts.Statement + ts.visitNode(node.statement, visitor) as ts.Statement, ]); } return ts.factory.updateForOfStatement( - node, node.awaitModifier, updatedInitializer, - // TODO: go/ts50upgrade - Remove after upgrade. - // tslint:disable-next-line:no-unnecessary-type-assertion - ts.visitNode(node.expression, visitor) as ts.Expression, - updatedStatement); + node, + node.awaitModifier, + updatedInitializer, + ts.visitNode(node.expression, visitor) as ts.Expression, + updatedStatement, + ); } - function visitor(node: ts.Node): ts.Node|ts.Node[] { + function visitor(node: ts.Node): ts.Node | ts.Node[] { if (transformerUtil.isAmbient(node)) { - if (!transformerUtil.hasModifierFlag(node as ts.Declaration, ts.ModifierFlags.Export)) { + if ( + !transformerUtil.hasModifierFlag( + node as ts.Declaration, + ts.ModifierFlags.Export, + ) + ) { return node; } return visitExportedAmbient(node); @@ -1450,14 +1822,18 @@ export function jsdocTransformer( // e.g. if the function below is the expression in a `return` statement. Parenthesizing // prevents ASI, as long as the opening paren remains on the same line (which it does). return ts.factory.createParenthesizedExpression( - visitFunctionLikeDeclaration( - node as ts.ArrowFunction | ts.FunctionExpression)); + visitFunctionLikeDeclaration( + node as ts.ArrowFunction | ts.FunctionExpression, + ), + ); case ts.SyntaxKind.Constructor: case ts.SyntaxKind.FunctionDeclaration: case ts.SyntaxKind.MethodDeclaration: case ts.SyntaxKind.GetAccessor: case ts.SyntaxKind.SetAccessor: - return visitFunctionLikeDeclaration(node as ts.FunctionLikeDeclaration); + return visitFunctionLikeDeclaration( + node as ts.FunctionLikeDeclaration, + ); case ts.SyntaxKind.ThisKeyword: return visitThisExpression(node as ts.ThisExpression); case ts.SyntaxKind.VariableStatement: @@ -1467,6 +1843,7 @@ export function jsdocTransformer( case ts.SyntaxKind.PropertyDeclaration: case ts.SyntaxKind.ModuleDeclaration: case ts.SyntaxKind.EnumMember: + case ts.SyntaxKind.EnumDeclaration: escapeIllegalJSDoc(node); break; case ts.SyntaxKind.Parameter: @@ -1475,8 +1852,12 @@ export function jsdocTransformer( // any comments on them, so that Closure doesn't error on them. // See test_files/parameter_properties.ts. const paramDecl = node as ts.ParameterDeclaration; - if (transformerUtil.hasModifierFlag( - paramDecl, ts.ModifierFlags.ParameterPropertyModifier)) { + if ( + transformerUtil.hasModifierFlag( + paramDecl, + ts.ModifierFlags.ParameterPropertyModifier, + ) + ) { ts.setSyntheticLeadingComments(paramDecl, []); jsdoc.suppressLeadingCommentsRecursively(paramDecl); } @@ -1490,10 +1871,8 @@ export function jsdocTransformer( return visitNonNullExpression(node as ts.NonNullExpression); case ts.SyntaxKind.PropertyAccessExpression: return visitPropertyAccessExpression( - node as ts.PropertyAccessExpression); - case ts.SyntaxKind.EnumDeclaration: - visitEnumDeclaration(node as ts.EnumDeclaration); - break; + node as ts.PropertyAccessExpression, + ); case ts.SyntaxKind.ForOfStatement: return visitForOfStatement(node as ts.ForOfStatement); case ts.SyntaxKind.DeleteExpression: diff --git a/src/module_type_translator.ts b/src/module_type_translator.ts index 151347dac..5a9ef9eba 100644 --- a/src/module_type_translator.ts +++ b/src/module_type_translator.ts @@ -17,7 +17,12 @@ import * as ts from 'typescript'; import {AnnotatorHost, moduleNameAsIdentifier} from './annotator_host'; import * as googmodule from './googmodule'; import * as jsdoc from './jsdoc'; -import {getIdentifierText, hasModifierFlag, reportDebugWarning, reportDiagnostic} from './transformer_util'; +import { + getIdentifierText, + hasModifierFlag, + reportDebugWarning, + reportDiagnostic, +} from './transformer_util'; import * as typeTranslator from './type_translator'; /** @@ -27,8 +32,9 @@ declare interface SymbolWithParent extends ts.Symbol { parent?: SymbolWithParent; } -function getDefinedModule(symbol: SymbolWithParent|undefined): ts.Symbol| - undefined { +function getDefinedModule( + symbol: SymbolWithParent | undefined, +): ts.Symbol | undefined { while (symbol) { if (symbol.flags & ts.SymbolFlags.Module) { return symbol; @@ -43,7 +49,9 @@ function getDefinedModule(symbol: SymbolWithParent|undefined): ts.Symbol| * destructuring. */ function getParameterName( - param: ts.ParameterDeclaration, index: number): string { + param: ts.ParameterDeclaration, + index: number, +): string { switch (param.name.kind) { case ts.SyntaxKind.Identifier: let name = getIdentifierText(param.name); @@ -61,8 +69,9 @@ function getParameterName( // The above list of kinds is exhaustive. param.name is 'never' at this // point. const paramName = param.name as ts.Node; - throw new Error(`unhandled function parameter kind: ${ - ts.SyntaxKind[paramName.kind]}`); + throw new Error( + `unhandled function parameter kind: ${ts.SyntaxKind[paramName.kind]}`, + ); } } @@ -100,17 +109,17 @@ export class ModuleTypeTranslator { private readonly additionalImports: ts.Statement[] = []; constructor( - readonly sourceFile: ts.SourceFile, - readonly typeChecker: ts.TypeChecker, - private readonly host: AnnotatorHost&googmodule.GoogModuleProcessorHost, - private readonly diagnostics: ts.Diagnostic[], - private readonly isForExterns: boolean, - private readonly useInternalNamespaceForExterns = false, + readonly sourceFile: ts.SourceFile, + readonly typeChecker: ts.TypeChecker, + private readonly host: AnnotatorHost & googmodule.GoogModuleProcessorHost, + private readonly diagnostics: ts.Diagnostic[], + private readonly isForExterns: boolean, + private readonly useInternalNamespaceForExterns = false, ) { // TODO: remove once AnnotatorHost.typeBlackListPaths is removed. this.host.unknownTypesPaths = - // tslint:disable-next-line:deprecation - this.host.unknownTypesPaths ?? this.host.typeBlackListPaths; + // tslint:disable-next-line:deprecation + this.host.unknownTypesPaths ?? this.host.typeBlackListPaths; } debugWarn(context: ts.Node, messageText: string) { @@ -143,14 +152,15 @@ export class ModuleTypeTranslator { try { return this.newTypeTranslator(context).translate(type); } catch (e: unknown) { - if (!(e instanceof Error)) throw e; // should not happen (tm) + if (!(e instanceof Error)) throw e; // should not happen (tm) const sourceFile = context.getSourceFile(); - const {line, character} = context.pos !== -1 ? - sourceFile.getLineAndCharacterOfPosition(context.pos) : - {line: 0, character: 0}; - e.message = `internal error converting type at ${sourceFile.fileName}:${ - line}:${character}:\n\n` + - e.message; + const {line, character} = + context.pos !== -1 + ? sourceFile.getLineAndCharacterOfPosition(context.pos) + : {line: 0, character: 0}; + e.message = + `internal error converting type at ${sourceFile.fileName}:${line}:${character}:\n\n` + + e.message; throw e; } } @@ -161,14 +171,18 @@ export class ModuleTypeTranslator { const translationContext = this.isForExterns ? this.sourceFile : context; const translator = new typeTranslator.TypeTranslator( - this.host, this.typeChecker, translationContext, - this.host.unknownTypesPaths || new Set(), this.symbolsToAliasedNames, - this.symbolToNameCache, - (sym: ts.Symbol) => void this.ensureSymbolDeclared(sym)); + this.host, + this.typeChecker, + translationContext, + this.host.unknownTypesPaths || new Set(), + this.symbolsToAliasedNames, + this.symbolToNameCache, + (sym: ts.Symbol) => void this.ensureSymbolDeclared(sym), + ); translator.isForExterns = this.isForExterns; translator.useInternalNamespaceForExterns = - this.useInternalNamespaceForExterns; - translator.warn = msg => void this.debugWarn(context, msg); + this.useInternalNamespaceForExterns; + translator.warn = (msg) => void this.debugWarn(context, msg); return translator; } @@ -197,8 +211,10 @@ export class ModuleTypeTranslator { * If the given declaration is an ES module export, returns true and adds a * goog.requireType alias for it in the current file. Otherwise returns false. */ - private addRequireTypeIfIsExported(decl: ts.Declaration, sym: ts.Symbol): - boolean { + private addRequireTypeIfIsExported( + decl: ts.Declaration, + sym: ts.Symbol, + ): boolean { // Check for Export | Default (default being a default export). if (!hasModifierFlag(decl, ts.ModifierFlags.ExportDefault)) return false; // Symbols declared in `declare global {...}` blocks are global and don't @@ -220,10 +236,13 @@ export class ModuleTypeTranslator { if (this.isForExterns) { this.error( - decl, `declaration from module used in ambient type: ${sym.name}`); + decl, + `declaration from module used in ambient type: ${sym.name}`, + ); } else if ( - sourceFile.isDeclarationFile && - !sourceFile.text.match(/^\/\/!! generated by (clutz|tsickle|clutz2)/)) { + sourceFile.isDeclarationFile && + !sourceFile.text.match(/^\/\/!! generated by (clutz|tsickle|clutz2)/) + ) { this.registerExternSymbolAliases(sourceFile.fileName, moduleSymbol); } else { // Actually import the symbol. @@ -238,9 +257,10 @@ export class ModuleTypeTranslator { * context, to make debugging the emitted Closure types a bit easier. */ private generateModulePrefix(importPath: string) { - const modulePrefix = importPath.replace(/(\/index)?(\.d)?\.[tj]sx?$/, '') - .replace(/^.*[/.](.+?)/, '$1') - .replace(/\W/g, '_'); + const modulePrefix = importPath + .replace(/(\/index)?(\.d)?\.[tj]sx?$/, '') + .replace(/^.*[/.](.+?)/, '$1') + .replace(/\W/g, '_'); return `tsickle_${modulePrefix || 'reqType'}_`; } @@ -254,25 +274,44 @@ export class ModuleTypeTranslator { * emit a `.default`. */ requireType( - context: ts.Node, importPath: string, moduleSymbol: ts.Symbol, - isDefaultImport = false) { + context: ts.Node, + importPath: string, + moduleSymbol: ts.Symbol, + isDefaultImport = false, + ) { if (this.host.untyped) return; // Already imported? Do not emit a duplicate requireType. if (this.requireTypeModules.has(moduleSymbol)) return; - if (typeTranslator.isAlwaysUnknownSymbol( - this.host.unknownTypesPaths, moduleSymbol)) { - return; // Do not emit goog.requireType for paths marked as always - // unknown. + if ( + typeTranslator.isAlwaysUnknownSymbol( + this.host.unknownTypesPaths, + moduleSymbol, + ) + ) { + return; // Do not emit goog.requireType for paths marked as always + // unknown. } const nsImport = googmodule.jsPathToNamespace( - this.host, context, this.diagnostics, importPath, () => moduleSymbol); - const requireTypePrefix = this.generateModulePrefix(importPath) + - String(this.requireTypeModules.size + 1); - const moduleNamespace = nsImport != null ? - nsImport : - this.host.pathToModuleName(this.sourceFile.fileName, importPath); - if (googmodule.jsPathToStripProperty( - this.host, importPath, () => moduleSymbol)) { + this.host, + context, + this.diagnostics, + importPath, + () => moduleSymbol, + ); + const requireTypePrefix = + this.generateModulePrefix(importPath) + + String(this.requireTypeModules.size + 1); + const moduleNamespace = + nsImport != null + ? nsImport + : this.host.pathToModuleName(this.sourceFile.fileName, importPath); + if ( + googmodule.jsPathToStripProperty( + this.host, + importPath, + () => moduleSymbol, + ) + ) { // Symbols using import-by-path with strip property should be mapped to a // default import. This makes sure that type annotations get emitted as // "@type {module_alias}", not "@type {module_alias.TheStrippedName}". @@ -286,36 +325,55 @@ export class ModuleTypeTranslator { // goog.requireType types, which allows using them in type annotations // without causing a load. // const requireTypePrefix = goog.requireType(moduleNamespace) - this.additionalImports.push(ts.factory.createVariableStatement( + this.additionalImports.push( + ts.factory.createVariableStatement( undefined, ts.factory.createVariableDeclarationList( - [ts.factory.createVariableDeclaration( - requireTypePrefix, /* exclamationToken */ undefined, - /* type */ undefined, - ts.factory.createCallExpression( - ts.factory.createPropertyAccessExpression( - ts.factory.createIdentifier('goog'), 'requireType'), - undefined, - [ts.factory.createStringLiteral(moduleNamespace)]))], - ts.NodeFlags.Const))); + [ + ts.factory.createVariableDeclaration( + requireTypePrefix, + /* exclamationToken */ undefined, + /* type */ undefined, + ts.factory.createCallExpression( + ts.factory.createPropertyAccessExpression( + ts.factory.createIdentifier('goog'), + 'requireType', + ), + undefined, + [ts.factory.createStringLiteral(moduleNamespace)], + ), + ), + ], + ts.NodeFlags.Const, + ), + ), + ); this.requireTypeModules.add(moduleSymbol); this.registerImportSymbolAliases( - nsImport, isDefaultImport, moduleSymbol, () => requireTypePrefix); + nsImport, + isDefaultImport, + moduleSymbol, + () => requireTypePrefix, + ); this.registerImportTypeSymbolAliases( - nsImport, isDefaultImport, moduleSymbol, requireTypePrefix); + nsImport, + isDefaultImport, + moduleSymbol, + requireTypePrefix, + ); } /** * Get qualified name of a symbol by navigating through its parents. */ private qualifiedNameFromSymbolChain( - leafSymbol: SymbolWithParent, - googNamespace: string|undefined, - isDefaultImport: boolean, - aliasPrefix: string, - namedDefaultImport: boolean, - ): string { + leafSymbol: SymbolWithParent, + googNamespace: string | undefined, + isDefaultImport: boolean, + aliasPrefix: string, + namedDefaultImport: boolean, + ): string { if (googNamespace && (isDefaultImport || namedDefaultImport)) { return aliasPrefix; } @@ -325,8 +383,10 @@ export class ModuleTypeTranslator { // itself. For those cases it is not enough just to append the symbol name // to the aliasPrefix, because we actually need to know the namespace // hierarchy and properly append that to the requireType-ed symbol. - while (typeSymbol.parent && - typeSymbol.parent.flags & ts.SymbolFlags.NamespaceModule) { + while ( + typeSymbol.parent && + typeSymbol.parent.flags & ts.SymbolFlags.NamespaceModule + ) { typeSymbol = typeSymbol.parent; symbols.push(typeSymbol); } @@ -343,8 +403,9 @@ export class ModuleTypeTranslator { aliasResolved = true; continue; } - qualifiedName = - qualifiedName ? qualifiedName + '.' + symbol.name : symbol.name; + qualifiedName = qualifiedName + ? qualifiedName + '.' + symbol.name + : symbol.name; } // parent being undefined indicates that this type is the global scope, // thus no need of alias prefix from `requireType`s. @@ -371,10 +432,14 @@ export class ModuleTypeTranslator { * reuse the results, because it will start to break builds. */ private registerImportTypeSymbolAliases( - googNamespace: string|undefined, isDefaultImport: boolean, - moduleSymbol: ts.Symbol, aliasPrefix: string) { - for (let sym of this.typeChecker.getExportsOfModule(moduleSymbol) as - SymbolWithParent[]) { + googNamespace: string | undefined, + isDefaultImport: boolean, + moduleSymbol: ts.Symbol, + aliasPrefix: string, + ) { + for (let sym of this.typeChecker.getExportsOfModule( + moduleSymbol, + ) as SymbolWithParent[]) { // Some users import {default as SomeAlias} from 'goog:...'; // The code below must recognize this as a default import to alias the // symbol to just the blank module name. @@ -386,23 +451,25 @@ export class ModuleTypeTranslator { // We only put into the cache when it's a class or an interface, because // more complex types (e.g. union) don't map to a single Symbol and // for other simple types it doesn't take much time to generate a string. - const typeSymbol: SymbolWithParent|undefined = - this.getTypeSymbolOfSymbolIfClassOrInterface(sym); + const typeSymbol: SymbolWithParent | undefined = + this.getTypeSymbolOfSymbolIfClassOrInterface(sym); if (!typeSymbol) continue; // In case the type is defined in a different file from the requireType-ed // module, indicating that this is a type alias and thus will either be // covered by other requireType statements, or should have been defined // in the immediate file itself. - if (typeSymbol.parent && - getDefinedModule(sym) !== getDefinedModule(typeSymbol)) { + if ( + typeSymbol.parent && + getDefinedModule(sym) !== getDefinedModule(typeSymbol) + ) { continue; } const qualifiedName = this.qualifiedNameFromSymbolChain( - typeSymbol, - googNamespace, - isDefaultImport, - aliasPrefix, - namedDefaultImport, + typeSymbol, + googNamespace, + isDefaultImport, + aliasPrefix, + namedDefaultImport, ); const cache = this.symbolToNameCache.get(typeSymbol); // Put in shorter symbols, as a proxy of prefering non-aliases. @@ -416,8 +483,9 @@ export class ModuleTypeTranslator { * Returns the symbol of the type for the given symbol, if the type is a class * or an interface. */ - private getTypeSymbolOfSymbolIfClassOrInterface(symbol: ts.Symbol): ts.Symbol - |undefined { + private getTypeSymbolOfSymbolIfClassOrInterface( + symbol: ts.Symbol, + ): ts.Symbol | undefined { const type = this.typeChecker.getDeclaredTypeOfSymbol(symbol); const typeSymbol = type.getSymbol(); if (!typeSymbol) { @@ -427,8 +495,9 @@ export class ModuleTypeTranslator { return undefined; } const objectFlags = (type as ts.ObjectType).objectFlags; - return objectFlags & ts.ObjectFlags.ClassOrInterface ? typeSymbol : - undefined; + return objectFlags & ts.ObjectFlags.ClassOrInterface + ? typeSymbol + : undefined; } /** @@ -445,8 +514,11 @@ export class ModuleTypeTranslator { * exported symbol. The registered alias is .. */ registerImportSymbolAliases( - googNamespace: string|undefined, isDefaultImport: boolean, - moduleSymbol: ts.Symbol, getAliasPrefix: (symbol: ts.Symbol) => string) { + googNamespace: string | undefined, + isDefaultImport: boolean, + moduleSymbol: ts.Symbol, + getAliasPrefix: (symbol: ts.Symbol) => string, + ) { for (let sym of this.typeChecker.getExportsOfModule(moduleSymbol)) { const aliasPrefix = getAliasPrefix(sym); // Some users import {default as SomeAlias} from 'goog:...'; @@ -456,9 +528,9 @@ export class ModuleTypeTranslator { // goog: imports don't actually use the .default property that TS thinks // they have. const qualifiedName = - googNamespace && (isDefaultImport || namedDefaultImport) ? - aliasPrefix : - aliasPrefix + '.' + sym.name; + googNamespace && (isDefaultImport || namedDefaultImport) + ? aliasPrefix + : aliasPrefix + '.' + sym.name; if (sym.flags & ts.SymbolFlags.Alias) { sym = this.typeChecker.getAliasedSymbol(sym); } @@ -473,8 +545,11 @@ export class ModuleTypeTranslator { * user-supplied `.d.ts` files. */ registerExternSymbolAliases(importPath: string, moduleSymbol: ts.Symbol) { - const moduleNamespace = - moduleNameAsIdentifier(this.host, importPath, this.sourceFile.fileName); + const moduleNamespace = moduleNameAsIdentifier( + this.host, + importPath, + this.sourceFile.fileName, + ); for (let sym of this.typeChecker.getExportsOfModule(moduleSymbol)) { // Some users import {default as SomeAlias} from 'goog:...'; // The code below must recognize this as a default import to alias the @@ -510,7 +585,7 @@ export class ModuleTypeTranslator { const declarations = sym.declarations!; // A symbol declared in this file does not need to be imported. const thisSourceFile = ts.getOriginalNode(this.sourceFile); - if (declarations.some(d => d.getSourceFile() === thisSourceFile)) { + if (declarations.some((d) => d.getSourceFile() === thisSourceFile)) { return; } @@ -529,43 +604,50 @@ export class ModuleTypeTranslator { if (!clutzDecl) return; const clutzDts = clutzDecl.getSourceFile(); - const clutzModule = - this.typeChecker.getSymbolsInScope(clutzDts, ts.SymbolFlags.Module) - .find( - (module: ts.Symbol) => module.getName().startsWith('"goog:') && - module.valueDeclaration?.getSourceFile() === clutzDts && - this.typeChecker.getExportsOfModule(module).some( - (exported: ts.Symbol) => { - if (exported.flags & ts.SymbolFlags.Alias) { - exported = - this.typeChecker.getAliasedSymbol(exported); - } - if (exported === sym) { - return true; - } - // In case the symbol is coming from a default export, - // we need to navigate through the child of the - // default nested to compare the symbol. - if (exported.exports) { - let found = false; - exported.exports.forEach((symbol, key) => { - found = found || symbol === sym; - }); - return found; - } - return false; - })); + const clutzModule = this.typeChecker + .getSymbolsInScope(clutzDts, ts.SymbolFlags.Module) + .find( + (module: ts.Symbol) => + module.getName().startsWith('"goog:') && + module.valueDeclaration?.getSourceFile() === clutzDts && + this.typeChecker + .getExportsOfModule(module) + .some((exported: ts.Symbol) => { + if (exported.flags & ts.SymbolFlags.Alias) { + exported = this.typeChecker.getAliasedSymbol(exported); + } + if (exported === sym) { + return true; + } + // In case the symbol is coming from a default export, + // we need to navigate through the child of the + // default nested to compare the symbol. + if (exported.exports) { + let found = false; + exported.exports.forEach((symbol, key) => { + found = found || symbol === sym; + }); + return found; + } + return false; + }), + ); if (clutzModule) { this.requireType( - clutzDecl, clutzModule.getName().slice(1, -1), clutzModule); + clutzDecl, + clutzModule.getName().slice(1, -1), + clutzModule, + ); } } insertAdditionalImports(sourceFile: ts.SourceFile) { let insertion = 0; // Skip over a leading file comment holder. - if (sourceFile.statements.length && - sourceFile.statements[0].kind === ts.SyntaxKind.NotEmittedStatement) { + if ( + sourceFile.statements.length && + sourceFile.statements[0].kind === ts.SyntaxKind.NotEmittedStatement + ) { insertion++; } return ts.factory.updateSourceFile(sourceFile, [ @@ -584,7 +666,10 @@ export class ModuleTypeTranslator { */ getJSDoc(node: ts.Node, reportWarnings: boolean): jsdoc.Tag[] { return jsdoc.getJSDocTags( - node, reportWarnings ? this.diagnostics : undefined, this.sourceFile); + node, + reportWarnings ? this.diagnostics : undefined, + this.sourceFile, + ); } getMutableJSDoc(node: ts.Node): jsdoc.MutableJSDoc { @@ -598,15 +683,21 @@ export class ModuleTypeTranslator { * `@param {...number} x`. The code below unwraps the Array<> wrapper. */ private resolveRestParameterType( - newTag: jsdoc.Tag, fnDecl: ts.SignatureDeclaration, - paramNode: ts.ParameterDeclaration) { + newTag: jsdoc.Tag, + fnDecl: ts.SignatureDeclaration, + paramNode: ts.ParameterDeclaration, + ) { const type = typeTranslator.restParameterType( - this.typeChecker, this.typeChecker.getTypeAtLocation(paramNode)); + this.typeChecker, + this.typeChecker.getTypeAtLocation(paramNode), + ); newTag.restParam = true; if (!type) { // If we fail to unwrap the Array<> type, emit an unknown type. this.debugWarn( - paramNode, 'failed to resolve rest parameter type, emitting ?'); + paramNode, + 'failed to resolve rest parameter type, emitting ?', + ); newTag.type = '?'; return; } @@ -627,25 +718,29 @@ export class ModuleTypeTranslator { * function statement; for overloads, name will have been merged. */ getFunctionTypeJSDoc( - fnDecls: ts.SignatureDeclaration[], extraTags: jsdoc.Tag[] = []): { - tags: jsdoc.Tag[], - parameterNames: string[], - thisReturnType: ts.Type|null + fnDecls: ts.SignatureDeclaration[], + extraTags: jsdoc.Tag[] = [], + ): { + tags: jsdoc.Tag[]; + parameterNames: string[]; + thisReturnType: ts.Type | null; } { const typeChecker = this.typeChecker; // De-duplicate tags and docs found for the fnDecls. const tagsByName = new Map(); function addTag(tag: jsdoc.Tag) { - if (tag.tagName === 'implements') return; // implements cannot be merged. + if (tag.tagName === 'implements') return; // implements cannot be merged. const existing = tagsByName.get(tag.tagName); tagsByName.set( - tag.tagName, existing ? jsdoc.merge([existing, tag]) : tag); + tag.tagName, + existing ? jsdoc.merge([existing, tag]) : tag, + ); } for (const extraTag of extraTags) addTag(extraTag); const isConstructor = - fnDecls.find(d => d.kind === ts.SyntaxKind.Constructor) !== undefined; + fnDecls.find((d) => d.kind === ts.SyntaxKind.Constructor) !== undefined; // For each parameter index i, paramTags[i] is an array of parameters // that can be found at index i. E.g. // function foo(x: string) @@ -657,7 +752,7 @@ export class ModuleTypeTranslator { const typeParameterNames = new Set(); const argCounts = []; - let thisReturnType: ts.Type|null = null; + let thisReturnType: ts.Type | null = null; for (const fnDecl of fnDecls) { // Construct the JSDoc comment by reading the existing JSDoc, if // any, and merging it with the known types of the function @@ -681,9 +776,14 @@ export class ModuleTypeTranslator { // Add @protected/@private if present, but not to function declarations, // function expressions, nor arrow functions (who are not class members, // so visibility does not apply). - if (fnDecls.every( - d => !ts.isFunctionDeclaration(d) && - !ts.isFunctionExpression(d) && !ts.isArrowFunction(d))) { + if ( + fnDecls.every( + (d) => + !ts.isFunctionDeclaration(d) && + !ts.isFunctionExpression(d) && + !ts.isArrowFunction(d), + ) + ) { if (flags & ts.ModifierFlags.Protected) { addTag({tagName: 'protected'}); } else if (flags & ts.ModifierFlags.Private) { @@ -723,15 +823,18 @@ export class ModuleTypeTranslator { const newTag: jsdoc.Tag = { tagName: isThisParam ? 'this' : 'param', - optional: paramNode.initializer !== undefined || - paramNode.questionToken !== undefined, + optional: + paramNode.initializer !== undefined || + paramNode.questionToken !== undefined, parameterName: isThisParam ? undefined : name, }; if (paramNode.dotDotDotToken === undefined) { // The simple case: a plain parameter type. newTag.type = this.typeToClosure( - fnDecl, this.typeChecker.getTypeAtLocation(paramNode)); + fnDecl, + this.typeChecker.getTypeAtLocation(paramNode), + ); } else { // The complex case: resolve the array member type in ...foo[]. this.resolveRestParameterType(newTag, fnDecl, paramNode); @@ -752,8 +855,10 @@ export class ModuleTypeTranslator { } } argCounts.push( - hasThisParam ? sig.declaration.parameters.length - 1 : - sig.declaration.parameters.length); + hasThisParam + ? sig.declaration.parameters.length - 1 + : sig.declaration.parameters.length, + ); // Return type. if (!isConstructor) { @@ -789,7 +894,7 @@ export class ModuleTypeTranslator { if (typeParameterNames.size > 0) { addTag({ tagName: 'template', - text: Array.from(typeParameterNames.values()).join(', ') + text: Array.from(typeParameterNames.values()).join(', '), }); } @@ -822,8 +927,10 @@ export class ModuleTypeTranslator { // If the tag is optional, mark parameters following optional as optional, // even if they are not, since Closure restricts this, see // https://github.com/google/closure-compiler/issues/2314 - if (!paramTag.restParam && - (paramTag.optional || foundOptional || i >= minArgsCount)) { + if ( + !paramTag.restParam && + (paramTag.optional || foundOptional || i >= minArgsCount) + ) { foundOptional = true; paramTag.optional = true; } @@ -842,8 +949,9 @@ export class ModuleTypeTranslator { return { tags: newDoc, - parameterNames: - newDoc.filter(t => t.tagName === 'param').map(t => t.parameterName!), + parameterNames: newDoc + .filter((t) => t.tagName === 'param') + .map((t) => t.parameterName!), thisReturnType, }; } @@ -851,7 +959,7 @@ export class ModuleTypeTranslator { /** Returns whether this declaration is in a `declare global {...} block */ function isGlobalAugmentation(decl: ts.Declaration) { - let current: ts.Node|undefined = decl; + let current: ts.Node | undefined = decl; while (current) { if (current.flags & ts.NodeFlags.GlobalAugmentation) return true; current = current.parent; diff --git a/src/ns_transformer.ts b/src/ns_transformer.ts index 67300c942..579b1e606 100644 --- a/src/ns_transformer.ts +++ b/src/ns_transformer.ts @@ -14,7 +14,15 @@ import * as ts from 'typescript'; import {AnnotatorHost} from './annotator_host'; -import {getIdentifierText, getPreviousDeclaration, hasModifierFlag, isAmbient, markAsMergedDeclaration, reportDiagnostic} from './transformer_util'; +import {getMutableJSDoc} from './jsdoc'; +import { + getIdentifierText, + getPreviousDeclaration, + hasModifierFlag, + isAmbient, + markAsMergedDeclaration, + reportDiagnostic, +} from './transformer_util'; /** * Transforms declaration merging namespaces. @@ -43,9 +51,11 @@ import {getIdentifierText, getPreviousDeclaration, hasModifierFlag, isAmbient, m * */ export function namespaceTransformer( - host: AnnotatorHost, tsOptions: ts.CompilerOptions, - typeChecker: ts.TypeChecker, - diagnostics: ts.Diagnostic[]): ts.TransformerFactory { + host: AnnotatorHost, + tsOptions: ts.CompilerOptions, + typeChecker: ts.TypeChecker, + diagnostics: ts.Diagnostic[], +): ts.TransformerFactory { return (context: ts.TransformationContext): ts.Transformer => { return (sourceFile: ts.SourceFile): ts.SourceFile => { let haveTransformedNs = false; @@ -59,10 +69,12 @@ export function namespaceTransformer( return sourceFile; } return ts.factory.updateSourceFile( - sourceFile, - ts.setTextRange( - ts.factory.createNodeArray(transformedStmts), - sourceFile.statements)); + sourceFile, + ts.setTextRange( + ts.factory.createNodeArray(transformedStmts), + sourceFile.statements, + ), + ); // Local functions follow. @@ -71,63 +83,131 @@ export function namespaceTransformer( // Returns the transformed module body statements, or [ns] if the // transformation fails. function transformNamespace( - ns: ts.ModuleDeclaration, - mergedDecl: ts.ClassDeclaration| - ts.InterfaceDeclaration): ts.Statement[] { + ns: ts.ModuleDeclaration, + mergedDecl: + | ts.ClassDeclaration + | ts.InterfaceDeclaration + | ts.EnumDeclaration, + ): ts.Statement[] { if (!ns.body || !ts.isModuleBlock(ns.body)) { if (ts.isModuleDeclaration(ns)) { error( - ns.name, - 'nested namespaces are not supported. (go/ts-merged-namespaces)'); + ns.name, + 'nested namespaces are not supported. (go/ts-merged-namespaces)', + ); } return [ns]; } const nsName = getIdentifierText(ns.name as ts.Identifier); + const mergingWithEnum = ts.isEnumDeclaration(mergedDecl); const transformedNsStmts: ts.Statement[] = []; for (const stmt of ns.body.statements) { if (ts.isEmptyStatement(stmt)) continue; if (ts.isClassDeclaration(stmt)) { + if (mergingWithEnum) { + errorNotAllowed(stmt, 'class'); + continue; + } transformInnerDeclaration( - stmt, (classDecl, notExported, hoistedIdent) => { - return ts.factory.updateClassDeclaration( - classDecl, notExported, hoistedIdent, - classDecl.typeParameters, classDecl.heritageClauses, - classDecl.members); - }); + stmt, + (classDecl, notExported, hoistedIdent) => { + return ts.factory.updateClassDeclaration( + classDecl, + notExported, + hoistedIdent, + classDecl.typeParameters, + classDecl.heritageClauses, + classDecl.members, + ); + }, + ); } else if (ts.isEnumDeclaration(stmt)) { + if (mergingWithEnum) { + errorNotAllowed(stmt, 'enum'); + continue; + } transformInnerDeclaration( - stmt, (enumDecl, notExported, hoistedIdent) => { - return ts.factory.updateEnumDeclaration( - enumDecl, notExported, hoistedIdent, enumDecl.members); - }); + stmt, + (enumDecl, notExported, hoistedIdent) => { + return ts.factory.updateEnumDeclaration( + enumDecl, + notExported, + hoistedIdent, + enumDecl.members, + ); + }, + ); } else if (ts.isInterfaceDeclaration(stmt)) { + if (mergingWithEnum) { + errorNotAllowed(stmt, 'interface'); + continue; + } transformInnerDeclaration( - stmt, (interfDecl, notExported, hoistedIdent) => { - return ts.factory.updateInterfaceDeclaration( - interfDecl, notExported, hoistedIdent, - interfDecl.typeParameters, interfDecl.heritageClauses, - interfDecl.members); - }); + stmt, + (interfDecl, notExported, hoistedIdent) => { + return ts.factory.updateInterfaceDeclaration( + interfDecl, + notExported, + hoistedIdent, + interfDecl.typeParameters, + interfDecl.heritageClauses, + interfDecl.members, + ); + }, + ); } else if (ts.isTypeAliasDeclaration(stmt)) { + if (mergingWithEnum) { + errorNotAllowed(stmt, 'type alias'); + continue; + } transformTypeAliasDeclaration(stmt); } else if (ts.isVariableStatement(stmt)) { - if ((ts.getCombinedNodeFlags(stmt.declarationList) & - ts.NodeFlags.Const) === 0) { + if ( + (ts.getCombinedNodeFlags(stmt.declarationList) & + ts.NodeFlags.Const) === + 0 + ) { error( - stmt, - 'non-const values are not supported. (go/ts-merged-namespaces)'); + stmt, + 'non-const values are not supported. (go/ts-merged-namespaces)', + ); + continue; } if (!ts.isInterfaceDeclaration(mergedDecl)) { error( - stmt, - 'const declaration only allowed when merging with an interface (go/ts-merged-namespaces)'); + stmt, + 'const declaration only allowed when merging with an interface (go/ts-merged-namespaces)', + ); + continue; } transformConstDeclaration(stmt); + } else if (ts.isFunctionDeclaration(stmt)) { + if (!ts.isEnumDeclaration(mergedDecl)) { + error( + stmt, + 'function declaration only allowed when merging with an enum (go/ts-merged-namespaces)', + ); + } + transformInnerDeclaration( + stmt, + (funcDecl, notExported, hoistedIdent) => { + return ts.factory.updateFunctionDeclaration( + funcDecl, + notExported, + funcDecl.asteriskToken, + hoistedIdent, + funcDecl.typeParameters, + funcDecl.parameters, + funcDecl.type, + funcDecl.body, + ); + }, + ); } else { error( - stmt, - `unsupported statement in declaration merging namespace '${ - nsName}' (go/ts-merged-namespaces)`); + stmt, + `unsupported statement in declaration merging namespace '${nsName}' (go/ts-merged-namespaces)`, + ); } } if (haveSeenError) { @@ -145,21 +225,30 @@ export function namespaceTransformer( // Local functions follow. - type DeclarationStatement = ts.Declaration&ts.DeclarationStatement; + function errorNotAllowed(stmt: ts.Statement, declKind: string) { + error( + stmt, + `${declKind} cannot be merged with enum declaration. (go/ts-merged-namespaces)`, + ); + } + + type DeclarationStatement = ts.Declaration & ts.DeclarationStatement; function transformConstDeclaration(varDecl: ts.VariableStatement) { for (let decl of varDecl.declarationList.declarations) { if (!decl.name || !ts.isIdentifier(decl.name)) { error( - decl, - 'Destructuring declarations are not supported. (go/ts-merged-namespaces)'); + decl, + 'Destructuring declarations are not supported. (go/ts-merged-namespaces)', + ); return; } const originalName = getIdentifierText(decl.name); if (!hasModifierFlag(decl, ts.ModifierFlags.Export)) { error( - decl, - `'${originalName}' must be exported. (go/ts-merged-namespaces)`); + decl, + `'${originalName}' must be exported. (go/ts-merged-namespaces)`, + ); return; } decl = fixReferences(decl); @@ -168,26 +257,33 @@ export function namespaceTransformer( return; } transformedNsStmts.push( - createInnerNameAlias(originalName, decl.initializer, varDecl)); + createInnerNameAlias(originalName, decl.initializer, varDecl), + ); } } function transformTypeAliasDeclaration( - aliasDecl: ts.TypeAliasDeclaration) { + aliasDecl: ts.TypeAliasDeclaration, + ) { // Check that the inner declaration is exported. const originalName = getIdentifierText(aliasDecl.name); if (!hasModifierFlag(aliasDecl, ts.ModifierFlags.Export)) { error( - aliasDecl, - `'${originalName}' must be exported. (go/ts-merged-namespaces)`); + aliasDecl, + `'${originalName}' must be exported. (go/ts-merged-namespaces)`, + ); } aliasDecl = fixReferences(aliasDecl); const notExported = ts.factory.createModifiersFromModifierFlags( - ts.getCombinedModifierFlags(aliasDecl) & - (~ts.ModifierFlags.Export)); + ts.getCombinedModifierFlags(aliasDecl) & ~ts.ModifierFlags.Export, + ); aliasDecl = ts.factory.updateTypeAliasDeclaration( - aliasDecl, notExported, aliasDecl.name, aliasDecl.typeParameters, - aliasDecl.type); + aliasDecl, + notExported, + aliasDecl.name, + aliasDecl.typeParameters, + aliasDecl.type, + ); // visitTypeAliasDeclaration() in jsdocTransformer() recognizes // that the type alias is declared in a transformed namespace and // generates the type alias as a property of the namespace. No @@ -196,22 +292,27 @@ export function namespaceTransformer( } function transformInnerDeclaration( + decl: T, + updateDecl: ( decl: T, - updateDecl: ( - decl: T, modifiers: ts.Modifier[]|undefined, - newIdent: ts.Identifier) => T) { + modifiers: ts.Modifier[] | undefined, + newIdent: ts.Identifier, + ) => T, + ) { if (!decl.name || !ts.isIdentifier(decl.name)) { error( - decl, - 'Anonymous declaration cannot be merged. (go/ts-merged-namespaces)'); + decl, + 'Anonymous declaration cannot be merged. (go/ts-merged-namespaces)', + ); return; } // Check that the inner declaration is exported. const originalName = getIdentifierText(decl.name); if (!hasModifierFlag(decl, ts.ModifierFlags.Export)) { error( - decl, - `'${originalName}' must be exported. (go/ts-merged-namespaces)`); + decl, + `'${originalName}' must be exported. (go/ts-merged-namespaces)`, + ); } decl = fixReferences(decl); @@ -221,12 +322,16 @@ export function namespaceTransformer( // The hoisted declaration is not directly exported. const notExported = ts.factory.createModifiersFromModifierFlags( - ts.getCombinedModifierFlags(decl) & (~ts.ModifierFlags.Export)); + ts.getCombinedModifierFlags(decl) & ~ts.ModifierFlags.Export, + ); const hoistedDecl = updateDecl(decl, notExported, hoistedIdent); transformedNsStmts.push(hoistedDecl); // Add alias `/** @const */ nsName.originalName = hoistedName;` - const aliasProp = - createInnerNameAlias(originalName, hoistedIdent, decl); + const aliasProp = createInnerNameAlias( + originalName, + hoistedIdent, + decl, + ); // Don't repeat any comments from the original declaration. They // are already on the hoisted declaration. ts.setEmitFlags(aliasProp, ts.EmitFlags.NoLeadingComments); @@ -234,18 +339,25 @@ export function namespaceTransformer( } function createInnerNameAlias( - propName: string, initializer: ts.Expression, - original: ts.Node): ts.Statement { - const prop = - ts.factory.createExpressionStatement(ts.factory.createAssignment( - ts.factory.createPropertyAccessExpression( - mergedDecl.name!, propName), - initializer)); + propName: string, + initializer: ts.Expression, + original: ts.Node, + ): ts.Statement { + const prop = ts.factory.createExpressionStatement( + ts.factory.createAssignment( + ts.factory.createPropertyAccessExpression( + mergedDecl.name!, + propName, + ), + initializer, + ), + ); ts.setTextRange(prop, original); ts.setOriginalNode(prop, original); - return ts.addSyntheticLeadingComment( - prop, ts.SyntaxKind.MultiLineCommentTrivia, '* @const ', - /* hasTrailingNewLine */ true); + const jsDoc = getMutableJSDoc(prop, diagnostics, sourceFile); + jsDoc.tags.push({tagName: 'const'}); + jsDoc.updateComment(); + return prop; } function isNamespaceRef(ident: ts.Identifier): boolean { @@ -263,12 +375,15 @@ export function namespaceTransformer( // Build a property access expression if the identifier refers to a // symbol defined in the transformed namespace. - function maybeFixIdentifier(ident: ts.Identifier): ts.Identifier| - ts.PropertyAccessExpression { + function maybeFixIdentifier( + ident: ts.Identifier, + ): ts.Identifier | ts.PropertyAccessExpression { if (isNamespaceRef(ident)) { const nsIdentifier = ts.factory.createIdentifier(nsName); - const nsProp = - ts.factory.createPropertyAccessExpression(nsIdentifier, ident); + const nsProp = ts.factory.createPropertyAccessExpression( + nsIdentifier, + ident, + ); ts.setOriginalNode(nsProp, ident); ts.setTextRange(nsProp, ident); return nsProp; @@ -278,13 +393,17 @@ export function namespaceTransformer( // Update the property access expression if the leftmost identifier // refers to a symbol defined in the transformed namespace. - function maybeFixPropertyAccess(prop: ts.PropertyAccessExpression): - ts.PropertyAccessExpression { + function maybeFixPropertyAccess( + prop: ts.PropertyAccessExpression, + ): ts.PropertyAccessExpression { if (ts.isPropertyAccessExpression(prop.expression)) { const updatedProp = maybeFixPropertyAccess(prop.expression); if (updatedProp !== prop.expression) { return ts.factory.updatePropertyAccessExpression( - prop, updatedProp, prop.name); + prop, + updatedProp, + prop.name, + ); } return prop; } @@ -295,7 +414,10 @@ export function namespaceTransformer( const nsProp = maybeFixIdentifier(prop.expression); if (nsProp !== prop.expression) { const newPropAccess = ts.factory.updatePropertyAccessExpression( - prop, nsProp, prop.name); + prop, + nsProp, + prop.name, + ); return newPropAccess; } return prop; @@ -306,7 +428,7 @@ export function namespaceTransformer( function fixReferences(node: T): T { // TODO: Are there other node types that need to be handled? const rootNode = node; - function refCheckVisitor(node: ts.Node): ts.Node|undefined { + function refCheckVisitor(node: ts.Node): ts.Node | undefined { if (ts.isTypeReferenceNode(node) || ts.isTypeQueryNode(node)) { // Type reference nodes are used for explicit type annotations of // properties, parameters, function results etc. References to @@ -357,20 +479,25 @@ export function namespaceTransformer( // For a merged namespace, the symbol must already have been declared // prior to the namespace declaration, or the compiler reports TS2434. if (!mergedDecl) { - transformedStmts.push(ns); // Nothing to do here. + transformedStmts.push(ns); // Nothing to do here. error( - ns.name, - 'transformation of plain namespace not supported. (go/ts-merged-namespaces)'); + ns.name, + 'transformation of plain namespace not supported. (go/ts-merged-namespaces)', + ); return; } - if (!ts.isInterfaceDeclaration(mergedDecl) && - !ts.isClassDeclaration(mergedDecl)) { - // The previous declaration is not a class or interface. - transformedStmts.push(ns); // Nothing to do here. + if ( + !ts.isInterfaceDeclaration(mergedDecl) && + !ts.isClassDeclaration(mergedDecl) && + !ts.isEnumDeclaration(mergedDecl) + ) { + // The previous declaration is not a class, enum, or interface. + transformedStmts.push(ns); // Nothing to do here. error( - ns.name, - 'merged declaration must be local class or interface. (go/ts-merged-namespaces)'); + ns.name, + 'merged declaration must be local class, enum, or interface. (go/ts-merged-namespaces)', + ); return; } diff --git a/src/path.ts b/src/path.ts index e1ed654ff..093e13098 100644 --- a/src/path.ts +++ b/src/path.ts @@ -23,9 +23,14 @@ declare module 'typescript' { function combinePaths(...paths: string[]): string; function getDirectoryPath(path: string): string; function convertToRelativePath( - absoluteOrRelativePath: string, basePath: string, - getCanonicalFileName: (path: string) => string): string; - function resolvePath(path: string, ...paths: Array): string; + absoluteOrRelativePath: string, + basePath: string, + getCanonicalFileName: (path: string) => string, + ): string; + function resolvePath( + path: string, + ...paths: Array + ): string; } export function isAbsolute(path: string): boolean { @@ -41,7 +46,7 @@ export function dirname(path: string): string { } export function relative(base: string, rel: string): string { - return ts.convertToRelativePath(rel, base, p => p); + return ts.convertToRelativePath(rel, base, (p) => p); } export function normalize(path: string): string { diff --git a/src/summary.ts b/src/summary.ts index ffbff10fb..9c1293333 100644 --- a/src/summary.ts +++ b/src/summary.ts @@ -48,12 +48,14 @@ export class FileSummary { private readonly strongRequireSet = new Map(); private readonly weakRequireSet = new Map(); private readonly dynamicRequireSet = new Map(); + private readonly maybeRequireSet = new Map(); private readonly modSet = new Map(); private readonly enhancedSet = new Map(); toggles: string[] = []; - modName: string|undefined; + modName: string | undefined; autochunk = false; enhanceable = false; + legacyNamespace = false; moduleType = ModuleType.UNKNOWN; private stringify(symbol: Symbol): string { @@ -97,6 +99,14 @@ export class FileSummary { return [...this.dynamicRequireSet.values()]; } + addMaybeRequire(maybeRequire: Symbol) { + this.maybeRequireSet.set(this.stringify(maybeRequire), maybeRequire); + } + + get maybeRequires(): Symbol[] { + return [...this.maybeRequireSet.values()]; + } + addMods(mods: Symbol) { this.modSet.set(this.stringify(mods), mods); } diff --git a/src/transformer_util.ts b/src/transformer_util.ts index 6c60ef0dd..024819af7 100644 --- a/src/transformer_util.ts +++ b/src/transformer_util.ts @@ -9,7 +9,10 @@ import * as ts from 'typescript'; /** @return true if node has the specified modifier flag set. */ -export function hasModifierFlag(declaration: ts.Declaration, flag: ts.ModifierFlags): boolean { +export function hasModifierFlag( + declaration: ts.Declaration, + flag: ts.ModifierFlags, +): boolean { return (ts.getCombinedModifierFlags(declaration) & flag) !== 0; } @@ -18,7 +21,7 @@ export function hasModifierFlag(declaration: ts.Declaration, flag: ts.ModifierFl * set. */ export function isAmbient(node: ts.Node): boolean { - let current: ts.Node|undefined = node; + let current: ts.Node | undefined = node; while (current) { if (hasModifierFlag(current as ts.Declaration, ts.ModifierFlags.Ambient)) { return true; @@ -78,15 +81,23 @@ export function unescapeName(name: ts.__String): string { * from the original statement as synthetic comments to it, so that they get retained in the output. */ export function createNotEmittedStatementWithComments( - sourceFile: ts.SourceFile, original: ts.Node): ts.Statement { + sourceFile: ts.SourceFile, + original: ts.Node, +): ts.Statement { let replacement = ts.factory.createNotEmittedStatement(original); // NB: synthetic nodes can have pos/end == -1. This is handled by the underlying implementation. - const leading = ts.getLeadingCommentRanges(sourceFile.text, original.pos) || []; - const trailing = ts.getTrailingCommentRanges(sourceFile.text, original.end) || []; - replacement = - ts.setSyntheticLeadingComments(replacement, synthesizeCommentRanges(sourceFile, leading)); - replacement = - ts.setSyntheticTrailingComments(replacement, synthesizeCommentRanges(sourceFile, trailing)); + const leading = + ts.getLeadingCommentRanges(sourceFile.text, original.pos) || []; + const trailing = + ts.getTrailingCommentRanges(sourceFile.text, original.end) || []; + replacement = ts.setSyntheticLeadingComments( + replacement, + synthesizeCommentRanges(sourceFile, leading), + ); + replacement = ts.setSyntheticTrailingComments( + replacement, + synthesizeCommentRanges(sourceFile, trailing), + ); return replacement; } @@ -94,7 +105,9 @@ export function createNotEmittedStatementWithComments( * Converts `ts.CommentRange`s into `ts.SynthesizedComment`s. */ export function synthesizeCommentRanges( - sourceFile: ts.SourceFile, parsedComments: ts.CommentRange[]): ts.SynthesizedComment[] { + sourceFile: ts.SourceFile, + parsedComments: ts.CommentRange[], +): ts.SynthesizedComment[] { const synthesizedComments: ts.SynthesizedComment[] = []; parsedComments.forEach(({kind, pos, end, hasTrailingNewLine}) => { let commentText = sourceFile.text.substring(pos, end).trim(); @@ -107,22 +120,17 @@ export function synthesizeCommentRanges( } commentText = commentText.replace(/(^\/\/)/g, ''); } - synthesizedComments.push({kind, text: commentText, hasTrailingNewLine, pos: -1, end: -1}); + synthesizedComments.push({ + kind, + text: commentText, + hasTrailingNewLine, + pos: -1, + end: -1, + }); }); return synthesizedComments; } -/** - * Creates a non emitted statement that can be used to store synthesized comments. - */ -export function createNotEmittedStatement(sourceFile: ts.SourceFile): ts.NotEmittedStatement { - const stmt = ts.factory.createNotEmittedStatement(sourceFile); - ts.setOriginalNode(stmt, undefined); - ts.setTextRange(stmt, {pos: 0, end: 0}); - ts.setEmitFlags(stmt, ts.EmitFlags.CustomPrologue); - return stmt; -} - /** * This is a version of `ts.visitEachChild` that works that calls our version * of `updateSourceFileNode`, so that typescript doesn't lose type information @@ -130,10 +138,16 @@ export function createNotEmittedStatement(sourceFile: ts.SourceFile): ts.NotEmit * See https://github.com/Microsoft/TypeScript/issues/17384 */ export function visitEachChild( - node: ts.Node, visitor: ts.Visitor, context: ts.TransformationContext): ts.Node { + node: ts.Node, + visitor: ts.Visitor, + context: ts.TransformationContext, +): ts.Node { if (node.kind === ts.SyntaxKind.SourceFile) { const sf = node as ts.SourceFile; - return updateSourceFileNode(sf, ts.visitLexicalEnvironment(sf.statements, visitor, context)); + return updateSourceFileNode( + sf, + ts.visitLexicalEnvironment(sf.statements, visitor, context), + ); } return ts.visitEachChild(node, visitor, context); @@ -146,18 +160,20 @@ export function visitEachChild( * TODO(#634): This has been fixed in TS 2.5. Investigate removal. */ export function updateSourceFileNode( - sf: ts.SourceFile, statements: ts.NodeArray): ts.SourceFile { + sf: ts.SourceFile, + statements: ts.NodeArray, +): ts.SourceFile { if (statements === sf.statements) { return sf; } sf = ts.factory.updateSourceFile( - sf, - statements, - sf.isDeclarationFile, - sf.referencedFiles, - sf.typeReferenceDirectives, - sf.hasNoDefaultLib, - sf.libReferenceDirectives, + sf, + ts.setTextRange(statements, sf.statements), + sf.isDeclarationFile, + sf.referencedFiles, + sf.typeReferenceDirectives, + sf.hasNoDefaultLib, + sf.libReferenceDirectives, ); return sf; } @@ -183,7 +199,9 @@ export function createSingleLineComment(original: ts.Node, text: string) { end: -1, }; return ts.setSyntheticTrailingComments( - ts.factory.createNotEmittedStatement(original), [comment]); + ts.factory.createNotEmittedStatement(original), + [comment], + ); } /** Creates a not emitted statement with the given text as a single line comment. */ @@ -196,7 +214,9 @@ export function createMultiLineComment(original: ts.Node, text: string) { end: -1, }; return ts.setSyntheticTrailingComments( - ts.factory.createNotEmittedStatement(original), [comment]); + ts.factory.createNotEmittedStatement(original), + [comment], + ); } /** @@ -207,10 +227,19 @@ export function createMultiLineComment(original: ts.Node, text: string) { * behind a debug flag, as warnings are only for tsickle to debug itself. */ export function reportDebugWarning( - host: {logWarning ? (d: ts.Diagnostic) : void}, node: ts.Node, messageText: string) { + host: {logWarning?(d: ts.Diagnostic): void}, + node: ts.Node, + messageText: string, +) { if (!host.logWarning) return; - host.logWarning(createDiagnostic( - node, messageText, /* textRange */ undefined, ts.DiagnosticCategory.Warning)); + host.logWarning( + createDiagnostic( + node, + messageText, + /* textRange */ undefined, + ts.DiagnosticCategory.Warning, + ), + ); } /** @@ -227,28 +256,36 @@ export function reportDebugWarning( * @param textRange pass to overrride the text range from the node with a more specific range. */ export function reportDiagnostic( - diagnostics: ts.Diagnostic[], node: ts.Node, messageText: string, textRange?: ts.TextRange, - category = ts.DiagnosticCategory.Error) { + diagnostics: ts.Diagnostic[], + node: ts.Node | undefined, + messageText: string, + textRange?: ts.TextRange, + category = ts.DiagnosticCategory.Error, +) { diagnostics.push(createDiagnostic(node, messageText, textRange, category)); } function createDiagnostic( - node: ts.Node, messageText: string, textRange: ts.TextRange|undefined, - category: ts.DiagnosticCategory): ts.Diagnostic { - let start, length: number; + node: ts.Node | undefined, + messageText: string, + textRange: ts.TextRange | undefined, + category: ts.DiagnosticCategory, +): ts.Diagnostic { + let start: number | undefined; + let length: number | undefined; // getStart on a synthesized node can crash (due to not finding an associated // source file). Make sure to use the original node. node = ts.getOriginalNode(node); if (textRange) { start = textRange.pos; length = textRange.end - textRange.pos; - } else { + } else if (node) { // Only use getStart if node has a valid pos, as it might be synthesized. start = node.pos >= 0 ? node.getStart() : 0; length = node.end - node.pos; } return { - file: node.getSourceFile(), + file: node?.getSourceFile(), start, length, messageText, @@ -262,12 +299,17 @@ function createDiagnostic( * non-synthetic comments on the given node, with their text included. The returned comments must * not be mutated, as their content might or might not be reflected back into the AST. */ -export function getAllLeadingComments(node: ts.Node): - ReadonlyArray> { - const allRanges: Array> = []; +export function getAllLeadingComments( + node: ts.Node, +): ReadonlyArray> { + const allRanges: Array> = []; const nodeText = node.getFullText(); const cr = ts.getLeadingCommentRanges(nodeText, 0); - if (cr) allRanges.push(...cr.map(c => ({...c, text: nodeText.substring(c.pos, c.end)}))); + if (cr) { + allRanges.push( + ...cr.map((c) => ({...c, text: nodeText.substring(c.pos, c.end)})), + ); + } const synthetic = ts.getSyntheticLeadingComments(node); if (synthetic) allRanges.push(...synthetic); return allRanges; @@ -277,11 +319,17 @@ export function getAllLeadingComments(node: ts.Node): * Creates a call expression corresponding to `goog.${methodName}(${literal})`. */ export function createGoogCall( - methodName: string, literal: ts.StringLiteral): ts.CallExpression { + methodName: string, + literal: ts.StringLiteral, +): ts.CallExpression { return ts.factory.createCallExpression( - ts.factory.createPropertyAccessExpression( - ts.factory.createIdentifier('goog'), methodName), - undefined, [literal]); + ts.factory.createPropertyAccessExpression( + ts.factory.createIdentifier('goog'), + methodName, + ), + undefined, + [literal], + ); } /** @@ -289,13 +337,15 @@ export function createGoogCall( * `$fnName` when pased `goog.$fnName`), or null if the given call expression is * not of the form `goog.$fnName`. */ -export function getGoogFunctionName(call: ts.CallExpression): string|null { +export function getGoogFunctionName(call: ts.CallExpression): string | null { if (!ts.isPropertyAccessExpression(call.expression)) { return null; } const propAccess = call.expression; - if (!ts.isIdentifier(propAccess.expression) || - propAccess.expression.escapedText !== 'goog') { + if ( + !ts.isIdentifier(propAccess.expression) || + propAccess.expression.escapedText !== 'goog' + ) { return null; } return propAccess.name.text; @@ -306,7 +356,9 @@ export function getGoogFunctionName(call: ts.CallExpression): string|null { * not check whether `goog` is the expected symbol (vs e.g. a local variable). */ export function isGoogCallExpressionOf( - n: ts.Node, fnName: string): n is ts.CallExpression { + n: ts.Node, + fnName: string, +): n is ts.CallExpression { return ts.isCallExpression(n) && getGoogFunctionName(n) === fnName; } @@ -317,9 +369,11 @@ export function isGoogCallExpressionOf( * variable). */ export function isAnyTsmesCall(n: ts.Node): n is ts.CallExpression { - return isGoogCallExpressionOf(n, 'tsMigrationExportsShim') || - isGoogCallExpressionOf(n, 'tsMigrationDefaultExportsShim') || - isGoogCallExpressionOf(n, 'tsMigrationNamedExportsShim'); + return ( + isGoogCallExpressionOf(n, 'tsMigrationExportsShim') || + isGoogCallExpressionOf(n, 'tsMigrationDefaultExportsShim') || + isGoogCallExpressionOf(n, 'tsMigrationNamedExportsShim') + ); } /** @@ -329,18 +383,23 @@ export function isAnyTsmesCall(n: ts.Node): n is ts.CallExpression { * (vs e.g. a local variable). */ export function isTsmesShorthandCall(n: ts.Node): n is ts.CallExpression { - return isGoogCallExpressionOf(n, 'tsMigrationDefaultExportsShim') || - isGoogCallExpressionOf(n, 'tsMigrationNamedExportsShim'); + return ( + isGoogCallExpressionOf(n, 'tsMigrationDefaultExportsShim') || + isGoogCallExpressionOf(n, 'tsMigrationNamedExportsShim') + ); } /** * Returns true if the given call executes * `goog.tsMigrationExportsShimDeclareLegacyNamespace`. */ -export function isTsmesDeclareLegacyNamespaceCall(n: ts.Node): - n is ts.CallExpression { +export function isTsmesDeclareLegacyNamespaceCall( + n: ts.Node, +): n is ts.CallExpression { return isGoogCallExpressionOf( - n, 'tsMigrationExportsShimDeclareLegacyNamespace'); + n, + 'tsMigrationExportsShimDeclareLegacyNamespace', + ); } /** @@ -356,25 +415,37 @@ export function isTsmesDeclareLegacyNamespaceCall(n: ts.Node): * https://github.com/google/closure-library/blob/master/closure/goog/base.js */ export function createGoogLoadedModulesRegistration( - moduleId: string, exports: ts.Expression): ts.Statement { - return ts.factory.createExpressionStatement(ts.factory.createAssignment( + moduleId: string, + exports: ts.Expression, +): ts.Statement { + return ts.factory.createExpressionStatement( + ts.factory.createAssignment( ts.factory.createElementAccessExpression( - ts.factory.createPropertyAccessExpression( - ts.factory.createIdentifier('goog'), - ts.factory.createIdentifier('loadedModules_')), - createSingleQuoteStringLiteral(moduleId)), + ts.factory.createPropertyAccessExpression( + ts.factory.createIdentifier('goog'), + ts.factory.createIdentifier('loadedModules_'), + ), + createSingleQuoteStringLiteral(moduleId), + ), ts.factory.createObjectLiteralExpression([ ts.factory.createPropertyAssignment('exports', exports), ts.factory.createPropertyAssignment( - 'type', + 'type', + ts.factory.createPropertyAccessExpression( ts.factory.createPropertyAccessExpression( - ts.factory.createPropertyAccessExpression( - ts.factory.createIdentifier('goog'), - ts.factory.createIdentifier('ModuleType')), - ts.factory.createIdentifier('GOOG'))), + ts.factory.createIdentifier('goog'), + ts.factory.createIdentifier('ModuleType'), + ), + ts.factory.createIdentifier('GOOG'), + ), + ), ts.factory.createPropertyAssignment( - 'moduleId', createSingleQuoteStringLiteral(moduleId)), - ]))); + 'moduleId', + createSingleQuoteStringLiteral(moduleId), + ), + ]), + ), + ); } /** @@ -395,7 +466,7 @@ export function markAsMergedDeclaration(decl: ts.Declaration) { * Returns the namespace declaration if node is contained inside a * namespace that has been transformed by namespaceTransformer. */ -export function getTransformedNs(node: ts.Node): ts.ModuleDeclaration|null { +export function getTransformedNs(node: ts.Node): ts.ModuleDeclaration | null { node = ts.getOriginalNode(node); let parent = node.parent; while (parent) { @@ -407,7 +478,6 @@ export function getTransformedNs(node: ts.Node): ts.ModuleDeclaration|null { return null; } - /** * Returns true if node (or its original if updated) is contained inside a * namespace that has been transformed by namespaceTransformer. @@ -421,14 +491,19 @@ export function nodeIsInTransformedNs(node: ts.Node): boolean { * of 'thisDecl'. */ export function getPreviousDeclaration( - sym: ts.Symbol, thisDecl: ts.Declaration): ts.Declaration|null { + sym: ts.Symbol, + thisDecl: ts.Declaration, +): ts.Declaration | null { if (!sym.declarations) return null; const sf = thisDecl.getSourceFile(); for (const decl of sym.declarations) { - if (!isAmbient(decl) && (decl.getSourceFile()) === sf && - (decl.pos < thisDecl.pos)) { + if ( + !isAmbient(decl) && + decl.getSourceFile() === sf && + decl.pos < thisDecl.pos + ) { return decl; } } return null; -} \ No newline at end of file +} diff --git a/src/ts_migration_exports_shim.ts b/src/ts_migration_exports_shim.ts index ac715764b..e909eb510 100644 --- a/src/ts_migration_exports_shim.ts +++ b/src/ts_migration_exports_shim.ts @@ -12,7 +12,14 @@ import * as ts from 'typescript'; import {ModulesManifest} from './modules_manifest'; import {FileSummary, ModuleType, Type} from './summary'; -import {getGoogFunctionName, isAnyTsmesCall, isGoogCallExpressionOf, isTsmesDeclareLegacyNamespaceCall, isTsmesShorthandCall, reportDiagnostic} from './transformer_util'; +import { + getGoogFunctionName, + isAnyTsmesCall, + isGoogCallExpressionOf, + isTsmesDeclareLegacyNamespaceCall, + isTsmesShorthandCall, + reportDiagnostic, +} from './transformer_util'; /** Provides dependencies for file generation. */ export interface TsMigrationExportsShimProcessorHost { @@ -45,11 +52,13 @@ export type TsMigrationExportsShimFileMap = Map; * `generateTsMigrationExportsShim` is false. */ export function createTsMigrationExportsShimTransformerFactory( - typeChecker: ts.TypeChecker, host: TsMigrationExportsShimProcessorHost, - manifest: ModulesManifest, tsickleDiagnostics: ts.Diagnostic[], - outputFileMap: TsMigrationExportsShimFileMap, - fileSummaries: Map): - ts.TransformerFactory { + typeChecker: ts.TypeChecker, + host: TsMigrationExportsShimProcessorHost, + manifest: ModulesManifest, + tsickleDiagnostics: ts.Diagnostic[], + outputFileMap: TsMigrationExportsShimFileMap, + fileSummaries: Map, +): ts.TransformerFactory { return (context: ts.TransformationContext): ts.Transformer => { return (src: ts.SourceFile): ts.SourceFile => { const srcFilename = host.rootDirsRelative(src.fileName); @@ -57,7 +66,13 @@ export function createTsMigrationExportsShimTransformerFactory( const srcIds = new FileIdGroup(srcFilename, srcModuleId); const generator = new Generator( - src, srcIds, typeChecker, host, manifest, tsickleDiagnostics); + src, + srcIds, + typeChecker, + host, + manifest, + tsickleDiagnostics, + ); const tsmesFile = srcIds.google3PathWithoutExtension() + '.tsmes.js'; const dtsFile = srcIds.google3PathWithoutExtension() + '.tsmes.d.ts'; if (!host.generateTsMigrationExportsShim) { @@ -99,21 +114,20 @@ function stripSupportedExtensions(path: string) { // .ts but not .d.ts const SUPPORTED_EXTENSIONS = /(?= this.diagnostics.length) { throw new Error( - 'googExports should be defined unless some diagnostic is reported.'); + 'googExports should be defined unless some diagnostic is reported.', + ); } return undefined; } @@ -284,9 +316,10 @@ class Generator { * If the exports are malformed, returns undefined. Diagnostics about * malformed exports are also logged. */ - private extractGoogExports(exportsExpr: ts.Expression): GoogExports - |undefined { - let googExports: GoogExports|undefined; + private extractGoogExports( + exportsExpr: ts.Expression, + ): GoogExports | undefined { + let googExports: GoogExports | undefined; const diagnosticCount = this.diagnostics.length; if (ts.isObjectLiteralExpression(exportsExpr)) { @@ -295,7 +328,7 @@ class Generator { if (ts.isShorthandPropertyAssignment(property)) { // {Bar} const symbol = - this.typeChecker.getShorthandAssignmentValueSymbol(property); + this.typeChecker.getShorthandAssignmentValueSymbol(property); this.checkIsModuleExport(property.name, symbol); googExports.set(property.name.text, property.name.text); } else if (ts.isPropertyAssignment(property)) { @@ -308,7 +341,7 @@ class Generator { const initializer = property.initializer; - let identifier: ts.Identifier|null = null; + let identifier: ts.Identifier | null = null; if (ts.isAsExpression(initializer)) { identifier = this.maybeExtractTypeName(initializer); } else if (ts.isIdentifier(initializer)) { @@ -327,8 +360,9 @@ class Generator { googExports.set(name.text, identifier.text); } else { this.report( - property, - `exports object must only contain (shorthand) properties`); + property, + `exports object must only contain (shorthand) properties`, + ); } } } else if (ts.isIdentifier(exportsExpr)) { @@ -346,18 +380,22 @@ class Generator { googExports = identifier.text; } else { this.report( - exportsExpr, - `exports object must be either an object literal ({A, B}) or the ` + - `identifier of a module export (A)`); + exportsExpr, + `exports object must be either an object literal ({A, B}) or the ` + + `identifier of a module export (A)`, + ); } - return (diagnosticCount === this.diagnostics.length) ? googExports : - undefined; + return diagnosticCount === this.diagnostics.length + ? googExports + : undefined; } - private maybeExtractTypeName(cast: ts.AsExpression): ts.Identifier|null { - if (!ts.isObjectLiteralExpression(cast.expression) || - cast.expression.properties.length !== 0) { + private maybeExtractTypeName(cast: ts.AsExpression): ts.Identifier | null { + if ( + !ts.isObjectLiteralExpression(cast.expression) || + cast.expression.properties.length !== 0 + ) { this.report(cast.expression, 'must be object literal with no keys'); return null; } @@ -388,7 +426,9 @@ class Generator { if (isAnyTsmesCall(node) || isTsmesDeclareLegacyNamespaceCall(node)) { const name = getGoogFunctionName(node); this.report( - node, `goog.${name} is only allowed in top level statements`); + node, + `goog.${name} is only allowed in top level statements`, + ); } ts.forEachChild(node, inner); }; @@ -407,56 +447,66 @@ class Generator { throw new Error('tsmes call must be extracted first'); } - let maybeDeclareLegacyNameCall: string|undefined = undefined; + let maybeDeclareLegacyNameCall: string | undefined = undefined; if (this.tsmesBreakdown.declareLegacyNamespaceStatement) { maybeDeclareLegacyNameCall = 'goog.module.declareLegacyNamespace();'; } // Note: We don't do a destructure here as that's not compatible with IE11. - const mainModuleRequire = - `var mainModule = goog.require('${this.srcIds.googModuleId}');`; + const mainModuleRequire = `var mainModule = goog.require('${this.srcIds.googModuleId}');`; let exportsAssignment: string; if (this.tsmesBreakdown.googExports instanceof Map) { // In the case that tsmes was passed named exports. - const exports = Array.from(this.tsmesBreakdown.googExports) - .map(([k, v]) => `exports.${k} = mainModule.${v};`); + const exports = Array.from(this.tsmesBreakdown.googExports).map( + ([k, v]) => `exports.${k} = mainModule.${v};`, + ); exportsAssignment = lines(...exports); } else { // In the case that tsmes was passed a default export. - exportsAssignment = - `exports = mainModule.${this.tsmesBreakdown.googExports};`; + exportsAssignment = `exports = mainModule.${this.tsmesBreakdown.googExports};`; } this.manifest.addModule( - this.outputIds.google3Path, this.outputIds.googModuleId); + this.outputIds.google3Path, + this.outputIds.googModuleId, + ); this.manifest.addReferencedModule( - this.outputIds.google3Path, this.srcIds.googModuleId); + this.outputIds.google3Path, + this.srcIds.googModuleId, + ); const isAutoChunk = containsAtPintoModule(this.src); - const pintoModuleAnnotation = isAutoChunk ? - '@pintomodule found in original_file' : - 'pintomodule absent in original_file'; + const pintoModuleAnnotation = isAutoChunk + ? '@pintomodule found in original_file' + : 'pintomodule absent in original_file'; const content = lines( - '/**', - ' * @fileoverview generator:ts_migration_exports_shim.ts', - ' * original_file:' + this.srcIds.google3Path, - ` * ${pintoModuleAnnotation}`, - ' */', - `goog.module('${this.outputIds.googModuleId}');`, - maybeDeclareLegacyNameCall, - mainModuleRequire, - exportsAssignment, - '', + '/**', + ' * @fileoverview generator:ts_migration_exports_shim.ts', + ' * original_file:' + this.srcIds.google3Path, + ` * ${pintoModuleAnnotation}`, + ' */', + `goog.module('${this.outputIds.googModuleId}');`, + maybeDeclareLegacyNameCall, + mainModuleRequire, + exportsAssignment, + '', ); const fileSummary = new FileSummary(); - fileSummary.addProvide( - {type: Type.CLOSURE, name: this.outputIds.googModuleId}); + fileSummary.addProvide({ + type: Type.CLOSURE, + name: this.outputIds.googModuleId, + }); fileSummary.addStrongRequire({type: Type.CLOSURE, name: 'goog'}); - fileSummary.addStrongRequire( - {type: Type.CLOSURE, name: this.srcIds.googModuleId}); + fileSummary.addStrongRequire({ + type: Type.CLOSURE, + name: this.srcIds.googModuleId, + }); + if (maybeDeclareLegacyNameCall) { + fileSummary.legacyNamespace = true; + } fileSummary.autochunk = isAutoChunk; fileSummary.moduleType = ModuleType.GOOG_MODULE; @@ -480,9 +530,9 @@ class Generator { const generatedFromComment = '// Generated from ' + this.srcIds.google3Path; const dependencyFileImports = lines( - `declare module 'ಠ_ಠ.clutz._dependencies' {`, - ` import '${this.srcIds.esModuleImportPath()}';`, - `}`, + `declare module 'ಠ_ಠ.clutz._dependencies' {`, + ` import '${this.srcIds.esModuleImportPath()}';`, + `}`, ); let clutzNamespaceDeclaration; @@ -491,52 +541,52 @@ class Generator { // In the case that tsmes was passed named exports. const clutzNamespace = this.srcIds.clutzNamespace(); - const clutzNamespaceReexports = - Array.from(this.tsmesBreakdown.googExports) - .map( - ([k, v]) => ` export import ${k} = ${clutzNamespace}.${v};`); + const clutzNamespaceReexports = Array.from( + this.tsmesBreakdown.googExports, + ).map(([k, v]) => ` export import ${k} = ${clutzNamespace}.${v};`); clutzNamespaceDeclaration = lines( - generatedFromComment, - `declare namespace ${this.outputIds.clutzNamespace()} {`, - ...clutzNamespaceReexports, - `}`, + generatedFromComment, + `declare namespace ${this.outputIds.clutzNamespace()} {`, + ...clutzNamespaceReexports, + `}`, ); googColonModuleDeclaration = lines( - generatedFromComment, - `declare module '${this.outputIds.clutzModuleId()}' {`, - ` import x = ${this.outputIds.clutzNamespace()};`, - ` export = x;`, - `}`, + generatedFromComment, + `declare module '${this.outputIds.clutzModuleId()}' {`, + ` import x = ${this.outputIds.clutzNamespace()};`, + ` export = x;`, + `}`, ); } else { // In the case that tsmes was passed a default export. clutzNamespaceDeclaration = lines( - generatedFromComment, - `declare namespace ಠ_ಠ.clutz {`, - ` export import ${this.outputIds.googModuleRewrittenId()} =`, - ` ${this.srcIds.clutzNamespace()}.${ - this.tsmesBreakdown.googExports};`, - `}`, + generatedFromComment, + `declare namespace ಠ_ಠ.clutz {`, + ` export import ${this.outputIds.googModuleRewrittenId()} =`, + ` ${this.srcIds.clutzNamespace()}.${ + this.tsmesBreakdown.googExports + };`, + `}`, ); googColonModuleDeclaration = lines( - generatedFromComment, - `declare module '${this.outputIds.clutzModuleId()}' {`, - ` import x = ${this.outputIds.clutzNamespace()};`, - ` export default x;`, - `}`, + generatedFromComment, + `declare module '${this.outputIds.clutzModuleId()}' {`, + ` import x = ${this.outputIds.clutzNamespace()};`, + ` export default x;`, + `}`, ); } return lines( - '/**', - ' * @fileoverview generator:ts_migration_exports_shim.ts', - ' */', - dependencyFileImports, - clutzNamespaceDeclaration, - googColonModuleDeclaration, - '', + '/**', + ' * @fileoverview generator:ts_migration_exports_shim.ts', + ' */', + dependencyFileImports, + clutzNamespaceDeclaration, + googColonModuleDeclaration, + '', ); } @@ -549,8 +599,9 @@ class Generator { } const outputStatements = [...this.src.statements]; - const tsmesIndex = - outputStatements.indexOf(this.tsmesBreakdown.callStatement); + const tsmesIndex = outputStatements.indexOf( + this.tsmesBreakdown.callStatement, + ); if (tsmesIndex < 0) { throw new Error('could not find tsmes call in file'); } @@ -560,10 +611,12 @@ class Generator { if (this.tsmesBreakdown.declareLegacyNamespaceStatement) { const dlnIndex = outputStatements.indexOf( - this.tsmesBreakdown.declareLegacyNamespaceStatement); + this.tsmesBreakdown.declareLegacyNamespaceStatement, + ); if (dlnIndex < 0) { throw new Error( - 'could not find the tsmes declareLegacyNamespace call in file'); + 'could not find the tsmes declareLegacyNamespace call in file', + ); } // Also delete the tsmes declareLegacyNamespace call. @@ -571,13 +624,18 @@ class Generator { } return ts.factory.updateSourceFile( - this.src, - ts.setTextRange( - ts.factory.createNodeArray(outputStatements), this.src.statements)); + this.src, + ts.setTextRange( + ts.factory.createNodeArray(outputStatements), + this.src.statements, + ), + ); } - private checkIsModuleExport(node: ts.Identifier, symbol: ts.Symbol|undefined): - boolean { + private checkIsModuleExport( + node: ts.Identifier, + symbol: ts.Symbol | undefined, + ): boolean { if (!symbol) { this.report(node, `could not resolve symbol of exported property`); } else if (this.mainExports.indexOf(symbol) === -1) { @@ -590,8 +648,12 @@ class Generator { private report(node: ts.Node, messageText: string): void { reportDiagnostic( - this.diagnostics, node, messageText, undefined, - ts.DiagnosticCategory.Error); + this.diagnostics, + node, + messageText, + undefined, + ts.DiagnosticCategory.Error, + ); } } @@ -602,10 +664,10 @@ class Generator { * exports like `exports = {Public: Local}`, it will be the Map {'Public' => * 'Local'}. */ -type GoogExports = string|Map; +type GoogExports = string | Map; -function lines(...lines: Array): string { - return lines.filter(line => line != null).join('\n'); +function lines(...lines: Array): string { + return lines.filter((line) => line != null).join('\n'); } interface TsmesCallBreakdown { @@ -623,8 +685,8 @@ interface TsmesCallBreakdown { */ class FileIdGroup { constructor( - readonly google3Path: string, - readonly googModuleId: string, + readonly google3Path: string, + readonly googModuleId: string, ) {} google3PathWithoutExtension(): string { @@ -649,7 +711,8 @@ class FileIdGroup { } function containsAtPintoModule(file: ts.SourceFile): boolean { - const leadingTrivia = - file.getFullText().substring(0, file.getLeadingTriviaWidth()); + const leadingTrivia = file + .getFullText() + .substring(0, file.getLeadingTriviaWidth()); return /\s@pintomodule\s/.test(leadingTrivia); } diff --git a/src/tsickle.ts b/src/tsickle.ts index 3b10f073a..8e46e3f52 100644 --- a/src/tsickle.ts +++ b/src/tsickle.ts @@ -12,7 +12,10 @@ import {AnnotatorHost} from './annotator_host'; import {assertAbsolute} from './cli_support'; import * as clutz from './clutz'; import {decoratorDownlevelTransformer} from './decorator_downlevel_transformer'; -import {transformDecoratorJsdoc, transformDecoratorsOutputForClosurePropertyRenaming} from './decorators'; +import { + transformDecoratorJsdoc, + transformDecoratorsOutputForClosurePropertyRenaming, +} from './decorators'; import {enumTransformer} from './enum_transformer'; import {generateExterns} from './externs'; import {transformFileoverviewCommentFactory} from './fileoverview_comment_transformer'; @@ -20,22 +23,23 @@ import * as googmodule from './googmodule'; import {jsdocTransformer, removeTypeAssertions} from './jsdoc_transformer'; import {ModulesManifest} from './modules_manifest'; import {namespaceTransformer} from './ns_transformer'; +import * as path from './path'; import {FileSummary, SummaryGenerationProcessorHost} from './summary'; import {isDtsFileName} from './transformer_util'; import * as tsmes from './ts_migration_exports_shim'; -import {makeTsickleDeclarationMarkerTransformerFactory} from './tsickle_declaration_marker'; // Exported for users as a default impl of pathToModuleName. export {pathToModuleName} from './cli_support'; // Retained here for API compatibility. export {getGeneratedExterns} from './externs'; -export {FileMap, ModulesManifest} from './modules_manifest'; -export {FileSummary, ModuleType, Symbol, Type} from './summary'; - -export interface TsickleHost extends googmodule.GoogModuleProcessorHost, - tsmes.TsMigrationExportsShimProcessorHost, - AnnotatorHost, - SummaryGenerationProcessorHost { +export {ModulesManifest, type FileMap} from './modules_manifest'; +export {FileSummary, ModuleType, Type, type Symbol} from './summary'; + +export interface TsickleHost + extends googmodule.GoogModuleProcessorHost, + tsmes.TsMigrationExportsShimProcessorHost, + AnnotatorHost, + SummaryGenerationProcessorHost { /** * Whether to downlevel decorators */ @@ -89,13 +93,13 @@ export interface TsickleHost extends googmodule.GoogModuleProcessorHost, generateSummary?: boolean; } - export function mergeEmitResults(emitResults: EmitResult[]): EmitResult { const diagnostics: ts.Diagnostic[] = []; let emitSkipped = true; const emittedFiles: string[] = []; - const externs: - {[fileName: string]: {output: string, moduleNamespace: string}} = {}; + const externs: { + [fileName: string]: {output: string; moduleNamespace: string}; + } = {}; const modulesManifest = new ModulesManifest(); const tsMigrationExportsShimFiles = new Map(); const fileSummaries = new Map(); @@ -133,7 +137,7 @@ export interface EmitResult extends ts.EmitResult { * externs.js files produced by tsickle, if any. module IDs are relative paths * from fileNameToModuleId. */ - externs: {[moduleId: string]: {output: string, moduleNamespace: string}}; + externs: {[moduleId: string]: {output: string; moduleNamespace: string}}; /** * Content for the generated files, keyed by their intended filename. @@ -153,27 +157,73 @@ export interface EmitTransformers { afterDeclarations?: ts.CustomTransformers['afterDeclarations']; } +function writeWithTsickleHeader( + writeFile: ts.WriteFileCallback, + rootDir: string, +) { + return ( + fileName: string, + content: string, + writeByteOrderMark: boolean, + onError: ((message: string) => void) | undefined, + sourceFiles: readonly ts.SourceFile[] | undefined, + data: ts.WriteFileCallbackData | undefined, + ) => { + if (fileName.endsWith('.d.ts')) { + // Add tsickle header. + const sources = sourceFiles?.map((sf) => + path.relative(rootDir, sf.fileName), + ); + content = `//!! generated by tsickle from ${ + sources?.join(' ') || '???' + }\n${content}`; + } + + writeFile( + fileName, + content, + writeByteOrderMark, + onError, + sourceFiles, + data, + ); + }; +} /** * @deprecated Exposed for backward compat with Angular. Use emit() instead. */ export function emitWithTsickle( - program: ts.Program, host: TsickleHost, tsHost: ts.CompilerHost, - tsOptions: ts.CompilerOptions, targetSourceFile?: ts.SourceFile, - writeFile?: ts.WriteFileCallback, cancellationToken?: ts.CancellationToken, - emitOnlyDtsFiles?: boolean, - customTransformers: EmitTransformers = {}): EmitResult { + program: ts.Program, + host: TsickleHost, + tsHost: ts.CompilerHost, + tsOptions: ts.CompilerOptions, + targetSourceFile?: ts.SourceFile, + writeFile?: ts.WriteFileCallback, + cancellationToken?: ts.CancellationToken, + emitOnlyDtsFiles?: boolean, + customTransformers: EmitTransformers = {}, +): EmitResult { return emit( - program, host, writeFile || tsHost.writeFile.bind(tsHost), - targetSourceFile, cancellationToken, emitOnlyDtsFiles, - customTransformers); + program, + host, + writeFile || tsHost.writeFile.bind(tsHost), + targetSourceFile, + cancellationToken, + emitOnlyDtsFiles, + customTransformers, + ); } export function emit( - program: ts.Program, host: TsickleHost, writeFile: ts.WriteFileCallback, - targetSourceFile?: ts.SourceFile, cancellationToken?: ts.CancellationToken, - emitOnlyDtsFiles?: boolean, - customTransformers: EmitTransformers = {}): EmitResult { + program: ts.Program, + host: TsickleHost, + writeFile: ts.WriteFileCallback, + targetSourceFile?: ts.SourceFile, + cancellationToken?: ts.CancellationToken, + emitOnlyDtsFiles?: boolean, + customTransformers: EmitTransformers = {}, +): EmitResult { for (const sf of program.getSourceFiles()) { assertAbsolute(sf.fileName); } @@ -186,14 +236,16 @@ export function emit( // so return an error here if it wasn't provided. return { emitSkipped: false, - diagnostics: [{ - category: ts.DiagnosticCategory.Error, - code: 0, - file: undefined, - start: undefined, - length: undefined, - messageText: 'TypeScript options must specify rootDir', - }], + diagnostics: [ + { + category: ts.DiagnosticCategory.Error, + code: 0, + file: undefined, + start: undefined, + length: undefined, + messageText: 'TypeScript options must specify rootDir', + }, + ], modulesManifest: new ModulesManifest(), externs: {}, tsMigrationExportsShimFiles: new Map(), @@ -204,77 +256,106 @@ export function emit( const modulesManifest = new ModulesManifest(); const tsMigrationExportsShimFiles = new Map(); const tsickleSourceTransformers: Array> = - []; + []; const fileSummaries = new Map(); tsickleSourceTransformers.push( - tsmes.createTsMigrationExportsShimTransformerFactory( - typeChecker, host, modulesManifest, tsickleDiagnostics, - tsMigrationExportsShimFiles, fileSummaries)); + tsmes.createTsMigrationExportsShimTransformerFactory( + typeChecker, + host, + modulesManifest, + tsickleDiagnostics, + tsMigrationExportsShimFiles, + fileSummaries, + ), + ); if (host.transformTypesToClosure) { // Only add @suppress {checkTypes} comments when also adding type // annotations. - tsickleSourceTransformers.push(transformFileoverviewCommentFactory( - tsOptions, tsickleDiagnostics, host.generateExtraSuppressions)); + tsickleSourceTransformers.push( + transformFileoverviewCommentFactory( + tsOptions, + tsickleDiagnostics, + host.generateExtraSuppressions, + ), + ); if (host.useDeclarationMergingTransformation) { - tsickleSourceTransformers.push(namespaceTransformer( - host, tsOptions, typeChecker, tsickleDiagnostics)); + tsickleSourceTransformers.push( + namespaceTransformer(host, tsOptions, typeChecker, tsickleDiagnostics), + ); } tsickleSourceTransformers.push( - jsdocTransformer(host, tsOptions, typeChecker, tsickleDiagnostics)); - tsickleSourceTransformers.push(enumTransformer(typeChecker)); + jsdocTransformer(host, tsOptions, typeChecker, tsickleDiagnostics), + ); + tsickleSourceTransformers.push(enumTransformer(host, typeChecker)); } if (host.transformDecorators) { tsickleSourceTransformers.push( - decoratorDownlevelTransformer(typeChecker, tsickleDiagnostics)); + decoratorDownlevelTransformer(typeChecker, tsickleDiagnostics), + ); } const tsTransformers: ts.CustomTransformers = { before: [ - ...(tsickleSourceTransformers || []) - .map(tf => skipTransformForSourceFileIfNeeded(host, tf)), + ...(tsickleSourceTransformers || []).map((tf) => + skipTransformForSourceFileIfNeeded(host, tf), + ), ...(customTransformers.beforeTs || []), ], after: [...(customTransformers.afterTs || [])], - afterDeclarations: [...(customTransformers.afterDeclarations || [])] + afterDeclarations: [...(customTransformers.afterDeclarations || [])], }; if (host.transformTypesToClosure) { // See comment on removeTypeAssertions. tsTransformers.before!.push(removeTypeAssertions()); } if (host.googmodule) { - tsTransformers.after!.push(googmodule.commonJsToGoogmoduleTransformer( - host, modulesManifest, typeChecker)); tsTransformers.after!.push( - transformDecoratorsOutputForClosurePropertyRenaming( - tsickleDiagnostics)); + googmodule.commonJsToGoogmoduleTransformer( + host, + modulesManifest, + typeChecker, + ), + ); + tsTransformers.after!.push( + transformDecoratorsOutputForClosurePropertyRenaming(tsickleDiagnostics), + ); tsTransformers.after!.push(transformDecoratorJsdoc()); } if (host.addDtsClutzAliases) { tsTransformers.afterDeclarations!.push( - clutz.makeDeclarationTransformerFactory(typeChecker, host)); + clutz.makeDeclarationTransformerFactory(typeChecker, host), + ); } - // Adds a marker to the top of tsickle-generated .d.ts files, should always go - // last - tsTransformers.afterDeclarations!.push( - makeTsickleDeclarationMarkerTransformerFactory(tsOptions)); - - const {diagnostics: tsDiagnostics, emitSkipped, emittedFiles} = program.emit( - targetSourceFile, writeFile, cancellationToken, emitOnlyDtsFiles, - tsTransformers); - - const externs: - {[fileName: string]: {output: string, moduleNamespace: string}} = {}; + const { + diagnostics: tsDiagnostics, + emitSkipped, + emittedFiles, + } = program.emit( + targetSourceFile, + writeWithTsickleHeader(writeFile, tsOptions.rootDir), + cancellationToken, + emitOnlyDtsFiles, + tsTransformers, + ); + + const externs: { + [fileName: string]: {output: string; moduleNamespace: string}; + } = {}; if (host.transformTypesToClosure) { - const sourceFiles = - targetSourceFile ? [targetSourceFile] : program.getSourceFiles(); + const sourceFiles = targetSourceFile + ? [targetSourceFile] + : program.getSourceFiles(); for (const sourceFile of sourceFiles) { const isDts = isDtsFileName(sourceFile.fileName); if (isDts && host.shouldSkipTsickleProcessing(sourceFile.fileName)) { continue; } - const {output, diagnostics, moduleNamespace} = - generateExterns(typeChecker, sourceFile, host); + const {output, diagnostics, moduleNamespace} = generateExterns( + typeChecker, + sourceFile, + host, + ); if (output) { externs[sourceFile.fileName] = {output, moduleNamespace}; } @@ -289,8 +370,10 @@ export function emit( // Warnings include stuff like "don't use @type in your jsdoc"; tsickle // warns and then fixes up the code to be Closure-compatible anyway. tsickleDiagnostics = tsickleDiagnostics.filter( - d => d.category === ts.DiagnosticCategory.Error || - !host.shouldIgnoreWarningsForPath(d.file!.fileName)); + (d) => + d.category === ts.DiagnosticCategory.Error || + !host.shouldIgnoreWarningsForPath(d.file!.fileName), + ); return { modulesManifest, @@ -304,8 +387,9 @@ export function emit( } function skipTransformForSourceFileIfNeeded( - host: TsickleHost, delegateFactory: ts.TransformerFactory): - ts.TransformerFactory { + host: TsickleHost, + delegateFactory: ts.TransformerFactory, +): ts.TransformerFactory { return (context: ts.TransformationContext) => { const delegate = delegateFactory(context); return (sourceFile: ts.SourceFile) => { diff --git a/src/tsickle_declaration_marker.ts b/src/tsickle_declaration_marker.ts deleted file mode 100644 index 76af06e42..000000000 --- a/src/tsickle_declaration_marker.ts +++ /dev/null @@ -1,38 +0,0 @@ -/** - * @license - * Copyright Google Inc. 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 * as ts from 'typescript'; - -import * as path from './path'; -import {createNotEmittedStatement, updateSourceFileNode} from './transformer_util'; - -/** Marks tsickle generated .d.ts's with a comment we can find later. */ -export function makeTsickleDeclarationMarkerTransformerFactory( - options: ts.CompilerOptions): ts.CustomTransformerFactory { - return (context: ts.TransformationContext): ts.CustomTransformer => { - return { - transformBundle(): ts.Bundle { - // The TS API wants declaration transfomers to be able to handle Bundle, - // but we don't support them within tsickle. - throw new Error('did not expect to transform a bundle'); - }, - transformSourceFile(sf: ts.SourceFile): ts.SourceFile { - if (!options.rootDir) return sf; - let syntheticFirstStatement = createNotEmittedStatement(sf); - syntheticFirstStatement = ts.addSyntheticTrailingComment( - syntheticFirstStatement, ts.SyntaxKind.SingleLineCommentTrivia, - `!! generated by tsickle from ${ - path.relative(options.rootDir, sf.fileName)}`, - /*hasTrailingNewLine=*/ true); - return updateSourceFileNode(sf, ts.factory.createNodeArray([ - syntheticFirstStatement, ...sf.statements - ])); - } - }; - }; -} diff --git a/src/type_translator.ts b/src/type_translator.ts index 324ec77e8..f90334505 100644 --- a/src/type_translator.ts +++ b/src/type_translator.ts @@ -10,7 +10,13 @@ import * as ts from 'typescript'; import {AnnotatorHost, moduleNameAsIdentifier} from './annotator_host'; import * as path from './path'; -import {getIdentifierText, hasModifierFlag, isAmbient, isMergedDeclaration, nodeIsInTransformedNs} from './transformer_util'; +import { + getIdentifierText, + hasModifierFlag, + isAmbient, + isMergedDeclaration, + nodeIsInTransformedNs, +} from './transformer_util'; /** * TypeScript allows you to write identifiers quoted, like: @@ -35,8 +41,9 @@ export function isValidClosurePropertyName(name: string): boolean { * Determines if fileName refers to a builtin lib.d.ts file. * This is a terrible hack but it mirrors a similar thing done in Clutz. */ -export function isDeclaredInBuiltinLibDTS(node: ts.Node|null| - undefined): boolean { +export function isDeclaredInBuiltinLibDTS( + node: ts.Node | null | undefined, +): boolean { const fileName = node?.getSourceFile()?.fileName; return !!fileName && fileName.match(/\blib\.(?:[^/]+\.)?d\.ts$/) != null; } @@ -45,13 +52,17 @@ export function isDeclaredInBuiltinLibDTS(node: ts.Node|null| * Returns true if the given node's source file is generated by Clutz, i.e. has * the magic Clutz header. */ -export function isDeclaredInClutzDts(node: ts.Node|null|undefined): boolean { +export function isDeclaredInClutzDts( + node: ts.Node | null | undefined, +): boolean { const sourceFile = node?.getSourceFile(); if (!sourceFile) return false; const clutz1Header = '//!! generated by clutz.'; const clutz2Header = '//!! generated by clutz2'; - return sourceFile.text.startsWith(clutz1Header) || - sourceFile.text.startsWith(clutz2Header); + return ( + sourceFile.text.startsWith(clutz1Header) || + sourceFile.text.startsWith(clutz2Header) + ); } /** @@ -72,9 +83,12 @@ export function typeValueConflictHandled(symbol: ts.Symbol) { // TODO(#1072): if the symbol comes from a tsickle-transpiled file, either .ts // or .d.ts with externs generation? then maybe we can emit it with name // mangling. - return symbol.declarations != null && - symbol.declarations.some( - n => isDeclaredInBuiltinLibDTS(n) || isDeclaredInClutzDts(n)); + return ( + symbol.declarations != null && + symbol.declarations.some( + (n) => isDeclaredInBuiltinLibDTS(n) || isDeclaredInClutzDts(n), + ) + ); } /** Returns a string describing the type for usage in debug logs. */ @@ -85,24 +99,37 @@ export function typeToDebugString(type: ts.Type): string { debugString += ` alias:${symbolToDebugString(type.aliasSymbol)}`; } if (type.aliasTypeArguments) { - debugString += ` aliasArgs:<${ - type.aliasTypeArguments.map(typeToDebugString).join(',')}>`; + debugString += ` aliasArgs:<${type.aliasTypeArguments + .map(typeToDebugString) + .join(',')}>`; } // Just the unique flags (powers of two). Declared in src/compiler/types.ts. const basicTypes: ts.TypeFlags[] = [ - ts.TypeFlags.Any, ts.TypeFlags.String, - ts.TypeFlags.Number, ts.TypeFlags.Boolean, - ts.TypeFlags.Enum, ts.TypeFlags.StringLiteral, - ts.TypeFlags.NumberLiteral, ts.TypeFlags.BooleanLiteral, - ts.TypeFlags.EnumLiteral, ts.TypeFlags.BigIntLiteral, - ts.TypeFlags.ESSymbol, ts.TypeFlags.UniqueESSymbol, - ts.TypeFlags.Void, ts.TypeFlags.Undefined, - ts.TypeFlags.Null, ts.TypeFlags.Never, - ts.TypeFlags.TypeParameter, ts.TypeFlags.Object, - ts.TypeFlags.Union, ts.TypeFlags.Intersection, - ts.TypeFlags.Index, ts.TypeFlags.IndexedAccess, - ts.TypeFlags.Conditional, ts.TypeFlags.Substitution, + ts.TypeFlags.Any, + ts.TypeFlags.String, + ts.TypeFlags.Number, + ts.TypeFlags.Boolean, + ts.TypeFlags.Enum, + ts.TypeFlags.StringLiteral, + ts.TypeFlags.NumberLiteral, + ts.TypeFlags.BooleanLiteral, + ts.TypeFlags.EnumLiteral, + ts.TypeFlags.BigIntLiteral, + ts.TypeFlags.ESSymbol, + ts.TypeFlags.UniqueESSymbol, + ts.TypeFlags.Void, + ts.TypeFlags.Undefined, + ts.TypeFlags.Null, + ts.TypeFlags.Never, + ts.TypeFlags.TypeParameter, + ts.TypeFlags.Object, + ts.TypeFlags.Union, + ts.TypeFlags.Intersection, + ts.TypeFlags.Index, + ts.TypeFlags.IndexedAccess, + ts.TypeFlags.Conditional, + ts.TypeFlags.Substitution, ]; for (const flag of basicTypes) { if ((type.flags & flag) !== 0) { @@ -146,8 +173,9 @@ export function typeToDebugString(type: ts.Type): string { /** Returns a string describing the symbol for usage in debug logs. */ export function symbolToDebugString(sym: ts.Symbol): string { - let debugString = - `${JSON.stringify(sym.name)} flags:0x${sym.flags.toString(16)}`; + let debugString = `${JSON.stringify(sym.name)} flags:0x${sym.flags.toString( + 16, + )}`; // Just the unique flags (powers of two). Declared in src/compiler/types.ts. const symbolFlags = [ @@ -191,14 +219,15 @@ export function symbolToDebugString(sym: ts.Symbol): string { * A module declared as "declare module 'external_name' {...}" (note the * quotes). */ -type AmbientModuleDeclaration = ts.ModuleDeclaration&{name: ts.StringLiteral}; +type AmbientModuleDeclaration = ts.ModuleDeclaration & {name: ts.StringLiteral}; /** * Searches for an ambient module declaration in the ancestors of declarations, * depth first, and returns the first or null if none found. */ -function getContainingAmbientModuleDeclaration(declarations: ts.Declaration[]): - AmbientModuleDeclaration|null { +function getContainingAmbientModuleDeclaration( + declarations: ts.Declaration[], +): AmbientModuleDeclaration | null { for (const declaration of declarations) { let parent = declaration.parent; while (parent) { @@ -218,8 +247,10 @@ function getContainingAmbientModuleDeclaration(declarations: ts.Declaration[]): function isTopLevelExternal(declarations: ts.Declaration[]) { for (const declaration of declarations) { if (declaration.parent === undefined) continue; - if (ts.isSourceFile(declaration.parent) && - ts.isExternalModule(declaration.parent)) { + if ( + ts.isSourceFile(declaration.parent) && + ts.isExternalModule(declaration.parent) + ) { return true; } } @@ -231,8 +262,10 @@ function isTopLevelExternal(declarations: ts.Declaration[]) { * of the same source file. */ function isDeclaredInSameFile(a: ts.Node, b: ts.Node) { - return ts.getOriginalNode(a).getSourceFile() === - ts.getOriginalNode(b).getSourceFile(); + return ( + ts.getOriginalNode(a).getSourceFile() === + ts.getOriginalNode(b).getSourceFile() + ); } /** @@ -285,14 +318,20 @@ export class TypeTranslator { * to mark a symbol as unknown. */ constructor( - private readonly host: AnnotatorHost, private readonly typeChecker: ts.TypeChecker, - private readonly node: ts.Node, private readonly pathUnknownSymbolsSet: Set, - private readonly symbolsToAliasedNames: Map, - private readonly symbolToNameCache: Map, - private readonly ensureSymbolDeclared: (sym: ts.Symbol) => void = () => {}) { + private readonly host: AnnotatorHost, + private readonly typeChecker: ts.TypeChecker, + private readonly node: ts.Node, + private readonly pathUnknownSymbolsSet: Set, + private readonly symbolsToAliasedNames: Map, + private readonly symbolToNameCache: Map, + private readonly ensureSymbolDeclared: (sym: ts.Symbol) => void = () => {}, + ) { // Normalize paths to not break checks on Windows. - this.pathUnknownSymbolsSet = - new Set(Array.from(this.pathUnknownSymbolsSet.values()).map(p => path.normalize(p))); + this.pathUnknownSymbolsSet = new Set( + Array.from(this.pathUnknownSymbolsSet.values()).map((p) => + path.normalize(p), + ), + ); } /** @@ -300,7 +339,7 @@ export class TypeTranslator { * @return a string representation of the symbol as a valid Closure type name, or `undefined` if * the type cannot be expressed (e.g. for anonymous types). */ - symbolToString(sym: ts.Symbol): string|undefined { + symbolToString(sym: ts.Symbol): string | undefined { // symbolToEntityName can be relatively expensive (40 ms calls with symbols in large namespaces // with many declarations, i.e. Clutz). symbolToString is idempotent per symbol and file, thus // we cache the entire operation to avoid the hit. @@ -310,7 +349,10 @@ export class TypeTranslator { // TypeScript resolves e.g. union types to their members, which can include symbols not declared // in the current scope. Ensure that all symbols found this way are actually declared. // This must happen before the alias check below, it might introduce a new alias for the symbol. - if (!this.isForExterns && (sym.flags & ts.SymbolFlags.TypeParameter) === 0) { + if ( + !this.isForExterns && + (sym.flags & ts.SymbolFlags.TypeParameter) === 0 + ) { this.ensureSymbolDeclared(sym); } @@ -320,21 +362,24 @@ export class TypeTranslator { // UseFullyQualifiedType). Declarations inside transformed namespaces have // been hoisted to the source file level, so we must resolve the type in // that context to get a qualified name. (b/239894067). - const context = nodeIsInTransformedNs(this.node) ? - this.node.getSourceFile() : - this.node; + const context = nodeIsInTransformedNs(this.node) + ? this.node.getSourceFile() + : this.node; const name = this.typeChecker.symbolToEntityName( - sym, ts.SymbolFlags.Type, context, - ts.NodeBuilderFlags.UseFullyQualifiedType | - ts.NodeBuilderFlags.UseOnlyExternalAliasing); + sym, + ts.SymbolFlags.Type, + context, + ts.NodeBuilderFlags.UseFullyQualifiedType | + ts.NodeBuilderFlags.UseOnlyExternalAliasing, + ); // name might be undefined, e.g. for anonymous classes. if (!name) return undefined; // TypeScript's symbolToEntityName returns a tree of Identifier objects. tsickle needs to // identify and alias specifiy symbols on it. The code below accesses the TypeScript @internal // symbol field on Identifier to do so. - type IdentifierWithSymbol = ts.Identifier&{symbol: ts.Symbol}; + type IdentifierWithSymbol = ts.Identifier & {symbol: ts.Symbol}; let str = ''; /** Recursively visits components of entity name and writes them to `str` above. */ const writeEntityWithSymbols = (name: ts.EntityName) => { @@ -390,16 +435,17 @@ export class TypeTranslator { * when referenced, they are written as just "X", which is not a top level declaration, so the * code below ignores them. */ - maybeGetMangledNamePrefix(symbol: ts.Symbol): string|'' { + maybeGetMangledNamePrefix(symbol: ts.Symbol): string | '' { if (!symbol.declarations) return ''; const declarations = symbol.declarations; - let ambientModuleDeclaration: AmbientModuleDeclaration|null = null; + let ambientModuleDeclaration: AmbientModuleDeclaration | null = null; // If the symbol is neither a top level declaration in an external module nor in an ambient // block, tsickle should not emit a prefix: it's either not an external symbol, or it's an // external symbol nested in a module, so it will need to be qualified, and the mangling prefix // goes on the qualifier. if (!isTopLevelExternal(declarations)) { - ambientModuleDeclaration = getContainingAmbientModuleDeclaration(declarations); + ambientModuleDeclaration = + getContainingAmbientModuleDeclaration(declarations); if (!ambientModuleDeclaration) return ''; } // At this point, the declaration is from an external module (possibly ambient). @@ -408,10 +454,15 @@ export class TypeTranslator { // (b) or the declaration must be an exported ambient declaration from the local file. // Ambient external declarations from other files are imported, so there's a local alias for the // module and no mangling is needed. - if (!this.isForExterns && - !declarations.every( - d => isDeclaredInSameFile(this.node, d) && isAmbient(d) && - hasModifierFlag(d, ts.ModifierFlags.Export))) { + if ( + !this.isForExterns && + !declarations.every( + (d) => + isDeclaredInSameFile(this.node, d) && + isAmbient(d) && + hasModifierFlag(d, ts.ModifierFlags.Export), + ) + ) { return ''; } // If from an ambient declaration, use and resolve the name from that. Otherwise, use the file @@ -426,9 +477,12 @@ export class TypeTranslator { context = ''; } const mangled = moduleNameAsIdentifier(this.host, fileName, context); - if (this.isForExterns && this.useInternalNamespaceForExterns && - !ambientModuleDeclaration && - isDeclaredInSameFile(this.node, declarations[0])) { + if ( + this.isForExterns && + this.useInternalNamespaceForExterns && + !ambientModuleDeclaration && + isDeclaredInSameFile(this.node, declarations[0]) + ) { return mangled + '_.'; } return mangled + '.'; @@ -440,7 +494,9 @@ export class TypeTranslator { // TypeAliases. The code below simply strips the prefix, the remaining type name then matches // Closure's type. private stripClutzNamespace(name: string) { - if (name.startsWith('ಠ_ಠ.clutz.')) return name.substring('ಠ_ಠ.clutz.'.length); + if (name.startsWith('ಠ_ಠ.clutz.')) { + return name.substring('ಠ_ಠ.clutz.'.length); + } return name; } @@ -473,11 +529,15 @@ export class TypeTranslator { for (const decl of type.symbol.declarations || []) { if (ts.isExternalModule(decl.getSourceFile())) isModule = true; if (decl.getSourceFile().isDeclarationFile) isAmbient = true; - let current: ts.Declaration|undefined = decl; + let current: ts.Declaration | undefined = decl; while (current) { - if (ts.getCombinedModifierFlags(current) & ts.ModifierFlags.Ambient) isAmbient = true; - if (current.kind === ts.SyntaxKind.ModuleDeclaration && - !isMergedDeclaration(current as ts.ModuleDeclaration)) { + if (ts.getCombinedModifierFlags(current) & ts.ModifierFlags.Ambient) { + isAmbient = true; + } + if ( + current.kind === ts.SyntaxKind.ModuleDeclaration && + !isMergedDeclaration(current as ts.ModuleDeclaration) + ) { isInUnsupportedNamespace = true; } current = current.parent as ts.Declaration | undefined; @@ -518,19 +578,6 @@ export class TypeTranslator { case ts.TypeFlags.BooleanLiteral: // See the note in translateUnion about booleans. return 'boolean'; - case ts.TypeFlags.Enum: - if (!type.symbol) { - this.warn(`EnumType without a symbol`); - return '?'; - } - // In TS5.0, enum types are represented as a union of enum literals. - // Each literal in the enum is a ts.TypeFlags.Enum. If the symbol is - // marked as being an enum member, translate it as the parent type (the - // name of the enum type). - if (type.symbol.flags & ts.SymbolFlags.EnumMember) { - return this.translateEnumLiteral(type); - } - return this.symbolToString(type.symbol) || '?'; case ts.TypeFlags.ESSymbol: case ts.TypeFlags.UniqueESSymbol: // ESSymbol indicates something typed symbol. @@ -549,7 +596,7 @@ export class TypeTranslator { case ts.TypeFlags.TypeParameter: // This is e.g. the T in a type like Foo. if (!type.symbol) { - this.warn(`TypeParameter without a symbol`); // should not happen (tm) + this.warn(`TypeParameter without a symbol`); // should not happen (tm) return '?'; } // In Closure, type parameters ("") are non-nullable by default, unlike references to @@ -570,8 +617,10 @@ export class TypeTranslator { case ts.TypeFlags.Conditional: case ts.TypeFlags.Substitution: // TODO(chinthoorie) : remove NonNullable logic after the TS 4.8 upgrade - if (type.aliasSymbol?.escapedName === 'NonNullable' && - isDeclaredInBuiltinLibDTS(type.aliasSymbol.declarations?.[0])) { + if ( + type.aliasSymbol?.escapedName === 'NonNullable' && + isDeclaredInBuiltinLibDTS(type.aliasSymbol.declarations?.[0]) + ) { let innerSymbol = undefined; // Pretend that NonNullable is really just T, as this doesn't // tend to affect optimization. T might not be a symbol we can @@ -584,8 +633,9 @@ export class TypeTranslator { const start = this.node.getStart(); const end = this.node.getEnd(); throw new Error( - `NonNullable missing expected type argument: - ${srcFile}(${start}-${end})`); + `NonNullable missing expected type argument: + ${srcFile}(${start}-${end})`, + ); // Fallthrough to returning '?' below } return innerSymbol ?? '?'; @@ -593,8 +643,10 @@ export class TypeTranslator { this.warn(`emitting ? for conditional/substitution type`); return '?'; case ts.TypeFlags.Intersection: - if (type.aliasSymbol?.escapedName === 'NonNullable' && - isDeclaredInBuiltinLibDTS(type.aliasSymbol.declarations?.[0])) { + if ( + type.aliasSymbol?.escapedName === 'NonNullable' && + isDeclaredInBuiltinLibDTS(type.aliasSymbol.declarations?.[0]) + ) { let innerSymbol = undefined; // Pretend that NonNullable is really just T, as this doesn't // tend to affect optimization. T might not be a symbol we can @@ -628,31 +680,38 @@ export class TypeTranslator { default: // Handle cases where multiple flags are set. + // Note: An enum is typed as a union with TS 5.0. This means + // multiple flags are present on enum types. `EnumLike` matches these. + if (type.flags & ts.TypeFlags.EnumLike) { + if (!type.symbol) { + this.warn(`EnumLike without a symbol`); + return '?'; + } + // In TS5.0, enum types are represented as a union of enum literals. + // Each literal in the enum is a ts.TypeFlags.EnumLiteral. If the + // symbol is marked as being an enum member, translate it by + // referencing the owning enum container. + if (type.symbol.flags & ts.SymbolFlags.EnumMember) { + return this.translateEnumLiteralMember(type); + } + + const typeStr = this.symbolToString(type.symbol); + return typeStr !== undefined ? `!${typeStr}` : '?'; + } + // Types with literal members are represented as // ts.TypeFlags.Union | [literal member] - // E.g. an enum typed value is a union type with the enum's members as its members. A - // boolean type is a union type with 'true' and 'false' as its members. + // E.g. A boolean type is a union type with 'true' and 'false' as its members. // Note also that in a more complex union, e.g. boolean|number, then it's a union of three // things (true|false|number) and ts.TypeFlags.Boolean doesn't show up at all. if (type.flags & ts.TypeFlags.Union) { - if (type.flags === (ts.TypeFlags.EnumLiteral | ts.TypeFlags.Union) && - type.symbol) { - // TS5.0 started to treat number enums as a union of its values. We - // want the name of the enum type if possible, not the union of the - // values. - const name = this.symbolToString(type.symbol); - return name ? '!' + name : - this.translateUnion(type as ts.UnionType); - } return this.translateUnion(type as ts.UnionType); } - if (type.flags & ts.TypeFlags.EnumLiteral) { - return this.translateEnumLiteral(type); - } - // The switch statement should have been exhaustive. - throw new Error(`unknown type flags ${type.flags} on ${typeToDebugString(type)}`); + throw new Error( + `unknown type flags ${type.flags} on ${typeToDebugString(type)}`, + ); } } @@ -664,13 +723,13 @@ export class TypeTranslator { // Union types that include literals (e.g. boolean, enum) can end up repeating the same Closure // type. For example: true | boolean will be translated to boolean | boolean. // Remove duplicates to produce types that read better. - const parts = new Set(types.map(t => this.translate(t))); + const parts = new Set(types.map((t) => this.translate(t))); // If it's a single element set, return the single member. if (parts.size === 1) return parts.values().next().value; return `(${Array.from(parts.values()).join('|')})`; } - private translateEnumLiteral(type: ts.Type): string { + private translateEnumLiteralMember(type: ts.Type): string { // Suppose you had: // enum EnumType { MEMBER } // then the type of "EnumType.MEMBER" is an enum literal (the thing passed to this function) @@ -692,7 +751,7 @@ export class TypeTranslator { // In that case, take the parent symbol of the enum member, which should be the enum // declaration. // tslint:disable-next-line:no-any working around a TS API deficiency. - const parent: ts.Symbol|undefined = (symbol as any)['parent']; + const parent: ts.Symbol | undefined = (symbol as any)['parent']; if (!parent) return '?'; symbol = parent; } @@ -742,7 +801,9 @@ export class TypeTranslator { // For user-defined types in this state, we may not have a Closure name // for the type. See the type_and_value test. if (!typeValueConflictHandled(type.symbol)) { - this.warn(`type/symbol conflict for ${type.symbol.name}, using {?} for now`); + this.warn( + `type/symbol conflict for ${type.symbol.name}, using {?} for now`, + ); return '?'; } } @@ -770,14 +831,17 @@ export class TypeTranslator { // fails to translate a more specific type before getting to // this point. throw new Error( - `reference loop in ${typeToDebugString(referenceType)} ${referenceType.flags}`); + `reference loop in ${typeToDebugString(referenceType)} ${ + referenceType.flags + }`, + ); } typeStr += this.translate(referenceType.target); // Translate can return '?' for a number of situations, e.g. type/value conflicts. // `?` is illegal syntax in Closure Compiler, so just return `?` here. if (typeStr === '?') return '?'; let typeArgs: readonly ts.Type[] = - this.typeChecker.getTypeArguments(referenceType) ?? []; + this.typeChecker.getTypeArguments(referenceType) ?? []; // Nested types have references to type parameters of all enclosing types. // Those are always at the beginning of the list of type arguments. const outerTypeParameters = referenceType.target.outerTypeParameters; @@ -796,8 +860,9 @@ export class TypeTranslator { // TypeScript's API is not correct. const maxExpectedTypeArgs = (localTypeParameters?.length ?? 0) + 1; if (typeArgs.length > maxExpectedTypeArgs) { - this.warn(`more type args (${typeArgs.length}) than expected (${ - maxExpectedTypeArgs})`); + this.warn( + `more type args (${typeArgs.length}) than expected (${maxExpectedTypeArgs})`, + ); } if (localTypeParameters && typeArgs.length > 0) { // Ignore 'this' type argument, which sometimes exists at the end. @@ -812,7 +877,7 @@ export class TypeTranslator { // `Node> !== Node`. if (t === referenceType) // return '?'; this.seenTypes.push(referenceType); - const params = typeArgs.map(t => this.translate(t)); + const params = typeArgs.map((t) => this.translate(t)); this.seenTypes.pop(); typeStr += `<${params.join(', ')}>`; } @@ -853,13 +918,33 @@ export class TypeTranslator { return '?'; } - if (type.symbol.flags & ts.SymbolFlags.Function || - type.symbol.flags & ts.SymbolFlags.Method) { - const sigs = - this.typeChecker.getSignaturesOfType(type, ts.SignatureKind.Call); + if ( + type.symbol.flags & ts.SymbolFlags.Function || + type.symbol.flags & ts.SymbolFlags.Method + ) { + const sigs = this.typeChecker.getSignaturesOfType( + type, + ts.SignatureKind.Call, + ); if (sigs.length === 1) { return this.signatureToClosure(sigs[0]); } + // Function has multiple declaration. Let's see if we can find a single + // declaration with an implementation. In this case all the other + // declarations are overloads and the implementation must have a + // signature that matches all of them. + const declWithBody = type.symbol.declarations?.filter( + (d): d is ts.FunctionLikeDeclaration => + isFunctionLikeDeclaration(d) && d.body != null, + ); + if (declWithBody?.length === 1) { + const sig = this.typeChecker.getSignatureFromDeclaration( + declWithBody[0], + ); + if (sig) { + return this.signatureToClosure(sig); + } + } this.warn('unhandled anonymous type with multiple call signatures'); return '?'; } @@ -882,7 +967,8 @@ export class TypeTranslator { const decl = ctors[0].declaration; if (!decl) { this.warn( - 'unhandled anonymous type with constructor signature but no declaration'); + 'unhandled anonymous type with constructor signature but no declaration', + ); return '?'; } if (decl.kind === ts.SyntaxKind.JSDocSignature) { @@ -892,14 +978,17 @@ export class TypeTranslator { // new (tee: T) is not supported by Closure, always set as ?. this.markTypeParameterAsUnknown( - this.symbolsToAliasedNames, decl.typeParameters); + this.symbolsToAliasedNames, + decl.typeParameters, + ); const params = this.convertParams(ctors[0], decl.parameters); - const paramsStr = params.length ? (', ' + params.join(', ')) : ''; + const paramsStr = params.length ? ', ' + params.join(', ') : ''; const constructedType = this.translate(ctors[0].getReturnType()); - let constructedTypeStr = constructedType[0] === '!' ? - constructedType.substring(1) : - constructedType; + let constructedTypeStr = + constructedType[0] === '!' + ? constructedType.substring(1) + : constructedType; // TypeScript also allows {} and unknown as return types of construct // signatures, though it will make sure that no primitive types are // returned. @@ -923,51 +1012,52 @@ export class TypeTranslator { return `function(new:${constructedTypeStr}${paramsStr})`; } - // members is an ES6 map, but the .d.ts defining it defined their own map - // type, so typescript doesn't believe that .keys() is iterable. - for (const field of ( - type.symbol.members.keys() as IterableIterator)) { - const fieldName = ts.unescapeLeadingUnderscores(field); - switch (field) { - case ts.InternalSymbolName.Call: - callable = true; - break; - case ts.InternalSymbolName.Index: - indexable = true; - break; - default: - if (!isValidClosurePropertyName(fieldName)) { - this.warn(`omitting inexpressible property name: ${field}`); - continue; - } - const member = type.symbol.members.get(field)!; - // optional members are handled by the type including |undefined in - // a union type. - const memberType = this.translate( - this.typeChecker.getTypeOfSymbolAtLocation(member, this.node)); - fields.push(`${fieldName}: ${memberType}`); - break; + callable = type.getCallSignatures().length > 0; + indexable = + type.getNumberIndexType() !== undefined || + type.getStringIndexType() !== undefined; + for (const prop of type.getProperties()) { + const propName = ts.symbolName(prop); + if (!isValidClosurePropertyName(propName)) { + // Internal symbol names may have a suffix @nnnn which seems to + // change randomly. That broke at least one golden test, which + // includes these warnings. Strip the suffix. + const sanitizedName = propName.replace(/@[0-9]*$/, ''); + this.warn(`omitting inexpressible property name: ${sanitizedName}`); + continue; } + // optional members are handled by the type including |undefined in + // a union type. + const propType = this.translate( + this.typeChecker.getTypeOfSymbolAtLocation(prop, this.node), + ); + fields.push(`${propName}: ${propType}`); } // Try to special-case plain key-value objects and functions. if (fields.length === 0) { if (callable && !indexable) { // A function type. - const sigs = - this.typeChecker.getSignaturesOfType(type, ts.SignatureKind.Call); + const sigs = this.typeChecker.getSignaturesOfType( + type, + ts.SignatureKind.Call, + ); if (sigs.length === 1) { return this.signatureToClosure(sigs[0]); } } else if (indexable && !callable) { // A plain key-value map type. let keyType = 'string'; - let valType = - this.typeChecker.getIndexTypeOfType(type, ts.IndexKind.String); + let valType = this.typeChecker.getIndexTypeOfType( + type, + ts.IndexKind.String, + ); if (!valType) { keyType = 'number'; - valType = - this.typeChecker.getIndexTypeOfType(type, ts.IndexKind.Number); + valType = this.typeChecker.getIndexTypeOfType( + type, + ts.IndexKind.Number, + ); } if (!valType) { this.warn('unknown index key type'); @@ -1017,16 +1107,22 @@ export class TypeTranslator { this.warn('signature with JSDoc declaration'); return 'Function'; } - this.markTypeParameterAsUnknown(this.symbolsToAliasedNames, sig.declaration.typeParameters); + this.markTypeParameterAsUnknown( + this.symbolsToAliasedNames, + sig.declaration.typeParameters, + ); let typeStr = `function(`; - let paramDecls: ReadonlyArray = sig.declaration.parameters || []; + let paramDecls: ReadonlyArray = + sig.declaration.parameters || []; const maybeThisParam = paramDecls[0]; // Oddly, the this type shows up in paramDecls, but not in the type's parameters. // Handle it here and then pass paramDecls down without its first element. if (maybeThisParam && maybeThisParam.name.getText() === 'this') { if (maybeThisParam.type) { - const thisType = this.typeChecker.getTypeAtLocation(maybeThisParam.type); + const thisType = this.typeChecker.getTypeAtLocation( + maybeThisParam.type, + ); typeStr += `this: (${this.translate(thisType)})`; if (paramDecls.length > 1) typeStr += ', '; } else { @@ -1038,7 +1134,9 @@ export class TypeTranslator { const params = this.convertParams(sig, paramDecls); typeStr += `${params.join(', ')})`; - const retType = this.translate(this.typeChecker.getReturnTypeOfSignature(sig)); + const retType = this.translate( + this.typeChecker.getReturnTypeOfSignature(sig), + ); if (retType) { typeStr += `: ${retType}`; } @@ -1051,8 +1149,10 @@ export class TypeTranslator { * match the signature parameters (e.g. there might be an additional this parameter). This * difference is handled by the caller, as is converting the "this" parameter. */ - private convertParams(sig: ts.Signature, paramDecls: ReadonlyArray): - string[] { + private convertParams( + sig: ts.Signature, + paramDecls: ReadonlyArray, + ): string[] { const paramTypes: string[] = []; for (let i = 0; i < sig.parameters.length; i++) { const param = sig.parameters[i]; @@ -1060,8 +1160,10 @@ export class TypeTranslator { // Parameters are optional if either marked '?' or if have a default const optional = !!paramDecl.questionToken || !!paramDecl.initializer; const varArgs = !!paramDecl.dotDotDotToken; - const paramType = - this.typeChecker.getTypeOfSymbolAtLocation(param, this.node); + const paramType = this.typeChecker.getTypeOfSymbolAtLocation( + param, + this.node, + ); let typeStr: string; if (varArgs) { // When translating (...x: number[]) into {...number}, remove the array. @@ -1105,8 +1207,9 @@ export class TypeTranslator { * @param decls the declarations whose symbols should be marked as unknown. */ markTypeParameterAsUnknown( - unknownSymbolsMap: Map, - decls: ReadonlyArray|undefined) { + unknownSymbolsMap: Map, + decls: ReadonlyArray | undefined, + ) { if (!decls || !decls.length) return; for (const tpd of decls) { const sym = this.typeChecker.getSymbolAtLocation(tpd.name); @@ -1120,17 +1223,19 @@ export class TypeTranslator { } /** @return true if sym should always have type {?}. */ -export function isAlwaysUnknownSymbol(pathUnknownSymbolsSet: Set|undefined, symbol: ts.Symbol) { +export function isAlwaysUnknownSymbol( + pathUnknownSymbolsSet: Set | undefined, + symbol: ts.Symbol, +) { if (pathUnknownSymbolsSet === undefined) return false; // Some builtin types, such as {}, get represented by a symbol that has no declarations. if (symbol.declarations === undefined) return false; - return symbol.declarations.every(n => { + return symbol.declarations.every((n) => { const fileName = path.normalize(n.getSourceFile().fileName); return pathUnknownSymbolsSet.has(fileName); }); } - /** * Extracts the contained element type from a rest parameter. * @@ -1143,9 +1248,13 @@ export function isAlwaysUnknownSymbol(pathUnknownSymbolsSet: Set|undefin * function f(...xs: T) */ export function restParameterType( - typeChecker: ts.TypeChecker, type: ts.Type): ts.Type|undefined { - if (((type.flags & ts.TypeFlags.Object) === 0) && - (type.flags & ts.TypeFlags.TypeParameter)) { + typeChecker: ts.TypeChecker, + type: ts.Type, +): ts.Type | undefined { + if ( + (type.flags & ts.TypeFlags.Object) === 0 && + type.flags & ts.TypeFlags.TypeParameter + ) { // function f(...ts: T) has the Array type on the type // parameter constraint, not on the parameter itself. Resolve it. const baseConstraint = typeChecker.getBaseConstraintOfType(type); @@ -1173,3 +1282,17 @@ export function restParameterType( } return typeArgs[0]; } + +function isFunctionLikeDeclaration( + node: ts.Node, +): node is ts.FunctionLikeDeclaration { + return ( + ts.isFunctionDeclaration(node) || + ts.isMethodDeclaration(node) || + ts.isConstructorDeclaration(node) || + ts.isGetAccessorDeclaration(node) || + ts.isSetAccessorDeclaration(node) || + ts.isFunctionExpression(node) || + ts.isArrowFunction(node) + ); +} diff --git a/test/closure.ts b/test/closure.ts index 77c24bd77..167a336cb 100644 --- a/test/closure.ts +++ b/test/closure.ts @@ -33,7 +33,7 @@ export interface Options { * repeated form expected by the compiler. */ export interface Flags { - [flag: string]: boolean|string|string[]; + [flag: string]: boolean | string | string[]; } /** The type of compilation results, containing exit code and console output. */ @@ -53,7 +53,8 @@ function flagsToArgs(flags: Flags): string[] { args.push(`--${flag}`); } else if (typeof value === 'string') { args.push(`--${flag}=${value}`); - } else { // string[] + } else { + // string[] for (const val of value) { args.push(`--${flag}=${val}`); } @@ -83,16 +84,16 @@ export function compile(options: Options, flags: Flags): Promise { let stderr = ''; if (!compilerProcess.stdout) throw new Error('missing stdout'); if (!compilerProcess.stderr) throw new Error('missing stderr'); - compilerProcess.stdout.on('data', data => { + compilerProcess.stdout.on('data', (data) => { stdout += data; }); - compilerProcess.stderr.on('data', data => { + compilerProcess.stderr.on('data', (data) => { stderr += data; }); - compilerProcess.on('close', exitCode => { + compilerProcess.on('close', (exitCode) => { resolve({stdout, stderr, exitCode: exitCode || 0}); }); - compilerProcess.on('error', err => { + compilerProcess.on('error', (err) => { reject(err); }); }); diff --git a/test/decorator_downlevel_transformer_test.ts b/test/decorator_downlevel_transformer_test.ts index 8e6561df3..2a3f6151c 100644 --- a/test/decorator_downlevel_transformer_test.ts +++ b/test/decorator_downlevel_transformer_test.ts @@ -17,7 +17,6 @@ import * as testSupport from './test_support'; const testCaseFileName = 'testcase.ts'; - describe('decorator_downlevel_transformer', () => { beforeEach(() => { testSupport.addDiffMatchers(); @@ -30,15 +29,18 @@ describe('decorator_downlevel_transformer', () => { // annotator without the compiler complaining we didn't actually provide a // value [ - path.join(rootDir, 'bar.d.ts'), `declare module "bar" { + path.join(rootDir, 'bar.d.ts'), + `declare module "bar" { export class BarService {} type FakeDecorator = any; - }` - ] + }`, + ], ]); - const {program} = - testSupport.createProgramAndHost(sources, testSupport.compilerOptions); + const {program} = testSupport.createProgramAndHost( + sources, + testSupport.compilerOptions, + ); if (!allowErrors) { const diagnostics = ts.getPreEmitDiagnostics(program); testSupport.expectDiagnosticsEmpty(diagnostics); @@ -62,8 +64,14 @@ describe('decorator_downlevel_transformer', () => { const files = new Map(); const {diagnostics} = tsickle.emit( - program, transformerHost, (path, contents) => {}, undefined, undefined, - undefined, {beforeTs: [createAstPrintingTransform(files)]}); + program, + transformerHost, + (path, contents) => {}, + undefined, + undefined, + undefined, + {beforeTs: [createAstPrintingTransform(files)]}, + ); if (!allowErrors) { testSupport.expectDiagnosticsEmpty(diagnostics); @@ -71,7 +79,7 @@ describe('decorator_downlevel_transformer', () => { return { output: files.get(path.join(rootDir, testCaseFileName))!, - diagnostics + diagnostics, }; } @@ -128,9 +136,8 @@ class Foo { `); }); - it('transforms decorated classes with function expression annotation declaration', - () => { - expectTranslated(` + it('transforms decorated classes with function expression annotation declaration', () => { + expectTranslated(` /** @Annotation */ function Test(t: any) {}; @Test class Foo { @@ -145,11 +152,10 @@ class Foo { ]; } `); - }); + }); - it('transforms decorated classes with an exported annotation declaration', - () => { - expectTranslated(` + it('transforms decorated classes with an exported annotation declaration', () => { + expectTranslated(` import {FakeDecorator} from 'bar'; /** @Annotation */ export let Test: FakeDecorator; @Test @@ -165,7 +171,7 @@ class Foo { ]; } `); - }); + }); it('accepts various complicated decorators', () => { expectTranslated(` @@ -322,9 +328,8 @@ class Foo { `); }); - it('stores non annotated parameters if the class has at least one decorator', - () => { - expectTranslated(` + it('stores non annotated parameters if the class has at least one decorator', () => { + expectTranslated(` import {BarService, FakeDecorator} from 'bar'; /** @Annotation */ let Test1: FakeDecorator; @Test1() @@ -353,7 +358,7 @@ class Foo { ]; } `); - }); + }); it('handles complex ctor parameters', () => { expectTranslated(` @@ -547,18 +552,19 @@ class ClassWithDecorators { it('errors on weird class members', () => { const {diagnostics} = translate( - ` + ` /** @Annotation */ let Test1: Function; let param: any; class Foo { @Test1('somename') [param]() {} }`, - true /* allow errors */); + true /* allow errors */, + ); - expect(testSupport.formatDiagnostics(diagnostics)) - .toBe( - 'testcase.ts(5,3): error TS0: cannot process decorators on strangely named method\n'); + expect(testSupport.formatDiagnostics(diagnostics)).toBe( + 'testcase.ts(5,3): error TS0: cannot process decorators on strangely named method\n', + ); }); it('avoids mangling code relying on ASI', () => { expectTranslated(` diff --git a/test/e2e_closure_test.ts b/test/e2e_closure_test.ts index 6446a1252..889752382 100644 --- a/test/e2e_closure_test.ts +++ b/test/e2e_closure_test.ts @@ -20,43 +20,43 @@ describe('golden file tests', () => { }); it('compile with Closure', (done) => { // Declaration tests do not produce .js files. - const tests = goldenTests().filter(t => !t.isDeclarationTest); + const tests = goldenTests().filter((t) => !t.isDeclarationTest); // Collect all JavaScript outputs generated from .ts files. - const goldenJs = ([] as string[]).concat(...tests.map(t => t.jsPaths())); + const goldenJs = ([] as string[]).concat(...tests.map((t) => t.jsPaths())); // Manually add extra .js files that are not generated from .ts. Several tests include `.d.ts` // files describing symbols defined in JavaScript, e.g. for `goog:...` style Clutz imports. // These definitions must be included here so that Closure Compiler sees all definitions. goldenJs.push( - 'src/closure_externs.js', - 'third_party/tslib/externs.js', - 'third_party/tslib/tslib.js', - 'test/googbase_fake.js', - 'test_files/augment/shim.js', - 'test_files/clutz_type_value.no_externs/type_value.js', - 'test_files/clutz.no_externs/default_export.js', - 'test_files/clutz.no_externs/some_name_space.js', - 'test_files/clutz.no_externs/some_other.js', - 'test_files/declare_export_dts/shim.js', - 'test_files/declare_import/closure_default_export.js', - 'test_files/declare_import/closure_named_export.js', - 'test_files/declare_import/exporting.js', - 'test_files/declare/shim.js', - 'test_files/direct_externs_type_reference/shim.js', - 'test_files/export_equals.shim/shim.js', - 'test_files/fake_goog_reflect.js', - 'test_files/googmodule_esmodule.declaration.no_externs/some_module.js', - 'test_files/googmodule_esmodule.no_externs/some_module.js', - 'test_files/import_by_path.no_externs/jsprovides.js', - 'test_files/import_equals/exporter.js', - 'test_files/import_from_goog.no_externs/closure_LegacyModule.js', - 'test_files/import_from_goog.no_externs/closure_Module.js', - 'test_files/import_from_goog.no_externs/closure_OtherModule.js', - 'test_files/import_from_goog.no_externs/transitive_type.js', - 'test_files/no_dollar_type_reference.no_externs/closure_x.js', - 'test_files/no_dollar_type_reference.no_externs/closure_y.js', - 'test_files/type_propaccess.no_externs/nested_clazz.js', + 'src/closure_externs.js', + 'third_party/tslib/externs.js', + 'third_party/tslib/tslib.js', + 'test/googbase_fake.js', + 'test_files/augment/shim.js', + 'test_files/clutz_type_value.no_externs/type_value.js', + 'test_files/clutz.no_externs/default_export.js', + 'test_files/clutz.no_externs/some_name_space.js', + 'test_files/clutz.no_externs/some_other.js', + 'test_files/declare_export_dts/shim.js', + 'test_files/declare_import/closure_default_export.js', + 'test_files/declare_import/closure_named_export.js', + 'test_files/declare_import/exporting.js', + 'test_files/declare/shim.js', + 'test_files/direct_externs_type_reference/shim.js', + 'test_files/export_equals.shim/shim.js', + 'test_files/fake_goog_reflect.js', + 'test_files/googmodule_esmodule.declaration.no_externs/some_module.js', + 'test_files/googmodule_esmodule.no_externs/some_module.js', + 'test_files/import_by_path.no_externs/jsprovides.js', + 'test_files/import_equals/exporter.js', + 'test_files/import_from_goog.no_externs/closure_LegacyModule.js', + 'test_files/import_from_goog.no_externs/closure_Module.js', + 'test_files/import_from_goog.no_externs/closure_OtherModule.js', + 'test_files/import_from_goog.no_externs/transitive_type.js', + 'test_files/no_dollar_type_reference.no_externs/closure_x.js', + 'test_files/no_dollar_type_reference.no_externs/closure_y.js', + 'test_files/type_propaccess.no_externs/nested_clazz.js', ); - const externs = tests.map(t => t.externsPath()).filter(fs.existsSync); + const externs = tests.map((t) => t.externsPath()).filter(fs.existsSync); const startTime = Date.now(); const total = goldenJs.length; if (!total) throw new Error('No JS files in ' + JSON.stringify(goldenJs)); @@ -122,27 +122,34 @@ describe('golden file tests', () => { // if you have any async expression in the function body whose result // is unused(!). - closure.compile({}, CLOSURE_FLAGS) - .then(({exitCode, stdout, stderr}) => { - const durationMs = Date.now() - startTime; - console.error('Closure compilation of', total, 'files done after', durationMs, 'ms'); - // Some problems only print as warnings, without a way to promote them to errors. - // So treat any stderr output as a reason to fail the test. - // In JDK 9+, closure-compiler prints warnigns about unsafe access via reflection from - // com.google.protobuf.UnsafeUtil. Ignore those. - stderr = stderr.replace(/WARNING: .*\n/g, ''); - if (exitCode !== 0 || stderr.length > 0) { - // expect() with a message abbreviates the text, so just emit - // everything here. - console.error(stderr); - fail('Closure Compiler warned or errored'); - } - }) - .catch(err => { - expect(err).toBe(null); - }) - .then(() => { - done(); - }); + closure + .compile({}, CLOSURE_FLAGS) + .then(({exitCode, stdout, stderr}) => { + const durationMs = Date.now() - startTime; + console.error( + 'Closure compilation of', + total, + 'files done after', + durationMs, + 'ms', + ); + // Some problems only print as warnings, without a way to promote them to errors. + // So treat any stderr output as a reason to fail the test. + // In JDK 9+, closure-compiler prints warnigns about unsafe access via reflection from + // com.google.protobuf.UnsafeUtil. Ignore those. + stderr = stderr.replace(/WARNING: .*\n/g, ''); + if (exitCode !== 0 || stderr.length > 0) { + // expect() with a message abbreviates the text, so just emit + // everything here. + console.error(stderr); + fail('Closure Compiler warned or errored'); + } + }) + .catch((err) => { + expect(err).toBe(null); + }) + .then(() => { + done(); + }); }, 60000 /* ms timeout */); }); diff --git a/test/golden_tsickle_test.ts b/test/golden_tsickle_test.ts index e37775f0b..4dd3aaf82 100644 --- a/test/golden_tsickle_test.ts +++ b/test/golden_tsickle_test.ts @@ -34,13 +34,18 @@ const UPDATE_GOLDENS = !!process.env['UPDATE_GOLDENS']; * @param goldenPath The absolute path to the matching golden file. */ function compareAgainstGolden( - output: string|null, goldenPath: string, test: testSupport.GoldenFileTest) { - let golden: string|null = null; + output: string | null, + goldenPath: string, + test: testSupport.GoldenFileTest, +) { + let golden: string | null = null; try { golden = fs.readFileSync(goldenPath, 'utf-8'); } catch (e: unknown) { - if ((e as {code: string}).code === 'ENOENT' && - (UPDATE_GOLDENS || output === null)) { + if ( + (e as {code: string}).code === 'ENOENT' && + (UPDATE_GOLDENS || output === null) + ) { // A missing file is acceptable if we're updating goldens or // if we're expected to produce no output. } else { @@ -55,9 +60,9 @@ function compareAgainstGolden( if (UPDATE_GOLDENS && output !== golden) { // Ensure goldenPath refers to the path within the original source root, and // not some testing environment symlink. - goldenPath = fs.lstatSync(goldenPath).isSymbolicLink() ? - fs.readlinkSync(goldenPath) : - goldenPath; + goldenPath = fs.lstatSync(goldenPath).isSymbolicLink() + ? fs.readlinkSync(goldenPath) + : goldenPath; console.log('Updating golden file for', goldenPath); if (output !== null) { fs.writeFileSync(goldenPath, output, {encoding: 'utf-8'}); @@ -65,7 +70,8 @@ function compareAgainstGolden( // We don't delete the file automatically in case the existence of the // file triggers an assertion. throw new Error( - `Expected ${goldenPath} to be absent. Please delete it manually.`); + `Expected ${goldenPath} to be absent. Please delete it manually.`, + ); } } else { expect(output).withContext(`${goldenPath}`).toEqualWithDiff(golden!); @@ -115,8 +121,10 @@ testFn('golden tests', () => { // Test that creating declarations does not throw declaration: emitDeclarations, }; - const {program, host: tsHost} = - testSupport.createProgramAndHost(tsSources, tsCompilerOptions); + const {program, host: tsHost} = testSupport.createProgramAndHost( + tsSources, + tsCompilerOptions, + ); { const diagnostics = ts.getPreEmitDiagnostics(program); if (diagnostics.length) { @@ -134,11 +142,13 @@ testFn('golden tests', () => { // test_files/ignored_ambient_external_module/ignored.d.ts unknownTypesPaths: new Set([ path.join( - tsCompilerOptions.rootDir!, - 'test_files/jsdoc_types/nevertyped.ts'), + tsCompilerOptions.rootDir!, + 'test_files/jsdoc_types/nevertyped.ts', + ), path.join( - tsCompilerOptions.rootDir!, - 'test_files/ignored_ambient_external_module/ignored.d.ts'), + tsCompilerOptions.rootDir!, + 'test_files/ignored_ambient_external_module/ignored.d.ts', + ), ]), transformDecorators: !test.isPureTransformerTest, transformTypesToClosure: !test.isPureTransformerTest, @@ -149,7 +159,7 @@ testFn('golden tests', () => { // test.isDeclarationTest is true). addDtsClutzAliases: true, useDeclarationMergingTransformation: - test.isNamespaceTransformationEnabled, + test.isNamespaceTransformationEnabled, untyped: test.isUntypedTest, provideExternalModuleDtsNamespace: !test.hasShim, logWarning: (diag: ts.Diagnostic) => { @@ -159,8 +169,12 @@ testFn('golden tests', () => { shouldIgnoreWarningsForPath: () => false, pathToModuleName: (context, importPath) => { return testSupport.pathToModuleName( - tsCompilerOptions.rootDir!, context, importPath, - tsCompilerOptions, tsHost); + tsCompilerOptions.rootDir!, + context, + importPath, + tsCompilerOptions, + tsHost, + ); }, fileNameToModuleId: (fileName) => { assertAbsolute(fileName); @@ -175,7 +189,7 @@ testFn('golden tests', () => { }; const tscOutput = new Map(); - const targetSource: ts.SourceFile|undefined = undefined; + const targetSource: ts.SourceFile | undefined = undefined; /** Returns true if we test the emitted output for the given path. */ function shouldCompareOutputToGolden(fileName: string): boolean { @@ -195,38 +209,44 @@ testFn('golden tests', () => { } const {diagnostics, externs, tsMigrationExportsShimFiles} = tsickle.emit( - program, transformerHost, (fileName: string, data: string) => { - if (shouldCompareOutputToGolden(fileName)) { - tscOutput.set(fileName, data); - } - }, targetSource); + program, + transformerHost, + (fileName: string, data: string) => { + if (shouldCompareOutputToGolden(fileName)) { + tscOutput.set(fileName, data); + } + }, + targetSource, + ); for (const d of diagnostics) { allDiagnostics.add(d); } const diagnosticsByFile = new Map(); for (const d of allDiagnostics) { - const fileName = d.file?.fileName ?? - 'unhandled diagnostic with no file name attached'; + const fileName = + d.file?.fileName ?? 'unhandled diagnostic with no file name attached'; let diags = diagnosticsByFile.get(fileName); - if (!diags) diagnosticsByFile.set(fileName, diags = []); + if (!diags) diagnosticsByFile.set(fileName, (diags = [])); diags.push(d); } if (!test.isDeclarationTest) { const sortedPaths = test.jsPaths().sort(); const actualPaths = Array.from(tscOutput.keys()) - .map(p => p.replace(/^\.\//, '')) - .sort(); + .map((p) => p.replace(/^\.\//, '')) + .sort(); expect(sortedPaths) - .withContext(`${test.jsPaths} vs ${actualPaths}`) - .toEqual(actualPaths); + .withContext(`${test.jsPaths} vs ${actualPaths}`) + .toEqual(actualPaths); } - let allExterns: string|null = null; + let allExterns: string | null = null; if (!test.name.endsWith('.no_externs')) { // Concatenate externs for the files that are in this tests sources (but // not other, shared .d.ts files). - const filteredExterns: {[k: string]: { output: string; moduleNamespace: string; }} = {}; + const filteredExterns: { + [k: string]: {output: string; moduleNamespace: string}; + } = {}; let anyExternsGenerated = false; for (const fileName of tsSources.keys()) { if (externs[fileName]) { @@ -235,8 +255,10 @@ testFn('golden tests', () => { } } if (anyExternsGenerated) { - allExterns = - getGeneratedExterns(filteredExterns, tsCompilerOptions.rootDir!); + allExterns = getGeneratedExterns( + filteredExterns, + tsCompilerOptions.rootDir!, + ); } } compareAgainstGolden(allExterns, test.externsPath(), test); @@ -244,27 +266,25 @@ testFn('golden tests', () => { for (const absFilename of test.tsMigrationExportsShimPaths()) { const relativeFilename = rootDirsRelative(absFilename); const exportShim = - tsMigrationExportsShimFiles.get(relativeFilename) ?? null; - compareAgainstGolden( - exportShim, - absFilename, - test, - ); + tsMigrationExportsShimFiles.get(relativeFilename) ?? null; + compareAgainstGolden(exportShim, absFilename, test); } for (const [outputPath, output] of tscOutput) { - const tsPath = - outputPath.replace(/\.js$|\.d.ts$/, '.ts').replace(/^\.\//, ''); + const tsPath = outputPath + .replace(/\.js$|\.d.ts$/, '.ts') + .replace(/^\.\//, ''); const diags = diagnosticsByFile.get(tsPath); diagnosticsByFile.delete(tsPath); let out = output; if (diags) { - out = testSupport.formatDiagnostics(diags) - .trim() - .split('\n') - .map(line => `// ${line}\n`) - .join('') + - out; + out = + testSupport + .formatDiagnostics(diags) + .trim() + .split('\n') + .map((line) => `// ${line}\n`) + .join('') + out; } compareAgainstGolden(out, outputPath, test); } @@ -276,14 +296,16 @@ testFn('golden tests', () => { continue; } expect(testSupport.formatDiagnostics(diags)) - .withContext(`unhandled diagnostics for ${path}`) - .toBe(''); + .withContext(`unhandled diagnostics for ${path}`) + .toBe(''); } } if (dtsDiags.length) { compareAgainstGolden( - testSupport.formatDiagnostics(dtsDiags), - path.join(test.root, 'dtsdiagnostics.txt'), test); + testSupport.formatDiagnostics(dtsDiags), + path.join(test.root, 'dtsdiagnostics.txt'), + test, + ); } }); } diff --git a/test/googmodule_test.ts b/test/googmodule_test.ts index a258a7bcf..5a2ccdda3 100644 --- a/test/googmodule_test.ts +++ b/test/googmodule_test.ts @@ -13,6 +13,7 @@ import * as googmodule from '../src/googmodule'; import {ModulesManifest} from '../src/modules_manifest'; import * as testSupport from './test_support'; +import {outdent} from './test_support'; interface ResolvedNamespace { name: string; @@ -25,67 +26,69 @@ interface ProcessOptions { } function processES5( - fileName: string, content: string, - {isES5 = true, pathToNamespaceMap}: ProcessOptions = {}) { + fileName: string, + content: string, + {isES5 = true, pathToNamespaceMap}: ProcessOptions = {}, +) { const options = Object.assign({}, testSupport.compilerOptions); options.outDir = 'fakeOutDir'; const rootDir = options.rootDir!; options.target = isES5 ? ts.ScriptTarget.ES5 : options.target; fileName = path.join(rootDir, fileName); - const tsHost = - testSupport.createSourceCachingHost(new Map([[fileName, content]])); + const tsHost = testSupport.createSourceCachingHost( + new Map([[fileName, content]]), + ); const host: googmodule.GoogModuleProcessorHost = { fileNameToModuleId: (fn: string) => path.relative(rootDir, fn), - pathToModuleName: (context, fileName) => testSupport.pathToModuleName( - rootDir, context, fileName, options, tsHost), + pathToModuleName: (context, fileName) => + testSupport.pathToModuleName(rootDir, context, fileName, options, tsHost), options, transformDynamicImport: 'closure', }; if (pathToNamespaceMap) { - host.jsPathToModuleName = (importPath: string) => - pathToNamespaceMap.get(importPath)?.name; + host.jsPathToModuleName = (importPath: string) => { + const module = pathToNamespaceMap.get(importPath); + if (!module) return undefined; + return { + name: module.name, + multipleProvides: false, + }; + }; host.jsPathToStripProperty = (importPath: string) => - pathToNamespaceMap.get(importPath)?.stripProperty; + pathToNamespaceMap.get(importPath)?.stripProperty; } const program = ts.createProgram([fileName], options, tsHost); // NB: this intentionally only checks for syntactical issues, but allows // semantic issues, such as missing imports to make the tests below easier to // write. - expect(testSupport.formatDiagnostics(program.getSyntacticDiagnostics())) - .toBe(''); + expect(testSupport.formatDiagnostics(program.getSyntacticDiagnostics())).toBe( + '', + ); const typeChecker = program.getTypeChecker(); const diagnostics: ts.Diagnostic[] = []; const manifest = new ModulesManifest(); - let output: string|null = null; + let output: string | null = null; const transformers = { - after: [googmodule.commonJsToGoogmoduleTransformer( - host, manifest, typeChecker)] + after: [ + googmodule.commonJsToGoogmoduleTransformer(host, manifest, typeChecker), + ], }; - const res = program.emit(undefined, (fn, content) => { - output = content; - }, undefined, false, transformers); + const res = program.emit( + undefined, + (fn, content) => { + output = content; + }, + undefined, + false, + transformers, + ); diagnostics.push(...res.diagnostics); expect(diagnostics).toEqual([]); if (!output) throw new Error('no output'); return {output, manifest, rootDir}; } -/** - * Remove the first line (if empty) and unindents the all other lines by the - * amount of leading whitespace in the second line. - */ -function outdent(str: string) { - const lines = str.split('\n'); - if (lines.length < 2) return str; - if (lines.shift() !== '') return str; - const indent = lines[0].match(/^ */)![0].length; - for (let i = 0; i < lines.length; i++) { - lines[i] = lines[i].substring(indent); - } - return lines.join('\n'); -} - describe('convertCommonJsToGoogModule', () => { beforeEach(() => { testSupport.addDiffMatchers(); @@ -97,110 +100,137 @@ describe('convertCommonJsToGoogModule', () => { it('adds a goog.module call', () => { // NB: no line break added below. - expectCommonJs('a.ts', `console.log('hello');`).toBe(outdent(` + expectCommonJs('a.ts', `console.log('hello');`).toBe( + outdent(` goog.module('a'); var module = module || { id: 'a.ts' }; goog.require('tslib'); console.log('hello'); - `)); + `), + ); }); it('adds a goog.module call for ES6 mode', () => { // NB: no line break added below. - expectCommonJs('a.ts', `console.log('hello');`, false).toBe(outdent(` + expectCommonJs('a.ts', `console.log('hello');`, false).toBe( + outdent(` goog.module('a'); var module = module || { id: 'a.ts' }; goog.require('tslib'); console.log('hello'); - `)); + `), + ); }); it('adds a goog.module call to empty files', () => { - expectCommonJs('a.ts', ``).toBe(outdent(` + expectCommonJs('a.ts', ``).toBe( + outdent(` goog.module('a'); var module = module || { id: 'a.ts' }; goog.require('tslib'); - `)); + `), + ); }); it('adds a goog.module call to empty-looking files', () => { - expectCommonJs('a.ts', `// empty`).toBe(outdent(` + expectCommonJs('a.ts', `// empty`).toBe( + outdent(` goog.module('a'); var module = module || { id: 'a.ts' }; goog.require('tslib'); // empty - `)); + `), + ); }); it('strips use strict directives', () => { // NB: no line break added below. - expectCommonJs('a.ts', outdent(` + expectCommonJs( + 'a.ts', + outdent(` "use strict"; console.log('hello'); - `)).toBe(outdent(` + `), + ).toBe( + outdent(` goog.module('a'); var module = module || { id: 'a.ts' }; goog.require('tslib'); console.log('hello'); - `)); + `), + ); }); it('converts imports to goog.require calls', () => { - expectCommonJs('a.ts', `import {x} from 'req/mod'; console.log(x);`) - .toBe(outdent(` + expectCommonJs('a.ts', `import {x} from 'req/mod'; console.log(x);`).toBe( + outdent(` goog.module('a'); var module = module || { id: 'a.ts' }; goog.require('tslib'); var mod_1 = goog.require('req.mod'); console.log(mod_1.x); - `)); + `), + ); }); it('converts imports to goog.require calls using const in ES6 mode', () => { expectCommonJs( - 'a.ts', `import {x} from 'req/mod'; console.log(x);`, - /* es5 mode= */ false) - .toBe(outdent(` + 'a.ts', + `import {x} from 'req/mod'; console.log(x);`, + /* es5 mode= */ false, + ).toBe( + outdent(` goog.module('a'); var module = module || { id: 'a.ts' }; goog.require('tslib'); const mod_1 = goog.require('req.mod'); console.log(mod_1.x); - `)); + `), + ); }); it('converts side-effect import to goog.require calls', () => { - expectCommonJs('a.ts', `import 'req/mod';`).toBe(outdent(` + expectCommonJs('a.ts', `import 'req/mod';`).toBe( + outdent(` goog.module('a'); var module = module || { id: 'a.ts' }; goog.require('tslib'); goog.require('req.mod'); - `)); + `), + ); }); - it('converts imports to goog.require calls without assignments after comments', - () => { - expectCommonJs('a.ts', outdent(` + it('converts imports to goog.require calls without assignments after comments', () => { + expectCommonJs( + 'a.ts', + outdent(` // Comment import 'req/mod'; - `)).toBe(outdent(` + `), + ).toBe( + outdent(` goog.module('a'); var module = module || { id: 'a.ts' }; goog.require('tslib'); // Comment goog.require('req.mod'); - `)); - }); + `), + ); + }); it('keeps fileoverview comments before imports', () => { - expectCommonJs('a.ts', outdent(` + expectCommonJs( + 'a.ts', + outdent(` /** @modName {mod_a} */ import {dep} from './dep'; import {sharedDep} from './shared_dep'; console.log('in mod_a', dep, sharedDep); - `)).toBe(outdent(` + `), + ).toBe( + outdent(` /** @modName {mod_a} */ goog.module('a'); var module = module || { id: 'a.ts' }; @@ -208,17 +238,22 @@ describe('convertCommonJsToGoogModule', () => { var dep_1 = goog.require('dep'); var shared_dep_1 = goog.require('shared_dep'); console.log('in mod_a', dep_1.dep, shared_dep_1.sharedDep); - `)); + `), + ); }); it('keeps fileoverview comments not separated by newlines', () => { - expectCommonJs('a.ts', outdent(` + expectCommonJs( + 'a.ts', + outdent(` /** @modName {mod_a} */ import {dep} from './dep'; import {sharedDep} from './shared_dep'; console.log('in mod_a', dep, sharedDep); - `)).toBe(outdent(` + `), + ).toBe( + outdent(` goog.module('a'); var module = module || { id: 'a.ts' }; goog.require('tslib'); @@ -226,11 +261,14 @@ describe('convertCommonJsToGoogModule', () => { var dep_1 = goog.require('dep'); var shared_dep_1 = goog.require('shared_dep'); console.log('in mod_a', dep_1.dep, shared_dep_1.sharedDep); - `)); + `), + ); }); it('keeps fileoverview comments before elided imports', () => { - expectCommonJs('a.ts', outdent(` + expectCommonJs( + 'a.ts', + outdent(` /** @fileoverview Hello Comment. */ import {TypeFromDep} from './dep'; @@ -239,7 +277,9 @@ describe('convertCommonJsToGoogModule', () => { const x: TypeFromDep = 1; console.log('in mod_a', x); - `)).toBe(outdent(` + `), + ).toBe( + outdent(` /** @fileoverview Hello Comment. */ goog.module('a'); var module = module || { id: 'a.ts' }; @@ -247,26 +287,33 @@ describe('convertCommonJsToGoogModule', () => { // Only uses the import as a type. var x = 1; console.log('in mod_a', x); - `)); + `), + ); }); describe('ES5 export *', () => { it('converts export * statements', () => { - expectCommonJs('a.ts', `export * from 'req/mod';`, true).toBe(outdent(` + expectCommonJs('a.ts', `export * from 'req/mod';`, true).toBe( + outdent(` goog.module('a'); var module = module || { id: 'a.ts' }; var tslib_1 = goog.require('tslib'); var tsickle_module_1_ = goog.require('req.mod'); tslib_1.__exportStar(tsickle_module_1_, exports); - `)); + `), + ); }); it('uses correct module name with subsequent exports', () => { - expectCommonJs('a.ts', outdent(` + expectCommonJs( + 'a.ts', + outdent(` export * from 'req/mod'; import {x} from 'req/mod'; console.log(x); - `)).toBe(outdent(` + `), + ).toBe( + outdent(` goog.module('a'); var module = module || { id: 'a.ts' }; var tslib_1 = goog.require('tslib'); @@ -274,15 +321,20 @@ describe('convertCommonJsToGoogModule', () => { tslib_1.__exportStar(tsickle_module_1_, exports); var mod_1 = tsickle_module_1_; console.log(mod_1.x); - `)); + `), + ); }); it('reuses an existing imported variable name', () => { - expectCommonJs('a.ts', outdent(` + expectCommonJs( + 'a.ts', + outdent(` import {x} from 'req/mod'; export * from 'req/mod'; console.log(x); - `)).toBe(outdent(` + `), + ).toBe( + outdent(` goog.module('a'); var module = module || { id: 'a.ts' }; var tslib_1 = goog.require('tslib'); @@ -290,111 +342,150 @@ describe('convertCommonJsToGoogModule', () => { var tsickle_module_1_ = mod_1; tslib_1.__exportStar(tsickle_module_1_, exports); console.log(mod_1.x); - `)); + `), + ); }); }); it('resolves relative module URIs', () => { // See below for more fine-grained unit tests. - expectCommonJs('a/b.ts', outdent(` + expectCommonJs( + 'a/b.ts', + outdent(` import {x} from './req/mod'; console.log(x); - `)).toBe(outdent(` + `), + ).toBe( + outdent(` goog.module('a.b'); var module = module || { id: 'a/b.ts' }; goog.require('tslib'); var mod_1 = goog.require('a.req.mod'); console.log(mod_1.x); - `)); + `), + ); }); it('avoids mangling module names in goog: imports', () => { - expectCommonJs('a/b.ts', outdent(` + expectCommonJs( + 'a/b.ts', + outdent(` import Foo from 'goog:foo_bar.baz'; console.log(Foo); - `)).toBe(outdent(` + `), + ).toBe( + outdent(` goog.module('a.b'); var module = module || { id: 'a/b.ts' }; goog.require('tslib'); var goog_foo_bar_baz_1 = goog.require('foo_bar.baz'); console.log(goog_foo_bar_baz_1); - `)); + `), + ); }); it('resolves default goog: module imports', () => { - expectCommonJs('a/b.ts', outdent(` + expectCommonJs( + 'a/b.ts', + outdent(` import Foo from 'goog:use.Foo'; console.log(Foo); - `)).toBe(outdent(` + `), + ).toBe( + outdent(` goog.module('a.b'); var module = module || { id: 'a/b.ts' }; goog.require('tslib'); var goog_use_Foo_1 = goog.require('use.Foo'); console.log(goog_use_Foo_1); - `)); + `), + ); }); it('resolves renamed default goog: module imports', () => { - expectCommonJs('a/b.ts', outdent(` + expectCommonJs( + 'a/b.ts', + outdent(` import {default as Foo} from 'goog:use.Foo'; console.log(Foo); - `)).toBe(outdent(` + `), + ).toBe( + outdent(` goog.module('a.b'); var module = module || { id: 'a/b.ts' }; goog.require('tslib'); var goog_use_Foo_1 = goog.require('use.Foo'); console.log(goog_use_Foo_1); - `)); + `), + ); }); it('resolves exported default goog: module imports', () => { - expectCommonJs('a/b.ts', outdent(` + expectCommonJs( + 'a/b.ts', + outdent(` export {default as Foo} from 'goog:use.Foo'; - `)).toBe(outdent(` + `), + ).toBe( + outdent(` goog.module('a.b'); var module = module || { id: 'a/b.ts' }; goog.require('tslib'); var goog_use_Foo_1 = goog.require('use.Foo'); exports.Foo = goog_use_Foo_1; - `)); + `), + ); }); - it('rewrites access to .default properties on goog: module namespace imports', - () => { - expectCommonJs('a/b.ts', outdent(` + it('rewrites access to .default properties on goog: module namespace imports', () => { + expectCommonJs( + 'a/b.ts', + outdent(` import * as Foo from 'goog:use.Foo'; console.log(Foo.default); - `)).toBe(outdent(` + `), + ).toBe( + outdent(` goog.module('a.b'); var module = module || { id: 'a/b.ts' }; goog.require('tslib'); var Foo = goog.require('use.Foo'); console.log(Foo); - `)); - }); + `), + ); + }); it('leaves single .default accesses alone', () => { // This is a repro for a bug when no goog: symbols are found. - expectCommonJs('a/b.ts', outdent(` + expectCommonJs( + 'a/b.ts', + outdent(` console.log(this.default); console.log(foo.bar.default); - `)).toBe(outdent(` + `), + ).toBe( + outdent(` goog.module('a.b'); var module = module || { id: 'a/b.ts' }; goog.require('tslib'); console.log(this.default); console.log(foo.bar.default); - `)); + `), + ); }); it('strips "use strict" (implied by goog.module)', () => { - expectCommonJs('a/b.ts', outdent(` + expectCommonJs( + 'a/b.ts', + outdent(` /** * docstring here */ "use strict"; var foo = bar; - `)).toBe(outdent(` + `), + ).toBe( + outdent(` /** * docstring here */ @@ -402,16 +493,21 @@ describe('convertCommonJsToGoogModule', () => { var module = module || { id: 'a/b.ts' }; goog.require('tslib'); var foo = bar; - `)); + `), + ); }); it('deduplicates module imports', () => { - expectCommonJs('a/b.ts', outdent(` + expectCommonJs( + 'a/b.ts', + outdent(` import Foo from 'goog:foo'; import Foo2 from 'goog:foo'; Foo; Foo2; - `)).toBe(outdent(` + `), + ).toBe( + outdent(` goog.module('a.b'); var module = module || { id: 'a/b.ts' }; goog.require('tslib'); @@ -419,20 +515,25 @@ describe('convertCommonJsToGoogModule', () => { var goog_foo_2 = goog_foo_1; goog_foo_1; goog_foo_2; - `)); + `), + ); }); it('gathers referenced modules', () => { - const {output, manifest, rootDir} = processES5('a/b.ts', outdent(` + const {output, manifest, rootDir} = processES5( + 'a/b.ts', + outdent(` import '../foo/bare_require'; import sym from 'goog:foo.bar'; import {es6RelativeRequire} from './relative'; import {es6NonRelativeRequire} from 'non/relative'; console.log(sym, es6RelativeRequire, es6NonRelativeRequire); - `)); + `), + ); // Sanity check the output. - expect(output).toBe(outdent(` + expect(output).toBe( + outdent(` goog.module('a.b'); var module = module || { id: 'a/b.ts' }; goog.require('tslib'); @@ -441,30 +542,30 @@ describe('convertCommonJsToGoogModule', () => { var relative_1 = goog.require('a.relative'); var relative_2 = goog.require('non.relative'); console.log(goog_foo_bar_1, relative_1.es6RelativeRequire, relative_2.es6NonRelativeRequire); - `)); - - expect(manifest.getReferencedModules(path.join(rootDir, 'a/b.ts'))) - .toEqual([ - 'foo.bare_require', - 'foo.bar', - 'a.relative', - 'non.relative', - ]); + `), + ); + + expect(manifest.getReferencedModules(path.join(rootDir, 'a/b.ts'))).toEqual( + ['foo.bare_require', 'foo.bar', 'a.relative', 'non.relative'], + ); }); it(`skips the exports assignment if there's another one`, () => { expectCommonJs( - 'a.ts', `export {}; console.log('hello'); exports = 1;`, false) - .toBe(outdent(` + 'a.ts', + `export {}; console.log('hello'); exports = 1;`, + false, + ).toBe( + outdent(` goog.module('a'); var module = module || { id: 'a.ts' }; goog.require('tslib'); console.log('hello'); exports = 1; - `)); + `), + ); }); - it('rewrites live export bindings', () => { const before = ` Object.defineProperty(exports, 'foo', { @@ -472,12 +573,14 @@ describe('convertCommonJsToGoogModule', () => { }); `; - expectCommonJs('a.ts', before, false).toBe(outdent(` + expectCommonJs('a.ts', before, false).toBe( + outdent(` goog.module('a'); var module = module || { id: 'a.ts' }; goog.require('tslib'); exports.foo = ns.bar; - `)); + `), + ); }); it('elides default export values', () => { @@ -490,7 +593,8 @@ describe('convertCommonJsToGoogModule', () => { exports.boff = 4; `; - expectCommonJs('a.ts', before, false).toBe(outdent(` + expectCommonJs('a.ts', before, false).toBe( + outdent(` goog.module('a'); var module = module || { id: 'a.ts' }; goog.require('tslib'); @@ -498,7 +602,8 @@ describe('convertCommonJsToGoogModule', () => { exports.bar = 2; exports.baz = 3; exports.boff = 4; - `)); + `), + ); }); describe('dynamic import', () => { @@ -509,19 +614,22 @@ describe('convertCommonJsToGoogModule', () => { })(); export {}; `; - const beforeLines = (processES5('project/file.ts', before, { - isES5: false, - }).output as string) - .split(/\n/g); - - expect(beforeLines).toEqual(outdent(` + const beforeLines = ( + processES5('project/file.ts', before, { + isES5: false, + }).output as string + ).split(/\n/g); + + expect(beforeLines).toEqual( + outdent(` goog.module('project.file'); var module = module || { id: 'project/file.ts' }; const tslib_1 = goog.require('tslib'); (() => tslib_1.__awaiter(void 0, void 0, void 0, function* () { const starImport = yield goog.requireDynamic('project.relpath'); }))(); - `).split(/\n/g)); + `).split(/\n/g), + ); }); it('handles dynamic imports for ES5', () => { @@ -531,12 +639,14 @@ describe('convertCommonJsToGoogModule', () => { })(); export {}; `; - const beforeLines = (processES5('project/file.ts', before, { - isES5: true, - }).output as string) - .split(/\n/g); - - expect(beforeLines).toEqual(outdent(` + const beforeLines = ( + processES5('project/file.ts', before, { + isES5: true, + }).output as string + ).split(/\n/g); + + expect(beforeLines).toEqual( + outdent(` goog.module('project.file'); var module = module || { id: 'project/file.ts' }; var tslib_1 = goog.require('tslib'); @@ -551,7 +661,8 @@ describe('convertCommonJsToGoogModule', () => { } }); }); })(); - `).split(/\n/g)); + `).split(/\n/g), + ); }); it('handles dynamic imports with destructuring LHS', () => { @@ -561,19 +672,22 @@ describe('convertCommonJsToGoogModule', () => { })(); export {}; `; - const beforeLines = (processES5('project/file.ts', before, { - isES5: false, - }).output as string) - .split(/\n/g); - - expect(beforeLines).toEqual(outdent(` + const beforeLines = ( + processES5('project/file.ts', before, { + isES5: false, + }).output as string + ).split(/\n/g); + + expect(beforeLines).toEqual( + outdent(` goog.module('project.file'); var module = module || { id: 'project/file.ts' }; const tslib_1 = goog.require('tslib'); (() => tslib_1.__awaiter(void 0, void 0, void 0, function* () { const { Foo } = yield goog.requireDynamic('project.relpath'); }))(); - `).split(/\n/g)); + `).split(/\n/g), + ); }); it('handles dynamic imports for ES5 with destructuring LHS', () => { @@ -583,12 +697,14 @@ describe('convertCommonJsToGoogModule', () => { })(); export {}; `; - const beforeLines = (processES5('project/file.ts', before, { - isES5: true, - }).output as string) - .split(/\n/g); - - expect(beforeLines).toEqual(outdent(` + const beforeLines = ( + processES5('project/file.ts', before, { + isES5: true, + }).output as string + ).split(/\n/g); + + expect(beforeLines).toEqual( + outdent(` goog.module('project.file'); var module = module || { id: 'project/file.ts' }; var tslib_1 = goog.require('tslib'); @@ -603,7 +719,8 @@ describe('convertCommonJsToGoogModule', () => { } }); }); })(); - `).split(/\n/g)); + `).split(/\n/g), + ); }); }); @@ -618,26 +735,33 @@ describe('convertCommonJsToGoogModule', () => { } it('resolves module names', () => { - expectCommonJs('a.ts', `import {x} from 'path/to/mod'; console.log(x);`) - .toBe(outdent(` + expectCommonJs( + 'a.ts', + `import {x} from 'path/to/mod'; console.log(x);`, + ).toBe( + outdent(` goog.module('a'); var module = module || { id: 'a.ts' }; goog.require('tslib'); var mod_1 = goog.require('my.mod'); console.log(mod_1.x); - `)); + `), + ); }); it('resolves strip property', () => { expectCommonJs( - 'a.ts', `import {prop} from 'path/to/strip/prop'; console.log(prop);`) - .toBe(outdent(` + 'a.ts', + `import {prop} from 'path/to/strip/prop'; console.log(prop);`, + ).toBe( + outdent(` goog.module('a'); var module = module || { id: 'a.ts' }; goog.require('tslib'); var prop_1 = goog.require('my.strip.prop'); console.log(prop_1); - `)); + `), + ); }); }); }); diff --git a/test/jsdoc_test.ts b/test/jsdoc_test.ts index d22654720..54b385863 100644 --- a/test/jsdoc_test.ts +++ b/test/jsdoc_test.ts @@ -20,7 +20,9 @@ describe('jsdoc.parse', () => { }); it('grabs plain text from jsdoc', () => { const source = '/** jsdoc comment */'; - expect(parse(source)).toEqual({tags: [{tagName: '', text: 'jsdoc comment'}]}); + expect(parse(source)).toEqual({ + tags: [{tagName: '', text: 'jsdoc comment'}], + }); }); it('gathers @tags from jsdoc', () => { const source = `/** @@ -39,12 +41,12 @@ describe('jsdoc.parse', () => { { tagName: 'param', parameterName: 'bar', - text: 'multiple\n line comment' + text: 'multiple\n line comment', }, {tagName: 'return', text: 'foobar'}, {tagName: 'nosideeffects'}, {tagName: 'nospacebeforebracket', text: '{text in bracket}'}, - ] + ], }); }); it('warns on type annotations in parameters', () => { @@ -54,21 +56,21 @@ describe('jsdoc.parse', () => { expect(parse(source)).toEqual({ tags: [], warnings: [ - 'the type annotation on @param is redundant with its TypeScript type, remove the {...} part' - ] + 'the type annotation on @param is redundant with its TypeScript type, remove the {...} part', + ], }); }); it('warns on @type annotations', () => { const source = `/** @type {string} foo */`; expect(parse(source)).toEqual({ tags: [], - warnings: ['@type annotations are redundant with TypeScript equivalents'] + warnings: ['@type annotations are redundant with TypeScript equivalents'], }); }); it('allows @suppress annotations', () => { const source = `/** @suppress {checkTypes} I hate types */`; expect(parse(source)).toEqual({ - tags: [{tagName: 'suppress', type: 'checkTypes', text: ' I hate types'}] + tags: [{tagName: 'suppress', type: 'checkTypes', text: ' I hate types'}], }); const malformed = `/** @suppress malformed */`; expect(parse(malformed)).toEqual({ @@ -80,10 +82,13 @@ describe('jsdoc.parse', () => { describe('jsdoc.toString', () => { it('filters duplicated @deprecated tags', () => { - expect(jsdoc.toString([ - {tagName: 'deprecated'}, {tagName: 'param', parameterName: 'hello', text: 'world'}, - {tagName: 'deprecated'} - ])).toBe(`/** + expect( + jsdoc.toString([ + {tagName: 'deprecated'}, + {tagName: 'param', parameterName: 'hello', text: 'world'}, + {tagName: 'deprecated'}, + ]), + ).toBe(`/** * @deprecated * @param hello world */ @@ -91,9 +96,11 @@ describe('jsdoc.toString', () => { }); it('escapes @argument tags', () => { - expect(jsdoc.toString([ - {tagName: 'argument', parameterName: 'hello', text: 'world'}, - ])).toBe(`/** + expect( + jsdoc.toString([ + {tagName: 'argument', parameterName: 'hello', text: 'world'}, + ]), + ).toBe(`/** * \\@argument hello world */ `); diff --git a/test/test_support.ts b/test/test_support.ts index c270ac144..3df85cd96 100644 --- a/test/test_support.ts +++ b/test/test_support.ts @@ -9,7 +9,12 @@ // Install source-map-support so that stack traces are mapped back to TS code. import 'source-map-support'; -import {DIFF_DELETE, DIFF_EQUAL, DIFF_INSERT, diff_match_patch as DiffMatchPatch} from 'diff-match-patch'; +import { + DIFF_DELETE, + DIFF_EQUAL, + DIFF_INSERT, + diff_match_patch as DiffMatchPatch, +} from 'diff-match-patch'; import * as fs from 'fs'; import * as glob from 'glob'; import * as path from 'path'; @@ -87,7 +92,7 @@ export const baseCompilerOptions: ts.CompilerOptions = { paths: { // The compiler builtin 'tslib' library is looked up by name, // so this entry controls which code is used for tslib. - 'tslib': [tslibPath()] + 'tslib': [tslibPath()], }, }; @@ -126,22 +131,26 @@ const cachedLibDir = path.normalize(path.dirname(ts.getDefaultLibFilePath({}))); /** Creates a ts.Program from a set of input files. */ export function createProgram( - sources: Map, - tsCompilerOptions: ts.CompilerOptions = compilerOptions): ts.Program { + sources: Map, + tsCompilerOptions: ts.CompilerOptions = compilerOptions, +): ts.Program { return createProgramAndHost(sources, tsCompilerOptions).program; } export function createSourceCachingHost( - sources: Map, - tsCompilerOptions: ts.CompilerOptions = compilerOptions): ts.CompilerHost { + sources: Map, + tsCompilerOptions: ts.CompilerOptions = compilerOptions, +): ts.CompilerHost { const host = ts.createCompilerHost(tsCompilerOptions); host.getCurrentDirectory = () => { return rootDir(); }; - host.getSourceFile = (fileName: string, languageVersion: ts.ScriptTarget, - onError?: (msg: string) => void): ts.SourceFile| - undefined => { + host.getSourceFile = ( + fileName: string, + languageVersion: ts.ScriptTarget, + onError?: (msg: string) => void, + ): ts.SourceFile | undefined => { cliSupport.assertAbsolute(fileName); // Normalize path to fix wrong directory separators on Windows which // would break the equality check. @@ -150,22 +159,36 @@ export function createSourceCachingHost( // Cache files in TypeScript's lib directory. if (fileName.startsWith(cachedLibDir)) { const sf = ts.createSourceFile( - fileName, fs.readFileSync(fileName, 'utf8'), ts.ScriptTarget.Latest, - true); + fileName, + fs.readFileSync(fileName, 'utf8'), + ts.ScriptTarget.Latest, + true, + ); cachedLibs.set(fileName, sf); return sf; } if (fileName === tslibPath()) { return ts.createSourceFile( - fileName, fs.readFileSync(fileName, 'utf8'), ts.ScriptTarget.Latest, - true); + fileName, + fs.readFileSync(fileName, 'utf8'), + ts.ScriptTarget.Latest, + true, + ); } const contents = sources.get(fileName); if (contents !== undefined) { - return ts.createSourceFile(fileName, contents, ts.ScriptTarget.Latest, true); + return ts.createSourceFile( + fileName, + contents, + ts.ScriptTarget.Latest, + true, + ); } - throw new Error(`unexpected file read of ${fileName} not in ${ - Array.from(sources.keys())}`); + throw new Error( + `unexpected file read of ${fileName} not in ${Array.from( + sources.keys(), + )}`, + ); }; const originalFileExists = host.fileExists; host.fileExists = (fileName: string): boolean => { @@ -192,11 +215,15 @@ export function createSourceCachingHost( } export function createProgramAndHost( - sources: Map, - tsCompilerOptions: ts.CompilerOptions = - compilerOptions): {host: ts.CompilerHost, program: ts.Program} { + sources: Map, + tsCompilerOptions: ts.CompilerOptions = compilerOptions, +): {host: ts.CompilerHost; program: ts.Program} { const host = createSourceCachingHost(sources); - const program = ts.createProgram(Array.from(sources.keys()), tsCompilerOptions, host); + const program = ts.createProgram( + Array.from(sources.keys()), + tsCompilerOptions, + host, + ); return {program, host}; } @@ -209,7 +236,10 @@ export class GoldenFileTest { * @param tsPaths Relative paths from this.path to all .ts/.d.ts files in the * test. */ - constructor(readonly root: string, private readonly tsPaths: string[]) { + constructor( + readonly root: string, + private readonly tsPaths: string[], + ) { cliSupport.assertAbsolute(this.root); } @@ -226,24 +256,28 @@ export class GoldenFileTest { */ inputPaths(): string[] { return this.tsPaths - .filter(p => { - // For .declaration tests, .d.ts's are goldens, not inputs, when there - // is a corresponding .ts file. - if (this.isDeclarationTest && p.endsWith('.d.ts') && - this.tsPaths.includes(p.replace(/\.d\.ts$/, '.ts'))) { - return false; - } - return true; - }) - .map(p => path.join(this.root, p)); + .filter((p) => { + // For .declaration tests, .d.ts's are goldens, not inputs, when there + // is a corresponding .ts file. + if ( + this.isDeclarationTest && + p.endsWith('.d.ts') && + this.tsPaths.includes(p.replace(/\.d\.ts$/, '.ts')) + ) { + return false; + } + return true; + }) + .map((p) => path.join(this.root, p)); } /** * Gets the absolute paths to the expected .js outputs of the test. */ jsPaths(): string[] { - return this.tsPaths.filter(f => !/\.d\.ts/.test(f)) - .map(f => path.join(this.root, GoldenFileTest.tsPathToJs(f))); + return this.tsPaths + .filter((f) => !/\.d\.ts/.test(f)) + .map((f) => path.join(this.root, GoldenFileTest.tsPathToJs(f))); } /** @@ -251,13 +285,13 @@ export class GoldenFileTest { */ tsMigrationExportsShimPaths(): string[] { return this.tsPaths - .map( - (p) => - [p.replace('.ts', '.tsmes.d.ts'), - p.replace('.ts', '.tsmes.js')]) - .flat() - .map((p) => path.join(this.root, p)) - .filter((p) => fs.existsSync(p)); + .map((p) => [ + p.replace('.ts', '.tsmes.d.ts'), + p.replace('.ts', '.tsmes.js'), + ]) + .flat() + .map((p) => path.join(this.root, p)) + .filter((p) => fs.existsSync(p)); } /** @@ -303,29 +337,32 @@ export function goldenTests(): GoldenFileTest[] { const basePath = path.join(rootDir(), 'test_files'); const testNames = fs.readdirSync(basePath); - let testDirs = testNames.map(testName => path.join(basePath, testName)) - .filter(testDir => fs.statSync(testDir).isDirectory()); + let testDirs = testNames + .map((testName) => path.join(basePath, testName)) + .filter((testDir) => fs.statSync(testDir).isDirectory()); if (!isInBazel()) { // TODO(nickreid): the migration shim code is incompatible with the open // source build. testDirs = testDirs.filter( - testDir => !testDir.includes('ts_migration_exports_shim')); + (testDir) => !testDir.includes('ts_migration_exports_shim'), + ); } - let tests = testDirs.map(testDir => { - let tsPaths = glob.sync(path.join(testDir, '**/*.ts')); + let tests = testDirs.map((testDir) => { + let tsPaths = glob.sync(path.join(testDir, '**/*.ts')).sort(); tsPaths = tsPaths.concat(glob.sync(path.join(testDir, '*.tsx'))); - tsPaths = tsPaths.filter(p => !p.match(/\.(tsickle|decorated|tsmes)\./)); - const tsFiles = tsPaths.map(f => path.relative(testDir, f)); - tsFiles.sort(); // Source order is significant for externs concatenation after the test. + tsPaths = tsPaths.filter((p) => !p.match(/\.(tsickle|decorated|tsmes)\./)); + const tsFiles = tsPaths.map((f) => path.relative(testDir, f)); + tsFiles.sort(); // Source order is significant for externs concatenation after the test. return new GoldenFileTest(testDir, tsFiles); }); if (process.env['TESTBRIDGE_TEST_ONLY']) { const re = new RegExp(process.env['TESTBRIDGE_TEST_ONLY']); - tests = tests.filter(t => re.test(t.name)); + tests = tests.filter((t) => re.test(t.name)); if (tests.length === 0) { - console.error(`'--test_filter=${ - process.env['TESTBRIDGE_TEST_ONLY']}' did not match any tests`); + console.error( + `'--test_filter=${process.env['TESTBRIDGE_TEST_ONLY']}' did not match any tests`, + ); process.exit(1); } } @@ -337,7 +374,9 @@ export function goldenTests(): GoldenFileTest[] { * verification by the e2e_clutz_dts_test. */ export function allDtsPaths(): string[] { - return glob.sync(path.join(rootDir(), 'test_files', '**/*.d.ts')); + // Sorting is no longer supported by glob.sync + // (https://github.com/isaacs/node-glob/issues/372) + return glob.sync(path.join(rootDir(), 'test_files', '**/*.d.ts')).sort(); } /** @@ -345,12 +384,14 @@ export function allDtsPaths(): string[] { * readable, diff using diff-match-patch. */ function diffStrings( - actual: {}, expected: {}): {pass: boolean, message?: string} { + actual: {}, + expected: {}, +): {pass: boolean; message?: string} { if (actual === expected) return {pass: true}; if (typeof actual !== 'string' || typeof expected !== 'string') { return { pass: false, - message: `toEqualWithDiff takes two strings, got ${actual}, ${expected}` + message: `toEqualWithDiff takes two strings, got ${actual}, ${expected}`, }; } const dmp = new DiffMatchPatch(); @@ -363,8 +404,8 @@ function diffStrings( } let message = - '\nStrings differ:\n\x1B[37;41m⌊missing expected content⌋\x1b[0m ' + - '/ \x1B[90;42m⌈new actual content⌉\x1b[0m\n\n'; + '\nStrings differ:\n\x1B[37;41m⌊missing expected content⌋\x1b[0m ' + + '/ \x1B[90;42m⌈new actual content⌉\x1b[0m\n\n'; for (const [diffKind, text] of diff) { switch (diffKind) { case DIFF_EQUAL: @@ -430,7 +471,8 @@ export function formatDiagnostics(diags: ReadonlyArray): string { export function expectDiagnosticsEmpty(diags: ReadonlyArray) { if (diags.length !== 0) { throw new Error( - `Expected no diagnostics but got: ` + formatDiagnostics(diags)); + `Expected no diagnostics but got: ` + formatDiagnostics(diags), + ); } } @@ -442,14 +484,36 @@ export function expectDiagnosticsEmpty(diags: ReadonlyArray) { * environment-specific path). */ export function pathToModuleName( - rootModulePath: string, context: string, fileName: string, - options: ts.CompilerOptions, host: ts.ModuleResolutionHost): string { + rootModulePath: string, + context: string, + fileName: string, + options: ts.CompilerOptions, + host: ts.ModuleResolutionHost, +): string { const resolved = ts.resolveModuleName(fileName, context, options, host); - if (resolved && resolved.resolvedModule && - resolved.resolvedModule.resolvedFileName) { + if ( + resolved && + resolved.resolvedModule && + resolved.resolvedModule.resolvedFileName + ) { fileName = resolved.resolvedModule.resolvedFileName; } if (fileName === tslibPath()) return 'tslib'; return cliSupport.pathToModuleName(rootModulePath, context, fileName); } + +/** + * Remove the first line (if empty) and unindents the all other lines by the + * amount of leading whitespace in the second line. + */ +export function outdent(str: string) { + const lines = str.split('\n'); + if (lines.length < 2) return str; + if (lines.shift() !== '') return str; + const indent = lines[0].match(/^ */)![0].length; + for (let i = 0; i < lines.length; i++) { + lines[i] = lines[i].substring(indent); + } + return lines.join('\n'); +} diff --git a/test/ts_migration_exports_shim/goog_colon_and_clutz_ref.ts b/test/ts_migration_exports_shim/goog_colon_and_clutz_ref.ts index 2ddd0a7a7..cede6ebef 100644 --- a/test/ts_migration_exports_shim/goog_colon_and_clutz_ref.ts +++ b/test/ts_migration_exports_shim/goog_colon_and_clutz_ref.ts @@ -13,10 +13,18 @@ * same. */ -import {DefaultExportClassFromJs, DefaultExportTypeFromJs, NamedExportClassFromJs, RenamedExportedTypeFromJs} from 'goog:goog.module.ref'; +import { + DefaultExportClassFromJs, + DefaultExportTypeFromJs, + NamedExportClassFromJs, + RenamedExportedTypeFromJs, +} from 'goog:goog.module.ref'; import DefaultExportType from 'goog:migrated.module.default.type'; import DefaultExportClass from 'goog:migrated.module.default.value'; -import {NamedExportClassRenamed, RenamedExportedType} from 'goog:migrated.module.named'; +import { + NamedExportClassRenamed, + RenamedExportedType, +} from 'goog:migrated.module.named'; // tslint:disable diff --git a/test/ts_migration_exports_shim/migrated_default_type.ts b/test/ts_migration_exports_shim/migrated_default_type.ts index e250d2d37..300f0ecc8 100644 --- a/test/ts_migration_exports_shim/migrated_default_type.ts +++ b/test/ts_migration_exports_shim/migrated_default_type.ts @@ -24,4 +24,6 @@ const b: DefaultExportType = { /** See what happens when we use the syntax for shimming default exports. */ goog.tsMigrationExportsShim( - 'migrated.module.default.type', {} as DefaultExportType); + 'migrated.module.default.type', + {} as DefaultExportType, +); diff --git a/test/ts_migration_exports_shim/migrated_default_value.ts b/test/ts_migration_exports_shim/migrated_default_value.ts index ff40dae88..caabdcdb6 100644 --- a/test/ts_migration_exports_shim/migrated_default_value.ts +++ b/test/ts_migration_exports_shim/migrated_default_value.ts @@ -27,4 +27,6 @@ DefaultExportClass.use(new DefaultExportClass()); /** See what happens when we use the syntax for shimming default exports. */ goog.tsMigrationExportsShim( - 'migrated.module.default.value', DefaultExportClass); + 'migrated.module.default.value', + DefaultExportClass, +); diff --git a/test/tsickle_test.ts b/test/tsickle_test.ts index 7fbb9592a..f48c4052e 100644 --- a/test/tsickle_test.ts +++ b/test/tsickle_test.ts @@ -13,14 +13,15 @@ import {assertAbsolute} from '../src/cli_support'; import * as tsickle from '../src/tsickle'; import * as testSupport from './test_support'; +import {outdent} from './test_support'; describe('emitWithTsickle', () => { function emitWithTsickle( - tsSources: {[fileName: string]: string}, - tsConfigOverride: Partial = {}, - tsickleHostOverride: Partial = {}, - customTransformers?: tsickle.EmitTransformers): - {[fileName: string]: string} { + tsSources: {[fileName: string]: string}, + tsConfigOverride: Partial = {}, + tsickleHostOverride: Partial = {}, + customTransformers?: tsickle.EmitTransformers, + ) { const tsCompilerOptions: ts.CompilerOptions = { ...testSupport.compilerOptions, target: ts.ScriptTarget.ES5, @@ -30,10 +31,14 @@ describe('emitWithTsickle', () => { const sources = new Map(); for (const fileName of Object.keys(tsSources)) { sources.set( - path.join(tsCompilerOptions.rootDir!, fileName), tsSources[fileName]); + path.join(tsCompilerOptions.rootDir!, fileName), + tsSources[fileName], + ); } - const {program} = - testSupport.createProgramAndHost(sources, tsCompilerOptions); + const {program} = testSupport.createProgramAndHost( + sources, + tsCompilerOptions, + ); testSupport.expectDiagnosticsEmpty(ts.getPreEmitDiagnostics(program)); const tsickleHost: tsickle.TsickleHost = { generateExtraSuppressions: false, @@ -55,65 +60,137 @@ describe('emitWithTsickle', () => { return importPath.replace(/\/|\\/g, '.'); }, fileNameToModuleId: (fileName) => fileName.replace(/^\.\//, ''), - ...tsickleHostOverride, options: tsCompilerOptions, rootDirsRelative: testSupport.relativeToTsickleRoot, - transformDynamicImport: 'closure' + transformDynamicImport: 'closure', + ...tsickleHostOverride, }; const jsSources: {[fileName: string]: string} = {}; - tsickle.emit( - program, tsickleHost, - (fileName: string, data: string) => { - jsSources[path.relative(tsCompilerOptions.rootDir!, fileName)] = data; - }, - /* sourceFile */ undefined, - /* cancellationToken */ undefined, /* emitOnlyDtsFiles */ undefined, - customTransformers); - return jsSources; + const {diagnostics} = tsickle.emit( + program, + tsickleHost, + (fileName: string, data: string) => { + jsSources[path.relative(tsCompilerOptions.rootDir!, fileName)] = data; + }, + /* sourceFile */ undefined, + /* cancellationToken */ undefined, + /* emitOnlyDtsFiles */ undefined, + customTransformers, + ); + return {jsSources, diagnostics}; } + it('should run custom transformers for files with skipTsickleProcessing', () => { + function transformValue(context: ts.TransformationContext) { + return (sourceFile: ts.SourceFile): ts.SourceFile => { + return visitNode(sourceFile) as ts.SourceFile; + + function visitNode(node: ts.Node): ts.Node { + if (node.kind === ts.SyntaxKind.NumericLiteral) { + return ts.factory.createNumericLiteral(2); + } + return ts.visitEachChild(node, visitNode, context); + } + }; + } - it('should run custom transformers for files with skipTsickleProcessing', - () => { - function transformValue(context: ts.TransformationContext) { - return (sourceFile: ts.SourceFile): ts.SourceFile => { - return visitNode(sourceFile) as ts.SourceFile; - - function visitNode(node: ts.Node): ts.Node { - if (node.kind === ts.SyntaxKind.NumericLiteral) { - return ts.factory.createNumericLiteral(2); - } - return ts.visitEachChild(node, visitNode, context); - } - }; - } - - const tsSources = { - 'a.ts': `export const x = 1;`, - }; - const jsSources = emitWithTsickle( - tsSources, undefined, { - shouldSkipTsickleProcessing: () => true, - }, - {beforeTs: [transformValue]}); - - expect(jsSources['a.js']).toContain('exports.x = 2;'); - }); - - it('should export const enums when preserveConstEnums is true', () => { const tsSources = { - 'a.ts': `export const enum Foo { Bar };`, - 'b.ts': `export * from './a';`, + 'a.ts': `export const x = 1;`, }; + const {jsSources} = emitWithTsickle( + tsSources, + undefined, + { + shouldSkipTsickleProcessing: () => true, + }, + {beforeTs: [transformValue]}, + ); - const jsSources = emitWithTsickle( - tsSources, { - preserveConstEnums: true, - module: ts.ModuleKind.ES2015, - }, - {googmodule: false}); + expect(jsSources['a.js']).toContain('exports.x = 2;'); + }); + + it('escapes JSDoc on const enums in unoptimized namespaces', () => { + const tsSources = { + 'a.ts': outdent(` + namespace Foo { + /** @customTag */ + export const enum Bar { A } + } + `), + }; + + const {jsSources} = emitWithTsickle( + tsSources, + { + preserveConstEnums: true, + module: ts.ModuleKind.ES2015, + }, + { + useDeclarationMergingTransformation: false, + }, + ); + + expect(jsSources['a.js']).toContain( + outdent(` + (function (Foo) { + /** + * \\@customTag + */ + var Bar; + `), + ); + }); + + describe('const enum exports', () => { + it('should export value when preserveConstEnums is enabled (from .ts file)', () => { + const tsSources = { + // Simulate a.ts, b.ts and c.ts being in the same compilation unit. + 'a.ts': `export const enum Foo { Bar }`, + 'b.ts': `export * from './a';`, + 'c.ts': `export {Foo as Bar} from './a';`, + }; + + const {jsSources} = emitWithTsickle(tsSources, { + preserveConstEnums: true, + module: ts.ModuleKind.ES2015, + }); + + expect(jsSources['b.js']).toContain(`export { Foo } from './a';`); + expect(jsSources['c.js']).toContain(`export { Foo as Bar } from './a';`); + }); - expect(jsSources['b.js']).toContain(`export { Foo } from './a';`); + it('should export type when preserveConstEnums is enabled (from .d.ts file)', () => { + const tsSources = { + // Simulate a.d.ts coming from a different compilation unit. + 'a.d.ts': `export declare const enum Foo { Bar = 0 }`, + 'b.ts': `export * from './a';`, + 'c.ts': `export {Foo as Bar} from './a';`, + }; + + const {jsSources} = emitWithTsickle(tsSources, { + preserveConstEnums: true, + module: ts.ModuleKind.ES2015, + }); + + expect(jsSources['b.js']).toContain(`exports.Foo; // re-export typedef`); + expect(jsSources['c.js']).toContain(`exports.Bar; // re-export typedef`); + }); + + it('should export type when preserveConstEnums is disabled', () => { + const tsSources = { + 'a.ts': `export const enum Foo { Bar }`, + 'b.ts': `export * from './a';`, + 'c.ts': `export {Foo as Bar} from './a';`, + }; + + const {jsSources} = emitWithTsickle(tsSources, { + preserveConstEnums: false, + module: ts.ModuleKind.ES2015, + }); + + expect(jsSources['b.js']).toContain(`exports.Foo; // re-export typedef`); + expect(jsSources['c.js']).toContain(`exports.Bar; // re-export typedef`); + }); }); it('should not go into an infinite loop with a self-referential type', () => { @@ -121,35 +198,86 @@ describe('emitWithTsickle', () => { 'a.ts': `export function f() : typeof f { return f; }`, }; - const jsSources = emitWithTsickle(tsSources, { + const {jsSources} = emitWithTsickle(tsSources, { module: ts.ModuleKind.ES2015, }); - expect(jsSources['a.js']).toContain(` -/** - * @return {function(): ?} - */ -export function f() { return f; } -`); + expect(jsSources['a.js']).toContain( + outdent(` + /** + * @return {function(): ?} + */ + export function f() { return f; } + `), + ); + }); + + it('reports multi-provides error with jsPathToModuleName impl', () => { + const tsSources = { + 'a.ts': `import {} from 'google3/multi/provide';`, + 'clutz.d.ts': `declare module 'google3/multi/provide' { export {}; }`, + }; + const {diagnostics} = emitWithTsickle( + tsSources, + /* tsConfigOverride= */ undefined, + /* tsickleHostOverride= */ { + jsPathToModuleName(importPath: string) { + if (importPath === 'google3/multi/provide') { + return { + name: 'multi.provide', + multipleProvides: true, + }; + } + return undefined; + }, + }, + ); + expect(testSupport.formatDiagnostics(diagnostics)).toContain( + 'referenced JavaScript module google3/multi/provide provides multiple namespaces and cannot be imported by path', + ); + }); + + it('allows side-effect import of multi-provides module', () => { + const tsSources = { + 'a.ts': `import 'google3/multi/provide';`, + 'clutz.d.ts': `declare module 'google3/multi/provide' { export {}; }`, + }; + const {jsSources} = emitWithTsickle( + tsSources, + /* tsConfigOverride= */ undefined, + /* tsickleHostOverride= */ { + googmodule: true, + jsPathToModuleName(importPath: string) { + if (importPath === 'google3/multi/provide') { + return { + name: 'multi.provide', + multipleProvides: true, + }; + } + return undefined; + }, + }, + ); + expect(jsSources['a.js']).toContain(`goog.require('multi.provide');`); }); describe('regressions', () => { - it('should produce correct .d.ts files when expanding `export *` with es2015 module syntax', - () => { - const tsSources = { - 'a.ts': `export const x = 1;`, - 'b.ts': `export * from './a';\n`, - }; - const jsSources = emitWithTsickle( - tsSources, { - declaration: true, - module: ts.ModuleKind.ES2015, - }, - {googmodule: false}); - - expect(jsSources['b.d.ts']) - .toEqual(`//!! generated by tsickle from b.ts -export * from './a';\n`); - }); + it('should produce correct .d.ts files when expanding `export *` with es2015 module syntax', () => { + const tsSources = { + 'a.ts': `export const x = 1;`, + 'b.ts': `export * from './a';\n`, + }; + const {jsSources} = emitWithTsickle(tsSources, { + declaration: true, + module: ts.ModuleKind.ES2015, + }); + + expect(jsSources['b.d.ts']).toEqual( + outdent(` + //!! generated by tsickle from b.ts + export * from './a'; + `), + ); + }); }); }); diff --git a/test/type_translator_test.ts b/test/type_translator_test.ts index 3a493c058..cfe643887 100644 --- a/test/type_translator_test.ts +++ b/test/type_translator_test.ts @@ -12,29 +12,42 @@ import * as typeTranslator from '../src/type_translator'; describe('isBuiltinLibDTS', () => { it('matches builtins', () => { - expect(typeTranslator.isDeclaredInBuiltinLibDTS( - createNodeInSourceFile('lib.d.ts'))) - .toBe(true); - expect(typeTranslator.isDeclaredInBuiltinLibDTS( - createNodeInSourceFile('lib.es6.d.ts'))) - .toBe(true); + expect( + typeTranslator.isDeclaredInBuiltinLibDTS( + createNodeInSourceFile('lib.d.ts'), + ), + ).toBe(true); + expect( + typeTranslator.isDeclaredInBuiltinLibDTS( + createNodeInSourceFile('lib.es6.d.ts'), + ), + ).toBe(true); }); - it('doesn\'t match others', () => { - expect(typeTranslator.isDeclaredInBuiltinLibDTS( - createNodeInSourceFile('lib.ts'))) - .toBe(false); - expect(typeTranslator.isDeclaredInBuiltinLibDTS( - createNodeInSourceFile('libfoo.d.tts'))) - .toBe(false); - expect(typeTranslator.isDeclaredInBuiltinLibDTS( - createNodeInSourceFile('lib.a/b.d.tts'))) - .toBe(false); + it("doesn't match others", () => { + expect( + typeTranslator.isDeclaredInBuiltinLibDTS( + createNodeInSourceFile('lib.ts'), + ), + ).toBe(false); + expect( + typeTranslator.isDeclaredInBuiltinLibDTS( + createNodeInSourceFile('libfoo.d.tts'), + ), + ).toBe(false); + expect( + typeTranslator.isDeclaredInBuiltinLibDTS( + createNodeInSourceFile('lib.a/b.d.tts'), + ), + ).toBe(false); }); }); function createNodeInSourceFile(sourceFileName: string): ts.Node { const sF = ts.createSourceFile( - sourceFileName, `export const a = 'hello world';`, ts.ScriptTarget.ES5); + sourceFileName, + `export const a = 'hello world';`, + ts.ScriptTarget.ES5, + ); return sF.getChildAt(0); } diff --git a/test_files/abstract/abstract.js b/test_files/abstract/abstract.js index 93226c052..3020474d2 100644 --- a/test_files/abstract/abstract.js +++ b/test_files/abstract/abstract.js @@ -1,9 +1,7 @@ /** - * * @fileoverview * Generated from: test_files/abstract/abstract.ts * @suppress {uselessCode} - * */ goog.module('test_files.abstract.abstract'); var module = module || { id: 'test_files/abstract/abstract.ts' }; diff --git a/test_files/async_functions/async_functions.js b/test_files/async_functions/async_functions.js index 55fe1d0f1..f6c70d481 100644 --- a/test_files/async_functions/async_functions.js +++ b/test_files/async_functions/async_functions.js @@ -2,14 +2,12 @@ goog.module('test_files.async_functions.async_functions'); var module = module || { id: 'test_files/async_functions/async_functions.ts' }; const tslib_1 = goog.require('tslib'); /** - * * @fileoverview * Exercises various forms of async functions. When TypeScript downlevels these * functions, it inserts a reference to 'this' which then tickles a Closure * check around whether 'this' has a known type. * Generated from: test_files/async_functions/async_functions.ts * @suppress {uselessCode} - * */ /** * @param {string} param diff --git a/test_files/augment/externs.js b/test_files/augment/externs.js index 18fb334ad..077c7e11f 100644 --- a/test_files/augment/externs.js +++ b/test_files/augment/externs.js @@ -8,8 +8,6 @@ var test_files$augment$angular$index_ = {}; /** @type {!test_files$augment$angular$index_.angular.IAngularStatic} */ test_files$augment$angular$index_.angular; -/** @const */ -test_files$augment$angular$index_.angular = {}; /** * @record * @struct diff --git a/test_files/augment/user.js b/test_files/augment/user.js index b9e98fb82..dcde6707a 100644 --- a/test_files/augment/user.js +++ b/test_files/augment/user.js @@ -1,9 +1,7 @@ /** - * * @fileoverview * Generated from: test_files/augment/user.ts * @suppress {checkTypes} - * */ goog.module('test_files.augment.user'); var module = module || { id: 'test_files/augment/user.ts' }; diff --git a/test_files/basic.untyped/basic.untyped.js b/test_files/basic.untyped/basic.untyped.js index 36c8d2723..2de145c88 100644 --- a/test_files/basic.untyped/basic.untyped.js +++ b/test_files/basic.untyped/basic.untyped.js @@ -1,10 +1,8 @@ /** - * * @fileoverview This test is just a random collection of typed code, to ensure * the output is all with {?} annotations. * Generated from: test_files/basic.untyped/basic.untyped.ts * @suppress {uselessCode} - * */ goog.module('test_files.basic.untyped.basic.untyped'); var module = module || { id: 'test_files/basic.untyped/basic.untyped.ts' }; diff --git a/test_files/cast_extends/cast_extends.js b/test_files/cast_extends/cast_extends.js index bdf2b6a9f..1484302df 100644 --- a/test_files/cast_extends/cast_extends.js +++ b/test_files/cast_extends/cast_extends.js @@ -1,13 +1,11 @@ // test_files/cast_extends/cast_extends.ts(18,1): warning TS0: unhandled type flags: Intersection // test_files/cast_extends/cast_extends.ts(24,10): warning TS0: unhandled type flags: Intersection /** - * * @fileoverview Reproduces an issue where tsickle would emit a cast for the * "extends" clause, and Closure would report an error due to the extends * expression not resolving to a plain identifier. * Generated from: test_files/cast_extends/cast_extends.ts * @suppress {checkTypes,uselessCode} - * */ goog.module('test_files.cast_extends.cast_extends'); var module = module || { id: 'test_files/cast_extends/cast_extends.ts' }; diff --git a/test_files/class.untyped/class.js b/test_files/class.untyped/class.js index 5d68e3b5a..46ca33f87 100644 --- a/test_files/class.untyped/class.js +++ b/test_files/class.untyped/class.js @@ -1,10 +1,8 @@ // test_files/class.untyped/class.ts(48,1): warning TS0: type/symbol conflict for Zone, using {?} for now /** - * * @fileoverview * Generated from: test_files/class.untyped/class.ts * @suppress {uselessCode} - * */ goog.module('test_files.class.untyped.class'); var module = module || { id: 'test_files/class.untyped/class.ts' }; diff --git a/test_files/class/class.js b/test_files/class/class.js index 2dff26d72..58672f4ac 100644 --- a/test_files/class/class.js +++ b/test_files/class/class.js @@ -6,7 +6,6 @@ // test_files/class/class.ts(136,38): warning TS0: type/symbol conflict for Zone, using {?} for now // test_files/class/class.ts(136,1): warning TS0: dropped implements: {?} type /** - * * @fileoverview This test exercises the various ways classes and interfaces can * interact. There are three types of classy things: interface, class, abstract * class And there are two keywords for relating them: extends, implements You @@ -16,7 +15,6 @@ * Generated from: test_files/class/class.ts * @suppress {uselessCode} * @suppress {dangerousUnrecognizedTypeError} - * */ goog.module('test_files.class.class'); var module = module || { id: 'test_files/class/class.ts' }; diff --git a/test_files/clutz.no_externs/import_default.js b/test_files/clutz.no_externs/import_default.js index ae4bb2d6a..390d3e2b1 100644 --- a/test_files/clutz.no_externs/import_default.js +++ b/test_files/clutz.no_externs/import_default.js @@ -1,8 +1,6 @@ /** - * * @fileoverview Reproduces a problem where a renamed Clutz default export ({default as X}) would * produce type annotations including an indirection to the aliased symbol. - * * Generated from: test_files/clutz.no_externs/import_default.ts */ goog.module('test_files.clutz.no_externs.import_default'); diff --git a/test_files/clutz_imports.declaration.no_externs/clutz2_output_demo8.d.ts b/test_files/clutz_imports.declaration.no_externs/clutz2_output_demo8.d.ts deleted file mode 100644 index 3874d54c0..000000000 --- a/test_files/clutz_imports.declaration.no_externs/clutz2_output_demo8.d.ts +++ /dev/null @@ -1,18 +0,0 @@ -//!! generated by clutz2 -/** - * @fileoverview This file contains the Clutz2 output for a simple goog.provide. - * It was manually created and is a support file for the actual test. - */ - -declare namespace ಠ_ಠ.clutz { - namespace demo8 { - export class C { - private noStructuralTyping_demo8$C: any; - } - } // namespace demo8 -} // ಠ_ಠ.clutz - -declare module 'goog:demo8' { - import demo8 = ಠ_ಠ.clutz.demo8; - export default demo8; -} diff --git a/test_files/clutz_imports.declaration.no_externs/clutz_output_demo1.d.ts b/test_files/clutz_imports.declaration.no_externs/clutz_output_demo1.d.ts deleted file mode 100644 index 923e8b723..000000000 --- a/test_files/clutz_imports.declaration.no_externs/clutz_output_demo1.d.ts +++ /dev/null @@ -1,16 +0,0 @@ -//!! generated by clutz. -/** - * @fileoverview This file contains the Clutz output for a simple goog.module. - * It was manually created and is a support file for the actual test. - */ - -declare namespace ಠ_ಠ.clutz.module$exports$demo1 { - class C { - private noStructuralTyping_module$exports$demo1_C: any; - foo(): void; - } -} -declare module 'goog:demo1' { -import demo1 = ಠ_ಠ.clutz.module$exports$demo1; - export = demo1; -} diff --git a/test_files/clutz_imports.declaration.no_externs/clutz_output_demo2.d.ts b/test_files/clutz_imports.declaration.no_externs/clutz_output_demo2.d.ts deleted file mode 100644 index 52c32f839..000000000 --- a/test_files/clutz_imports.declaration.no_externs/clutz_output_demo2.d.ts +++ /dev/null @@ -1,16 +0,0 @@ -//!! generated by clutz. -/** - * @fileoverview This file contains the Clutz output for a simple goog.provide. - * It was manually created and is a support file for the actual test. - */ - -declare namespace ಠ_ಠ.clutz.demo2 { - class C { - private noStructuralTyping_demo2_C: any; - bar(): void; - } -} -declare module 'goog:demo2' { -import demo2 = ಠ_ಠ.clutz.demo2; - export = demo2; -} diff --git a/test_files/clutz_imports.declaration.no_externs/clutz_output_demo3.d.ts b/test_files/clutz_imports.declaration.no_externs/clutz_output_demo3.d.ts deleted file mode 100644 index 5c68d9376..000000000 --- a/test_files/clutz_imports.declaration.no_externs/clutz_output_demo3.d.ts +++ /dev/null @@ -1,16 +0,0 @@ -//!! generated by clutz. -/** - * @fileoverview This file contains the Clutz output for a simple goog.module. - * It was manually created and is a support file for the actual test. - */ - -declare namespace ಠ_ಠ.clutz { - class module$exports$demo3 { - private noStructuralTyping_module$exports$demo3: any; - bar(): void; - } -} -declare module 'goog:demo3' { -import demo3 = ಠ_ಠ.clutz.module$exports$demo3; - export default demo3; -} diff --git a/test_files/clutz_imports.declaration.no_externs/clutz_output_demo4.d.ts b/test_files/clutz_imports.declaration.no_externs/clutz_output_demo4.d.ts deleted file mode 100644 index 4758a64fd..000000000 --- a/test_files/clutz_imports.declaration.no_externs/clutz_output_demo4.d.ts +++ /dev/null @@ -1,13 +0,0 @@ -//!! generated by clutz. -/** - * @fileoverview This file contains the Clutz output for a simple goog.provide. - * It was manually created and is a support file for the actual test. - */ - -declare namespace ಠ_ಠ.clutz.demo4 { - function f(): void; -} -declare module 'goog:demo4' { -import demo4 = ಠ_ಠ.clutz.demo4; - export = demo4; -} diff --git a/test_files/clutz_imports.declaration.no_externs/clutz_output_demo5.d.ts b/test_files/clutz_imports.declaration.no_externs/clutz_output_demo5.d.ts deleted file mode 100644 index 9cb9ad9ae..000000000 --- a/test_files/clutz_imports.declaration.no_externs/clutz_output_demo5.d.ts +++ /dev/null @@ -1,17 +0,0 @@ -//!! generated by clutz. -/** - * @fileoverview This file contains the Clutz output for a simple goog.module. - * It was manually created and is a support file for the actual test. - */ - -declare namespace ಠ_ಠ.clutz.module$exports$demo5 { - class C { - private noStructuralTyping_module$exports$demo5_C : any; - f ( ) : void ; - } -} -declare module 'goog:demo5' { - import demo5 = ಠ_ಠ.clutz.module$exports$demo5; - export = demo5; -} - diff --git a/test_files/clutz_imports.declaration.no_externs/clutz_output_demo6.d.ts b/test_files/clutz_imports.declaration.no_externs/clutz_output_demo6.d.ts deleted file mode 100644 index 96b67ac90..000000000 --- a/test_files/clutz_imports.declaration.no_externs/clutz_output_demo6.d.ts +++ /dev/null @@ -1,18 +0,0 @@ -//!! generated by clutz. -/** - * @fileoverview This file contains the Clutz output for a simple goog.module, - * with a generic class. - * It was manually created and is a support file for the actual test. - */ - -declare namespace ಠ_ಠ.clutz.module$exports$demo6 { - class C < T = any > { - private noStructuralTyping_module$exports$demo6_C : [ T ]; - foo ( ) : void ; - } -} -declare module 'goog:demo6' { - import demo6 = ಠ_ಠ.clutz.module$exports$demo6; - export = demo6; -} - diff --git a/test_files/clutz_imports.declaration.no_externs/clutz_output_demo7.d.ts b/test_files/clutz_imports.declaration.no_externs/clutz_output_demo7.d.ts deleted file mode 100644 index 5333dad3a..000000000 --- a/test_files/clutz_imports.declaration.no_externs/clutz_output_demo7.d.ts +++ /dev/null @@ -1,11 +0,0 @@ -//!! generated by clutz. -/** - * @fileoverview This file contains the Clutz output for an externs file. - * It was manually created and is a support file for the actual test. - */ - -declare namespace demo7 { - class C { - foo(): void; - } -} diff --git a/test_files/clutz_imports.declaration.no_externs/user_code.d.ts b/test_files/clutz_imports.declaration.no_externs/user_code.d.ts deleted file mode 100644 index 0a3d3ba90..000000000 --- a/test_files/clutz_imports.declaration.no_externs/user_code.d.ts +++ /dev/null @@ -1,53 +0,0 @@ -// test_files/clutz_imports.declaration.no_externs/user_code.ts(39,1): warning TS0: anonymous type has no symbol -//!! generated by tsickle from test_files/clutz_imports.declaration.no_externs/user_code.ts -import "test_files/clutz_imports.declaration.no_externs/clutz_output_demo1"; -import "test_files/clutz_imports.declaration.no_externs/clutz_output_demo2"; -import "test_files/clutz_imports.declaration.no_externs/clutz2_output_demo8"; -import "test_files/clutz_imports.declaration.no_externs/clutz_output_demo4"; -import "test_files/clutz_imports.declaration.no_externs/clutz_output_demo6"; -import "test_files/clutz_imports.declaration.no_externs/clutz_output_demo5"; -import "test_files/clutz_imports.declaration.no_externs/clutz_output_demo7"; -/** - * @fileoverview This file simulates a TypeScript file that interacts with Clutz - * types. The expected output is that the generated .d.ts file has explicit - * "import" statements that refer directly to the paths that define some of - * the Clutz symbols (either goog: or look of disapproval) referenced in the - * public API of this file. - */ -import * as demo1 from 'goog:demo1'; -/** - * demo1 is exposed in the public API via an import, so we expect the output - * d.ts to have an import of the module underlying goog:demo1. - */ -export declare function f1(c: demo1.C): void; -/** - * demo2 is exposed in the public API via a direct reference to the look of - * disapproval namespace, so we expect the output d.ts to have an import of the - * module underlying goog:demo2. - * - * demo8 is the same, but the d.ts file is generated by Clutz2. - */ -export declare function f2(c: ಠ_ಠ.clutz.demo2.C, c2: ಠ_ಠ.clutz.demo8.C): void; -/** - * demo4 verifies that the Clutz type via 'typeof' still produces an import - * statement in the output. (It differs from the above in that a typeof node - * in the TS AST contains the reference to a Clutz symbol as a value, not a - * type.) - */ -export type f4 = typeof ಠ_ಠ.clutz.demo4; -export declare function f5(): ಠ_ಠ.clutz.module$exports$demo6.C<ಠ_ಠ.clutz.module$exports$demo5.C> | undefined; -/** - * demo7 contains typings generated from externs. - * - * Even though we don't reference the internal Clutz namespace here, we expect - * the output d.ts to have an import to the demo7 file. - */ -export declare function f6(c: demo7.C): void; -declare global { - namespace ಠ_ಠ.clutz { - export { f1 as module$contents$test_files$clutz_imports$declaration$no_externs$user_code_f1, f2 as module$contents$test_files$clutz_imports$declaration$no_externs$user_code_f2, f4 as module$contents$test_files$clutz_imports$declaration$no_externs$user_code_f4, f5 as module$contents$test_files$clutz_imports$declaration$no_externs$user_code_f5, f6 as module$contents$test_files$clutz_imports$declaration$no_externs$user_code_f6 }; - export namespace module$exports$test_files$clutz_imports$declaration$no_externs$user_code { - export { f1, f2, f4, f5, f6 }; - } - } -} diff --git a/test_files/clutz_imports.declaration.no_externs/user_code.ts b/test_files/clutz_imports.declaration.no_externs/user_code.ts deleted file mode 100644 index 9c2741eea..000000000 --- a/test_files/clutz_imports.declaration.no_externs/user_code.ts +++ /dev/null @@ -1,69 +0,0 @@ -/** - * @fileoverview This file simulates a TypeScript file that interacts with Clutz - * types. The expected output is that the generated .d.ts file has explicit - * "import" statements that refer directly to the paths that define some of - * the Clutz symbols (either goog: or look of disapproval) referenced in the - * public API of this file. - */ - -import * as demo1 from 'goog:demo1'; -import demo3 from 'goog:demo3'; - -/** - * demo1 is exposed in the public API via an import, so we expect the output - * d.ts to have an import of the module underlying goog:demo1. - */ -export function f1(c: demo1.C) {} - -/** - * demo2 is exposed in the public API via a direct reference to the look of - * disapproval namespace, so we expect the output d.ts to have an import of the - * module underlying goog:demo2. - * - * demo8 is the same, but the d.ts file is generated by Clutz2. - */ -export function f2(c: ಠ_ಠ.clutz.demo2.C, c2: ಠ_ಠ.clutz.demo8.C) {} - -/** - * demo3 is used by this module, but not exported, so we don't expect an import - * of the underlying module in the output d.ts. - */ -function f3(c: demo3) {} - -/** - * demo4 verifies that the Clutz type via 'typeof' still produces an import - * statement in the output. (It differs from the above in that a typeof node - * in the TS AST contains the reference to a Clutz symbol as a value, not a - * type.) - */ -export type f4 = typeof ಠ_ಠ.clutz.demo4; - -/** - * This next example verifies that references generated by TS are still handled. - * The internal function here references a Clutz type, which normally would - * stay internal-only and not affect the d.ts. But then we export a function - * that uses inference to refer to this type. - * - * This is a special case because the Clutz type appears in the d.ts but it - * is generated by a codepath in the TS compiler that causes the type to have no - * symbol present in the TypeChecker. - * - * This also uses a generic, to cover one additional case. - * We expect both demo5 and demo6 to show up in the public API of the d.ts. - */ -function internal(): - ಠ_ಠ.clutz.module$exports$demo6.C<ಠ_ಠ.clutz.module$exports$demo5.C>| - undefined { - return undefined; -} -export function f5() { - return internal(); -} - -/** - * demo7 contains typings generated from externs. - * - * Even though we don't reference the internal Clutz namespace here, we expect - * the output d.ts to have an import to the demo7 file. - */ -export function f6(c: demo7.C) {} diff --git a/test_files/clutz_type_value.no_externs/user.js b/test_files/clutz_type_value.no_externs/user.js index 787b8adab..4128b4022 100644 --- a/test_files/clutz_type_value.no_externs/user.js +++ b/test_files/clutz_type_value.no_externs/user.js @@ -1,10 +1,8 @@ /** - * * @fileoverview This test verifies that a type/value-conflict symbol that * occurs in a clutz file still can be used in a heritage clause. * Generated from: test_files/clutz_type_value.no_externs/user.ts * @suppress {uselessCode} - * */ goog.module('test_files.clutz_type_value.no_externs.user'); var module = module || { id: 'test_files/clutz_type_value.no_externs/user.ts' }; diff --git a/test_files/comments/comments.js b/test_files/comments/comments.js index 943006a73..c234595a9 100644 --- a/test_files/comments/comments.js +++ b/test_files/comments/comments.js @@ -1,9 +1,7 @@ /** - * * @fileoverview * Generated from: test_files/comments/comments.ts * @suppress {uselessCode} - * */ goog.module('test_files.comments.comments'); var module = module || { id: 'test_files/comments/comments.ts' }; diff --git a/test_files/comments/freestanding_jsdoc.js b/test_files/comments/freestanding_jsdoc.js new file mode 100644 index 000000000..3b4fba061 --- /dev/null +++ b/test_files/comments/freestanding_jsdoc.js @@ -0,0 +1,25 @@ +/** + * @fileoverview Tsickle should escape unknown JSDoc tags in comments not + * attached to any particular node. + * Generated from: test_files/comments/freestanding_jsdoc.ts + */ +/** + * \@unknowntag + */ +goog.module('test_files.comments.freestanding_jsdoc'); +var module = module || { id: 'test_files/comments/freestanding_jsdoc.ts' }; +goog.require('tslib'); +/** + * \@unknowntag + */ +class Foo { +} +/** + * \@param a it's a string + */ +/** + * This is "bar". + * @param {string} a + * @return {void} + */ +function bar(a) { } diff --git a/test_files/comments/freestanding_jsdoc.ts b/test_files/comments/freestanding_jsdoc.ts new file mode 100644 index 000000000..ec06f5803 --- /dev/null +++ b/test_files/comments/freestanding_jsdoc.ts @@ -0,0 +1,15 @@ +/** + * @fileoverview Tsickle should escape unknown JSDoc tags in comments not + * attached to any particular node. + */ + +/** @unknowntag */ + +/** @unknowntag */ +class Foo {} + + +/** @param a it's a string */ + +/** This is "bar". */ +function bar(a: string) {} diff --git a/test_files/comments/trailing_no_semicolon.js b/test_files/comments/trailing_no_semicolon.js new file mode 100644 index 000000000..897b2be10 --- /dev/null +++ b/test_files/comments/trailing_no_semicolon.js @@ -0,0 +1,20 @@ +/** + * @fileoverview Tests that the JSDoc comment of `other` is only emitted once. + * Without the trailing semicolon after `noExplicitSemicolon` TypeScript seems + * to duplicate the trailing comment as soon as a custom transformer modifies + * the variable statement. + * Generated from: test_files/comments/trailing_no_semicolon.ts + */ +goog.module('test_files.comments.trailing_no_semicolon'); +var module = module || { id: 'test_files/comments/trailing_no_semicolon.ts' }; +goog.require('tslib'); +/** @type {number} */ +const noExplicitSemicolon = 0; +/** + * This is a comment with a JSDoc tag + * JSCompiler doesn't recognize + * + * \@foobar + * @type {number} + */ +exports.other = 1; diff --git a/test_files/comments/trailing_no_semicolon.ts b/test_files/comments/trailing_no_semicolon.ts new file mode 100644 index 000000000..b68b9844a --- /dev/null +++ b/test_files/comments/trailing_no_semicolon.ts @@ -0,0 +1,17 @@ +/** + * @fileoverview Tests that the JSDoc comment of `other` is only emitted once. + * Without the trailing semicolon after `noExplicitSemicolon` TypeScript seems + * to duplicate the trailing comment as soon as a custom transformer modifies + * the variable statement. + */ + + +const noExplicitSemicolon = 0 + +/** + * This is a comment with a JSDoc tag + * JSCompiler doesn't recognize + * + * @foobar + */ +export const other = 1; diff --git a/test_files/conditional_rest_tuple_type/conditional_rest_tuple_type.js b/test_files/conditional_rest_tuple_type/conditional_rest_tuple_type.js index 3d59702e8..b1d85f603 100644 --- a/test_files/conditional_rest_tuple_type/conditional_rest_tuple_type.js +++ b/test_files/conditional_rest_tuple_type/conditional_rest_tuple_type.js @@ -2,10 +2,8 @@ // test_files/conditional_rest_tuple_type/conditional_rest_tuple_type.ts(8,14): warning TS0: unable to translate rest args type // test_files/conditional_rest_tuple_type/conditional_rest_tuple_type.ts(9,31): warning TS0: failed to resolve rest parameter type, emitting ? /** - * * @fileoverview Tests an interaction between conditional types and rest (...) * types. - * * Generated from: test_files/conditional_rest_tuple_type/conditional_rest_tuple_type.ts */ goog.module('test_files.conditional_rest_tuple_type.conditional_rest_tuple_type'); diff --git a/test_files/ctors/ctors.js b/test_files/ctors/ctors.js index 0b71e35c9..f16e84213 100644 --- a/test_files/ctors/ctors.js +++ b/test_files/ctors/ctors.js @@ -1,9 +1,7 @@ /** - * * @fileoverview * Generated from: test_files/ctors/ctors.ts * @suppress {uselessCode} - * */ goog.module('test_files.ctors.ctors'); var module = module || { id: 'test_files/ctors/ctors.ts' }; diff --git a/test_files/debugger/user.js b/test_files/debugger/user.js index 45bf4e64e..004d4db2e 100644 --- a/test_files/debugger/user.js +++ b/test_files/debugger/user.js @@ -1,9 +1,7 @@ /** - * * @fileoverview * Generated from: test_files/debugger/user.ts * @suppress {checkTypes} - * */ // TODO: the type below should be emitted as `outer.debugger.Foo`. However // TypeScript does not take the re-export in the outer namespace into account, diff --git a/test_files/decl_merge/imported_inner_decl.js b/test_files/decl_merge/imported_inner_decl.js index fbbf4cd3f..7448ae67c 100644 --- a/test_files/decl_merge/imported_inner_decl.js +++ b/test_files/decl_merge/imported_inner_decl.js @@ -1,9 +1,7 @@ /** - * * @fileoverview Ensure transformed inner classes and enums can be * imported and used, and the types are properly annotated in the * JS output. - * * Generated from: test_files/decl_merge/imported_inner_decl.ts */ goog.module('test_files.decl_merge.imported_inner_decl'); diff --git a/test_files/decl_merge/inner_class.js b/test_files/decl_merge/inner_class.js index 2ef74eb06..6e5b4ef81 100644 --- a/test_files/decl_merge/inner_class.js +++ b/test_files/decl_merge/inner_class.js @@ -1,14 +1,12 @@ // test_files/decl_merge/inner_class.ts(49,7): warning TS0: anonymous type has no symbol // test_files/decl_merge/inner_class.ts(51,13): warning TS0: anonymous type has no symbol /** - * * @fileoverview Ensure inner classes defined with declaration merging * are properly transformed and hoisted out of the namespace, and * no iife is created for the namespace. * * Generated from: test_files/decl_merge/inner_class.ts * @suppress {uselessCode,checkTypes} - * */ goog.module('test_files.decl_merge.inner_class'); var module = module || { id: 'test_files/decl_merge/inner_class.ts' }; diff --git a/test_files/decl_merge/inner_enum.js b/test_files/decl_merge/inner_enum.js index 812e0e5f9..6015d193b 100644 --- a/test_files/decl_merge/inner_enum.js +++ b/test_files/decl_merge/inner_enum.js @@ -1,12 +1,10 @@ /** - * * @fileoverview Ensure enums nested in a class, defined with declaration * merging are properly transformed and hoisted out of the namespace, and no * iife is created for the namespace. * * Generated from: test_files/decl_merge/inner_enum.ts * @suppress {uselessCode} - * */ goog.module('test_files.decl_merge.inner_enum'); var module = module || { id: 'test_files/decl_merge/inner_enum.ts' }; diff --git a/test_files/decl_merge/inner_interface.js b/test_files/decl_merge/inner_interface.js index 27d6cb6ba..9db707d05 100644 --- a/test_files/decl_merge/inner_interface.js +++ b/test_files/decl_merge/inner_interface.js @@ -1,12 +1,10 @@ /** - * * @fileoverview Ensure interfaces nested in an outer class or interface, * defined with declaration merging are properly transformed and hoisted out of * the namespace, and no iife is created for the namespace. * * Generated from: test_files/decl_merge/inner_interface.ts * @suppress {uselessCode} - * */ goog.module('test_files.decl_merge.inner_interface'); var module = module || { id: 'test_files/decl_merge/inner_interface.ts' }; @@ -54,7 +52,10 @@ if (false) { */ OC$I.prototype.bar = function (e) { }; } -/** @const */ +/** + * Bla interface + * @const + */ OC.I = OC$I; /** * @record @@ -81,14 +82,19 @@ const OI$E = { }; OI$E[OI$E.a] = 'a'; OI$E[OI$E.b] = 'b'; -/** @const */ +/** + * Bla enum + * @const + */ OI.E = OI$E; /** @const */ OI.C1 = 0; /** @const */ OI.C2 = 'string const'; -/** Bla const */ -/** @const */ +/** + * Bla const + * @const + */ OI.C3 = OI.E.a; /** * @param {!OC.J} j diff --git a/test_files/decl_merge/inner_interface.ts b/test_files/decl_merge/inner_interface.ts index 6a3943e5a..5e6d86478 100644 --- a/test_files/decl_merge/inner_interface.ts +++ b/test_files/decl_merge/inner_interface.ts @@ -39,4 +39,4 @@ function f(j: OC.J) { function g(): OI.E { return OI.E.a; -} \ No newline at end of file +} diff --git a/test_files/decl_merge/inner_typedef.js b/test_files/decl_merge/inner_typedef.js index 763f42800..54d300d34 100644 --- a/test_files/decl_merge/inner_typedef.js +++ b/test_files/decl_merge/inner_typedef.js @@ -1,11 +1,9 @@ /** - * * @fileoverview Ensure that a type alias declared in a declaration * merging namespace is generated as a property of the merged outer class. * * Generated from: test_files/decl_merge/inner_typedef.ts * @suppress {uselessCode,checkTypes} - * */ goog.module('test_files.decl_merge.inner_typedef'); var module = module || { id: 'test_files/decl_merge/inner_typedef.ts' }; diff --git a/test_files/decl_merge/outer_enum.js b/test_files/decl_merge/outer_enum.js new file mode 100644 index 000000000..8b7382f4c --- /dev/null +++ b/test_files/decl_merge/outer_enum.js @@ -0,0 +1,29 @@ +/** + * @fileoverview Ensure that a function declared in a declaration + * merging namespace is generated as a property of the merged outer enum. + * + * Generated from: test_files/decl_merge/outer_enum.ts + * @suppress {uselessCode,checkTypes} + */ +goog.module('test_files.decl_merge.outer_enum'); +var module = module || { id: 'test_files/decl_merge/outer_enum.ts' }; +goog.require('tslib'); +/** @enum {number} */ +const E = { + a: 42, + b: 43, +}; +exports.E = E; +E[E.a] = 'a'; +E[E.b] = 'b'; +/** + * @param {string} s + * @return {!E} + */ +function E$fromString(s) { + return s === 'a' ? E.a : E.b; +} +/** @const */ +E.fromString = E$fromString; +/** @type {!E} */ +const e = E.fromString('a'); diff --git a/test_files/decl_merge/outer_enum.ts b/test_files/decl_merge/outer_enum.ts new file mode 100644 index 000000000..b89996cc3 --- /dev/null +++ b/test_files/decl_merge/outer_enum.ts @@ -0,0 +1,20 @@ +/** + * @fileoverview Ensure that a function declared in a declaration + * merging namespace is generated as a property of the merged outer enum. + * + * @suppress {uselessCode,checkTypes} + */ + +export enum E { + a = 42, + b +} + +// tslint:disable-next-line:no-namespace +export namespace E { + export function fromString(s: string) { + return s === 'a' ? E.a : E.b; + }; +} + +const e = E.fromString('a'); diff --git a/test_files/decl_merge/rejected_ns.js b/test_files/decl_merge/rejected_ns.js index 54aa1d565..566444941 100644 --- a/test_files/decl_merge/rejected_ns.js +++ b/test_files/decl_merge/rejected_ns.js @@ -1,20 +1,19 @@ -// test_files/decl_merge/rejected_ns.ts(34,1): warning TS0: type/symbol conflict for Inbetween, using {?} for now +// test_files/decl_merge/rejected_ns.ts(32,1): warning TS0: type/symbol conflict for Inbetween, using {?} for now // test_files/decl_merge/rejected_ns.ts(9,11): error TS0: transformation of plain namespace not supported. (go/ts-merged-namespaces) -// test_files/decl_merge/rejected_ns.ts(13,11): error TS0: merged declaration must be local class or interface. (go/ts-merged-namespaces) -// test_files/decl_merge/rejected_ns.ts(21,11): error TS0: merged declaration must be local class or interface. (go/ts-merged-namespaces) -// test_files/decl_merge/rejected_ns.ts(26,3): error TS0: const declaration only allowed when merging with an interface (go/ts-merged-namespaces) -// test_files/decl_merge/rejected_ns.ts(38,3): error TS0: non-const values are not supported. (go/ts-merged-namespaces) -// test_files/decl_merge/rejected_ns.ts(40,9): error TS0: 'K' must be exported. (go/ts-merged-namespaces) -// test_files/decl_merge/rejected_ns.ts(42,16): error TS0: Destructuring declarations are not supported. (go/ts-merged-namespaces) -// test_files/decl_merge/rejected_ns.ts(47,11): error TS0: nested namespaces are not supported. (go/ts-merged-namespaces) +// test_files/decl_merge/rejected_ns.ts(13,11): error TS0: merged declaration must be local class, enum, or interface. (go/ts-merged-namespaces) +// test_files/decl_merge/rejected_ns.ts(19,3): error TS0: const declaration only allowed when merging with an interface (go/ts-merged-namespaces) +// test_files/decl_merge/rejected_ns.ts(24,3): error TS0: function declaration only allowed when merging with an enum (go/ts-merged-namespaces) +// test_files/decl_merge/rejected_ns.ts(36,3): error TS0: non-const values are not supported. (go/ts-merged-namespaces) +// test_files/decl_merge/rejected_ns.ts(38,9): error TS0: 'K' must be exported. (go/ts-merged-namespaces) +// test_files/decl_merge/rejected_ns.ts(40,16): error TS0: Destructuring declarations are not supported. (go/ts-merged-namespaces) +// test_files/decl_merge/rejected_ns.ts(44,3): error TS0: function declaration only allowed when merging with an enum (go/ts-merged-namespaces) +// test_files/decl_merge/rejected_ns.ts(48,11): error TS0: nested namespaces are not supported. (go/ts-merged-namespaces) /** - * * @fileoverview Test namespace transformations that are not supported * and result in compiler errors. * * Generated from: test_files/decl_merge/rejected_ns.ts * @suppress {uselessCode,checkTypes} - * */ goog.module('test_files.decl_merge.rejected_ns'); var module = module || { id: 'test_files/decl_merge/rejected_ns.ts' }; @@ -24,21 +23,21 @@ goog.require('tslib'); * @return {void} */ function funcToBeMerged() { } -/** @enum {number} */ -const Colors = { - red: 0, - green: 1, - blue: 2, -}; -Colors[Colors.red] = 'red'; -Colors[Colors.green] = 'green'; -Colors[Colors.blue] = 'blue'; // Adding const values is only allowed on interfaces. class Cabbage { } (function (Cabbage) { Cabbage.C = 0; })(Cabbage || (Cabbage = {})); +// Adding functions is only allowed on enums. +(function (Cabbage) { + /** + * @return {void} + */ + function foo() { } + Cabbage.foo = foo; + ; +})(Cabbage || (Cabbage = {})); /** @type {{a: number, b: string}} */ const o = { a: 0, @@ -60,6 +59,13 @@ var Inbetween; // Destructuring declarations are not allowed. Inbetween.a = o.a, Inbetween.b = o.b; })(Inbetween || (Inbetween = {})); +(function (Inbetween) { + /** + * @return {void} + */ + function foo() { } + Inbetween.foo = foo; +})(Inbetween || (Inbetween = {})); // Nested namespaces are not supported. class A { } diff --git a/test_files/decl_merge/rejected_ns.ts b/test_files/decl_merge/rejected_ns.ts index 2e414d803..fdb0df004 100644 --- a/test_files/decl_merge/rejected_ns.ts +++ b/test_files/decl_merge/rejected_ns.ts @@ -12,13 +12,6 @@ namespace notMerging {} function funcToBeMerged() {} namespace funcToBeMerged {} -// Declaration merging with enums is not supported. -enum Colors { - red, - green, - blue -} -namespace Colors {} // Adding const values is only allowed on interfaces. class Cabbage {} @@ -26,6 +19,11 @@ namespace Cabbage { export const C = 0; } +// Adding functions is only allowed on enums. +namespace Cabbage { + export function foo() {}; +} + const o = { a: 0, b: '' @@ -42,6 +40,9 @@ namespace Inbetween { export const {a, b} = o; } +namespace Inbetween { + export function foo() {} +} // Nested namespaces are not supported. class A {} namespace A.B {} diff --git a/test_files/declare_export/declare_export.js b/test_files/declare_export/declare_export.js index fb7dc47dd..d73c4759c 100644 --- a/test_files/declare_export/declare_export.js +++ b/test_files/declare_export/declare_export.js @@ -1,7 +1,3 @@ -/** - * @fileoverview added by tsickle - * Generated from: test_files/declare_export/declare_export.ts - */ // All of the types/values declared in this file should // 1) generate externs // 2) generate an export @@ -12,6 +8,10 @@ // should be namespaced into a private namespace. // E.g. "export declare interface Error" should not conflict with the // Closure builtin Error type. +/** + * @fileoverview added by tsickle + * Generated from: test_files/declare_export/declare_export.ts + */ goog.module('test_files.declare_export.declare_export'); var module = module || { id: 'test_files/declare_export/declare_export.ts' }; goog.require('tslib'); diff --git a/test_files/declare_export_dts/user.js b/test_files/declare_export_dts/user.js index 8b7f2fa28..c46edcc05 100644 --- a/test_files/declare_export_dts/user.js +++ b/test_files/declare_export_dts/user.js @@ -1,9 +1,7 @@ /** - * * @fileoverview * Generated from: test_files/declare_export_dts/user.ts * @suppress {checkTypes} - * */ goog.module('test_files.declare_export_dts.user'); var module = module || { id: 'test_files/declare_export_dts/user.ts' }; diff --git a/test_files/declare_import/declare_import_in_ts.js b/test_files/declare_import/declare_import_in_ts.js index 1ab369a39..8334701d8 100644 --- a/test_files/declare_import/declare_import_in_ts.js +++ b/test_files/declare_import/declare_import_in_ts.js @@ -4,13 +4,11 @@ // test_files/declare_import/declare_import_in_ts.ts(22,1): warning TS0: dropped extends: {?} type // test_files/declare_import/declare_import_in_ts.ts(25,1): warning TS0: dropped extends: {?} type /** - * * @fileoverview Tests that imports in .ts resolve to the correct result names. See externs.ts * addImportAliases. * * The code below tests mixing symbols from .d.ts and .ts files, to make sure type references are * uniformly generated. - * * Generated from: test_files/declare_import/declare_import_in_ts.ts */ goog.module('test_files.declare_import.declare_import_in_ts'); diff --git a/test_files/declare_var_and_ns/externs.js b/test_files/declare_var_and_ns/externs.js index eb909e167..9b522a4c1 100644 --- a/test_files/declare_var_and_ns/externs.js +++ b/test_files/declare_var_and_ns/externs.js @@ -6,8 +6,6 @@ // Generated from: test_files/declare_var_and_ns/declare_var_and_ns.d.ts /** @type {!globalVariable.SomeInterface} */ var globalVariable; -/** @const */ -var globalVariable = {}; /** * @record * @struct diff --git a/test_files/decorator/decorator.js b/test_files/decorator/decorator.js index d378afbf4..d3e726cdd 100644 --- a/test_files/decorator/decorator.js +++ b/test_files/decorator/decorator.js @@ -4,14 +4,12 @@ var module = module || { id: 'test_files/decorator/decorator.ts' }; const tslib_1 = goog.require('tslib'); const __tsickle_googReflect = goog.require("goog.reflect"); /** - * * @fileoverview OtherClass is reachable via the imports for './external' and * './external2'. Test that were using it from the right import, and not just * the first that allows access to the value. That is important when imports are * elided. * Generated from: test_files/decorator/decorator.ts * @suppress {uselessCode} - * */ const tsickle_default_export_1 = goog.requireType("test_files.decorator.default_export"); const tsickle_external_2 = goog.requireType("test_files.decorator.external"); diff --git a/test_files/decorator/default_export.js b/test_files/decorator/default_export.js index d2580a9bc..cf42cd7e1 100644 --- a/test_files/decorator/default_export.js +++ b/test_files/decorator/default_export.js @@ -1,9 +1,7 @@ /** - * * @fileoverview Tests using a default imported class for in a decorated ctor. * Generated from: test_files/decorator/default_export.ts * @suppress {uselessCode} - * */ goog.module('test_files.decorator.default_export'); var module = module || { id: 'test_files/decorator/default_export.ts' }; diff --git a/test_files/decorator/export_const.js b/test_files/decorator/export_const.js index 7244ec0c9..533749b16 100644 --- a/test_files/decorator/export_const.js +++ b/test_files/decorator/export_const.js @@ -3,10 +3,8 @@ goog.module('test_files.decorator.export_const'); var module = module || { id: 'test_files/decorator/export_const.ts' }; const tslib_1 = goog.require('tslib'); /** - * * @fileoverview Decorated class, whose type and value are exported separately. * The value used afterwards. - * * Generated from: test_files/decorator/export_const.ts */ /** diff --git a/test_files/decorator/only_types.js b/test_files/decorator/only_types.js index 0d51e5b62..dec44532f 100644 --- a/test_files/decorator/only_types.js +++ b/test_files/decorator/only_types.js @@ -1,10 +1,8 @@ /** - * * @fileoverview only_types only exports types, so TypeScript will elide the * import entirely. * Generated from: test_files/decorator/only_types.ts * @suppress {uselessCode} - * */ goog.module('test_files.decorator.only_types'); var module = module || { id: 'test_files/decorator/only_types.ts' }; diff --git a/test_files/doc_params/doc_params.js b/test_files/doc_params/doc_params.js index 9550608d8..0b4ba83b3 100644 --- a/test_files/doc_params/doc_params.js +++ b/test_files/doc_params/doc_params.js @@ -1,9 +1,7 @@ /** - * * @fileoverview * Generated from: test_files/doc_params/doc_params.ts * @suppress {uselessCode} - * */ goog.module('test_files.doc_params.doc_params'); var module = module || { id: 'test_files/doc_params/doc_params.ts' }; diff --git a/test_files/docs_on_ctor_param_properties/docs_on_ctor_param_properties.js b/test_files/docs_on_ctor_param_properties/docs_on_ctor_param_properties.js index f0a2b8095..22a62f359 100644 --- a/test_files/docs_on_ctor_param_properties/docs_on_ctor_param_properties.js +++ b/test_files/docs_on_ctor_param_properties/docs_on_ctor_param_properties.js @@ -1,9 +1,7 @@ /** - * * @fileoverview * Generated from: test_files/docs_on_ctor_param_properties/docs_on_ctor_param_properties.ts * @suppress {uselessCode} - * */ goog.module('test_files.docs_on_ctor_param_properties.docs_on_ctor_param_properties'); var module = module || { id: 'test_files/docs_on_ctor_param_properties/docs_on_ctor_param_properties.ts' }; diff --git a/test_files/enum.no_nstransform/enum.js b/test_files/enum.no_nstransform/enum.js new file mode 100644 index 000000000..c30ae982f --- /dev/null +++ b/test_files/enum.no_nstransform/enum.js @@ -0,0 +1,40 @@ +/** + * @fileoverview Check that enums are translated to a var declaration + * when namespace transformation is turned off, i.e. the build target + * has the attribute --allow_unoptimized_namespaces. + * Generated from: test_files/enum.no_nstransform/enum.ts + * @suppress {checkTypes,uselessCode} + */ +goog.module('test_files.enum.no_nstransform.enum'); +var module = module || { id: 'test_files/enum.no_nstransform/enum.ts' }; +goog.require('tslib'); +/** + * This enum should be translated to `var E = {...}` instead of the usual + * `const E = {...}` + * @enum {number} + */ +var E = { + e0: 0, + e1: 1, + e2: 2, +}; +exports.E = E; +E[E.e0] = 'e0'; +E[E.e1] = 'e1'; +E[E.e2] = 'e2'; +// We need to emit the enum as a var declaration so that declaration +// merging with a namespace works. The unoptimized namespace is emitted +// by tsc as a var declaration and an IIFE. +var E; +(function (E) { + /** + * @param {string} s + * @return {?} + */ + function fromString(s) { + return E.e0; + } + E.fromString = fromString; +})(E || (E = {})); +/** @type {!E} */ +const foo = E.e2; diff --git a/test_files/enum.no_nstransform/enum.ts b/test_files/enum.no_nstransform/enum.ts new file mode 100644 index 000000000..4f829e1d3 --- /dev/null +++ b/test_files/enum.no_nstransform/enum.ts @@ -0,0 +1,27 @@ +/** + * @fileoverview Check that enums are translated to a var declaration + * when namespace transformation is turned off, i.e. the build target + * has the attribute --allow_unoptimized_namespaces. + * @suppress {checkTypes,uselessCode} + */ + +/** + * This enum should be translated to `var E = {...}` instead of the usual + * `const E = {...}` + */ +export enum E { + e0 = 0, + e1, + e2 +} + +// We need to emit the enum as a var declaration so that declaration +// merging with a namespace works. The unoptimized namespace is emitted +// by tsc as a var declaration and an IIFE. +export namespace E { + export function fromString(s: string) { + return E.e0; + } +} + +const foo = E.e2; diff --git a/test_files/enum.puretransform/enum.js b/test_files/enum.puretransform/enum.js new file mode 100644 index 000000000..b0a8c2eb8 --- /dev/null +++ b/test_files/enum.puretransform/enum.js @@ -0,0 +1,21 @@ +/** + * @fileoverview Test devmode (i.e. no JSDoc or special enum transformer) emit + * for enum merged with namespace. + * @suppress {missingProperties} + */ +goog.module('test_files.enum.puretransform.enum'); +var module = module || { id: 'test_files/enum.puretransform/enum.ts' }; +goog.require('tslib'); +var E; +(function (E) { + E[E["e0"] = 0] = "e0"; + E[E["e1"] = 1] = "e1"; + E[E["e2"] = 2] = "e2"; +})(E || (E = {})); +exports.E = E; +(function (E) { + function fromString(s) { + return E.e0; + } + E.fromString = fromString; +})(E || (E = {})); diff --git a/test_files/enum.puretransform/enum.ts b/test_files/enum.puretransform/enum.ts new file mode 100644 index 000000000..ecca84131 --- /dev/null +++ b/test_files/enum.puretransform/enum.ts @@ -0,0 +1,17 @@ +/** + * @fileoverview Test devmode (i.e. no JSDoc or special enum transformer) emit + * for enum merged with namespace. + * @suppress {missingProperties} + */ + +export enum E { + e0 = 0, + e1, + e2 +} + +export namespace E { + export function fromString(s: string) { + return E.e0; + } +} diff --git a/test_files/enum/enum.js b/test_files/enum/enum.js index 8041302d5..c42c2a62a 100644 --- a/test_files/enum/enum.js +++ b/test_files/enum/enum.js @@ -1,11 +1,9 @@ // test_files/enum/enum.ts(7,7): warning TS0: should not emit a 'never' type /** - * * @fileoverview Line with a missing semicolon should not break the following * enum. * Generated from: test_files/enum/enum.ts * @suppress {checkTypes,uselessCode} - * */ goog.module('test_files.enum.enum'); var module = module || { id: 'test_files/enum/enum.ts' }; @@ -57,7 +55,8 @@ let variableUsingExportedEnum; const ComponentIndex = { Scheme: 1, UserInfo: 2, - Domain: 0, + // TODO: b/313666408 - Fix tsc to not duplicate comments like the following + Domain: 0, // Be sure to exercise the code with a 0 enum value. // Be sure to exercise the code with a 0 enum value. UserInfo2: 2, }; diff --git a/test_files/enum/enum.ts b/test_files/enum/enum.ts index 8f913933b..b070ff424 100644 --- a/test_files/enum/enum.ts +++ b/test_files/enum/enum.ts @@ -36,6 +36,7 @@ let variableUsingExportedEnum: EnumTest2; enum ComponentIndex { Scheme = 1, UserInfo, + // TODO: b/313666408 - Fix tsc to not duplicate comments like the following Domain = 0, // Be sure to exercise the code with a 0 enum value. UserInfo2 = UserInfo, } diff --git a/test_files/enum/enum_user.js b/test_files/enum/enum_user.js index d2643c681..f96d6f479 100644 --- a/test_files/enum/enum_user.js +++ b/test_files/enum/enum_user.js @@ -1,9 +1,7 @@ /** - * * @fileoverview * Generated from: test_files/enum/enum_user.ts * @suppress {checkTypes,uselessCode} - * */ goog.module('test_files.enum.enum_user'); var module = module || { id: 'test_files/enum/enum_user.ts' }; diff --git a/test_files/enum_ref_import/enum_ref_import.js b/test_files/enum_ref_import/enum_ref_import.js index 9c49024bb..2c33c6ac1 100644 --- a/test_files/enum_ref_import/enum_ref_import.js +++ b/test_files/enum_ref_import/enum_ref_import.js @@ -1,5 +1,4 @@ /** - * * @fileoverview TypeScript statically resolves enum member values to constants, * if possible, and directly emits those constants. Because of this, TS should * elide any imports for modules referenced in the expressions of such constant @@ -12,7 +11,6 @@ * (`var ValuesInInitializer = {ENUM_MEMBER: "x"}`), TypeScript no longer elides * the import (for `Enum` here). Thus we emit code that has an unncessary * import. - * * Generated from: test_files/enum_ref_import/enum_ref_import.ts */ goog.module('test_files.enum_ref_import.enum_ref_import'); diff --git a/test_files/enum_value_literal_type/enum_value_literal_type.js b/test_files/enum_value_literal_type/enum_value_literal_type.js index 5b9520ef5..4ea9c7a03 100644 --- a/test_files/enum_value_literal_type/enum_value_literal_type.js +++ b/test_files/enum_value_literal_type/enum_value_literal_type.js @@ -1,11 +1,11 @@ -/** - * @fileoverview added by tsickle - * Generated from: test_files/enum_value_literal_type/enum_value_literal_type.ts - */ // Note: if you only have one value in the enum, then the type of "x" below // is just ExportedEnum, regardless of the annotation. This might be a bug // in TypeScript but this test is just trying to verify the behavior of // exporting an enum's value, not that. +/** + * @fileoverview added by tsickle + * Generated from: test_files/enum_value_literal_type/enum_value_literal_type.ts + */ goog.module('test_files.enum_value_literal_type.enum_value_literal_type'); var module = module || { id: 'test_files/enum_value_literal_type/enum_value_literal_type.ts' }; goog.require('tslib'); diff --git a/test_files/eventmap/eventmap.js b/test_files/eventmap/eventmap.js index 3e94f5787..993d6b368 100644 --- a/test_files/eventmap/eventmap.js +++ b/test_files/eventmap/eventmap.js @@ -1,9 +1,7 @@ /** - * * @fileoverview * Generated from: test_files/eventmap/eventmap.ts * @suppress {checkTypes} - * */ goog.module('test_files.eventmap.eventmap'); var module = module || { id: 'test_files/eventmap/eventmap.ts' }; diff --git a/test_files/export/export.js b/test_files/export/export.js index 614faff4b..85df4d44b 100644 --- a/test_files/export/export.js +++ b/test_files/export/export.js @@ -21,6 +21,8 @@ exports.RenamedTypeDef; // re-export typedef exports.TypeDef; // re-export typedef /** @typedef {!tsickle_export_helper_1.Interface} */ exports.Interface; // re-export typedef +/** @typedef {!tsickle_export_helper_1.ConstEnum} */ +exports.ConstEnum; // re-export typedef /** @typedef {!tsickle_export_helper_1.DeclaredType} */ exports.DeclaredType; // re-export typedef /** @typedef {!tsickle_export_helper_1.DeclaredInterface} */ diff --git a/test_files/export/export_helper.js b/test_files/export/export_helper.js index 5cad2eee1..952036917 100644 --- a/test_files/export/export_helper.js +++ b/test_files/export/export_helper.js @@ -1,10 +1,8 @@ /** - * * @fileoverview This file isn't itself a test case, but it is imported by the * export.in.ts test case. * Generated from: test_files/export/export_helper.ts * @suppress {uselessCode} - * */ goog.module('test_files.export.export_helper'); var module = module || { id: 'test_files/export/export_helper.ts' }; @@ -16,6 +14,8 @@ exports.export4 = export_helper_2_1.export4; exports.TypeDef; // re-export typedef /** @typedef {!tsickle_export_helper_2_1.Interface} */ exports.Interface; // re-export typedef +/** @typedef {!tsickle_export_helper_2_1.ConstEnum} */ +exports.ConstEnum; // re-export typedef /** @typedef {!tsickle_export_helper_2_1.DeclaredType} */ exports.DeclaredType; // re-export typedef /** @typedef {!tsickle_export_helper_2_1.DeclaredInterface} */ diff --git a/test_files/export/export_helper_2.js b/test_files/export/export_helper_2.js index 3359e9128..e90e5fafd 100644 --- a/test_files/export/export_helper_2.js +++ b/test_files/export/export_helper_2.js @@ -1,10 +1,8 @@ /** - * * @fileoverview This file isn't itself a test case, but it is imported by the * export.in.ts test case. * Generated from: test_files/export/export_helper_2.ts * @suppress {uselessCode} - * */ goog.module('test_files.export.export_helper_2'); var module = module || { id: 'test_files/export/export_helper_2.ts' }; diff --git a/test_files/export/export_helper_3.js b/test_files/export/export_helper_3.js index d63681078..8bd165acb 100644 --- a/test_files/export/export_helper_3.js +++ b/test_files/export/export_helper_3.js @@ -1,10 +1,8 @@ /** - * * @fileoverview This file isn't itself a test case, but it is imported by the * export.in.ts test case. * Generated from: test_files/export/export_helper_3.ts * @suppress {uselessCode} - * */ goog.module('test_files.export.export_helper_3'); var module = module || { id: 'test_files/export/export_helper_3.ts' }; diff --git a/test_files/export/export_star_imported.js b/test_files/export/export_star_imported.js index 574f72e86..5e1db0dc0 100644 --- a/test_files/export/export_star_imported.js +++ b/test_files/export/export_star_imported.js @@ -1,9 +1,7 @@ /** - * * @fileoverview * Generated from: test_files/export/export_star_imported.ts * @suppress {checkTypes} - * */ goog.module('test_files.export.export_star_imported'); var module = module || { id: 'test_files/export/export_star_imported.ts' }; @@ -22,6 +20,8 @@ exports.RenamedTypeDef; // re-export typedef exports.TypeDef; // re-export typedef /** @typedef {!tsickle_export_helper_1.Interface} */ exports.Interface; // re-export typedef +/** @typedef {!tsickle_export_helper_1.ConstEnum} */ +exports.ConstEnum; // re-export typedef /** @typedef {!tsickle_export_helper_1.DeclaredType} */ exports.DeclaredType; // re-export typedef /** @typedef {!tsickle_export_helper_1.DeclaredInterface} */ diff --git a/test_files/export_declare_namespace/user.js b/test_files/export_declare_namespace/user.js index da3f9b82b..3daabba18 100644 --- a/test_files/export_declare_namespace/user.js +++ b/test_files/export_declare_namespace/user.js @@ -1,9 +1,7 @@ /** - * * @fileoverview * Generated from: test_files/export_declare_namespace/user.ts * @suppress {checkTypes} - * */ goog.module('test_files.export_declare_namespace.user'); var module = module || { id: 'test_files/export_declare_namespace/user.ts' }; diff --git a/test_files/export_destructuring/export_destructuring.js b/test_files/export_destructuring/export_destructuring.js new file mode 100644 index 000000000..5958c9f94 --- /dev/null +++ b/test_files/export_destructuring/export_destructuring.js @@ -0,0 +1,28 @@ +goog.module('test_files.export_destructuring.export_destructuring'); +var module = module || { id: 'test_files/export_destructuring/export_destructuring.ts' }; +goog.require('tslib'); +var _a; +/** + * @fileoverview added by tsickle + * Generated from: test_files/export_destructuring/export_destructuring.ts + */ +/** + * @param {number} n + * @return {!Array} + */ +function signal(n) { + return [n, n + 1]; +} +/** + * @param {number} n + * @return {{c: number, d: number}} + */ +function objectLiteral(n) { + return { c: n, d: n + 1 }; +} +const [a__tsickle_destructured_1, b__tsickle_destructured_2] = signal(0); +exports.a = /** @type {number} */ (a__tsickle_destructured_1); +exports.b = /** @type {number} */ (b__tsickle_destructured_2); +_a = objectLiteral(0); +exports.c = _a.c; +exports.d = _a.d; diff --git a/test_files/export_destructuring/export_destructuring.ts b/test_files/export_destructuring/export_destructuring.ts new file mode 100644 index 000000000..87fcaff10 --- /dev/null +++ b/test_files/export_destructuring/export_destructuring.ts @@ -0,0 +1,11 @@ + +function signal(n: number) { + return [n, n + 1]; +} +function objectLiteral(n: number) { + return {c: n, d: n + 1}; +} + +export const [a, b] = signal(0); + +export const {c, d} = objectLiteral(0); diff --git a/test_files/export_local_type/export_local_type.js b/test_files/export_local_type/export_local_type.js index 704f40282..6f7c38b58 100644 --- a/test_files/export_local_type/export_local_type.js +++ b/test_files/export_local_type/export_local_type.js @@ -1,9 +1,7 @@ /** - * * @fileoverview Regression test to ensure local type symbols can be exported. * Generated from: test_files/export_local_type/export_local_type.ts * @suppress {uselessCode} - * */ goog.module('test_files.export_local_type.export_local_type'); var module = module || { id: 'test_files/export_local_type/export_local_type.ts' }; diff --git a/test_files/export_merged/main.js b/test_files/export_merged/main.js index f288445ac..9d4512e1c 100644 --- a/test_files/export_merged/main.js +++ b/test_files/export_merged/main.js @@ -1,11 +1,9 @@ /** - * * @fileoverview Test to ensure that only one assignment to * `exports.A` is emitted when A is a namespace with merged declarations. * Tsickle eliminates the second assignment. * Generated from: test_files/export_merged/main.ts * @suppress {checkTypes} - * */ goog.module('test_files.export_merged.main'); var module = module || { id: 'test_files/export_merged/main.ts' }; diff --git a/test_files/export_multi/export_multi.js b/test_files/export_multi/export_multi.js index 8d99eb867..5d15c4896 100644 --- a/test_files/export_multi/export_multi.js +++ b/test_files/export_multi/export_multi.js @@ -3,10 +3,8 @@ var module = module || { id: 'test_files/export_multi/export_multi.ts' }; goog.require('tslib'); var _a, _b; /** - * * @fileoverview Some export forms that create multi-expression 'export' * statements, which are illegal under Closure and must be rewritten. - * * Generated from: test_files/export_multi/export_multi.ts */ /** @enum {string} */ diff --git a/test_files/export_star_as_ns/star_as_ns.js b/test_files/export_star_as_ns/star_as_ns.js index b76ac41a1..37e558ac2 100644 --- a/test_files/export_star_as_ns/star_as_ns.js +++ b/test_files/export_star_as_ns/star_as_ns.js @@ -1,9 +1,7 @@ /** - * * @fileoverview Tests exporting a namespace with a given name from Closure. This doesn't expand * each export like the `export * from '...'` syntax, so it's output just an assignment of the * imported module to a property on `exports`. - * * Generated from: test_files/export_star_as_ns/star_as_ns.ts */ goog.module('test_files.export_star_as_ns.star_as_ns'); diff --git a/test_files/exporting_decorator/exporting.js b/test_files/exporting_decorator/exporting.js index 508e77404..a9c9a8f30 100644 --- a/test_files/exporting_decorator/exporting.js +++ b/test_files/exporting_decorator/exporting.js @@ -3,11 +3,9 @@ var module = module || { id: 'test_files/exporting_decorator/exporting.ts' }; const tslib_1 = goog.require('tslib'); const __tsickle_googReflect = goog.require("goog.reflect"); /** - * * @fileoverview * Generated from: test_files/exporting_decorator/exporting.ts * @suppress {uselessCode} - * */ /** * \@ExportDecoratedItems diff --git a/test_files/extend_and_implement/extend_and_implement.js b/test_files/extend_and_implement/extend_and_implement.js index da6b18cee..1f5e862a2 100644 --- a/test_files/extend_and_implement/extend_and_implement.js +++ b/test_files/extend_and_implement/extend_and_implement.js @@ -1,12 +1,10 @@ // test_files/extend_and_implement/extend_and_implement.ts(16,1): warning TS0: dropped implements: cannot implements a class /** - * * @fileoverview Reproduces a problem where tsickle would emit "\\@extends * {ClassInImplements}", conflicting the ES6 extends syntax, leading to * incorrect optimization results. * Generated from: test_files/extend_and_implement/extend_and_implement.ts * @suppress {uselessCode} - * */ goog.module('test_files.extend_and_implement.extend_and_implement'); var module = module || { id: 'test_files/extend_and_implement/extend_and_implement.ts' }; diff --git a/test_files/fields/fields.js b/test_files/fields/fields.js index ce105e3ef..ff99fe59a 100644 --- a/test_files/fields/fields.js +++ b/test_files/fields/fields.js @@ -1,9 +1,7 @@ /** - * * @fileoverview * Generated from: test_files/fields/fields.ts * @suppress {uselessCode} - * */ goog.module('test_files.fields.fields'); var module = module || { id: 'test_files/fields/fields.ts' }; diff --git a/test_files/fields_no_ctor/fields_no_ctor.js b/test_files/fields_no_ctor/fields_no_ctor.js index b82bd65b9..4b5b53fd9 100644 --- a/test_files/fields_no_ctor/fields_no_ctor.js +++ b/test_files/fields_no_ctor/fields_no_ctor.js @@ -1,9 +1,7 @@ /** - * * @fileoverview * Generated from: test_files/fields_no_ctor/fields_no_ctor.ts * @suppress {uselessCode} - * */ goog.module('test_files.fields_no_ctor.fields_no_ctor'); var module = module || { id: 'test_files/fields_no_ctor/fields_no_ctor.ts' }; diff --git a/test_files/file_comment/before_import.js b/test_files/file_comment/before_import.js index d34e74f12..8a7bb01ca 100644 --- a/test_files/file_comment/before_import.js +++ b/test_files/file_comment/before_import.js @@ -1,9 +1,7 @@ /** - * * @fileoverview fileoverview comment before import. transformer_util.ts has * special logic to handle comments before import/require() calls. This file * tests the regular import case. - * * Generated from: test_files/file_comment/before_import.ts */ goog.module('test_files.file_comment.before_import'); diff --git a/test_files/file_comment/comment_before_class.js b/test_files/file_comment/comment_before_class.js index 97cdbc3fc..81cc56706 100644 --- a/test_files/file_comment/comment_before_class.js +++ b/test_files/file_comment/comment_before_class.js @@ -1,9 +1,7 @@ /** - * * @fileoverview Class handling code does not special cases comments preceding * it before its JSDoc block. This comment would not get emitted if detached * source file comments were not emitted separately. - * * Generated from: test_files/file_comment/comment_before_class.ts */ goog.module('test_files.file_comment.comment_before_class'); diff --git a/test_files/file_comment/comment_before_elided_import.js b/test_files/file_comment/comment_before_elided_import.js index e3518f9a9..fe127f4d8 100644 --- a/test_files/file_comment/comment_before_elided_import.js +++ b/test_files/file_comment/comment_before_elided_import.js @@ -1,8 +1,6 @@ /** - * * @fileoverview This is a comment before an import, where the import will be elided but the comment * must be kept. - * * Generated from: test_files/file_comment/comment_before_elided_import.ts */ goog.module('test_files.file_comment.comment_before_elided_import'); diff --git a/test_files/file_comment/comment_before_var.js b/test_files/file_comment/comment_before_var.js index 9c8295ad5..331913513 100644 --- a/test_files/file_comment/comment_before_var.js +++ b/test_files/file_comment/comment_before_var.js @@ -1,10 +1,8 @@ /** - * * @fileoverview This comment must not be emitted twice. * Generated from: test_files/file_comment/comment_before_var.ts * @mods {google3.java.com.google.javascript.typescript.examples.boqui.boqui} * @modName {foobar} - * */ goog.module('test_files.file_comment.comment_before_var'); var module = module || { id: 'test_files/file_comment/comment_before_var.ts' }; diff --git a/test_files/file_comment/comment_no_tag.js b/test_files/file_comment/comment_no_tag.js index 67ed9dfdc..b18d7621d 100644 --- a/test_files/file_comment/comment_no_tag.js +++ b/test_files/file_comment/comment_no_tag.js @@ -1,8 +1,10 @@ +/** + * A comment without any tags. + */ /** * @fileoverview added by tsickle * Generated from: test_files/file_comment/comment_no_tag.ts */ -/** A comment without any tags. */ // here comes code. goog.module('test_files.file_comment.comment_no_tag'); var module = module || { id: 'test_files/file_comment/comment_no_tag.ts' }; diff --git a/test_files/file_comment/comment_with_text.js b/test_files/file_comment/comment_with_text.js index 8d30d2133..7271aeb4c 100644 --- a/test_files/file_comment/comment_with_text.js +++ b/test_files/file_comment/comment_with_text.js @@ -1,9 +1,7 @@ /** - * * @fileoverview * Generated from: test_files/file_comment/comment_with_text.ts * @suppress {undefinedVars} because we don't like them errors - * */ goog.module('test_files.file_comment.comment_with_text'); var module = module || { id: 'test_files/file_comment/comment_with_text.ts' }; diff --git a/test_files/file_comment/export_star.js b/test_files/file_comment/export_star.js index b40bb7c13..93a879b4f 100644 --- a/test_files/file_comment/export_star.js +++ b/test_files/file_comment/export_star.js @@ -1,9 +1,7 @@ /** - * * @fileoverview fileoverview comment before export. transformer_util.ts has * special logic to handle comments before import/require() calls. This file * tests the export * case. - * * Generated from: test_files/file_comment/export_star.ts */ goog.module('test_files.file_comment.export_star'); diff --git a/test_files/file_comment/fileoverview_comment_add_suppress_before_license.js b/test_files/file_comment/fileoverview_comment_add_suppress_before_license.js index 55497bb9d..7fe31d22e 100644 --- a/test_files/file_comment/fileoverview_comment_add_suppress_before_license.js +++ b/test_files/file_comment/fileoverview_comment_add_suppress_before_license.js @@ -1,11 +1,9 @@ /** - * * @fileoverview a comment with a license tag. * * Generated from: test_files/file_comment/fileoverview_comment_add_suppress_before_license.ts * @license * Some license - * */ // here comes code. goog.module('test_files.file_comment.fileoverview_comment_add_suppress_before_license'); diff --git a/test_files/file_comment/fileoverview_comment_merge_suppress.js b/test_files/file_comment/fileoverview_comment_merge_suppress.js index 6d7e30ea7..94e1afbb8 100644 --- a/test_files/file_comment/fileoverview_comment_merge_suppress.js +++ b/test_files/file_comment/fileoverview_comment_merge_suppress.js @@ -1,11 +1,11 @@ /** - * * @fileoverview Tests merging JSDoc tags in fileoverview comments. * Generated from: test_files/file_comment/fileoverview_comment_merge_suppress.ts * @suppress {extraRequire} - * */ -/** second comment here */ +/** + * second comment here + */ goog.module('test_files.file_comment.fileoverview_comment_merge_suppress'); var module = module || { id: 'test_files/file_comment/fileoverview_comment_merge_suppress.ts' }; goog.require('tslib'); diff --git a/test_files/file_comment/fileoverview_in_comment_text.js b/test_files/file_comment/fileoverview_in_comment_text.js index e955e9c23..058eb0166 100644 --- a/test_files/file_comment/fileoverview_in_comment_text.js +++ b/test_files/file_comment/fileoverview_in_comment_text.js @@ -1,8 +1,6 @@ /** - * * @fileoverview Tests that mere mentions of file overview tags in comment bodies don't get * reported as errors. - * * Generated from: test_files/file_comment/fileoverview_in_comment_text.ts */ goog.module('test_files.file_comment.fileoverview_in_comment_text'); diff --git a/test_files/file_comment/latecomment_front.js b/test_files/file_comment/latecomment_front.js index 49f9731a4..c4c471d81 100644 --- a/test_files/file_comment/latecomment_front.js +++ b/test_files/file_comment/latecomment_front.js @@ -1,4 +1,6 @@ -/** @license Here is a license comment. */ +/** + * @license Here is a license comment. + */ /** * @fileoverview with a late fileoverview comment before the first statement. * Generated from: test_files/file_comment/latecomment_front.ts diff --git a/test_files/file_comment/multiple_comments.js b/test_files/file_comment/multiple_comments.js index bae1c03af..755a16261 100644 --- a/test_files/file_comment/multiple_comments.js +++ b/test_files/file_comment/multiple_comments.js @@ -7,17 +7,17 @@ * found in the LICENSE file at https://angular.io/license */ /** - * @fileoverview This comment is ignored by Closure compiler. - * @suppress {undefinedVars} + * \@fileoverview This comment is ignored by Closure compiler. + * \@suppress {undefinedVars} */ /** - * * @fileoverview The last fileoverview actually takes effect. * Generated from: test_files/file_comment/multiple_comments.ts * @suppress {const} - * */ -/** Here's another trailing comment */ +/** + * Here's another trailing comment + */ goog.module('test_files.file_comment.multiple_comments'); var module = module || { id: 'test_files/file_comment/multiple_comments.ts' }; goog.require('tslib'); diff --git a/test_files/file_comment/side_effect_import.js b/test_files/file_comment/side_effect_import.js index 19328b47d..f95734da0 100644 --- a/test_files/file_comment/side_effect_import.js +++ b/test_files/file_comment/side_effect_import.js @@ -1,9 +1,7 @@ /** - * * @fileoverview This is a fileoverview comment preceding a side-effect import. * transformer_util.ts has special logic to handle comments before * import/require() calls. This file tests the side-effect import case. - * * Generated from: test_files/file_comment/side_effect_import.ts */ goog.module('test_files.file_comment.side_effect_import'); diff --git a/test_files/functions/functions.js b/test_files/functions/functions.js index 7b6d5a283..a76945fa5 100644 --- a/test_files/functions/functions.js +++ b/test_files/functions/functions.js @@ -1,10 +1,8 @@ // test_files/functions/functions.ts(38,20): warning TS0: failed to resolve rest parameter type, emitting ? /** - * * @fileoverview * Generated from: test_files/functions/functions.ts * @suppress {checkTypes} - * */ goog.module('test_files.functions.functions'); var module = module || { id: 'test_files/functions/functions.ts' }; diff --git a/test_files/functions/two_jsdoc_blocks.js b/test_files/functions/two_jsdoc_blocks.js index 419f4117b..04b9c56a6 100644 --- a/test_files/functions/two_jsdoc_blocks.js +++ b/test_files/functions/two_jsdoc_blocks.js @@ -1,7 +1,5 @@ /** - * * @fileoverview This text here matches the text below in length. - * * Generated from: test_files/functions/two_jsdoc_blocks.ts */ goog.module('test_files.functions.two_jsdoc_blocks'); diff --git a/test_files/generic_extends/user.js b/test_files/generic_extends/user.js index 681c975ed..62c2cfe34 100644 --- a/test_files/generic_extends/user.js +++ b/test_files/generic_extends/user.js @@ -1,9 +1,7 @@ /** - * * @fileoverview Tests template parameters in extends clauses. * Generated from: test_files/generic_extends/user.ts * @suppress {uselessCode} - * */ goog.module('test_files.generic_extends.user'); var module = module || { id: 'test_files/generic_extends/user.ts' }; diff --git a/test_files/generic_in_prop_access/user.js b/test_files/generic_in_prop_access/user.js index aca6c60b2..c2afe32d9 100644 --- a/test_files/generic_in_prop_access/user.js +++ b/test_files/generic_in_prop_access/user.js @@ -2,13 +2,11 @@ // test_files/generic_in_prop_access/user.ts(17,9): warning TS0: unhandled type flags: IncludesWildcard // test_files/generic_in_prop_access/user.ts(17,18): warning TS0: unhandled type flags: IncludesWildcard /** - * * @fileoverview Tests template parameters for identifier in property access * expression, where TypeScript narrows its type only on usage, i.e. in the * return statement below. * Generated from: test_files/generic_in_prop_access/user.ts * @suppress {uselessCode} - * */ goog.module('test_files.generic_in_prop_access.user'); var module = module || { id: 'test_files/generic_in_prop_access/user.ts' }; diff --git a/test_files/generic_local_var/generic_local_var.js b/test_files/generic_local_var/generic_local_var.js index 41f488810..c974e8078 100644 --- a/test_files/generic_local_var/generic_local_var.js +++ b/test_files/generic_local_var/generic_local_var.js @@ -1,9 +1,7 @@ /** - * * @fileoverview * Generated from: test_files/generic_local_var/generic_local_var.ts * @suppress {uselessCode} - * */ goog.module('test_files.generic_local_var.generic_local_var'); var module = module || { id: 'test_files/generic_local_var/generic_local_var.ts' }; diff --git a/test_files/generic_nested_classes/user.js b/test_files/generic_nested_classes/user.js index 984e4ebbe..8740e5f8c 100644 --- a/test_files/generic_nested_classes/user.js +++ b/test_files/generic_nested_classes/user.js @@ -1,8 +1,6 @@ /** - * * @fileoverview Tests template parameters for generic classes nested inside * another generic class. - * * Generated from: test_files/generic_nested_classes/user.ts */ goog.module('test_files.generic_nested_classes.user'); diff --git a/test_files/ignored_ambient_external_module/user.js b/test_files/ignored_ambient_external_module/user.js index 0e777e082..5b0332550 100644 --- a/test_files/ignored_ambient_external_module/user.js +++ b/test_files/ignored_ambient_external_module/user.js @@ -1,9 +1,7 @@ /** - * * @fileoverview Regression test for type-ignored ambient modules. * Generated from: test_files/ignored_ambient_external_module/user.ts * @suppress {uselessCode} - * */ goog.module('test_files.ignored_ambient_external_module.user'); var module = module || { id: 'test_files/ignored_ambient_external_module/user.ts' }; diff --git a/test_files/implement_reexported_interface/interface.js b/test_files/implement_reexported_interface/interface.js index 0b83ac28b..0c7ef013c 100644 --- a/test_files/implement_reexported_interface/interface.js +++ b/test_files/implement_reexported_interface/interface.js @@ -1,9 +1,7 @@ /** - * * @fileoverview See user.ts for the actual test. * Generated from: test_files/implement_reexported_interface/interface.ts * @suppress {uselessCode} - * */ goog.module('test_files.implement_reexported_interface.interface'); var module = module || { id: 'test_files/implement_reexported_interface/interface.ts' }; diff --git a/test_files/implement_reexported_interface/user.js b/test_files/implement_reexported_interface/user.js index 2951f119f..37126cbd9 100644 --- a/test_files/implement_reexported_interface/user.js +++ b/test_files/implement_reexported_interface/user.js @@ -1,5 +1,4 @@ /** - * * @fileoverview Tests that a re-exported interface can be implemented. * * This reproduces a bug where tsickle would define re-exports as just @@ -9,7 +8,6 @@ * * Generated from: test_files/implement_reexported_interface/user.ts * @suppress {uselessCode} - * */ goog.module('test_files.implement_reexported_interface.user'); var module = module || { id: 'test_files/implement_reexported_interface/user.ts' }; diff --git a/test_files/implements/implements.js b/test_files/implements/implements.js index 156c40adf..e2e6e712a 100644 --- a/test_files/implements/implements.js +++ b/test_files/implements/implements.js @@ -1,12 +1,10 @@ // test_files/implements/implements.ts(13,1): warning TS0: dropped implements: dropped implements of a type literal: MyRecord // test_files/implements/implements.ts(19,1): warning TS0: dropped implements: dropped implements of a type literal: RecordAlias /** - * * @fileoverview Tests various types of 'implements' clauses, e.g. 'implements' * of a generic type alias of an underlying interface. * Generated from: test_files/implements/implements.ts * @suppress {uselessCode} - * */ goog.module('test_files.implements.implements'); var module = module || { id: 'test_files/implements/implements.ts' }; diff --git a/test_files/import_by_path.declaration.no_externs/clutz_input.d.ts b/test_files/import_by_path.declaration.no_externs/clutz_input.d.ts deleted file mode 100644 index 7dde5e336..000000000 --- a/test_files/import_by_path.declaration.no_externs/clutz_input.d.ts +++ /dev/null @@ -1,14 +0,0 @@ -// Mocks for Clutz-generated .d.ts. - -declare namespace ಠ_ಠ.clutz.another.module { - export class SomeClass {} -} -declare module 'goog:another.module' { -import SomeClass = ಠ_ಠ.clutz.another.module.SomeClass; - export {SomeClass}; -} -declare module 'google3/another/file' { -import SomeClass = ಠ_ಠ.clutz.another.module.SomeClass; - export {SomeClass}; - const __clutz_actual_namespace: 'another.module'; -} diff --git a/test_files/import_by_path.declaration.no_externs/decluser.d.ts b/test_files/import_by_path.declaration.no_externs/decluser.d.ts deleted file mode 100644 index c845f22b6..000000000 --- a/test_files/import_by_path.declaration.no_externs/decluser.d.ts +++ /dev/null @@ -1,14 +0,0 @@ -//!! generated by tsickle from test_files/import_by_path.declaration.no_externs/decluser.ts -import "test_files/import_by_path.declaration.no_externs/clutz_input"; -import { SomeClass } from 'google3/another/file'; -export declare class UsingPathImports { - someField?: SomeClass; -} -declare global { - namespace ಠ_ಠ.clutz { - export { UsingPathImports as module$contents$test_files$import_by_path$declaration$no_externs$decluser_UsingPathImports }; - export namespace module$exports$test_files$import_by_path$declaration$no_externs$decluser { - export { UsingPathImports }; - } - } -} diff --git a/test_files/import_by_path.declaration.no_externs/decluser.ts b/test_files/import_by_path.declaration.no_externs/decluser.ts deleted file mode 100644 index 7b01a7f96..000000000 --- a/test_files/import_by_path.declaration.no_externs/decluser.ts +++ /dev/null @@ -1,5 +0,0 @@ -import {SomeClass} from 'google3/another/file'; - -export class UsingPathImports { - someField?: SomeClass; -} diff --git a/test_files/import_by_path.declaration.no_externs/jsprovides.js b/test_files/import_by_path.declaration.no_externs/jsprovides.js deleted file mode 100644 index 26ec015a9..000000000 --- a/test_files/import_by_path.declaration.no_externs/jsprovides.js +++ /dev/null @@ -1,7 +0,0 @@ -/** - * @fileoverview Description of this file. - */ - -goog.module('another.module'); - -exports.SomeClass = class {}; diff --git a/test_files/import_by_path.no_externs/conflicting_multiple.js b/test_files/import_by_path.no_externs/conflicting_multiple.js index aa3b6a369..8a7c23055 100644 --- a/test_files/import_by_path.no_externs/conflicting_multiple.js +++ b/test_files/import_by_path.no_externs/conflicting_multiple.js @@ -1,11 +1,9 @@ // test_files/import_by_path.no_externs/conflicting_multiple.ts(7,21): error TS0: referenced JavaScript module google3/path/to/multiple_provides/conflicting provides multiple namespaces and cannot be imported by path. /** - * * @fileoverview Negative test: this TS file attempts to import a JS module that * provides multiple conflicting namespaces by path, which is an error. * Generated from: test_files/import_by_path.no_externs/conflicting_multiple.ts * @suppress {checkTypes} - * */ goog.module('test_files.import_by_path.no_externs.conflicting_multiple'); var module = module || { id: 'test_files/import_by_path.no_externs/conflicting_multiple.ts' }; diff --git a/test_files/import_by_path.no_externs/conflicting_multiple_bystar.js b/test_files/import_by_path.no_externs/conflicting_multiple_bystar.js index e87f89b4a..2593b973f 100644 --- a/test_files/import_by_path.no_externs/conflicting_multiple_bystar.js +++ b/test_files/import_by_path.no_externs/conflicting_multiple_bystar.js @@ -1,11 +1,9 @@ // test_files/import_by_path.no_externs/conflicting_multiple_bystar.ts(7,25): error TS0: referenced JavaScript module google3/path/to/multiple_provides/conflicting provides multiple namespaces and cannot be imported by path. /** - * * @fileoverview Negative test: import by star reports errors for conflicting * symbols. * Generated from: test_files/import_by_path.no_externs/conflicting_multiple_bystar.ts * @suppress {checkTypes} - * */ goog.module('test_files.import_by_path.no_externs.conflicting_multiple_bystar'); var module = module || { id: 'test_files/import_by_path.no_externs/conflicting_multiple_bystar.ts' }; diff --git a/test_files/import_by_path.no_externs/conflicting_multiple_empty.js b/test_files/import_by_path.no_externs/conflicting_multiple_empty.js index 1f6a533de..f44348150 100644 --- a/test_files/import_by_path.no_externs/conflicting_multiple_empty.js +++ b/test_files/import_by_path.no_externs/conflicting_multiple_empty.js @@ -1,10 +1,8 @@ // test_files/import_by_path.no_externs/conflicting_multiple_empty.ts(6,41): error TS0: referenced JavaScript module google3/path/to/multiple_provides/conflicting provides multiple namespaces and cannot be imported by path. /** - * * @fileoverview Negative test for importing no symbols from a module, by path. * Generated from: test_files/import_by_path.no_externs/conflicting_multiple_empty.ts * @suppress {checkTypes} - * */ goog.module('test_files.import_by_path.no_externs.conflicting_multiple_empty'); var module = module || { id: 'test_files/import_by_path.no_externs/conflicting_multiple_empty.ts' }; diff --git a/test_files/import_by_path.no_externs/conflicting_multiple_type.js b/test_files/import_by_path.no_externs/conflicting_multiple_type.js index c9c947883..ca9642b14 100644 --- a/test_files/import_by_path.no_externs/conflicting_multiple_type.js +++ b/test_files/import_by_path.no_externs/conflicting_multiple_type.js @@ -1,11 +1,9 @@ // test_files/import_by_path.no_externs/conflicting_multiple_type.ts(7,25): error TS0: referenced JavaScript module google3/path/to/multiple_provides/conflicting provides multiple namespaces and cannot be imported by path. /** - * * @fileoverview Negative test: import type reports errors for conflicting * symbols. * Generated from: test_files/import_by_path.no_externs/conflicting_multiple_type.ts * @suppress {checkTypes} - * */ goog.module('test_files.import_by_path.no_externs.conflicting_multiple_type'); var module = module || { id: 'test_files/import_by_path.no_externs/conflicting_multiple_type.ts' }; diff --git a/test_files/import_by_path.no_externs/multiple_side_effect.js b/test_files/import_by_path.no_externs/multiple_side_effect.js index 346710866..99f55ade5 100644 --- a/test_files/import_by_path.no_externs/multiple_side_effect.js +++ b/test_files/import_by_path.no_externs/multiple_side_effect.js @@ -1,12 +1,10 @@ /** - * * @fileoverview Imports a module with conflicting provides, but with a * side-effect import. tsickle only reports an error when code imports a symbol * from a module with conflicting symbol exports, but not for a side effect * import. * Generated from: test_files/import_by_path.no_externs/multiple_side_effect.ts * @suppress {checkTypes} - * */ goog.module('test_files.import_by_path.no_externs.multiple_side_effect'); var module = module || { id: 'test_files/import_by_path.no_externs/multiple_side_effect.ts' }; diff --git a/test_files/import_by_path.no_externs/user.js b/test_files/import_by_path.no_externs/user.js index 79123c82b..7d67dd835 100644 --- a/test_files/import_by_path.no_externs/user.js +++ b/test_files/import_by_path.no_externs/user.js @@ -1,10 +1,8 @@ /** - * * @fileoverview Tests that tsickle emits goog namespace references when * importing modules by path. * Generated from: test_files/import_by_path.no_externs/user.ts * @suppress {checkTypes} - * */ goog.module('test_files.import_by_path.no_externs.user'); var module = module || { id: 'test_files/import_by_path.no_externs/user.ts' }; diff --git a/test_files/import_by_path.no_externs/user_default.js b/test_files/import_by_path.no_externs/user_default.js index ab205f98f..f9f65929d 100644 --- a/test_files/import_by_path.no_externs/user_default.js +++ b/test_files/import_by_path.no_externs/user_default.js @@ -1,10 +1,8 @@ /** - * * @fileoverview Tests that tsickle emits goog namespace references when * importing modules by path, and handles named to default export conversion. * Generated from: test_files/import_by_path.no_externs/user_default.ts * @suppress {checkTypes} - * */ goog.module('test_files.import_by_path.no_externs.user_default'); var module = module || { id: 'test_files/import_by_path.no_externs/user_default.ts' }; diff --git a/test_files/import_by_path.no_externs/using_multiple.js b/test_files/import_by_path.no_externs/using_multiple.js index b62126875..264ebebc7 100644 --- a/test_files/import_by_path.no_externs/using_multiple.js +++ b/test_files/import_by_path.no_externs/using_multiple.js @@ -1,9 +1,7 @@ /** - * * @fileoverview Using a namespace that provides multiple, nested symbols. * Generated from: test_files/import_by_path.no_externs/using_multiple.ts * @suppress {checkTypes} - * */ goog.module('test_files.import_by_path.no_externs.using_multiple'); var module = module || { id: 'test_files/import_by_path.no_externs/using_multiple.ts' }; diff --git a/test_files/import_equals/import_equals_type_usage.js b/test_files/import_equals/import_equals_type_usage.js index 1cf2bb37b..1c7c58912 100644 --- a/test_files/import_equals/import_equals_type_usage.js +++ b/test_files/import_equals/import_equals_type_usage.js @@ -1,9 +1,7 @@ /** - * * @fileoverview Tests type only usage of symbols imported using import equals * syntax. TypeScript elides those imports, so type references have to use * tsickle's requireType symbols. - * * Generated from: test_files/import_equals/import_equals_type_usage.ts */ goog.module('test_files.import_equals.import_equals_type_usage'); diff --git a/test_files/import_from_goog.no_externs/import_from_goog.js b/test_files/import_from_goog.no_externs/import_from_goog.js index b9f6ad3e9..94b4640c5 100644 --- a/test_files/import_from_goog.no_externs/import_from_goog.js +++ b/test_files/import_from_goog.no_externs/import_from_goog.js @@ -1,7 +1,5 @@ /** - * * @fileoverview - * * Generated from: test_files/import_from_goog.no_externs/import_from_goog.ts */ goog.module('test_files.import_from_goog.no_externs.import_from_goog'); diff --git a/test_files/import_only_types/types_and_constenum.js b/test_files/import_only_types/types_and_constenum.js index bbf8cc5e3..2b9533a1a 100644 --- a/test_files/import_only_types/types_and_constenum.js +++ b/test_files/import_only_types/types_and_constenum.js @@ -1,10 +1,10 @@ +// const enum values are inlined, so even though const enums are values, +// TypeScript might not generate any imports for them, which means modules +// containing only types and const enums must be "force loaded". /** * @fileoverview added by tsickle * Generated from: test_files/import_only_types/types_and_constenum.ts */ -// const enum values are inlined, so even though const enums are values, -// TypeScript might not generate any imports for them, which means modules -// containing only types and const enums must be "force loaded". goog.module('test_files.import_only_types.types_and_constenum'); var module = module || { id: 'test_files/import_only_types/types_and_constenum.ts' }; goog.require('tslib'); diff --git a/test_files/import_only_types/types_only.js b/test_files/import_only_types/types_only.js index 4ac33c807..10620fa63 100644 --- a/test_files/import_only_types/types_only.js +++ b/test_files/import_only_types/types_only.js @@ -1,10 +1,8 @@ /** - * * @fileoverview Exports only types, but must still be goog.require'd for * Closure Compiler. * Generated from: test_files/import_only_types/types_only.ts * @suppress {uselessCode} - * */ goog.module('test_files.import_only_types.types_only'); var module = module || { id: 'test_files/import_only_types/types_only.ts' }; diff --git a/test_files/import_prefixed/import_prefixed_mixed.js b/test_files/import_prefixed/import_prefixed_mixed.js index b3f17b062..4682dfa41 100644 --- a/test_files/import_prefixed/import_prefixed_mixed.js +++ b/test_files/import_prefixed/import_prefixed_mixed.js @@ -1,3 +1,5 @@ +// This file imports exporter with a prefix import (* as ...), and then uses the +// import in a type and in a value position. /** * @fileoverview added by tsickle * Generated from: test_files/import_prefixed/import_prefixed_mixed.ts @@ -6,8 +8,6 @@ goog.module('test_files.import_prefixed.import_prefixed_mixed'); var module = module || { id: 'test_files/import_prefixed/import_prefixed_mixed.ts' }; goog.require('tslib'); const tsickle_exporter_1 = goog.requireType("test_files.import_prefixed.exporter"); -// This file imports exporter with a prefix import (* as ...), and then uses the -// import in a type and in a value position. const exporter = goog.require('test_files.import_prefixed.exporter'); /** @type {(string|number)} */ let someVar; diff --git a/test_files/import_prefixed/import_prefixed_types.js b/test_files/import_prefixed/import_prefixed_types.js index a70c737d6..81cd2afda 100644 --- a/test_files/import_prefixed/import_prefixed_types.js +++ b/test_files/import_prefixed/import_prefixed_types.js @@ -1,3 +1,7 @@ +// This file imports exporter with a prefix import (* as ...), and then only +// uses the import in a type position. +// tsickle emits a goog.forwardDeclare for the type and uses it to refer to the +// type TypeExport. /** * @fileoverview added by tsickle * Generated from: test_files/import_prefixed/import_prefixed_types.ts @@ -6,10 +10,6 @@ goog.module('test_files.import_prefixed.import_prefixed_types'); var module = module || { id: 'test_files/import_prefixed/import_prefixed_types.ts' }; goog.require('tslib'); const tsickle_exporter_1 = goog.requireType("test_files.import_prefixed.exporter"); -// This file imports exporter with a prefix import (* as ...), and then only -// uses the import in a type position. -// tsickle emits a goog.forwardDeclare for the type and uses it to refer to the -// type TypeExport. /** @type {(string|number)} */ const someVar = 1; console.log(someVar); diff --git a/test_files/interface/implement_import.js b/test_files/interface/implement_import.js index 7633da261..46e2816c2 100644 --- a/test_files/interface/implement_import.js +++ b/test_files/interface/implement_import.js @@ -1,9 +1,7 @@ /** - * * @fileoverview * Generated from: test_files/interface/implement_import.ts * @suppress {uselessCode} - * */ goog.module('test_files.interface.implement_import'); var module = module || { id: 'test_files/interface/implement_import.ts' }; diff --git a/test_files/interface/interface.js b/test_files/interface/interface.js index c5a06ade8..8803c1d2b 100644 --- a/test_files/interface/interface.js +++ b/test_files/interface/interface.js @@ -1,9 +1,7 @@ /** - * * @fileoverview * Generated from: test_files/interface/interface.ts * @suppress {uselessCode} - * */ goog.module('test_files.interface.interface'); var module = module || { id: 'test_files/interface/interface.ts' }; diff --git a/test_files/interface/interface_extends.js b/test_files/interface/interface_extends.js index 5f075481b..81dfbaefe 100644 --- a/test_files/interface/interface_extends.js +++ b/test_files/interface/interface_extends.js @@ -1,9 +1,7 @@ /** - * * @fileoverview * Generated from: test_files/interface/interface_extends.ts * @suppress {uselessCode} - * */ goog.module('test_files.interface.interface_extends'); var module = module || { id: 'test_files/interface/interface_extends.ts' }; diff --git a/test_files/interface/interface_merge.js b/test_files/interface/interface_merge.js index 22ebf38e0..059c717d7 100644 --- a/test_files/interface/interface_merge.js +++ b/test_files/interface/interface_merge.js @@ -1,10 +1,8 @@ /** - * * @fileoverview Test to ensure that there is only one record declaration * for a merged interface. * Generated from: test_files/interface/interface_merge.ts * @suppress {uselessCode} - * */ goog.module('test_files.interface.interface_merge'); var module = module || { id: 'test_files/interface/interface_merge.ts' }; diff --git a/test_files/interface/interface_type_params.js b/test_files/interface/interface_type_params.js index 28a6f033f..18d5587af 100644 --- a/test_files/interface/interface_type_params.js +++ b/test_files/interface/interface_type_params.js @@ -1,9 +1,7 @@ /** - * * @fileoverview * Generated from: test_files/interface/interface_type_params.ts * @suppress {uselessCode} - * */ goog.module('test_files.interface.interface_type_params'); var module = module || { id: 'test_files/interface/interface_type_params.ts' }; diff --git a/test_files/internal.declaration/internal.d.ts b/test_files/internal.declaration/internal.d.ts index eae8448e9..1bd9c76a6 100644 --- a/test_files/internal.declaration/internal.d.ts +++ b/test_files/internal.declaration/internal.d.ts @@ -1,3 +1,8 @@ // test_files/internal.declaration/internal.ts(27,18): error TS0: transformation of plain namespace not supported. (go/ts-merged-namespaces) //!! generated by tsickle from test_files/internal.declaration/internal.ts +/** + * @fileoverview Test to reproduce that \@internal declarations are not + * re-exported for Clutz. There should not be any `.d.ts` aliases generated for + * the declarations below. + */ export {}; diff --git a/test_files/invalid_closure_properties/invalid_closure_properties.js b/test_files/invalid_closure_properties/invalid_closure_properties.js index 48c31a288..f7a01d3f1 100644 --- a/test_files/invalid_closure_properties/invalid_closure_properties.js +++ b/test_files/invalid_closure_properties/invalid_closure_properties.js @@ -1,9 +1,8 @@ // test_files/invalid_closure_properties/invalid_closure_properties.ts(18,12): warning TS0: omitting inexpressible property name: with spaces +// test_files/invalid_closure_properties/invalid_closure_properties.ts(18,12): warning TS0: omitting inexpressible property name: __@observable /** - * * @fileoverview Check the type generated when using a builtin symbol as * a computed property. - * * Generated from: test_files/invalid_closure_properties/invalid_closure_properties.ts */ // This test is verifying the type of this expression, which ultimately diff --git a/test_files/jsdoc/enum_tag.js b/test_files/jsdoc/enum_tag.js index 2d8efab60..583eeba39 100644 --- a/test_files/jsdoc/enum_tag.js +++ b/test_files/jsdoc/enum_tag.js @@ -3,12 +3,10 @@ // test_files/jsdoc/enum_tag.ts(21,1): warning TS0: @enum annotations are redundant with TypeScript equivalents // test_files/jsdoc/enum_tag.ts(30,3): warning TS0: @enum annotations are redundant with TypeScript equivalents /** - * * @fileoverview Checks that JSDoc `\@enum` tags on an `enum` are flagged as * warnings. * Generated from: test_files/jsdoc/enum_tag.ts * @suppress {uselessCode} - * */ goog.module('test_files.jsdoc.enum_tag'); var module = module || { id: 'test_files/jsdoc/enum_tag.ts' }; diff --git a/test_files/jsdoc/jsdoc.js b/test_files/jsdoc/jsdoc.js index e27f2c9c4..065088050 100644 --- a/test_files/jsdoc/jsdoc.js +++ b/test_files/jsdoc/jsdoc.js @@ -13,11 +13,9 @@ // test_files/jsdoc/jsdoc.ts(96,3): warning TS0: @constructor annotations are redundant with TypeScript equivalents // test_files/jsdoc/jsdoc.ts(144,1): warning TS0: the type annotation on @define is redundant with its TypeScript type, remove the {...} part /** - * * @fileoverview * Generated from: test_files/jsdoc/jsdoc.ts * @suppress {uselessCode} - * */ goog.module('test_files.jsdoc.jsdoc'); var module = module || { id: 'test_files/jsdoc/jsdoc.ts' }; @@ -210,3 +208,19 @@ const DEFINE_WITH_DECLARED_TYPE = 'y'; * @type {number} */ const logTypeInCompiler = 0; +class NgInjectClass { + /** + * @ngInject + * @public + */ + constructor() { } +} +class NgInjectClassWithStruct { + // The @struct tag does not mean anything to TypeScript but is ok to use. + // TODO(b/335205805): don't escape @ngInject here. + /** + * @struct \@ngInject + * @public + */ + constructor() { } +} diff --git a/test_files/jsdoc/jsdoc.ts b/test_files/jsdoc/jsdoc.ts index 819138761..47ba6b29c 100644 --- a/test_files/jsdoc/jsdoc.ts +++ b/test_files/jsdoc/jsdoc.ts @@ -158,3 +158,15 @@ const DEFINE_WITH_DECLARED_TYPE: string = 'y'; /** @logTypeInCompiler */ const logTypeInCompiler = 0; + +class NgInjectClass { + /** @ngInject */ + constructor() {} +} + +class NgInjectClassWithStruct { + // The @struct tag does not mean anything to TypeScript but is ok to use. + // TODO(b/335205805): don't escape @ngInject here. + /** @struct @ngInject */ + constructor() {} +} diff --git a/test_files/jsdoc_types.untyped/jsdoc_types.js b/test_files/jsdoc_types.untyped/jsdoc_types.js index 32495b6b0..bc4842968 100644 --- a/test_files/jsdoc_types.untyped/jsdoc_types.js +++ b/test_files/jsdoc_types.untyped/jsdoc_types.js @@ -1,11 +1,11 @@ -/** - * @fileoverview added by tsickle - * Generated from: test_files/jsdoc_types.untyped/jsdoc_types.ts - */ /** * This test tests importing a type across module boundaries, * ensuring that the type gets the proper name in JSDoc comments. */ +/** + * @fileoverview added by tsickle + * Generated from: test_files/jsdoc_types.untyped/jsdoc_types.ts + */ goog.module('test_files.jsdoc_types.untyped.jsdoc_types'); var module = module || { id: 'test_files/jsdoc_types.untyped/jsdoc_types.ts' }; goog.require('tslib'); diff --git a/test_files/jsdoc_types.untyped/module1.js b/test_files/jsdoc_types.untyped/module1.js index 13ccf0e63..65201999d 100644 --- a/test_files/jsdoc_types.untyped/module1.js +++ b/test_files/jsdoc_types.untyped/module1.js @@ -1,11 +1,9 @@ // test_files/jsdoc_types.untyped/module1.ts(9,3): warning TS0: handle unnamed member: // 'quoted-bad-name': string; /** - * * @fileoverview * Generated from: test_files/jsdoc_types.untyped/module1.ts * @suppress {uselessCode} - * */ goog.module('test_files.jsdoc_types.untyped.module1'); var module = module || { id: 'test_files/jsdoc_types.untyped/module1.ts' }; diff --git a/test_files/jsdoc_types.untyped/module2.js b/test_files/jsdoc_types.untyped/module2.js index 11d7ed2ab..b5c7d41a7 100644 --- a/test_files/jsdoc_types.untyped/module2.js +++ b/test_files/jsdoc_types.untyped/module2.js @@ -1,9 +1,7 @@ /** - * * @fileoverview * Generated from: test_files/jsdoc_types.untyped/module2.ts * @suppress {uselessCode} - * */ goog.module('test_files.jsdoc_types.untyped.module2'); var module = module || { id: 'test_files/jsdoc_types.untyped/module2.ts' }; diff --git a/test_files/jsdoc_types.untyped/nevertyped.js b/test_files/jsdoc_types.untyped/nevertyped.js index 53df7c44c..b46af99da 100644 --- a/test_files/jsdoc_types.untyped/nevertyped.js +++ b/test_files/jsdoc_types.untyped/nevertyped.js @@ -1,10 +1,8 @@ /** - * * @fileoverview This filename is specially marked in the tsickle test suite * runner so that its types are always {?}. * Generated from: test_files/jsdoc_types.untyped/nevertyped.ts * @suppress {uselessCode} - * */ goog.module('test_files.jsdoc_types.untyped.nevertyped'); var module = module || { id: 'test_files/jsdoc_types.untyped/nevertyped.ts' }; diff --git a/test_files/jsdoc_types/initialized_unknown.js b/test_files/jsdoc_types/initialized_unknown.js index 841a8051c..dbf1770e7 100644 --- a/test_files/jsdoc_types/initialized_unknown.js +++ b/test_files/jsdoc_types/initialized_unknown.js @@ -1,8 +1,6 @@ /** - * * @fileoverview Tests that initialized variables that end up untyped (`?`) do not get an explicit * type annotation, so that Closure's type inference can kick in and possibly do a better job. - * * Generated from: test_files/jsdoc_types/initialized_unknown.ts */ // This should not have a type annotation. diff --git a/test_files/jsdoc_types/jsdoc_types.js b/test_files/jsdoc_types/jsdoc_types.js index c87f87294..3b5dbf80d 100644 --- a/test_files/jsdoc_types/jsdoc_types.js +++ b/test_files/jsdoc_types/jsdoc_types.js @@ -1,12 +1,10 @@ // test_files/jsdoc_types/jsdoc_types.ts(40,1): warning TS0: dropped implements: {?} type // test_files/jsdoc_types/jsdoc_types.ts(43,1): warning TS0: dropped implements: {?} type /** - * * @fileoverview This test tests importing a type across module boundaries, * ensuring that the type gets the proper name in JSDoc comments. * Generated from: test_files/jsdoc_types/jsdoc_types.ts * @suppress {uselessCode} - * */ goog.module('test_files.jsdoc_types.jsdoc_types'); var module = module || { id: 'test_files/jsdoc_types/jsdoc_types.ts' }; diff --git a/test_files/jsdoc_types/module1.js b/test_files/jsdoc_types/module1.js index b33112fba..75f54a394 100644 --- a/test_files/jsdoc_types/module1.js +++ b/test_files/jsdoc_types/module1.js @@ -1,11 +1,9 @@ // test_files/jsdoc_types/module1.ts(9,3): warning TS0: handle unnamed member: // 'quoted-bad-name': string; /** - * * @fileoverview * Generated from: test_files/jsdoc_types/module1.ts * @suppress {uselessCode} - * */ goog.module('test_files.jsdoc_types.module1'); var module = module || { id: 'test_files/jsdoc_types/module1.ts' }; diff --git a/test_files/jsdoc_types/module2.js b/test_files/jsdoc_types/module2.js index 00be7aceb..9253b8a46 100644 --- a/test_files/jsdoc_types/module2.js +++ b/test_files/jsdoc_types/module2.js @@ -1,9 +1,7 @@ /** - * * @fileoverview * Generated from: test_files/jsdoc_types/module2.ts * @suppress {uselessCode} - * */ goog.module('test_files.jsdoc_types.module2'); var module = module || { id: 'test_files/jsdoc_types/module2.ts' }; diff --git a/test_files/jsdoc_types/nevertyped.js b/test_files/jsdoc_types/nevertyped.js index 48e25af43..35f5240cd 100644 --- a/test_files/jsdoc_types/nevertyped.js +++ b/test_files/jsdoc_types/nevertyped.js @@ -1,10 +1,8 @@ /** - * * @fileoverview This filename is specially marked in the tsickle test suite * runner so that its types are always {?}. * Generated from: test_files/jsdoc_types/nevertyped.ts * @suppress {uselessCode} - * */ goog.module('test_files.jsdoc_types.nevertyped'); var module = module || { id: 'test_files/jsdoc_types/nevertyped.ts' }; diff --git a/test_files/jsx.no_externs/jsx.js b/test_files/jsx.no_externs/jsx.js index 4f66efa06..28ff759f1 100644 --- a/test_files/jsx.no_externs/jsx.js +++ b/test_files/jsx.no_externs/jsx.js @@ -1,10 +1,8 @@ /** - * * @fileoverview Fake a subcomponent, just to exercise components within * components. * Generated from: test_files/jsx.no_externs/jsx.tsx * @suppress {checkTypes} - * */ goog.module('test_files.jsx.no_externs.jsx.tsx'); var module = module || { id: 'test_files/jsx.no_externs/jsx.tsx' }; diff --git a/test_files/methods/methods.js b/test_files/methods/methods.js index 014e90fb5..267257f32 100644 --- a/test_files/methods/methods.js +++ b/test_files/methods/methods.js @@ -1,9 +1,7 @@ /** - * * @fileoverview * Generated from: test_files/methods/methods.ts * @suppress {uselessCode} - * */ goog.module('test_files.methods.methods'); var module = module || { id: 'test_files/methods/methods.ts' }; diff --git a/test_files/namespaced.no_nstransform/export_enum_in_namespace.js b/test_files/namespaced.no_nstransform/export_enum_in_namespace.js index 46a4f40bd..205ade23e 100644 --- a/test_files/namespaced.no_nstransform/export_enum_in_namespace.js +++ b/test_files/namespaced.no_nstransform/export_enum_in_namespace.js @@ -1,8 +1,6 @@ /** - * * @fileoverview tsickle's Closure compatible exported enum emit does not work in namespaces. Bar * below must be exported onto foo, which tsickle does by disabling its emit for namespace'd enums. - * * Generated from: test_files/namespaced.no_nstransform/export_enum_in_namespace.ts */ // tslint:disable:no-namespace diff --git a/test_files/namespaced.no_nstransform/export_namespace.js b/test_files/namespaced.no_nstransform/export_namespace.js index aaf41e832..79bc246ee 100644 --- a/test_files/namespaced.no_nstransform/export_namespace.js +++ b/test_files/namespaced.no_nstransform/export_namespace.js @@ -1,8 +1,8 @@ +// tslint:disable:no-namespace /** * @fileoverview added by tsickle * Generated from: test_files/namespaced.no_nstransform/export_namespace.ts */ -// tslint:disable:no-namespace goog.module('test_files.namespaced.no_nstransform.export_namespace'); var module = module || { id: 'test_files/namespaced.no_nstransform/export_namespace.ts' }; goog.require('tslib'); diff --git a/test_files/namespaced.no_nstransform/merged_namespace.js b/test_files/namespaced.no_nstransform/merged_namespace.js index d16313489..1c5ee04fc 100644 --- a/test_files/namespaced.no_nstransform/merged_namespace.js +++ b/test_files/namespaced.no_nstransform/merged_namespace.js @@ -1,10 +1,8 @@ /** - * * @fileoverview Test transpilation of namespaces merging with classes or * functions. * Generated from: test_files/namespaced.no_nstransform/merged_namespace.ts * @suppress {checkTypes,constantProperty} - * */ goog.module('test_files.namespaced.no_nstransform.merged_namespace'); var module = module || { id: 'test_files/namespaced.no_nstransform/merged_namespace.ts' }; diff --git a/test_files/namespaced.no_nstransform/reopen_ns.js b/test_files/namespaced.no_nstransform/reopen_ns.js index 6e3fb0f1c..7c6581c91 100644 --- a/test_files/namespaced.no_nstransform/reopen_ns.js +++ b/test_files/namespaced.no_nstransform/reopen_ns.js @@ -1,9 +1,7 @@ /** - * * @fileoverview * Generated from: test_files/namespaced.no_nstransform/reopen_ns.ts * @suppress {checkTypes,constantProperty} - * */ goog.module('test_files.namespaced.no_nstransform.reopen_ns'); var module = module || { id: 'test_files/namespaced.no_nstransform/reopen_ns.ts' }; diff --git a/test_files/nullable/nullable.js b/test_files/nullable/nullable.js index 0c1227bf3..b1db91e20 100644 --- a/test_files/nullable/nullable.js +++ b/test_files/nullable/nullable.js @@ -1,9 +1,7 @@ /** - * * @fileoverview * Generated from: test_files/nullable/nullable.ts * @suppress {checkTypes,uselessCode} - * */ goog.module('test_files.nullable.nullable'); var module = module || { id: 'test_files/nullable/nullable.ts' }; diff --git a/test_files/optional_chaining/keyed_access.js b/test_files/optional_chaining/keyed_access.js index 444c1de93..917c0f69d 100644 --- a/test_files/optional_chaining/keyed_access.js +++ b/test_files/optional_chaining/keyed_access.js @@ -3,7 +3,6 @@ var module = module || { id: 'test_files/optional_chaining/keyed_access.ts' }; goog.require('tslib'); var _a; /** - * * @fileoverview Tests that tsickle correctly handles casting to the correct * type after an optional property access. There was a bug where tsickle's * non-nullable assertion transformation would remove type information from a @@ -12,7 +11,6 @@ var _a; * that crash. * Generated from: test_files/optional_chaining/keyed_access.ts * @suppress {checkTypes,uselessCode} - * */ /** * @record diff --git a/test_files/optional_chaining/optional_chaining.js b/test_files/optional_chaining/optional_chaining.js index adfb2d0c4..68b0bad04 100644 --- a/test_files/optional_chaining/optional_chaining.js +++ b/test_files/optional_chaining/optional_chaining.js @@ -3,7 +3,6 @@ var module = module || { id: 'test_files/optional_chaining/optional_chaining.ts' goog.require('tslib'); var _a, _b, _c; /** - * * @fileoverview Tests that tsickle handles non-nullable assertions in optional * chains correctly. The correct behavior is not emitting any special casts * because Closure Compiler will not check possibly-undefined property access. @@ -12,7 +11,6 @@ var _a, _b, _c; * For more information see jsdoc_transformer.ts. * Generated from: test_files/optional_chaining/optional_chaining.ts * @suppress {checkTypes} - * */ /** @type {(undefined|{a: (undefined|{b: number})})} */ let basic; diff --git a/test_files/optional_method/optional_method.js b/test_files/optional_method/optional_method.js index f23c04180..e840e1607 100644 --- a/test_files/optional_method/optional_method.js +++ b/test_files/optional_method/optional_method.js @@ -1,9 +1,7 @@ /** - * * @fileoverview * Generated from: test_files/optional_method/optional_method.ts * @suppress {uselessCode} - * */ goog.module('test_files.optional_method.optional_method'); var module = module || { id: 'test_files/optional_method/optional_method.ts' }; diff --git a/test_files/parameter_properties/parameter_properties.js b/test_files/parameter_properties/parameter_properties.js index 414cb35d8..e8efc0922 100644 --- a/test_files/parameter_properties/parameter_properties.js +++ b/test_files/parameter_properties/parameter_properties.js @@ -1,9 +1,7 @@ /** - * * @fileoverview * Generated from: test_files/parameter_properties/parameter_properties.ts * @suppress {uselessCode} - * */ goog.module('test_files.parameter_properties.parameter_properties'); var module = module || { id: 'test_files/parameter_properties/parameter_properties.ts' }; diff --git a/test_files/partial/partial.js b/test_files/partial/partial.js index fffb1fea6..8ee0b17b7 100644 --- a/test_files/partial/partial.js +++ b/test_files/partial/partial.js @@ -1,10 +1,8 @@ // test_files/partial/partial.ts(12,1): warning TS0: dropped implements: dropped implements of a type literal: Partial /** - * * @fileoverview * Generated from: test_files/partial/partial.ts * @suppress {uselessCode} - * */ goog.module('test_files.partial.partial'); var module = module || { id: 'test_files/partial/partial.ts' }; diff --git a/test_files/private_field/private_field.js b/test_files/private_field/private_field.js index e58718c1d..fbb9e6b6f 100644 --- a/test_files/private_field/private_field.js +++ b/test_files/private_field/private_field.js @@ -3,13 +3,11 @@ var module = module || { id: 'test_files/private_field/private_field.ts' }; var _ContainsPrivateField_someField; const tslib_1 = goog.require('tslib'); /** - * * @fileoverview Tests the generation of private field accessors from Tsickle. * They do not generate any externs, as they do not exist on the class * themselves when downleveled by TypeScript. * Generated from: test_files/private_field/private_field.ts * @suppress {checkTypes,uselessCode} - * */ class ContainsPrivateField { /** diff --git a/test_files/promiseconstructor/promiseconstructor.js b/test_files/promiseconstructor/promiseconstructor.js index 5d98c9949..573ecffad 100644 --- a/test_files/promiseconstructor/promiseconstructor.js +++ b/test_files/promiseconstructor/promiseconstructor.js @@ -1,12 +1,10 @@ /** - * * @fileoverview typeof Promise actually resolves to "PromiseConstructor" in * TypeScript, which is a type that doesn't exist in Closure's type world. This * code passes the e2e test because closure_externs.js declares * PromiseConstructor. * Generated from: test_files/promiseconstructor/promiseconstructor.ts * @suppress {checkTypes} - * */ goog.module('test_files.promiseconstructor.promiseconstructor'); var module = module || { id: 'test_files/promiseconstructor/promiseconstructor.ts' }; diff --git a/test_files/protected/protected.js b/test_files/protected/protected.js index a35368df4..7804e4b8c 100644 --- a/test_files/protected/protected.js +++ b/test_files/protected/protected.js @@ -1,10 +1,8 @@ /** - * * @fileoverview This test checks that we emit \\@private/\\@protected where * necessary. * Generated from: test_files/protected/protected.ts * @suppress {uselessCode} - * */ goog.module('test_files.protected.protected'); var module = module || { id: 'test_files/protected/protected.ts' }; diff --git a/test_files/readonly/readonly.js b/test_files/readonly/readonly.js index 880045022..2761f0c92 100644 --- a/test_files/readonly/readonly.js +++ b/test_files/readonly/readonly.js @@ -1,9 +1,7 @@ /** - * * @fileoverview Tests `readonly` properties are annotated with `\@const`. * Generated from: test_files/readonly/readonly.ts * @suppress {uselessCode} - * */ goog.module('test_files.readonly.readonly'); var module = module || { id: 'test_files/readonly/readonly.ts' }; diff --git a/test_files/recursive_alias/recursive_alias.js b/test_files/recursive_alias/recursive_alias.js index c21da5784..7a3d559b6 100644 --- a/test_files/recursive_alias/recursive_alias.js +++ b/test_files/recursive_alias/recursive_alias.js @@ -1,10 +1,8 @@ /** - * * @fileoverview This test checks that tsickle breaks out of recursive type * definitions where the type being declared is used as a type parameter. * Generated from: test_files/recursive_alias/recursive_alias.ts * @suppress {uselessCode} - * */ goog.module('test_files.recursive_alias.recursive_alias'); var module = module || { id: 'test_files/recursive_alias/recursive_alias.ts' }; diff --git a/test_files/recursive_union/recursive_union.js b/test_files/recursive_union/recursive_union.js index c54e1699c..76e366612 100644 --- a/test_files/recursive_union/recursive_union.js +++ b/test_files/recursive_union/recursive_union.js @@ -1,8 +1,6 @@ /** - * * @fileoverview Reproduces a reported crash in tsickle with recursive union * types. - * * Generated from: test_files/recursive_union/recursive_union.ts */ goog.module('test_files.recursive_union.recursive_union'); diff --git a/test_files/rest_parameters_any/rest_parameters_any.js b/test_files/rest_parameters_any/rest_parameters_any.js index 53fc88f07..36b539615 100644 --- a/test_files/rest_parameters_any/rest_parameters_any.js +++ b/test_files/rest_parameters_any/rest_parameters_any.js @@ -1,10 +1,8 @@ // test_files/rest_parameters_any/rest_parameters_any.ts(26,7): warning TS0: unable to translate rest args type /** - * * @fileoverview This test covers the rest parameter of function and method * signatures. This includes signatures only consisting of a rest parameter and * signatures mixing both explicit declarations and a rest parameter. - * * Generated from: test_files/rest_parameters_any/rest_parameters_any.ts */ goog.module('test_files.rest_parameters_any.rest_parameters_any'); diff --git a/test_files/rest_parameters_generic_empty/rest_parameters_generic_empty.js b/test_files/rest_parameters_generic_empty/rest_parameters_generic_empty.js index ab73a5c47..7696cd3db 100644 --- a/test_files/rest_parameters_generic_empty/rest_parameters_generic_empty.js +++ b/test_files/rest_parameters_generic_empty/rest_parameters_generic_empty.js @@ -1,9 +1,7 @@ // test_files/rest_parameters_generic_empty/rest_parameters_generic_empty.ts(13,7): warning TS0: unable to translate rest args type /** - * * @fileoverview Tests what happens when a rest args (...x) param is * instantiated in a context where it creates a zero-argument function. - * * Generated from: test_files/rest_parameters_generic_empty/rest_parameters_generic_empty.ts */ goog.module('test_files.rest_parameters_generic_empty.rest_parameters_generic_empty'); diff --git a/test_files/rest_parameters_tuple/rest_parameters_tuple.js b/test_files/rest_parameters_tuple/rest_parameters_tuple.js index a54e3ecdd..054437fc1 100644 --- a/test_files/rest_parameters_tuple/rest_parameters_tuple.js +++ b/test_files/rest_parameters_tuple/rest_parameters_tuple.js @@ -1,9 +1,7 @@ // test_files/rest_parameters_tuple/rest_parameters_tuple.ts(6,20): warning TS0: failed to resolve rest parameter type, emitting ? /** - * * @fileoverview Tests that complex union/tuple types for rest parameters get emitted as a fallback * '?' unknown type. - * * Generated from: test_files/rest_parameters_tuple/rest_parameters_tuple.ts */ goog.module('test_files.rest_parameters_tuple.rest_parameters_tuple'); diff --git a/test_files/return_this/return_this.js b/test_files/return_this/return_this.js index 7584d583f..0b8ba95bb 100644 --- a/test_files/return_this/return_this.js +++ b/test_files/return_this/return_this.js @@ -1,9 +1,7 @@ /** - * * @fileoverview * Generated from: test_files/return_this/return_this.ts * @suppress {uselessCode} - * */ goog.module('test_files.return_this.return_this'); var module = module || { id: 'test_files/return_this/return_this.ts' }; diff --git a/test_files/scope_collision/collision.js b/test_files/scope_collision/collision.js index a1c521661..165a45666 100644 --- a/test_files/scope_collision/collision.js +++ b/test_files/scope_collision/collision.js @@ -1,10 +1,8 @@ /** - * * @fileoverview * TODO(b/195232797): remove the checkTypes suppression * Generated from: test_files/scope_collision/collision.ts * @suppress {checkTypes,uselessCode} - * */ goog.module('test_files.scope_collision.collision'); var module = module || { id: 'test_files/scope_collision/collision.ts' }; diff --git a/test_files/side_effect_import/module1.js b/test_files/side_effect_import/module1.js index 517cb3a27..8beca783c 100644 --- a/test_files/side_effect_import/module1.js +++ b/test_files/side_effect_import/module1.js @@ -1,7 +1,5 @@ /** - * * @fileoverview A module for importing from the main test. - * * Generated from: test_files/side_effect_import/module1.ts */ goog.module('test_files.side_effect_import.module1'); diff --git a/test_files/side_effect_import/module2.js b/test_files/side_effect_import/module2.js index a951e38cd..e41f5cc0d 100644 --- a/test_files/side_effect_import/module2.js +++ b/test_files/side_effect_import/module2.js @@ -1,7 +1,5 @@ /** - * * @fileoverview A module for importing from the main test. - * * Generated from: test_files/side_effect_import/module2.ts */ goog.module('test_files.side_effect_import.module2'); diff --git a/test_files/side_effect_import/side_effect_import.js b/test_files/side_effect_import/side_effect_import.js index e8b0a1b48..cff92894d 100644 --- a/test_files/side_effect_import/side_effect_import.js +++ b/test_files/side_effect_import/side_effect_import.js @@ -1,8 +1,6 @@ /** - * * @fileoverview Use some side-effect imports and verify that tsickle generates * proper module code from them. - * * Generated from: test_files/side_effect_import/side_effect_import.ts */ // tslint:disable diff --git a/test_files/single_value_enum/single_value_enum.js b/test_files/single_value_enum/single_value_enum.js index 38755e3b0..1ec501ab8 100644 --- a/test_files/single_value_enum/single_value_enum.js +++ b/test_files/single_value_enum/single_value_enum.js @@ -1,10 +1,8 @@ /** - * * @fileoverview Regression test for single valued enums. TypeScript's getBaseTypeOfLiteralType * returns the EnumLiteral type for SingleValuedEnum.C below, instead of SingleValuedEnum directly. * Previously, tsickle would then emit the type as `SingleValuedEnum.C`, which is illegal in * Closure. - * * Generated from: test_files/single_value_enum/single_value_enum.ts */ goog.module('test_files.single_value_enum.single_value_enum'); diff --git a/test_files/spread_type/spread_type.js b/test_files/spread_type/spread_type.js new file mode 100644 index 000000000..97f34f8a6 --- /dev/null +++ b/test_files/spread_type/spread_type.js @@ -0,0 +1,49 @@ +/** + * @fileoverview Checks that spread operator in type literals is + * handled correctly. + * Test cases adapted from b/333548529. + * + * Generated from: test_files/spread_type/spread_type.ts + * @suppress {checkTypes} + */ +goog.module('test_files.spread_type.spread_type'); +var module = module || { id: 'test_files/spread_type/spread_type.ts' }; +goog.require('tslib'); +/** + * @return {boolean} + */ +function randBool() { + return Math.random() < 0.5; +} +/** + * @return {{bar: (undefined|string)}} + */ +function spread1() { + return Object.assign({}, (randBool() && { bar: 'baz' })); +} +/** @type {{bar: (undefined|string)}} */ +const result1 = spread1(); +/** + * @return {{bar: (undefined|string), foo: number}} + */ +function spread2() { + return Object.assign({ foo: 1 }, (randBool() && { bar: 'baz' })); +} +/** @type {{bar: (undefined|string), foo: number}} */ +const result2 = spread2(); +/** + * @return {{bar: (undefined|string)}} + */ +function optional1() { + return { bar: randBool() ? 'baz' : undefined }; +} +/** + * @return {{bar: (undefined|string)}} + */ +function optional2() { + /** @type {{bar: (undefined|string)}} */ + const ret = {}; + if (randBool()) + ret.bar = 'baz'; + return ret; +} diff --git a/test_files/spread_type/spread_type.ts b/test_files/spread_type/spread_type.ts new file mode 100644 index 000000000..a55d9c608 --- /dev/null +++ b/test_files/spread_type/spread_type.ts @@ -0,0 +1,33 @@ +/** + * @fileoverview Checks that spread operator in type literals is + * handled correctly. + * Test cases adapted from b/333548529. + * + * @suppress {checkTypes} + */ + +function randBool() { + return Math.random() < 0.5; +} + +function spread1() { + return {...(randBool() && {bar: 'baz'})}; +} + +const result1 = spread1(); + +function spread2() { + return {foo: 1, ...(randBool() && {bar: 'baz'})}; +} + +const result2 = spread2(); + +function optional1() { + return {bar: randBool() ? 'baz' : undefined}; +} + +function optional2() { + const ret: {bar?: string} = {}; + if (randBool()) ret.bar = 'baz'; + return ret; +} diff --git a/test_files/static/static.js b/test_files/static/static.js index 2f1e69ab7..ede55d851 100644 --- a/test_files/static/static.js +++ b/test_files/static/static.js @@ -1,9 +1,7 @@ /** - * * @fileoverview * Generated from: test_files/static/static.ts * @suppress {uselessCode} - * */ goog.module('test_files.static.static'); var module = module || { id: 'test_files/static/static.ts' }; diff --git a/test_files/string_manipulations/uncapitalize_lowercase.js b/test_files/string_manipulations/uncapitalize_lowercase.js index 35fbb414a..0ecf75208 100644 --- a/test_files/string_manipulations/uncapitalize_lowercase.js +++ b/test_files/string_manipulations/uncapitalize_lowercase.js @@ -1,8 +1,6 @@ /** - * * @fileoverview A short test that ensures that string manipulation types (such * as `Uncapitalize`) are converted to a generic `string` type. - * * Generated from: test_files/string_manipulations/uncapitalize_lowercase.ts */ goog.module('test_files.string_manipulations.uncapitalize_lowercase'); diff --git a/test_files/structural.untyped/structural.untyped.js b/test_files/structural.untyped/structural.untyped.js index 05e20d61f..6820b456c 100644 --- a/test_files/structural.untyped/structural.untyped.js +++ b/test_files/structural.untyped/structural.untyped.js @@ -1,10 +1,8 @@ /** - * * @fileoverview Ensure that a class is structurally equivalent to an object * literal with the same fields. * Generated from: test_files/structural.untyped/structural.untyped.ts * @suppress {uselessCode} - * */ goog.module('test_files.structural.untyped.structural.untyped'); var module = module || { id: 'test_files/structural.untyped/structural.untyped.ts' }; diff --git a/test_files/super/super.js b/test_files/super/super.js index 5e3ba3b10..c42fc4bf7 100644 --- a/test_files/super/super.js +++ b/test_files/super/super.js @@ -1,9 +1,7 @@ /** - * * @fileoverview * Generated from: test_files/super/super.ts * @suppress {uselessCode} - * */ goog.module('test_files.super.super'); var module = module || { id: 'test_files/super/super.ts' }; diff --git a/test_files/this_type/this_type.js b/test_files/this_type/this_type.js index 79e8db3d0..ed2663119 100644 --- a/test_files/this_type/this_type.js +++ b/test_files/this_type/this_type.js @@ -1,9 +1,7 @@ /** - * * @fileoverview * Generated from: test_files/this_type/this_type.ts * @suppress {checkTypes,uselessCode} - * */ goog.module('test_files.this_type.this_type'); var module = module || { id: 'test_files/this_type/this_type.ts' }; diff --git a/test_files/transitive_symbol_type_only/exporter.js b/test_files/transitive_symbol_type_only/exporter.js index 93811bc1b..e443533b1 100644 --- a/test_files/transitive_symbol_type_only/exporter.js +++ b/test_files/transitive_symbol_type_only/exporter.js @@ -1,9 +1,7 @@ /** - * * @fileoverview * Generated from: test_files/transitive_symbol_type_only/exporter.ts * @suppress {uselessCode} - * */ goog.module('test_files.transitive_symbol_type_only.exporter'); var module = module || { id: 'test_files/transitive_symbol_type_only/exporter.ts' }; diff --git a/test_files/transitive_symbol_type_only/transitive_symbol_type_only.js b/test_files/transitive_symbol_type_only/transitive_symbol_type_only.js index 02b070483..64e46af3b 100644 --- a/test_files/transitive_symbol_type_only/transitive_symbol_type_only.js +++ b/test_files/transitive_symbol_type_only/transitive_symbol_type_only.js @@ -1,9 +1,7 @@ /** - * * @fileoverview This file uses a type alias that references a type defined in another file. The * test makes sure there is no hard goog.require for the transitive file, as that breaks strict * dependency checking in some systems. - * * Generated from: test_files/transitive_symbol_type_only/transitive_symbol_type_only.ts */ goog.module('test_files.transitive_symbol_type_only.transitive_symbol_type_only'); diff --git a/test_files/ts_migration_exports_shim.no_externs/bad.js b/test_files/ts_migration_exports_shim.no_externs/bad.js index 7d33226ea..0df005185 100644 --- a/test_files/ts_migration_exports_shim.no_externs/bad.js +++ b/test_files/ts_migration_exports_shim.no_externs/bad.js @@ -9,13 +9,11 @@ // test_files/ts_migration_exports_shim.no_externs/bad.ts(51,23): error TS0: export types must be plain identifiers // test_files/ts_migration_exports_shim.no_externs/bad.ts(52,28): error TS0: must be a type reference /** - * * @fileoverview negative tests for the tsMigrationExportsShim transformation. * * Suppress expected errors for :test_files_compilation_test * Generated from: test_files/ts_migration_exports_shim.no_externs/bad.ts * @suppress {checkTypes,uselessCode,visibility} - * */ // Allowed to be exported by tsmes. goog.module('test_files.ts_migration_exports_shim.no_externs.bad'); diff --git a/test_files/ts_migration_exports_shim.no_externs/bad_default_shorthand_with_more_than_one_export.js b/test_files/ts_migration_exports_shim.no_externs/bad_default_shorthand_with_more_than_one_export.js index c3fd6c3b4..9278ef4ac 100644 --- a/test_files/ts_migration_exports_shim.no_externs/bad_default_shorthand_with_more_than_one_export.js +++ b/test_files/ts_migration_exports_shim.no_externs/bad_default_shorthand_with_more_than_one_export.js @@ -1,13 +1,11 @@ // test_files/ts_migration_exports_shim.no_externs/bad_default_shorthand_with_more_than_one_export.ts(14,1): error TS0: can only call goog.tsMigrationDefaultExportsShim when there is exactly one export. /** - * * @fileoverview negative tests for the tsMigrationDefaultExportsShim * transformation. * * Suppress expected errors for :test_files_compilation_test * Generated from: test_files/ts_migration_exports_shim.no_externs/bad_default_shorthand_with_more_than_one_export.ts * @suppress {checkTypes,visibility} - * */ goog.module('test_files.ts_migration_exports_shim.no_externs.bad_default_shorthand_with_more_than_one_export'); var module = module || { id: 'test_files/ts_migration_exports_shim.no_externs/bad_default_shorthand_with_more_than_one_export.ts' }; diff --git a/test_files/ts_migration_exports_shim.no_externs/bad_default_shorthand_with_no_exports.js b/test_files/ts_migration_exports_shim.no_externs/bad_default_shorthand_with_no_exports.js index 04c90a758..1e8357877 100644 --- a/test_files/ts_migration_exports_shim.no_externs/bad_default_shorthand_with_no_exports.js +++ b/test_files/ts_migration_exports_shim.no_externs/bad_default_shorthand_with_no_exports.js @@ -1,13 +1,11 @@ // test_files/ts_migration_exports_shim.no_externs/bad_default_shorthand_with_no_exports.ts(11,1): error TS0: can only call goog.tsMigrationDefaultExportsShim when there is exactly one export. /** - * * @fileoverview negative tests for the tsMigrationDefaultExportsShim * transformation. * * Suppress expected errors for :test_files_compilation_test * Generated from: test_files/ts_migration_exports_shim.no_externs/bad_default_shorthand_with_no_exports.ts * @suppress {checkTypes,visibility} - * */ goog.module('test_files.ts_migration_exports_shim.no_externs.bad_default_shorthand_with_no_exports'); var module = module || { id: 'test_files/ts_migration_exports_shim.no_externs/bad_default_shorthand_with_no_exports.ts' }; diff --git a/test_files/ts_migration_exports_shim.no_externs/bad_dln_only.js b/test_files/ts_migration_exports_shim.no_externs/bad_dln_only.js index 8267b8ff9..e4c7433da 100644 --- a/test_files/ts_migration_exports_shim.no_externs/bad_dln_only.js +++ b/test_files/ts_migration_exports_shim.no_externs/bad_dln_only.js @@ -1,13 +1,11 @@ // test_files/ts_migration_exports_shim.no_externs/bad_dln_only.ts(12,1): error TS0: goog.tsMigrationExportsShimDeclareLegacyNamespace requires a goog.tsMigration*ExportsShim call as well /** - * * @fileoverview negative test for the tsMigrationExportsShim transformation for * tsMigrationExportsShimDeclareLegacyNamespace. * * Suppress expected errors for :test_files_compilation_test * Generated from: test_files/ts_migration_exports_shim.no_externs/bad_dln_only.ts * @suppress {checkTypes,visibility} - * */ goog.module('test_files.ts_migration_exports_shim.no_externs.bad_dln_only'); var module = module || { id: 'test_files/ts_migration_exports_shim.no_externs/bad_dln_only.ts' }; diff --git a/test_files/ts_migration_exports_shim.no_externs/correct_default_shorthand.js b/test_files/ts_migration_exports_shim.no_externs/correct_default_shorthand.js index dddf471b7..936378b22 100644 --- a/test_files/ts_migration_exports_shim.no_externs/correct_default_shorthand.js +++ b/test_files/ts_migration_exports_shim.no_externs/correct_default_shorthand.js @@ -1,9 +1,7 @@ /** - * * @fileoverview An example export to be re-exported. * Generated from: test_files/ts_migration_exports_shim.no_externs/correct_default_shorthand.ts * @suppress {uselessCode} - * */ goog.module('test_files.ts_migration_exports_shim.no_externs.correct_default_shorthand'); var module = module || { id: 'test_files/ts_migration_exports_shim.no_externs/correct_default_shorthand.ts' }; diff --git a/test_files/ts_migration_exports_shim.no_externs/correct_default_type.js b/test_files/ts_migration_exports_shim.no_externs/correct_default_type.js index 87b1b3e43..4d45eea5e 100644 --- a/test_files/ts_migration_exports_shim.no_externs/correct_default_type.js +++ b/test_files/ts_migration_exports_shim.no_externs/correct_default_type.js @@ -1,9 +1,7 @@ /** - * * @fileoverview An example export to be re-exported. * Generated from: test_files/ts_migration_exports_shim.no_externs/correct_default_type.ts * @suppress {uselessCode} - * */ goog.module('test_files.ts_migration_exports_shim.no_externs.correct_default_type'); var module = module || { id: 'test_files/ts_migration_exports_shim.no_externs/correct_default_type.ts' }; diff --git a/test_files/ts_migration_exports_shim.no_externs/correct_default_value.js b/test_files/ts_migration_exports_shim.no_externs/correct_default_value.js index 57963c261..061d3f24d 100644 --- a/test_files/ts_migration_exports_shim.no_externs/correct_default_value.js +++ b/test_files/ts_migration_exports_shim.no_externs/correct_default_value.js @@ -1,9 +1,7 @@ /** - * * @fileoverview An example export to be re-exported. * Generated from: test_files/ts_migration_exports_shim.no_externs/correct_default_value.ts * @suppress {uselessCode} - * */ goog.module('test_files.ts_migration_exports_shim.no_externs.correct_default_value'); var module = module || { id: 'test_files/ts_migration_exports_shim.no_externs/correct_default_value.ts' }; diff --git a/test_files/ts_migration_exports_shim.no_externs/correct_default_with_re_export.js b/test_files/ts_migration_exports_shim.no_externs/correct_default_with_re_export.js index 30ec51f46..79bd8ab5c 100644 --- a/test_files/ts_migration_exports_shim.no_externs/correct_default_with_re_export.js +++ b/test_files/ts_migration_exports_shim.no_externs/correct_default_with_re_export.js @@ -1,9 +1,7 @@ /** - * * @fileoverview An example export with a re-export to be re-exported via TSMES. * Generated from: test_files/ts_migration_exports_shim.no_externs/correct_default_with_re_export.ts * @suppress {uselessCode} - * */ goog.module('test_files.ts_migration_exports_shim.no_externs.correct_default_with_re_export'); var module = module || { id: 'test_files/ts_migration_exports_shim.no_externs/correct_default_with_re_export.ts' }; diff --git a/test_files/ts_migration_exports_shim.no_externs/correct_named.js b/test_files/ts_migration_exports_shim.no_externs/correct_named.js index 9631d1c08..ff7a0197d 100644 --- a/test_files/ts_migration_exports_shim.no_externs/correct_named.js +++ b/test_files/ts_migration_exports_shim.no_externs/correct_named.js @@ -1,9 +1,7 @@ /** - * * @fileoverview An example export to be re-exported. * Generated from: test_files/ts_migration_exports_shim.no_externs/correct_named.ts * @suppress {uselessCode} - * */ goog.module('test_files.ts_migration_exports_shim.no_externs.correct_named'); var module = module || { id: 'test_files/ts_migration_exports_shim.no_externs/correct_named.ts' }; diff --git a/test_files/ts_migration_exports_shim.no_externs/correct_named_shorthand.js b/test_files/ts_migration_exports_shim.no_externs/correct_named_shorthand.js index 34b0ed71b..983d5ad11 100644 --- a/test_files/ts_migration_exports_shim.no_externs/correct_named_shorthand.js +++ b/test_files/ts_migration_exports_shim.no_externs/correct_named_shorthand.js @@ -1,9 +1,7 @@ /** - * * @fileoverview An example export to be re-exported. * Generated from: test_files/ts_migration_exports_shim.no_externs/correct_named_shorthand.ts * @suppress {uselessCode} - * */ goog.module('test_files.ts_migration_exports_shim.no_externs.correct_named_shorthand'); var module = module || { id: 'test_files/ts_migration_exports_shim.no_externs/correct_named_shorthand.ts' }; diff --git a/test_files/ts_migration_exports_shim.no_externs/pintomodule.js b/test_files/ts_migration_exports_shim.no_externs/pintomodule.js index 3f751b053..b31922f3b 100644 --- a/test_files/ts_migration_exports_shim.no_externs/pintomodule.js +++ b/test_files/ts_migration_exports_shim.no_externs/pintomodule.js @@ -1,10 +1,8 @@ /** - * * @fileoverview This file is marked as a pintomodule, which must be propagated * into the .tsmes.closure.js file. * Generated from: test_files/ts_migration_exports_shim.no_externs/pintomodule.ts * @pintomodule - * */ goog.module('test_files.ts_migration_exports_shim.no_externs.pintomodule'); var module = module || { id: 'test_files/ts_migration_exports_shim.no_externs/pintomodule.ts' }; diff --git a/test_files/ts_migration_exports_shim.tsmes_disabled.no_externs/emits_other_errors.js b/test_files/ts_migration_exports_shim.tsmes_disabled.no_externs/emits_other_errors.js index ac139161d..98d5a6f6d 100644 --- a/test_files/ts_migration_exports_shim.tsmes_disabled.no_externs/emits_other_errors.js +++ b/test_files/ts_migration_exports_shim.tsmes_disabled.no_externs/emits_other_errors.js @@ -2,13 +2,11 @@ // test_files/ts_migration_exports_shim.tsmes_disabled.no_externs/emits_other_errors.ts(62,3): error TS0: goog.tsMigrationExportsShim is only allowed in top level statements // test_files/ts_migration_exports_shim.tsmes_disabled.no_externs/emits_other_errors.ts(35,1): error TS0: calls to goog.tsMigration*ExportsShim are not enabled. Please set generate_ts_migration_exports_shim = True in the BUILD file to enable this feature. /** - * * @fileoverview negative tests for the tsMigrationExportsShim transformation. * * Suppress expected errors for :test_files_compilation_test * Generated from: test_files/ts_migration_exports_shim.tsmes_disabled.no_externs/emits_other_errors.ts * @suppress {checkTypes,uselessCode,visibility} - * */ // Allowed to be exported by tsmes. goog.module('test_files.ts_migration_exports_shim.tsmes_disabled.no_externs.emits_other_errors'); diff --git a/test_files/tuple_types/tuple_functions.js b/test_files/tuple_types/tuple_functions.js index 172f5edec..7045bfecb 100644 --- a/test_files/tuple_types/tuple_functions.js +++ b/test_files/tuple_types/tuple_functions.js @@ -1,10 +1,8 @@ /** - * * @fileoverview Tests that destructured parameters get aliased into more * specific local variables. * Generated from: test_files/tuple_types/tuple_functions.ts * @suppress {uselessCode} - * */ goog.module('test_files.tuple_types.tuple_functions'); var module = module || { id: 'test_files/tuple_types/tuple_functions.ts' }; diff --git a/test_files/tuple_types/tuple_types.js b/test_files/tuple_types/tuple_types.js index 354bf8d0d..b88153be8 100644 --- a/test_files/tuple_types/tuple_types.js +++ b/test_files/tuple_types/tuple_types.js @@ -1,8 +1,6 @@ /** - * * @fileoverview Tests that tuple types get emitted with local aliases to * attach Closure types to. - * * Generated from: test_files/tuple_types/tuple_types.ts */ goog.module('test_files.tuple_types.tuple_types'); diff --git a/test_files/type_alias_imported/type_alias_declare.js b/test_files/type_alias_imported/type_alias_declare.js index f9423970a..6f038d44a 100644 --- a/test_files/type_alias_imported/type_alias_declare.js +++ b/test_files/type_alias_imported/type_alias_declare.js @@ -1,11 +1,9 @@ /** - * * @fileoverview Declares the symbols used in union types in * type_alias_exporter. These symbols must ultimately be imported by * type_alias_imported. * Generated from: test_files/type_alias_imported/type_alias_declare.ts * @suppress {uselessCode} - * */ goog.module('test_files.type_alias_imported.type_alias_declare'); var module = module || { id: 'test_files/type_alias_imported/type_alias_declare.ts' }; diff --git a/test_files/type_alias_imported/type_alias_default_exporter.js b/test_files/type_alias_imported/type_alias_default_exporter.js index db6ed3a6f..39a8807a6 100644 --- a/test_files/type_alias_imported/type_alias_default_exporter.js +++ b/test_files/type_alias_imported/type_alias_default_exporter.js @@ -1,8 +1,6 @@ /** - * * @fileoverview Declares a type alias as default export. This allows testing that the appropriate * type reference is created (no .default property). - * * Generated from: test_files/type_alias_imported/type_alias_default_exporter.ts */ goog.module('test_files.type_alias_imported.type_alias_default_exporter'); diff --git a/test_files/type_and_value/module.js b/test_files/type_and_value/module.js index 186863eb2..355c66c43 100644 --- a/test_files/type_and_value/module.js +++ b/test_files/type_and_value/module.js @@ -1,12 +1,10 @@ // test_files/type_and_value/module.ts(7,1): warning TS0: type/symbol conflict for TypeAndValue, using {?} for now // test_files/type_and_value/module.ts(12,1): warning TS0: type/symbol conflict for TemplatizedTypeAndValue, using {?} for now /** - * * @fileoverview TypeAndValue is both a type and a value, which is allowed in * TypeScript but disallowed in Closure. * Generated from: test_files/type_and_value/module.ts * @suppress {uselessCode} - * */ // WARNING: interface has both a type and a value, skipping emit goog.module('test_files.type_and_value.module'); diff --git a/test_files/type_args_repeated/type_args_repeated.js b/test_files/type_args_repeated/type_args_repeated.js index 991be033f..cbbe7d756 100644 --- a/test_files/type_args_repeated/type_args_repeated.js +++ b/test_files/type_args_repeated/type_args_repeated.js @@ -1,10 +1,8 @@ /** - * * @fileoverview Test file to test that tsickle emits consistent closure types * when type args are repeated. * Generated from: test_files/type_args_repeated/type_args_repeated.ts * @suppress {checkTypes} - * */ goog.module('test_files.type_args_repeated.type_args_repeated'); var module = module || { id: 'test_files/type_args_repeated/type_args_repeated.ts' }; diff --git a/test_files/type_intersection/intersection.js b/test_files/type_intersection/intersection.js index 48ac0f6bf..944afa318 100644 --- a/test_files/type_intersection/intersection.js +++ b/test_files/type_intersection/intersection.js @@ -2,13 +2,11 @@ // test_files/type_intersection/intersection.ts(19,1): warning TS0: unhandled type flags: Intersection // test_files/type_intersection/intersection.ts(19,1): warning TS0: unhandled type flags: Intersection /** - * * @fileoverview Test that type alias declarations containing an intersection * of type literals does not lose property names in the externs. Regression * test for b/261049209. * Generated from: test_files/type_intersection/intersection.ts * @suppress {uselessCode} - * */ goog.module('test_files.type_intersection.intersection'); var module = module || { id: 'test_files/type_intersection/intersection.ts' }; diff --git a/test_files/type_narrowing/emit_extra_casts.js b/test_files/type_narrowing/emit_extra_casts.js index 0db1102a1..276251170 100644 --- a/test_files/type_narrowing/emit_extra_casts.js +++ b/test_files/type_narrowing/emit_extra_casts.js @@ -1,10 +1,8 @@ /** - * * @fileoverview Test that type casts are emitted when a type is used which was * narrowed since declaration. * Generated from: test_files/type_narrowing/emit_extra_casts.ts * @suppress {uselessCode} - * */ goog.module('test_files.type_narrowing.emit_extra_casts'); var module = module || { id: 'test_files/type_narrowing/emit_extra_casts.ts' }; diff --git a/test_files/type_propaccess.no_externs/type_propaccess.js b/test_files/type_propaccess.no_externs/type_propaccess.js index 212c22262..fd6946089 100644 --- a/test_files/type_propaccess.no_externs/type_propaccess.js +++ b/test_files/type_propaccess.no_externs/type_propaccess.js @@ -1,9 +1,7 @@ /** - * * @fileoverview * Generated from: test_files/type_propaccess.no_externs/type_propaccess.ts * @suppress {checkTypes} - * */ goog.module('test_files.type_propaccess.no_externs.type_propaccess'); var module = module || { id: 'test_files/type_propaccess.no_externs/type_propaccess.ts' }; diff --git a/test_files/typeof_function_overloads/user.js b/test_files/typeof_function_overloads/user.js new file mode 100644 index 000000000..f4b57e581 --- /dev/null +++ b/test_files/typeof_function_overloads/user.js @@ -0,0 +1,19 @@ +/** + * @fileoverview Test overloaded function type emit. + * Generated from: test_files/typeof_function_overloads/user.ts + */ +goog.module('test_files.typeof_function_overloads.user'); +var module = module || { id: 'test_files/typeof_function_overloads/user.ts' }; +goog.require('tslib'); +/** + * @param {?=} initialValue + * @return {null} + */ +function ɵinput(initialValue) { + return null; +} +exports.ɵinput = ɵinput; +/** @typedef {function(?=): null} */ +exports.InputFn; +/** @type {function(?=): null} */ +exports.input = ɵinput; diff --git a/test_files/typeof_function_overloads/user.ts b/test_files/typeof_function_overloads/user.ts new file mode 100644 index 000000000..f4233189f --- /dev/null +++ b/test_files/typeof_function_overloads/user.ts @@ -0,0 +1,11 @@ +/** + * @fileoverview Test overloaded function type emit. + */ + +export function ɵinput(): null; +export function ɵinput(initialValue: any): null; +export function ɵinput(initialValue?: any): null { + return null; +} +export type InputFn = typeof ɵinput; +export const input = ɵinput; diff --git a/test_files/underscore/underscore.js b/test_files/underscore/underscore.js index adba00106..534e6fb38 100644 --- a/test_files/underscore/underscore.js +++ b/test_files/underscore/underscore.js @@ -1,10 +1,8 @@ /** - * * @fileoverview Verify that double-underscored names in various places don't * get corrupted. See getIdentifierText() in tsickle.ts. * Generated from: test_files/underscore/underscore.ts * @suppress {uselessCode} - * */ goog.module('test_files.underscore.underscore'); var module = module || { id: 'test_files/underscore/underscore.ts' }; diff --git a/test_files/use_closure_externs/use_closure_externs.js b/test_files/use_closure_externs/use_closure_externs.js index 5765cba9e..863d189d5 100644 --- a/test_files/use_closure_externs/use_closure_externs.js +++ b/test_files/use_closure_externs/use_closure_externs.js @@ -1,10 +1,8 @@ /** - * * @fileoverview A source file that uses types that are used in .d.ts files, but * that are not available or use different names in Closure's externs. * Generated from: test_files/use_closure_externs/use_closure_externs.ts * @suppress {checkTypes} - * */ goog.module('test_files.use_closure_externs.use_closure_externs'); var module = module || { id: 'test_files/use_closure_externs/use_closure_externs.ts' }; diff --git a/test_files/visibility/public_override.js b/test_files/visibility/public_override.js index 31f3039e3..af2c752af 100644 --- a/test_files/visibility/public_override.js +++ b/test_files/visibility/public_override.js @@ -1,12 +1,10 @@ /** - * * @fileoverview Reproduces a problem where TS implicitly defaults to public * visibility, whereas Closure Compiler implicitly inherits the parent's class * visibility, leading to a mismatch and warning generated in Closure Compiler * for code that compiles fine in TS. * Generated from: test_files/visibility/public_override.ts * @suppress {uselessCode} - * */ goog.module('test_files.visibility.public_override'); var module = module || { id: 'test_files/visibility/public_override.ts' }; diff --git a/yarn.lock b/yarn.lock new file mode 100644 index 000000000..6489d7c35 --- /dev/null +++ b/yarn.lock @@ -0,0 +1,603 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@babel/code-frame@^7.0.0": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.12.13.tgz#dcfc826beef65e75c50e21d3837d7d95798dd658" + integrity sha512-HV1Cm0Q3ZrpCR93tkWOYiuYIgLxZXZFVG2VgK+MBWjUqZTundupbfx2aXarXuw5Ko5aMcjtJgbSs4vUGBS5v6g== + dependencies: + "@babel/highlight" "^7.12.13" + +"@babel/helper-validator-identifier@^7.14.0": + version "7.14.0" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.0.tgz#d26cad8a47c65286b15df1547319a5d0bcf27288" + integrity sha512-V3ts7zMSu5lfiwWDVWzRDGIN+lnCEUdaXgtVHJgLb1rGaA6jMrtB9EmE7L18foXJIE8Un/A/h6NJfGQp/e1J4A== + +"@babel/highlight@^7.12.13": + version "7.14.0" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.14.0.tgz#3197e375711ef6bf834e67d0daec88e4f46113cf" + integrity sha512-YSCOwxvTYEIMSGaBQb5kDDsCopDdiUGsqpatp3fOlI4+2HQSkTmEVWnVuySdAC5EWCqSWWTv0ib63RjR7dTBdg== + dependencies: + "@babel/helper-validator-identifier" "^7.14.0" + chalk "^2.0.0" + js-tokens "^4.0.0" + +"@types/diff-match-patch@^1.0.32": + version "1.0.32" + resolved "https://registry.yarnpkg.com/@types/diff-match-patch/-/diff-match-patch-1.0.32.tgz#d9c3b8c914aa8229485351db4865328337a3d09f" + integrity sha512-bPYT5ECFiblzsVzyURaNhljBH2Gh1t9LowgUwciMrNAhFewLkHT2H0Mto07Y4/3KCOGZHRQll3CTtQZ0X11D/A== + +"@types/events@*": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@types/events/-/events-3.0.0.tgz#2862f3f58a9a7f7c3e78d79f130dd4d71c25c2a7" + integrity sha512-EaObqwIvayI5a8dCzhFrjKzVwKLxjoG9T6Ppd5CEo07LRKfQ8Yokw54r5+Wq7FaBQ+yXRvQAYPrHwya1/UFt9g== + +"@types/glob@5.0.35": + version "5.0.35" + resolved "https://registry.yarnpkg.com/@types/glob/-/glob-5.0.35.tgz#1ae151c802cece940443b5ac246925c85189f32a" + integrity sha512-wc+VveszMLyMWFvXLkloixT4n0harUIVZjnpzztaZ0nKLuul7Z32iMt2fUFGAaZ4y1XWjFRMtCI5ewvyh4aIeg== + dependencies: + "@types/events" "*" + "@types/minimatch" "*" + "@types/node" "*" + +"@types/jasmine@^3.7.7": + version "3.7.7" + resolved "https://registry.yarnpkg.com/@types/jasmine/-/jasmine-3.7.7.tgz#56718af036be3c9f86eca560a22e39440b2b0784" + integrity sha512-yZzGe1d1T0y+imXDZ79F030nn8qbmiwpWKCZKvKN0KbTzwXAVYShUxkIxu1ba+vhIdabTGVGCfbtZC0oOam8TQ== + +"@types/minimatch@*": + version "3.0.4" + resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.4.tgz#f0ec25dbf2f0e4b18647313ac031134ca5b24b21" + integrity sha512-1z8k4wzFnNjVK/tlxvrWuK5WMt6mydWWP7+zvH5eFep4oj+UkrfiJTRtjCeBXNpwaA/FYqqtb4/QS4ianFpIRA== + +"@types/minimist@^1.2.1": + version "1.2.1" + resolved "https://registry.yarnpkg.com/@types/minimist/-/minimist-1.2.1.tgz#283f669ff76d7b8260df8ab7a4262cc83d988256" + integrity sha512-fZQQafSREFyuZcdWFAExYjBiCL7AUCdgsk80iO0q4yihYYdcIiH28CcuPTGFgLOCC8RlW49GSQxdHwZP+I7CNg== + +"@types/node@*": + version "15.12.1" + resolved "https://registry.yarnpkg.com/@types/node/-/node-15.12.1.tgz#9b60797dee1895383a725f828a869c86c6caa5c2" + integrity sha512-zyxJM8I1c9q5sRMtVF+zdd13Jt6RU4r4qfhTd7lQubyThvLfx6yYekWSQjGCGV2Tkecgxnlpl/DNlb6Hg+dmEw== + +"@types/node@^10.5.6": + version "10.17.60" + resolved "https://registry.yarnpkg.com/@types/node/-/node-10.17.60.tgz#35f3d6213daed95da7f0f73e75bcc6980e90597b" + integrity sha512-F0KIgDJfy2nA3zMLmWGKxcH2ZVEtCZXHHdOQs2gSaQ27+lNeEfGxzkIw90aXswATX7AZ33tahPbzy6KAfUreVw== + +"@types/source-map-support@^0.5.3": + version "0.5.3" + resolved "https://registry.yarnpkg.com/@types/source-map-support/-/source-map-support-0.5.3.tgz#acb6b3e499c20692552d16934c16162c84594e16" + integrity sha512-fvjMjVH8Rmokw2dWh1dkj90iX5R8FPjeZzjNH+6eFXReh0QnHFf1YBl3B0CF0RohIAA3SDRJsGeeUWKl6d7HqA== + dependencies: + source-map "^0.6.0" + +ansi-styles@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" + integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== + dependencies: + color-convert "^1.9.0" + +argparse@^1.0.7: + version "1.0.10" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" + integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== + dependencies: + sprintf-js "~1.0.2" + +balanced-match@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" + integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== + +brace-expansion@^1.1.7: + version "1.1.11" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" + integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +buffer-from@^1.0.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef" + integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A== + +builtin-modules@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-1.1.1.tgz#270f076c5a72c02f5b65a47df94c5fe3a278892f" + integrity sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8= + +chalk@2.x, chalk@^2.0.0, chalk@^2.3.0: + version "2.4.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" + integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== + dependencies: + ansi-styles "^3.2.1" + escape-string-regexp "^1.0.5" + supports-color "^5.3.0" + +clone-buffer@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/clone-buffer/-/clone-buffer-1.0.0.tgz#e3e25b207ac4e701af721e2cb5a16792cac3dc58" + integrity sha1-4+JbIHrE5wGvch4staFnksrD3Fg= + +clone-stats@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/clone-stats/-/clone-stats-1.0.0.tgz#b3782dff8bb5474e18b9b6bf0fdfe782f8777680" + integrity sha1-s3gt/4u1R04Yuba/D9/ngvh3doA= + +clone@^2.1.1: + version "2.1.2" + resolved "https://registry.yarnpkg.com/clone/-/clone-2.1.2.tgz#1b7f4b9f591f1e8f83670401600345a02887435f" + integrity sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18= + +cloneable-readable@^1.0.0: + version "1.1.3" + resolved "https://registry.yarnpkg.com/cloneable-readable/-/cloneable-readable-1.1.3.tgz#120a00cb053bfb63a222e709f9683ea2e11d8cec" + integrity sha512-2EF8zTQOxYq70Y4XKtorQupqF0m49MBz2/yf5Bj+MHjvpG3Hy7sImifnqD6UA+TKYxeSV+u6qqQPawN5UvnpKQ== + dependencies: + inherits "^2.0.1" + process-nextick-args "^2.0.0" + readable-stream "^2.3.5" + +coffeescript@~1.12.7: + version "1.12.7" + resolved "https://registry.yarnpkg.com/coffeescript/-/coffeescript-1.12.7.tgz#e57ee4c4867cf7f606bfc4a0f2d550c0981ddd27" + integrity sha512-pLXHFxQMPklVoEekowk8b3erNynC+DVJzChxS/LCBBgR6/8AJkHivkm//zbowcfc7BTCAjryuhx6gPqPRfsFoA== + +color-convert@^1.9.0: + version "1.9.3" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" + integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== + dependencies: + color-name "1.1.3" + +color-name@1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" + integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= + +commander@^2.12.1: + version "2.20.3" + resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" + integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= + +core-util-is@~1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" + integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac= + +diff-match-patch@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/diff-match-patch/-/diff-match-patch-1.0.5.tgz#abb584d5f10cd1196dfc55aa03701592ae3f7b37" + integrity sha512-IayShXAgj/QMXgB0IWmKx+rOPuGMhqm5w6jvFxmVenXKIzRqTAAsbBPT3kWQeGANj3jGgvcvv4yK6SxqYmikgw== + +diff@^4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" + integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== + +escape-string-regexp@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" + integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= + +esprima@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" + integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== + +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= + +function-bind@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" + integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== + +gaze@~1.1.2: + version "1.1.3" + resolved "https://registry.yarnpkg.com/gaze/-/gaze-1.1.3.tgz#c441733e13b927ac8c0ff0b4c3b033f28812924a" + integrity sha512-BRdNm8hbWzFzWHERTrejLqwHDfS4GibPoq5wjTPIoJHoBtKGPg3xAFfxmM+9ztbXelxcf2hwQcaz1PtmFeue8g== + dependencies: + globule "^1.0.0" + +glob@7.1.2: + version "7.1.2" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.2.tgz#c19c9df9a028702d678612384a6552404c636d15" + integrity sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.0.4" + once "^1.3.0" + path-is-absolute "^1.0.0" + +glob@^7.1.1, glob@^7.1.6, glob@~7.1.1: + version "7.1.7" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.7.tgz#3b193e9233f01d42d0b3f78294bbeeb418f94a90" + integrity sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.0.4" + once "^1.3.0" + path-is-absolute "^1.0.0" + +globule@^1.0.0: + version "1.3.2" + resolved "https://registry.yarnpkg.com/globule/-/globule-1.3.2.tgz#d8bdd9e9e4eef8f96e245999a5dee7eb5d8529c4" + integrity sha512-7IDTQTIu2xzXkT+6mlluidnWo+BypnbSoEVVQCGfzqnl5Ik8d3e1d4wycb8Rj9tWW+Z39uPWsdlquqiqPCd/pA== + dependencies: + glob "~7.1.1" + lodash "~4.17.10" + minimatch "~3.0.2" + +google-closure-compiler-java@^20190929.0.0: + version "20190929.0.0" + resolved "https://registry.yarnpkg.com/google-closure-compiler-java/-/google-closure-compiler-java-20190929.0.0.tgz#faa2c5750982a79c8a4c27999164842502d6b80a" + integrity sha512-fDThDeix5BDIQrP1ESznDq6VDLxY539JF2Hhm+/+XfgXz/kfxWB6RIcsHF+pI4QdNYEEaUGsE3gvF0bYpesUUQ== + +google-closure-compiler-js@^20190929.0.0: + version "20190929.0.0" + resolved "https://registry.yarnpkg.com/google-closure-compiler-js/-/google-closure-compiler-js-20190929.0.0.tgz#6b62c7122fcce86a978a5496fb593949452edefe" + integrity sha512-IB9GJCJPGcSNZWtferd15lA9InUaab9oWPZhJssZN3z/nsHPzV9SqKJLj2oajmcaf2uINhlOIsCVWZwC+AbwVA== + +google-closure-compiler-linux@^20190929.0.0: + version "20190929.0.0" + resolved "https://registry.yarnpkg.com/google-closure-compiler-linux/-/google-closure-compiler-linux-20190929.0.0.tgz#394b29e8c294498be34f5e86eb3f38fa5d2abe6a" + integrity sha512-gu/H1z7MqC43rXnGGoUyGdb12kTFpkDNw0huKj1ScXNvHgq5fQteicQKd7EpiKOIlMBJbJOKoVFNpU1nrAfNvQ== + +google-closure-compiler-osx@^20190929.0.0: + version "20190929.0.0" + resolved "https://registry.yarnpkg.com/google-closure-compiler-osx/-/google-closure-compiler-osx-20190929.0.0.tgz#06e501a0c7ae78b6bc16c260ba137c82bb9d933f" + integrity sha512-SZbp2BOhwjrJdrShZ4HrtBHOEJyKvOtka47uXyo83AdZMX22EV04z+mQCMFHtBautgG/mCsL8eX75nlMPXzkjg== + +google-closure-compiler-windows@^20190929.0.0: + version "20190929.0.0" + resolved "https://registry.yarnpkg.com/google-closure-compiler-windows/-/google-closure-compiler-windows-20190929.0.0.tgz#d6ab1ff5c74d87302884cc7691349d2f14e73b51" + integrity sha512-b1azZx19cQnYqwof+4KxWcjjOJ88QeDDIvmjCmuAZjXG5UC0os/1cutg0AeK3gZnXAsaQwAh3szy+QGKT6IgWw== + +google-closure-compiler@^20190929.0.0: + version "20190929.0.0" + resolved "https://registry.yarnpkg.com/google-closure-compiler/-/google-closure-compiler-20190929.0.0.tgz#9ddd9150e852fe6486e7840ba8277e67ee50ec72" + integrity sha512-psPXU3rfTbx4WsTOxtxCnNQqZdphdH1fS7KbqISJ3Bk1G6WMFapnCUHdnXsFz96i/XrVaTxjwUfrNdoz/F+PsA== + dependencies: + chalk "2.x" + google-closure-compiler-java "^20190929.0.0" + google-closure-compiler-js "^20190929.0.0" + minimist "1.x" + vinyl "2.x" + vinyl-sourcemaps-apply "^0.2.0" + optionalDependencies: + google-closure-compiler-linux "^20190929.0.0" + google-closure-compiler-osx "^20190929.0.0" + google-closure-compiler-windows "^20190929.0.0" + +growl@^1.10.5: + version "1.10.5" + resolved "https://registry.yarnpkg.com/growl/-/growl-1.10.5.tgz#f2735dc2283674fa67478b10181059355c369e5e" + integrity sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA== + +has-flag@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" + integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0= + +has@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" + integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== + dependencies: + function-bind "^1.1.1" + +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk= + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@2, inherits@^2.0.1, inherits@~2.0.3: + version "2.0.4" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + +is-core-module@^2.2.0: + version "2.4.0" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.4.0.tgz#8e9fc8e15027b011418026e98f0e6f4d86305cc1" + integrity sha512-6A2fkfq1rfeQZjxrZJGerpLCTHRNEBiSgnu0+obeJpEPZRUooHgsizvzv0ZjJwOz3iWIHdJtVWJ/tmPr3D21/A== + dependencies: + has "^1.0.3" + +isarray@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" + integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE= + +jasmine-core@~3.7.0: + version "3.7.1" + resolved "https://registry.yarnpkg.com/jasmine-core/-/jasmine-core-3.7.1.tgz#0401327f6249eac993d47bbfa18d4e8efacfb561" + integrity sha512-DH3oYDS/AUvvr22+xUBW62m1Xoy7tUlY1tsxKEJvl5JeJ7q8zd1K5bUwiOxdH+erj6l2vAMM3hV25Xs9/WrmuQ== + +jasmine-growl-reporter@~2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/jasmine-growl-reporter/-/jasmine-growl-reporter-2.0.0.tgz#4943a2481193d66a8a68ee2f38b6c360fb037859" + integrity sha512-RYwVfPaGgxQQSHDOt6jQ99/KAkFQ/Fiwg/AzBS+uO9A4UhGhxb7hwXaUUSU/Zs0MxBoFNqmIRC+7P4/+5O3lXg== + dependencies: + growl "^1.10.5" + +jasmine-node@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/jasmine-node/-/jasmine-node-3.0.0.tgz#f12b6fdd24633402ec23e8ea6fef6ffbcb464f90" + integrity sha512-vUa5Q7bQYwHHqi6FlJYndiKqZp+d+c3MKe0QUMwwrC4JRmoRV3zkg0buxB/uQ6qLh0NO34TNstpAnvaZ6xGlAA== + dependencies: + coffeescript "~1.12.7" + gaze "~1.1.2" + jasmine-growl-reporter "~2.0.0" + jasmine-reporters "~1.0.0" + mkdirp "~0.3.5" + requirejs "~2.3.6" + underscore "~1.9.1" + walkdir "~0.0.12" + +jasmine-reporters@~1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/jasmine-reporters/-/jasmine-reporters-1.0.2.tgz#ab613ed5977dc7487e85b3c12f6a8ea8db2ade31" + integrity sha1-q2E+1Zd9x0h+hbPBL2qOqNsq3jE= + dependencies: + mkdirp "~0.3.5" + +jasmine@^3.7.0: + version "3.7.0" + resolved "https://registry.yarnpkg.com/jasmine/-/jasmine-3.7.0.tgz#d36638c0c815e6ad5666676e386d79e2ccb70835" + integrity sha512-wlzGQ+cIFzMEsI+wDqmOwvnjTvolLFwlcpYLCqSPPH0prOQaW3P+IzMhHYn934l1imNvw07oCyX+vGUv3wmtSQ== + dependencies: + glob "^7.1.6" + jasmine-core "~3.7.0" + +js-tokens@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" + integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== + +js-yaml@^3.13.1: + version "3.14.1" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.1.tgz#dae812fdb3825fa306609a8717383c50c36a0537" + integrity sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g== + dependencies: + argparse "^1.0.7" + esprima "^4.0.0" + +lodash@~4.17.10: + version "4.17.21" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" + integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== + +minimatch@^3.0.4, minimatch@~3.0.2: + version "3.0.4" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" + integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== + dependencies: + brace-expansion "^1.1.7" + +minimist@1.x, minimist@^1.2.5: + version "1.2.5" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" + integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== + +mkdirp@^0.5.3: + version "0.5.5" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def" + integrity sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ== + dependencies: + minimist "^1.2.5" + +mkdirp@~0.3.5: + version "0.3.5" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.3.5.tgz#de3e5f8961c88c787ee1368df849ac4413eca8d7" + integrity sha1-3j5fiWHIjHh+4TaN+EmsRBPsqNc= + +once@^1.3.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= + dependencies: + wrappy "1" + +path-is-absolute@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" + integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= + +path-parse@^1.0.6: + version "1.0.7" + resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" + integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== + +process-nextick-args@^2.0.0, process-nextick-args@~2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" + integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== + +readable-stream@^2.3.5: + version "2.3.7" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57" + integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw== + dependencies: + core-util-is "~1.0.0" + inherits "~2.0.3" + isarray "~1.0.0" + process-nextick-args "~2.0.0" + safe-buffer "~5.1.1" + string_decoder "~1.1.1" + util-deprecate "~1.0.1" + +remove-trailing-separator@^1.0.1: + version "1.1.0" + resolved "https://registry.yarnpkg.com/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz#c24bce2a283adad5bc3f58e0d48249b92379d8ef" + integrity sha1-wkvOKig62tW8P1jg1IJJuSN52O8= + +replace-ext@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/replace-ext/-/replace-ext-1.0.1.tgz#2d6d996d04a15855d967443631dd5f77825b016a" + integrity sha512-yD5BHCe7quCgBph4rMQ+0KkIRKwWCrHDOX1p1Gp6HwjPM5kVoCdKGNhN7ydqqsX6lJEnQDKZ/tFMiEdQ1dvPEw== + +requirejs@~2.3.6: + version "2.3.6" + resolved "https://registry.yarnpkg.com/requirejs/-/requirejs-2.3.6.tgz#e5093d9601c2829251258c0b9445d4d19fa9e7c9" + integrity sha512-ipEzlWQe6RK3jkzikgCupiTbTvm4S0/CAU5GlgptkN5SO6F3u0UD0K18wy6ErDqiCyP4J4YYe1HuAShvsxePLg== + +resolve@^1.3.2: + version "1.20.0" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.20.0.tgz#629a013fb3f70755d6f0b7935cc1c2c5378b1975" + integrity sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A== + dependencies: + is-core-module "^2.2.0" + path-parse "^1.0.6" + +safe-buffer@~5.1.0, safe-buffer@~5.1.1: + version "5.1.2" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" + integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== + +semver@^5.3.0: + version "5.7.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" + integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== + +source-map-support@^0.5.19: + version "0.5.19" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.19.tgz#a98b62f86dcaf4f67399648c085291ab9e8fed61" + integrity sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw== + dependencies: + buffer-from "^1.0.0" + source-map "^0.6.0" + +source-map@^0.5.1: + version "0.5.7" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" + integrity sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w= + +source-map@^0.6.0: + version "0.6.1" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" + integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== + +source-map@^0.7.3: + version "0.7.3" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.3.tgz#5302f8169031735226544092e64981f751750383" + integrity sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ== + +sprintf-js@~1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" + integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw= + +string_decoder@~1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" + integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== + dependencies: + safe-buffer "~5.1.0" + +supports-color@^5.3.0: + version "5.5.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" + integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== + dependencies: + has-flag "^3.0.0" + +tslib@^1.13.0, tslib@^1.8.1: + version "1.14.1" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" + integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== + +tslib@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.2.0.tgz#fb2c475977e35e241311ede2693cee1ec6698f5c" + integrity sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w== + +tslint@^6.1.3: + version "6.1.3" + resolved "https://registry.yarnpkg.com/tslint/-/tslint-6.1.3.tgz#5c23b2eccc32487d5523bd3a470e9aa31789d904" + integrity sha512-IbR4nkT96EQOvKE2PW/djGz8iGNeJ4rF2mBfiYaR/nvUWYKJhLwimoJKgjIFEIDibBtOevj7BqCRL4oHeWWUCg== + dependencies: + "@babel/code-frame" "^7.0.0" + builtin-modules "^1.1.1" + chalk "^2.3.0" + commander "^2.12.1" + diff "^4.0.1" + glob "^7.1.1" + js-yaml "^3.13.1" + minimatch "^3.0.4" + mkdirp "^0.5.3" + resolve "^1.3.2" + semver "^5.3.0" + tslib "^1.13.0" + tsutils "^2.29.0" + +tsutils@^2.29.0: + version "2.29.0" + resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-2.29.0.tgz#32b488501467acbedd4b85498673a0812aca0b99" + integrity sha512-g5JVHCIJwzfISaXpXE1qvNalca5Jwob6FjI4AoPlqMusJ6ftFE7IkkFoMhVLRgK+4Kx3gkzb8UZK5t5yTTvEmA== + dependencies: + tslib "^1.8.1" + +typescript@~4.3: + version "4.3.2" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.3.2.tgz#399ab18aac45802d6f2498de5054fcbbe716a805" + integrity sha512-zZ4hShnmnoVnAHpVHWpTcxdv7dWP60S2FsydQLV8V5PbS3FifjWFFRiHSWpDJahly88PRyV5teTSLoq4eG7mKw== + +underscore@~1.9.1: + version "1.9.2" + resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.9.2.tgz#0c8d6f536d6f378a5af264a72f7bec50feb7cf2f" + integrity sha512-D39qtimx0c1fI3ya1Lnhk3E9nONswSKhnffBI0gME9C99fYOkNi04xs8K6pePLhvl1frbDemkaBQ5ikWllR2HQ== + +util-deprecate@~1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" + integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= + +vinyl-sourcemaps-apply@^0.2.0: + version "0.2.1" + resolved "https://registry.yarnpkg.com/vinyl-sourcemaps-apply/-/vinyl-sourcemaps-apply-0.2.1.tgz#ab6549d61d172c2b1b87be5c508d239c8ef87705" + integrity sha1-q2VJ1h0XLCsbh75cUI0jnI74dwU= + dependencies: + source-map "^0.5.1" + +vinyl@2.x: + version "2.2.1" + resolved "https://registry.yarnpkg.com/vinyl/-/vinyl-2.2.1.tgz#23cfb8bbab5ece3803aa2c0a1eb28af7cbba1974" + integrity sha512-LII3bXRFBZLlezoG5FfZVcXflZgWP/4dCwKtxd5ky9+LOtM4CS3bIRQsmR1KMnMW07jpE8fqR2lcxPZ+8sJIcw== + dependencies: + clone "^2.1.1" + clone-buffer "^1.0.0" + clone-stats "^1.0.0" + cloneable-readable "^1.0.0" + remove-trailing-separator "^1.0.1" + replace-ext "^1.0.0" + +walkdir@~0.0.12: + version "0.0.12" + resolved "https://registry.yarnpkg.com/walkdir/-/walkdir-0.0.12.tgz#2f24f1ade64aab1e458591d4442c8868356e9281" + integrity sha512-HFhaD4mMWPzFSqhpyDG48KDdrjfn409YQuVW7ckZYhW4sE87mYtWifdB/+73RA7+p4s4K18n5Jfx1kHthE1gBw== + +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=