/* * Copyright 2015 The Closure Compiler Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.javascript.jscomp; import com.google.javascript.jscomp.CompilerOptions.LanguageMode; public final class Es6TypedToEs6ConverterTest extends CompilerTestCase { @Override protected void setUp() throws Exception { super.setUp(); setAcceptedLanguage(LanguageMode.ECMASCRIPT6_TYPED); } @Override protected CompilerOptions getOptions() { CompilerOptions options = super.getOptions(); options.setLanguageIn(LanguageMode.ECMASCRIPT6_TYPED); options.setLanguageOut(LanguageMode.ECMASCRIPT_2015); return options; } @Override protected CompilerPass getProcessor(Compiler compiler) { return new Es6TypedToEs6Converter(compiler); } @Override protected int getNumRepetitions() { return 1; } public void testMemberVariable() { test(LINE_JOINER.join( "class C {", " mv: number;", " constructor() {", " this.f = 1;", " }", "}"), LINE_JOINER.join( "class C {", " constructor() {", " this.f = 1;", " }", "}", "/** @type {number} */ C.prototype.mv;")); test(LINE_JOINER.join( "class C {", " on: {", " p: string;", " }", "}"), LINE_JOINER.join( "class C {}", "/** @type {{p: string}} */ C.prototype.on;")); test( "export class C { foo: number; }", "export class C {} /** @type {number} */ C.prototype.foo;"); testDts( "declare class C { foo: number; }", "class C {} /** @type {number} */ C.prototype.foo;"); testDts( "export declare class C { foo: number; }", "export class C {} /** @type {number} */ C.prototype.foo;"); } public void testMemberVariable_noCtor() { test("class C { mv: number; }", "class C {} /** @type {number} */ C.prototype.mv;"); } public void testMemberVariable_static() { test("class C { static smv; }", "class C {} C.smv;"); } public void testMemberVariable_anonymousClass() { testSame("(class {})"); testSame("(class { f() {}})"); testError("(class { x: number; })", Es6TypedToEs6Converter.CANNOT_CONVERT_MEMBER_VARIABLES); } public void testComputedPropertyVariable() { test( LINE_JOINER.join( "class C {", " ['mv']: number;", " ['mv' + 2]: number;", " constructor() {", " this.f = 1;", " }", "}"), LINE_JOINER.join( "class C {", " constructor() {", " this.f = 1;", " }", "}", "/** @type {number} */ C.prototype['mv'];", "/** @type {number} */ C.prototype['mv' + 2];")); } public void testComputedPropertyVariable_static() { test("class C { static ['smv' + 2]: number; }", "class C {} /** @type {number} */ C['smv' + 2];"); } public void testUnionType() { test("var x: string | number;", "var /** string | number */ x;"); } // TypeQuery is currently not supported. public void testTypeQuery() { test("var x: typeof y | number;", "var /** ? | number */ x;", null, Es6TypedToEs6Converter.TYPE_QUERY_NOT_SUPPORTED); test("var x: (p1: typeof y) => number;", "var /** function(?): number */ x;", null, Es6TypedToEs6Converter.TYPE_QUERY_NOT_SUPPORTED); } public void testTypedParameter() { test("function f(p1: number) {}", "function f(/** number */ p1) {}"); } public void testOptionalParameter() { test("function f(p1?: number) {}", "function f(/** number= */ p1) {}"); test("function f(p1?) {}", "function f(/** ?= */ p1) {}"); } public void testOptionalProperty() { test("var x: {foo?};", "var /** {foo: (? | undefined)} */ x;"); test("var x: {foo?()};", "var /** {foo: (function(): ? | undefined)} */ x;"); test("var x: {foo?: string};", "var /** {foo: (string | undefined)} */ x;"); test("var x: {foo?: string | number};", "var /** {foo: ((string | number) | undefined)} */ x;"); test("var x: {foo?(): string};", "var /** {foo: ((function(): string) | undefined)} */ x;"); test("interface I {foo?: string}", "/** @interface */ class I {} /** @type {string | undefined} */ I.prototype.foo;"); test("interface I {foo?(): string}", LINE_JOINER.join( "/** @interface */ class I {}", "/** @type {(function(): string) | undefined} */ I.prototype.foo;")); test("interface I {foo?()}", LINE_JOINER.join( "/** @interface */ class I {}", "/** @type {(function(): ?) | undefined} */ I.prototype.foo;")); } public void testRestParameter() { test("function f(...p1: number[]) {}", "function f(/** ...number */ ...p1) {}"); testSame("function f(...p1) {}"); } public void testReturnType() { test("function f(...p1: number[]): void {}", "/** @return{void} */ function f(/** ...number */ ...p1) {}"); testSame("function f(...p1) {}"); } public void testBuiltins() { test("var x: any;", "var /** ? */ x;"); test("var x: number;", "var /** number */ x;"); test("var x: boolean;", "var /** boolean */ x;"); test("var x: string;", "var /** string */ x;"); test("var x: void;", "var /** void */ x;"); } public void testNamedType() { test("var x: foo;", "var /** !foo */ x;"); test("var x: foo.bar.Baz;", "var /** !foo.bar.Baz */ x;"); } public void testArrayType() { test("var x: string[];", "var /** !Array.<string> */ x;"); test("var x: string[][];", "var /** !Array.<!Array.<string>> */ x;"); test("var x: test.Type[];", "var /** !Array.<!test.Type> */ x;"); } public void testRecordType() { test("var x: {p; q};", "var /** {p: ?, q: ?} */ x;"); test("var x: {p: string; q: number};", "var /** {p: string, q: number} */ x;"); test("var x: {p: string, q: number};", "var /** {p: string, q: number} */ x;"); test("var x: {p: string; q: {p: string; q: number}};", "var /** {p: string, q: {p: string, q: number}}*/ x;"); test(LINE_JOINER.join( "var x: {", " p: string;", "};"), "var /** {p: string} */ x;"); test("var x: {foo(p1: number, p2?, ...p3: string[]): string;};", "var /** {foo: function(number, ?=, ...string): string} */ x;"); test("var x: {constructor(); q: number};", "var /** {constructor: function(): ?, q: number} */ x;"); } public void testParameterizedType() { test("var x: test.Type<string>;", "var /** !test.Type<string> */ x;"); test("var x: test.Type<A, B>;", "var /** !test.Type<!A, !B> */ x;"); test("var x: test.Type<A<X>, B>;", "var /** !test.Type<!A<!X>, !B> */ x;"); } public void testParameterizedArrayType() { test("var x: test.Type<number>[];", "var /** !Array.<!test.Type<number>> */ x;"); } public void testFunctionType() { test("var x: (foo: number) => boolean;", "var /** function(number): boolean */ x;"); test("var x: (foo?: number) => boolean;", "var /** function(number=): boolean */ x;"); test("var x: (...foo: number[]) => boolean;", "var /** function(...number): boolean */ x;"); test("var x: (foo, bar?: number) => boolean;", "var /** function(?, number=): boolean */ x;"); test("var x: (foo: string, ...bar) => boolean;", "var /** function(string, ...?): boolean */ x;"); } public void testGenericClass() { test("class Foo<T> {}", "/** @template T */ class Foo {}"); test("class Foo<U, V> {}", "/** @template U, V */ class Foo {}"); test("var Foo = class<T> {};", "var Foo = /** @template T */ class {};"); // Currently, bounded generics are not supported. test("class Foo<U extends () => boolean, V> {}", "/** @template U, V */ class Foo {}", null, Es6TypedToEs6Converter.CANNOT_CONVERT_BOUNDED_GENERICS); } public void testGenericFunction() { test("function foo<T>() {}", "/** @template T */ function foo() {}"); // test("var x = <K, V>(p) => 3;", "var x = /** @template K, V */ (p) => 3"); test("class Foo { f<T>() {} }", "class Foo { /** @template T */ f() {} }"); test("(function<T>() {})();", "(/** @template T */ function() {})();"); test("function* foo<T>() {}", "/** @template T */ function* foo() {}"); } public void testGenericInterface() { test("interface I<T> { foo: T; }", "/** @interface @template T */ class I {} /** @type {!T} */ I.prototype.foo;"); } public void testImplements() { test("class Foo implements Bar, Baz {}", "/** @implements {Bar} @implements {Baz} */ class Foo {}"); // The "extends" clause is handled by @link {Es6ToEs3Converter} test("class Foo extends Bar implements Baz {}", "/** @implements {Baz} */ class Foo extends Bar {}"); } public void testEnum() { test("enum E { Foo, Bar }", "/** @enum {number} */ var E = { Foo: 0, Bar: 1 }"); } public void testInterface() { test("interface I { foo: string; }", "/** @interface */ class I {} /** @type {string} */ I.prototype.foo;"); test("interface Foo extends Bar, Baz {}", "/** @interface @extends {Bar} @extends {Baz} */ class Foo {}"); test("interface I { foo(p: string): boolean; }", "/** @interface */ class I { /** @return {boolean} */ foo(/** string */ p) {} }"); testDts( "declare namespace foo.bar { interface J extends foo.I {} }", LINE_JOINER.join( "/** @const */ var foo = {}; /** @const */ foo.bar = {}", "/** @interface @extends {foo.I} */ foo.bar.J = class {};")); testDts( "declare namespace foo { interface I { bar: number; } }", LINE_JOINER.join( "/** @const */ var foo = {};", "/** @interface */ foo.I = class {}; /** @type {number} */ foo.I.prototype.bar;")); } public void testTypeAlias() { test("type Foo = number;", "/** @typedef{number} */ var Foo;"); testError("type Foo = number; var Foo = 3; ", Es6TypedToEs6Converter.TYPE_ALIAS_ALREADY_DECLARED); testError("let Foo = 3; type Foo = number;", Es6TypedToEs6Converter.TYPE_ALIAS_ALREADY_DECLARED); } public void testAmbientDeclaration() { testDts( "declare var x: number;", "var /** number */ x;"); testDts("declare let x;", "let x;"); testDts("declare const x;", "/** @const */ var x;"); testDts("declare function f(): number;", "/** @return {number} */ function f() {}"); testDts( "declare enum Foo {}", "/** @enum {number} */ var Foo = {}"); testDts("declare class C { constructor(); }", "class C { constructor() {} }"); testDts( "declare class C { foo(): number; }", "class C { /** @return {number} */ foo() {} }"); testDts("declare module foo {}", "/** @const */ var foo = {}"); // Accept "module" testDts("declare namespace foo {}", "/** @const */ var foo = {}"); testWarning("declare var x;", Es6TypedToEs6Converter.DECLARE_IN_NON_EXTERNS); } public void testIndexSignature() { test("interface I { [foo: string]: Bar<Baz>; }", "/** @interface @extends {IObject<string, !Bar<!Baz>>} */ class I {}"); test("interface I extends J { [foo: string]: Bar<Baz>; }", "/** @interface @extends {J} @extends {IObject<string, !Bar<!Baz>>} */ class I {}"); test("class C implements D { [foo: string]: number; }", "/** @implements {D} @implements {IObject<string, number>} */ class C {}"); testError("var x: { [foo: string]: number; };", Es6TypedToEs6Converter.UNSUPPORTED_RECORD_TYPE); } public void testCallSignature() { testError("interface I { (): string }", Es6TypedToEs6Converter.CALL_SIGNATURE_NOT_SUPPORTED); testError("interface I { new (): string }", Es6TypedToEs6Converter.CALL_SIGNATURE_NOT_SUPPORTED); } public void testAccessibilityModifier() { test("class Foo { private constructor() {} }", "class Foo { /** @private */ constructor() {} }"); test("class Foo { protected bar() {} }", "class Foo { /** @protected */ bar() {} }"); test("class Foo { protected static bar: number; }", "class Foo {} /** @protected @type {number} */ Foo.bar;"); test("class Foo { private get() {} }", "class Foo { /** @private */ get() {} }"); test("class Foo { public set() {} }", "class Foo { /** @public */ set() {} }"); test("class Foo { private ['foo']() {} }", "class Foo { /** @private */ ['foo']() {} }", null, Es6TypedToEs6Converter.COMPUTED_PROP_ACCESS_MODIFIER); test("class Foo { private ['foo']; }", "class Foo {} /** @private */ Foo.prototype['foo'];", null, Es6TypedToEs6Converter.COMPUTED_PROP_ACCESS_MODIFIER); } public void testAmbientNamespace() { testDts( "declare namespace foo { var i, j, k; }", "/** @const */ var foo = {}; foo.i; foo.j; foo.k;"); testDts( "declare namespace foo { let i, j, k; }", "/** @const */ var foo = {}; foo.i; foo.j; foo.k;"); testDts( "declare namespace foo { function f(); }", "/** @const */ var foo = {}; foo.f = function() {};"); testDts("declare namespace foo { interface I {} }", "/** @const */ var foo = {}; /** @interface */ foo.I = class {};"); testDts("declare namespace foo { interface I { bar: number; } }", LINE_JOINER.join( "/** @const */ var foo = {}; /** @interface */ foo.I = class {};", "/** @type {number} */ foo.I.prototype.bar;")); testDts("declare namespace foo { class C { bar: number; } }", LINE_JOINER.join( "/** @const */ var foo = {}; foo.C = class {};", "/** @type {number} */ foo.C.prototype.bar;")); testDts("declare namespace foo.bar { class C { baz(): number; } }", LINE_JOINER.join( "/** @const */ var foo = {}; /** @const */ foo.bar = {};", "foo.bar.C = class { /** @return {number} */ baz() {}};")); testDts("declare namespace foo { interface I {} class C implements I {} }", LINE_JOINER.join( "/** @const */ var foo = {};", "/** @interface */ foo.I = class {};", "/** @implements {foo.I} */ foo.C = class {}")); testDts("declare namespace foo { class A {} class B extends A {} }", LINE_JOINER.join( "/** @const */ var foo = {};", "foo.A = class {};", "foo.B = class extends foo.A {};")); testDts("declare namespace foo { class C {} var x: C; }", LINE_JOINER.join( "/** @const */ var foo = {};", "foo.C = class {};", "/** @type {!foo.C} */ foo.x;")); testDts("declare namespace foo { interface J {} interface I extends J {} }", LINE_JOINER.join( "/** @const */ var foo = {};", "/** @interface */ foo.J = class {};", "/** @interface @extends {foo.J} */ foo.I = class {};")); testDts("declare namespace foo { enum E {} }", "/** @const */ var foo = {}; /** @enum */ foo.E = {};"); testDts("declare namespace foo.bar {}", "/** @const */ var foo = {}; /** @const */ foo.bar = {};"); testDts( "declare namespace foo { module baw {} } declare namespace foo { module baz {} }", LINE_JOINER.join( "/** @const */ var foo = {};", "/** @const */ foo.baw = {}; /** @const */ foo.baz = {};")); testDts( "declare namespace foo { var x: Bar; } declare namespace foo { class Bar {} }", "/** @const */ var foo = {}; /** @type {!foo.Bar} */ foo.x; foo.Bar = class {};"); testDts("declare namespace foo {} declare var x;", "/** @const */ var foo = {}; var x;"); testDts("declare namespace foo.bar {} declare var x;", "/** @const */ var foo = {}; /** @const */ foo.bar = {}; var x;"); testDts( "export declare namespace foo.bar {}", "export /** @const */ var foo = {}; /** @const */ foo.bar = {};"); testDts( "export declare namespace foo.bar { export var x; }", "export /** @const */ var foo = {}; /** @const */ foo.bar = {}; foo.bar.x;"); testDts( "export declare namespace foo.bar {} export declare namespace foo.bar {}", "export /** @const */ var foo = {}; /** @const */ foo.bar = {};"); testDts( "export declare namespace foo { var i, j, k; }", "export /** @const */ var foo = {}; foo.i; foo.j; foo.k;"); } public void testExportDeclaration() { test("export var i: number;", "export var /** number */ i;"); test("export var i, j: string, k: number;", "export var i, /** string */ j, /** number */ k;"); test("export let i, j: string, k: number;", "export let i, /** string */ j, /** number */ k;"); test("export const i: number = 5, j: string = '5';", "export const /** number */ i = 5, /** string */ j = '5';"); test("export function f(): number {}", "export /** @return {number} */ function f() {}"); test("export interface I {}", "export /** @interface */ class I {}"); test("export interface I {} export class C implements I {}", "export /** @interface */ class I {} export /** @implements {I} */ class C {}"); testSame("export class A {} export class B extends A {}"); test("export class C {} export var x: C;", "export class C {} export var /** !C */ x;"); test("export enum E {}", "export /** @enum */ var E = {};"); testError("namespace foo { export var x; }", Es6TypedToEs6Converter.NON_AMBIENT_NAMESPACE_NOT_SUPPORTED); } public void testExportAmbientDeclaration() { testDts("export declare var i: number;", "export var /** number */ i;"); testDts( "export declare var i, j: string, k: number;", "export var i, /** string */ j, /** number */ k;"); testDts( "export declare let i, j: string, k: number;", "export let i, /** string */ j, /** number */ k;"); testDts( "export declare const i: number, j: string;", "export /** @const */ var /** number */ i, /** string */ j;"); testDts( "export declare function f(): number;", "export /** @return {number} */ function f() {}"); testDts( "export declare class A {} export declare class B extends A {}", "export class A {} export class B extends A {}"); testDts( "export declare class C {} export declare var x: C;", "export class C {} export var /** !C */ x;"); testDts("export declare enum E {}", "export /** @enum */ var E = {};"); testError("namespace foo { export declare var x; }", Es6TypedToEs6Converter.NON_AMBIENT_NAMESPACE_NOT_SUPPORTED); } public void testExportDeclarationInAmbientNamespace() { testDts( "declare namespace foo { export var i, j, k; }", "/** @const */ var foo = {}; foo.i; foo.j; foo.k;"); testDts( "declare namespace foo { export let i, j, k; }", "/** @const */ var foo = {}; foo.i; foo.j; foo.k;"); testDts( "declare namespace foo { export function f(); }", "/** @const */ var foo = {}; foo.f = function() {};"); testDts("declare namespace foo { export interface I {} }", "/** @const */ var foo = {}; /** @interface */ foo.I = class {};"); testDts( "declare namespace foo { export interface I {} export class C implements I {} }", LINE_JOINER.join( "/** @const */ var foo = {};", "/** @interface */ foo.I = class {};", "/** @implements {foo.I} */ foo.C = class {}")); testDts("declare namespace foo { export class A {} export class B extends A {} }", LINE_JOINER.join( "/** @const */ var foo = {};", "foo.A = class {};", "foo.B = class extends foo.A {};")); testDts("declare namespace foo { export class C {} export var x: C; }", LINE_JOINER.join( "/** @const */ var foo = {};", "foo.C = class {};", "/** @type {!foo.C} */ foo.x;")); testDts( "declare namespace foo { export interface J {} export interface I extends J {} }", LINE_JOINER.join( "/** @const */ var foo = {};", "/** @interface */ foo.J = class {};", "/** @interface @extends {foo.J} */ foo.I = class {};")); testDts("declare namespace foo { export enum E {} }", "/** @const */ var foo = {}; /** @enum */ foo.E = {};"); testDts("declare namespace foo.bar {}", "/** @const */ var foo = {}; /** @const */ foo.bar = {};"); testDts( LINE_JOINER.join( "declare namespace foo { export namespace bax {} export namespace bay {} }", "declare namespace foo { export namespace baz {} }"), LINE_JOINER.join( "/** @const */ var foo = {}; /** @const */ foo.bax = {};", "/** @const */ foo.bay = {}; /** @const */ foo.baz = {};")); testDts( LINE_JOINER.join( "declare namespace foo { export var x: Bar; }", "declare namespace foo { export class Bar {} }"), "/** @const */ var foo = {}; /** @type {!foo.Bar} */ foo.x; foo.Bar = class {};"); } public void testOverload() { test("interface I { foo(p1: number): number; foo(p1: number, p2: boolean): string }", "/** @interface */ class I { /** @type {!Function} */ foo() {} }", null, Es6TypedToEs6Converter.OVERLOAD_NOT_SUPPORTED); test("interface I { foo?(p1: number): number; foo?(p1: number, p2: boolean): string }", "/** @interface */ class I {} /** @type {!Function | undefined} */ I.prototype.foo;", null, Es6TypedToEs6Converter.OVERLOAD_NOT_SUPPORTED); testDts(LINE_JOINER.join( "declare function foo(p1: number): number;", "declare function foo(p1: number, p2: boolean): string"), "/** @type {!Function} */ function foo() {}", Es6TypedToEs6Converter.OVERLOAD_NOT_SUPPORTED); testDts(LINE_JOINER.join( "declare function foo(p1: number): number;", "declare function bar();", "declare function foo(p1: number, p2: boolean): string"), "/** @type {!Function} */ function foo() {} function bar() {}", Es6TypedToEs6Converter.OVERLOAD_NOT_SUPPORTED); testDts(LINE_JOINER.join( "declare function foo(): any;", "declare function foo(p1: number): number;", "declare function bar();", "declare function bar(p1: number, p2: boolean): string"), "/** @type {!Function} */ function foo() {} /** @type {!Function} */ function bar() {}", Es6TypedToEs6Converter.OVERLOAD_NOT_SUPPORTED, Es6TypedToEs6Converter.OVERLOAD_NOT_SUPPORTED); testDts(LINE_JOINER.join( "declare namespace goog {", " function foo(p1: number): number;", " function foo(p1: number, p2: boolean): string", "}"), "/** @const */ var goog = {}; /** @type {!Function} */ goog.foo = function() {}", Es6TypedToEs6Converter.OVERLOAD_NOT_SUPPORTED); testDts( LINE_JOINER.join( "declare namespace goog {", " interface I {", " foo(): number;", " foo(p1: number): string;", " }", " function foo(p1: number): number", " function foo(p1: number, p2: boolean): string", "}"), LINE_JOINER.join( "/** @const */ var goog = {};", "/** @interface */ goog.I = class { /** @type {!Function} */ foo() {} };", "/** @type {!Function} */ goog.foo = function() {};"), Es6TypedToEs6Converter.OVERLOAD_NOT_SUPPORTED, Es6TypedToEs6Converter.OVERLOAD_NOT_SUPPORTED); // Test to make sure nested functions with the same names declared in different functions are // not considered overloads. For now, our parser rejects overloaded nested functions so we can't // have a better test case. testSame(LINE_JOINER.join( "function f() {", " function g() {}", "}", "function h() {", " function g() {}", "}")); } public void testSpecializedSignature() { testDts( LINE_JOINER.join( "declare function foo(p1: number): number;", "declare function foo(p1: 'random'): string"), "/** @type {!Function} */ function foo() {}", Es6TypedToEs6Converter.SPECIALIZED_SIGNATURE_NOT_SUPPORTED, Es6TypedToEs6Converter.OVERLOAD_NOT_SUPPORTED); } private void testDts(String externsInput, String expectedExtern, DiagnosticType... warnings) { testExternChanges(externsInput, "", expectedExtern, warnings); } }