From 44106262841771e607f49014586ae4d276c6c898 Mon Sep 17 00:00:00 2001 From: Laura Harker Date: Thu, 9 May 2024 17:18:23 -0700 Subject: [PATCH] Propagate @modName to .tsmes files PiperOrigin-RevId: 632315251 --- 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 | 380 +++--- 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_shorthand.tsmes.js | 1 + .../correct_default_type.js | 2 - .../correct_default_type.tsmes.js | 1 + .../correct_default_type_literal.tsmes.js | 1 + .../correct_default_value.js | 2 - .../correct_default_value.tsmes.js | 1 + .../correct_default_with_re_export.js | 2 - .../correct_default_with_re_export.tsmes.js | 1 + .../correct_named.js | 2 - .../correct_named.tsmes.js | 1 + .../correct_named_shorthand.js | 2 - .../correct_named_shorthand.tsmes.js | 1 + .../modName.js | 8 + .../modName.ts | 6 + .../modName.tsmes.js | 9 + .../pintomodule.js | 2 - .../pintomodule.tsmes.js | 1 + .../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 ++++++++++ 243 files changed, 5786 insertions(+), 3133 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/ts_migration_exports_shim.no_externs/modName.js create mode 100644 test_files/ts_migration_exports_shim.no_externs/modName.ts create mode 100644 test_files/ts_migration_exports_shim.no_externs/modName.tsmes.js 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/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..cb9b0dd49 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,58 +447,77 @@ 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 leadingTrivia = this.src + .getFullText() + .substring(0, this.src.getLeadingTriviaWidth()); + const isAutoChunk = containsAtPintoModule(leadingTrivia); + const pintoModuleAnnotation = isAutoChunk + ? '@pintomodule found in original_file' + : 'pintomodule absent in original_file'; + const modName = findModName(leadingTrivia); + const modNameAnnotation = modName + ? `@modName {${modName}}` + : 'modName 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}`, + ` * ${modNameAnnotation}`, + ' */', + `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.modName = modName; fileSummary.moduleType = ModuleType.GOOG_MODULE; return [content, fileSummary]; @@ -480,9 +539,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 +550,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 +608,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 +620,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 +633,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 +657,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 +673,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 +694,8 @@ interface TsmesCallBreakdown { */ class FileIdGroup { constructor( - readonly google3Path: string, - readonly googModuleId: string, + readonly google3Path: string, + readonly googModuleId: string, ) {} google3PathWithoutExtension(): string { @@ -648,8 +719,11 @@ class FileIdGroup { } } -function containsAtPintoModule(file: ts.SourceFile): boolean { - const leadingTrivia = - file.getFullText().substring(0, file.getLeadingTriviaWidth()); +function containsAtPintoModule(leadingTrivia: string): boolean { return /\s@pintomodule\s/.test(leadingTrivia); } + +function findModName(leadingTrivia: string): string | undefined { + const result = /\s@modName *{(.*)}\s/.exec(leadingTrivia); + return result ? result[1] : undefined; +} 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_shorthand.tsmes.js b/test_files/ts_migration_exports_shim.no_externs/correct_default_shorthand.tsmes.js index 0a5489e53..fafc3d567 100644 --- a/test_files/ts_migration_exports_shim.no_externs/correct_default_shorthand.tsmes.js +++ b/test_files/ts_migration_exports_shim.no_externs/correct_default_shorthand.tsmes.js @@ -2,6 +2,7 @@ * @fileoverview generator:ts_migration_exports_shim.ts * original_file:third_party/javascript/node_modules/tsickle/test_files/ts_migration_exports_shim.no_externs/correct_default_shorthand.ts * pintomodule absent in original_file + * modName absent in original_file */ goog.module('project.CorrectDefaultShorthand'); goog.module.declareLegacyNamespace(); 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_type.tsmes.js b/test_files/ts_migration_exports_shim.no_externs/correct_default_type.tsmes.js index 9da4ac790..38abacbfa 100644 --- a/test_files/ts_migration_exports_shim.no_externs/correct_default_type.tsmes.js +++ b/test_files/ts_migration_exports_shim.no_externs/correct_default_type.tsmes.js @@ -2,6 +2,7 @@ * @fileoverview generator:ts_migration_exports_shim.ts * original_file:third_party/javascript/node_modules/tsickle/test_files/ts_migration_exports_shim.no_externs/correct_default_type.ts * pintomodule absent in original_file + * modName absent in original_file */ goog.module('project.MyDefaultType'); var mainModule = goog.require('test_files.ts_migration_exports_shim.no_externs.correct_default_type'); diff --git a/test_files/ts_migration_exports_shim.no_externs/correct_default_type_literal.tsmes.js b/test_files/ts_migration_exports_shim.no_externs/correct_default_type_literal.tsmes.js index 3eb893d34..d6cd5a54e 100644 --- a/test_files/ts_migration_exports_shim.no_externs/correct_default_type_literal.tsmes.js +++ b/test_files/ts_migration_exports_shim.no_externs/correct_default_type_literal.tsmes.js @@ -2,6 +2,7 @@ * @fileoverview generator:ts_migration_exports_shim.ts * original_file:third_party/javascript/node_modules/tsickle/test_files/ts_migration_exports_shim.no_externs/correct_default_type_literal.ts * pintomodule absent in original_file + * modName absent in original_file */ goog.module('project.MyDefaultTypeLiteral'); var mainModule = goog.require('test_files.ts_migration_exports_shim.no_externs.correct_default_type_literal'); 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_value.tsmes.js b/test_files/ts_migration_exports_shim.no_externs/correct_default_value.tsmes.js index b9234b003..b21411453 100644 --- a/test_files/ts_migration_exports_shim.no_externs/correct_default_value.tsmes.js +++ b/test_files/ts_migration_exports_shim.no_externs/correct_default_value.tsmes.js @@ -2,6 +2,7 @@ * @fileoverview generator:ts_migration_exports_shim.ts * original_file:third_party/javascript/node_modules/tsickle/test_files/ts_migration_exports_shim.no_externs/correct_default_value.ts * pintomodule absent in original_file + * modName absent in original_file */ goog.module('project.MyDefaultClass'); var mainModule = goog.require('test_files.ts_migration_exports_shim.no_externs.correct_default_value'); 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_default_with_re_export.tsmes.js b/test_files/ts_migration_exports_shim.no_externs/correct_default_with_re_export.tsmes.js index 6c9c9231a..28b78a44e 100644 --- a/test_files/ts_migration_exports_shim.no_externs/correct_default_with_re_export.tsmes.js +++ b/test_files/ts_migration_exports_shim.no_externs/correct_default_with_re_export.tsmes.js @@ -2,6 +2,7 @@ * @fileoverview generator:ts_migration_exports_shim.ts * original_file:third_party/javascript/node_modules/tsickle/test_files/ts_migration_exports_shim.no_externs/correct_default_with_re_export.ts * pintomodule absent in original_file + * modName absent in original_file */ goog.module('works.with.rexport.of.Bar'); var mainModule = goog.require('test_files.ts_migration_exports_shim.no_externs.correct_default_with_re_export'); 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.tsmes.js b/test_files/ts_migration_exports_shim.no_externs/correct_named.tsmes.js index ac143f40a..13275e185 100644 --- a/test_files/ts_migration_exports_shim.no_externs/correct_named.tsmes.js +++ b/test_files/ts_migration_exports_shim.no_externs/correct_named.tsmes.js @@ -2,6 +2,7 @@ * @fileoverview generator:ts_migration_exports_shim.ts * original_file:third_party/javascript/node_modules/tsickle/test_files/ts_migration_exports_shim.no_externs/correct_named.ts * pintomodule absent in original_file + * modName absent in original_file */ goog.module('project.named'); var mainModule = goog.require('test_files.ts_migration_exports_shim.no_externs.correct_named'); 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/correct_named_shorthand.tsmes.js b/test_files/ts_migration_exports_shim.no_externs/correct_named_shorthand.tsmes.js index 4742ef67e..e03cee7db 100644 --- a/test_files/ts_migration_exports_shim.no_externs/correct_named_shorthand.tsmes.js +++ b/test_files/ts_migration_exports_shim.no_externs/correct_named_shorthand.tsmes.js @@ -2,6 +2,7 @@ * @fileoverview generator:ts_migration_exports_shim.ts * original_file:third_party/javascript/node_modules/tsickle/test_files/ts_migration_exports_shim.no_externs/correct_named_shorthand.ts * pintomodule absent in original_file + * modName absent in original_file */ goog.module('project.CorrectNamedShorthand'); var mainModule = goog.require('test_files.ts_migration_exports_shim.no_externs.correct_named_shorthand'); diff --git a/test_files/ts_migration_exports_shim.no_externs/modName.js b/test_files/ts_migration_exports_shim.no_externs/modName.js new file mode 100644 index 000000000..7d46afb49 --- /dev/null +++ b/test_files/ts_migration_exports_shim.no_externs/modName.js @@ -0,0 +1,8 @@ +/** + * @fileoverview A file with \@modName that generates a tsmes shim. + * Generated from: test_files/ts_migration_exports_shim.no_externs/modName.ts + * @modName {tsickleTestModName} + */ +goog.module('test_files.ts_migration_exports_shim.no_externs.modName'); +var module = module || { id: 'test_files/ts_migration_exports_shim.no_externs/modName.ts' }; +goog.require('tslib'); diff --git a/test_files/ts_migration_exports_shim.no_externs/modName.ts b/test_files/ts_migration_exports_shim.no_externs/modName.ts new file mode 100644 index 000000000..1cd6cc295 --- /dev/null +++ b/test_files/ts_migration_exports_shim.no_externs/modName.ts @@ -0,0 +1,6 @@ +/** + * @fileoverview A file with @modName that generates a tsmes shim. + * @modName {tsickleTestModName} + */ + +goog.tsMigrationExportsShim('test.mod_name', {}); diff --git a/test_files/ts_migration_exports_shim.no_externs/modName.tsmes.js b/test_files/ts_migration_exports_shim.no_externs/modName.tsmes.js new file mode 100644 index 000000000..6b1f6277c --- /dev/null +++ b/test_files/ts_migration_exports_shim.no_externs/modName.tsmes.js @@ -0,0 +1,9 @@ +/** + * @fileoverview generator:ts_migration_exports_shim.ts + * original_file:third_party/javascript/node_modules/tsickle/test_files/ts_migration_exports_shim.no_externs/modName.ts + * pintomodule absent in original_file + * @modName {tsickleTestModName} + */ +goog.module('test.mod_name'); +var mainModule = goog.require('test_files.ts_migration_exports_shim.no_externs.modName'); + 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.no_externs/pintomodule.tsmes.js b/test_files/ts_migration_exports_shim.no_externs/pintomodule.tsmes.js index 6d941428e..56786167c 100644 --- a/test_files/ts_migration_exports_shim.no_externs/pintomodule.tsmes.js +++ b/test_files/ts_migration_exports_shim.no_externs/pintomodule.tsmes.js @@ -2,6 +2,7 @@ * @fileoverview generator:ts_migration_exports_shim.ts * original_file:third_party/javascript/node_modules/tsickle/test_files/ts_migration_exports_shim.no_externs/pintomodule.ts * @pintomodule found in original_file + * modName absent in original_file */ goog.module('test.pintomodule'); var mainModule = goog.require('test_files.ts_migration_exports_shim.no_externs.pintomodule'); 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=