blob: b2e4ca3146acca891200b00ba2a914fb0342320c [file] [log] [blame]
/*
* Copyright 2015 Google Inc.
*
* 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.gwt.dev.jjs.impl;
import com.google.gwt.core.ext.TreeLogger;
import com.google.gwt.dev.MinimalRebuildCache;
import com.google.gwt.dev.javac.testing.impl.MockJavaResource;
import com.google.gwt.dev.jjs.ast.JMethod;
import com.google.gwt.dev.jjs.ast.JProgram;
import com.google.gwt.dev.util.arg.SourceLevel;
import com.google.gwt.thirdparty.guava.common.collect.Lists;
import java.util.Arrays;
import java.util.List;
/**
* Tests for the JsInteropRestrictionChecker.
*/
public class JsInteropRestrictionCheckerTest extends OptimizerTestBase {
@Override
public void setUp() {
sourceLevel = SourceLevel.JAVA8;
}
// TODO: eventually test this for default methods in Java 8.
public void testCollidingAccidentalOverrideConcreteMethodFails() throws Exception {
addSnippetImport("jsinterop.annotations.JsType");
addSnippetClassDecl(
"@JsType",
"public static interface Foo {",
" void doIt(Foo foo);",
"}",
"@JsType",
"public static interface Bar {",
" void doIt(Bar bar);",
"}",
"public static class ParentBuggy {",
" public void doIt(Foo foo) {}",
" public void doIt(Bar bar) {}",
"}",
"public static class Buggy extends ParentBuggy implements Foo, Bar {",
"}");
assertBuggyFails(
"Line 14: 'void EntryPoint.ParentBuggy.doIt(EntryPoint.Bar)' "
+ "(exposed by 'EntryPoint.Buggy') and "
+ "'void EntryPoint.ParentBuggy.doIt(EntryPoint.Foo)' (exposed by 'EntryPoint.Buggy') "
+ "cannot both use the same JavaScript name 'doIt'.");
}
public void testCollidingAccidentalOverrideAbstractMethodFails() throws Exception {
addSnippetImport("jsinterop.annotations.JsType");
addSnippetClassDecl(
"@JsType",
"public static interface Foo {",
" void doIt(Foo foo);",
"}",
"@JsType",
"public static interface Bar {",
" void doIt(Bar bar);",
"}",
"public static abstract class Baz implements Foo, Bar {",
" public abstract void doIt(Foo foo);",
" public abstract void doIt(Bar bar);",
"}",
"public static class Buggy {} // Unrelated class");
assertBuggyFails(
"Line 14: 'void EntryPoint.Baz.doIt(EntryPoint.Bar)' and "
+ "'void EntryPoint.Baz.doIt(EntryPoint.Foo)' cannot both use the same "
+ "JavaScript name 'doIt'.");
}
public void testCollidingAccidentalOverrideHalfAndHalfFails() throws Exception {
addSnippetImport("jsinterop.annotations.JsType");
addSnippetClassDecl(
"public static interface Foo {",
" void doIt(Foo foo);",
"}",
"@JsType",
"public static interface Bar {",
" void doIt(Bar bar);",
"}",
"public static class ParentParent {",
" public void doIt(Bar x) {}",
"}",
"@JsType",
"public static class Parent extends ParentParent {",
" public void doIt(Foo x) {}",
"}",
"public static class Buggy extends Parent implements Bar {}");
assertBuggyFails(
"Line 12: 'void EntryPoint.ParentParent.doIt(EntryPoint.Bar)' "
+ "(exposed by 'EntryPoint.Buggy') and 'void EntryPoint.Parent.doIt(EntryPoint.Foo)' "
+ "cannot both use the same JavaScript name 'doIt'.");
}
public void testCollidingFieldExportsFails() throws Exception {
addSnippetImport("jsinterop.annotations.JsProperty");
addSnippetClassDecl(
"public static class Buggy {",
" @JsProperty",
" public static final int show = 0;",
" @JsProperty(name = \"show\")",
" public static final int display = 0;",
"}");
assertBuggyFails(
"Line 8: 'int EntryPoint.Buggy.display' cannot be exported because the global "
+ "name 'test.EntryPoint.Buggy.show' is already taken by "
+ "'int EntryPoint.Buggy.show'.");
}
public void testJsPropertyNonGetterStyleSucceeds() throws Exception {
addSnippetImport("jsinterop.annotations.JsType");
addSnippetImport("jsinterop.annotations.JsProperty");
addSnippetClassDecl(
"@JsType",
"public interface Buggy {",
" @JsProperty(name = \"x\") int x();",
" @JsProperty(name = \"x\") void x(int x);",
"}");
assertBuggySucceeds();
}
public void testJsPropertyGetterStyleSucceeds() throws Exception {
addSnippetImport("jsinterop.annotations.JsType");
addSnippetImport("jsinterop.annotations.JsProperty");
addSnippetClassDecl(
"@JsType",
"public interface Buggy {",
" @JsProperty static int getStaticX(){ return 0;}",
" @JsProperty static void setStaticX(int x){}",
" @JsProperty int getX();",
" @JsProperty void setX(int x);",
" @JsProperty boolean isY();",
" @JsProperty void setY(boolean y);",
"}");
assertBuggySucceeds();
}
public void testJsPropertyIncorrectGetterStyleFails() throws Exception {
addSnippetImport("jsinterop.annotations.JsType");
addSnippetImport("jsinterop.annotations.JsProperty");
addSnippetClassDecl(
"@JsType",
"public interface Buggy {",
" @JsProperty int isX();",
" @JsProperty int getY(int x);",
" @JsProperty void getZ();",
" @JsProperty void setX(int x, int y);",
" @JsProperty void setY();",
" @JsProperty int setZ(int z);",
" @JsProperty static void setStatic(){}",
"}");
assertBuggyFails(
"Line 7: JsProperty 'int EntryPoint.Buggy.isX()' cannot have a non-boolean return.",
"Line 8: JsProperty 'int EntryPoint.Buggy.getY(int)' should have a correct setter "
+ "or getter signature.",
"Line 9: JsProperty 'void EntryPoint.Buggy.getZ()' should have a correct setter "
+ "or getter signature.",
"Line 10: JsProperty 'void EntryPoint.Buggy.setX(int, int)' should have a correct setter "
+ "or getter signature.",
"Line 11: JsProperty 'void EntryPoint.Buggy.setY()' should have a correct setter "
+ "or getter signature.",
"Line 12: JsProperty 'int EntryPoint.Buggy.setZ(int)' should have a correct setter "
+ "or getter signature.",
"Line 13: JsProperty 'void EntryPoint.Buggy.setStatic()' should have a correct setter "
+ "or getter signature.");
}
public void testJsPropertyNonGetterStyleFails() throws Exception {
addSnippetImport("jsinterop.annotations.JsType");
addSnippetImport("jsinterop.annotations.JsProperty");
addSnippetClassDecl(
"@JsType",
"public interface Buggy {",
" @JsProperty boolean hasX();",
" @JsProperty int x();",
" @JsProperty void x(int x);",
"}");
assertBuggyFails(
"Line 7: JsProperty 'boolean EntryPoint.Buggy.hasX()' should either follow Java Bean "
+ "naming conventions or provide a name.",
"Line 8: JsProperty 'int EntryPoint.Buggy.x()' should either follow Java Bean "
+ "naming conventions or provide a name.",
"Line 9: JsProperty 'void EntryPoint.Buggy.x(int)' should either follow Java Bean "
+ "naming conventions or provide a name.");
}
public void testCollidingJsPropertiesTwoGettersFails() throws Exception {
addSnippetImport("jsinterop.annotations.JsType");
addSnippetImport("jsinterop.annotations.JsProperty");
addSnippetClassDecl(
"@JsType",
"public static interface Buggy {",
" @JsProperty",
" boolean isX();",
" @JsProperty",
" boolean getX();",
"}");
assertBuggyFails(
"Line 10: 'boolean EntryPoint.Buggy.getX()' and 'boolean EntryPoint.Buggy.isX()' "
+ "cannot both use the same JavaScript name 'x'.");
}
public void testCollidingJsPropertiesTwoSettersFails() throws Exception {
addSnippetImport("jsinterop.annotations.JsType");
addSnippetImport("jsinterop.annotations.JsProperty");
addSnippetClassDecl(
"@JsType",
"public static interface Buggy {",
" @JsProperty",
" void setX(boolean x);",
" @JsProperty",
" void setX(int x);",
"}");
assertBuggyFails(
"Line 10: 'void EntryPoint.Buggy.setX(int)' and 'void EntryPoint.Buggy.setX(boolean)' "
+ "cannot both use the same JavaScript name 'x'.");
}
public void testCollidingJsTypeAndJsPropertyGetterFails() throws Exception {
addSnippetImport("jsinterop.annotations.JsType");
addSnippetImport("jsinterop.annotations.JsProperty");
addSnippetClassDecl(
"@JsType",
"public static interface IBuggy {",
" boolean x(boolean foo);",
" @JsProperty",
" int getX();",
"}",
"public static class Buggy implements IBuggy {",
" public boolean x(boolean foo) {return false;}",
" public int getX() {return 0;}",
"}");
assertBuggyFails(
"Line 9: 'int EntryPoint.IBuggy.getX()' and 'boolean EntryPoint.IBuggy.x(boolean)' "
+ "cannot both use the same JavaScript name 'x'.",
"Line 13: 'int EntryPoint.Buggy.getX()' and 'boolean EntryPoint.Buggy.x(boolean)' "
+ "cannot both use the same JavaScript name 'x'.");
}
public void testCollidingJsTypeAndJsPropertySetterFails() throws Exception {
addSnippetImport("jsinterop.annotations.JsType");
addSnippetImport("jsinterop.annotations.JsProperty");
addSnippetClassDecl(
"@JsType",
"public static interface IBuggy {",
" boolean x(boolean foo);",
" @JsProperty",
" void setX(int a);",
"}",
"public static class Buggy implements IBuggy {",
" public boolean x(boolean foo) {return false;}",
" public void setX(int a) {}",
"}");
assertBuggyFails(
"Line 9: 'void EntryPoint.IBuggy.setX(int)' and 'boolean EntryPoint.IBuggy.x(boolean)' "
+ "cannot both use the same JavaScript name 'x'.",
"Line 13: 'void EntryPoint.Buggy.setX(int)' and 'boolean EntryPoint.Buggy.x(boolean)' "
+ "cannot both use the same JavaScript name 'x'.");
}
public void testCollidingPropertyAccessorExportsFails() throws Exception {
addSnippetImport("jsinterop.annotations.JsProperty");
addSnippetClassDecl(
"public static class Buggy {",
" @JsProperty",
" public static void setDisplay(int x) {}",
" @JsProperty(name = \"display\")",
" public static void setDisplay2(int x) {}",
"}");
assertBuggyFails(
"Line 8: 'void EntryPoint.Buggy.setDisplay2(int)' cannot be exported because the global "
+ "name 'test.EntryPoint.Buggy.display' is already taken "
+ "by 'void EntryPoint.Buggy.setDisplay(int)'.");
}
public void testCollidingMethodExportsFails() throws Exception {
addSnippetImport("jsinterop.annotations.JsMethod");
addSnippetClassDecl(
"public static class Buggy {",
" @JsMethod",
" public static void show() {}",
" @JsMethod(name = \"show\")",
" public static void display() {}",
"}");
assertBuggyFails(
"Line 8: 'void EntryPoint.Buggy.display()' cannot be exported because the global name "
+ "'test.EntryPoint.Buggy.show' is already taken "
+ "by 'void EntryPoint.Buggy.show()'.");
}
public void testCollidingMethodToPropertyAccessorExportsFails() throws Exception {
addSnippetImport("jsinterop.annotations.JsMethod");
addSnippetImport("jsinterop.annotations.JsProperty");
addSnippetClassDecl(
"public static class Buggy {",
" @JsProperty",
" public static void setShow(int x) {}",
" @JsMethod",
" public static void show() {}",
"}");
assertBuggyFails(
"Line 9: 'void EntryPoint.Buggy.show()' cannot be exported because the global name "
+ "'test.EntryPoint.Buggy.show' is already taken by "
+ "'void EntryPoint.Buggy.setShow(int)'.");
}
public void testCollidingMethodToFieldExportsFails() throws Exception {
addSnippetImport("jsinterop.annotations.JsMethod");
addSnippetImport("jsinterop.annotations.JsProperty");
addSnippetClassDecl(
"public static class Buggy {",
" @JsMethod",
" public static void show() {}",
" @JsProperty",
" public static final int show = 0;",
"}");
assertBuggyFails(
"Line 7: 'void EntryPoint.Buggy.show()' cannot be exported because the global name "
+ "'test.EntryPoint.Buggy.show' is already taken by "
+ "'int EntryPoint.Buggy.show'.");
}
public void testCollidingMethodToFieldJsTypeFails() throws Exception {
addSnippetImport("jsinterop.annotations.JsType");
addSnippetClassDecl(
"@JsType",
"public static class Buggy {",
" public void show() {}",
" public final int show = 0;",
"}");
assertBuggyFails(
"Line 6: 'void EntryPoint.Buggy.show()' and 'int EntryPoint.Buggy.show' cannot both use "
+ "the same JavaScript name 'show'.");
}
public void testCollidingMethodToMethodJsTypeFails() throws Exception {
addSnippetImport("jsinterop.annotations.JsType");
addSnippetClassDecl(
"@JsType",
"public static class Buggy {",
" public void show(int x) {}",
" public void show() {}",
"}");
assertBuggyFails(
"Line 7: 'void EntryPoint.Buggy.show()' and 'void EntryPoint.Buggy.show(int)' cannot both "
+ "use the same JavaScript name 'show'.");
}
public void testCollidingSubclassExportedFieldToFieldJsTypeSucceeds() throws Exception {
addSnippetImport("jsinterop.annotations.JsType");
addSnippetClassDecl(
"@JsType",
"public static class ParentBuggy {",
" public int foo = 55;",
"}",
"public static class Buggy extends ParentBuggy {",
" public int foo = 110;",
"}");
assertBuggySucceeds();
}
public void testCollidingSubclassExportedFieldToMethodJsTypeSucceeds() throws Exception {
addSnippetImport("jsinterop.annotations.JsType");
addSnippetClassDecl(
"@JsType",
"public static class ParentBuggy {",
" public int foo = 55;",
"}",
"public static class Buggy extends ParentBuggy {",
" public void foo(int a) {}",
"}");
assertBuggySucceeds();
}
public void testCollidingSubclassExportedMethodToMethodJsTypeSucceeds() throws Exception {
addSnippetImport("jsinterop.annotations.JsType");
addSnippetClassDecl(
"@JsType",
"public static class ParentBuggy {",
" public void foo() {}",
"}",
"public static class Buggy extends ParentBuggy {",
" public void foo(int a) {}",
"}");
assertBuggySucceeds();
}
public void testCollidingSubclassFieldToExportedFieldJsTypeSucceeds() throws Exception {
addSnippetImport("jsinterop.annotations.JsType");
addSnippetClassDecl(
"public static class ParentBuggy {",
" public int foo = 55;",
"}",
"@JsType",
"public static class Buggy extends ParentBuggy {",
" public int foo = 110;",
"}");
assertBuggySucceeds();
}
public void testCollidingSubclassFieldToExportedMethodJsTypeSucceeds() throws Exception {
addSnippetImport("jsinterop.annotations.JsType");
addSnippetClassDecl(
"public static class ParentBuggy {",
" public int foo = 55;",
"}",
"@JsType",
"public static class Buggy extends ParentBuggy {",
" public void foo(int a) {}",
"}");
assertBuggySucceeds();
}
public void testCollidingSubclassFieldToFieldJsTypeFails() throws Exception {
addSnippetImport("jsinterop.annotations.JsType");
addSnippetClassDecl(
"@JsType",
"public static class ParentBuggy {",
" public int foo = 55;",
"}",
"@JsType",
"public static class Buggy extends ParentBuggy {",
" public int foo = 110;",
"}");
assertBuggyFails(
"Line 10: 'int EntryPoint.Buggy.foo' and 'int EntryPoint.ParentBuggy.foo' cannot both use "
+ "the same JavaScript name 'foo'.");
}
public void testCollidingSubclassFieldToMethodJsTypeFails() throws Exception {
addSnippetImport("jsinterop.annotations.JsType");
addSnippetClassDecl(
"@JsType",
"public static class ParentBuggy {",
" public int foo = 55;",
"}",
"@JsType",
"public static class Buggy extends ParentBuggy {",
" public void foo(int a) {}",
"}");
assertBuggyFails(
"Line 10: 'void EntryPoint.Buggy.foo(int)' and 'int EntryPoint.ParentBuggy.foo' cannot "
+ "both use the same JavaScript name 'foo'.");
}
public void testCollidingSubclassMethodToExportedMethodJsTypeSucceeds() throws Exception {
addSnippetImport("jsinterop.annotations.JsType");
addSnippetClassDecl(
"public static class ParentBuggy {",
" public void foo() {}",
"}",
"@JsType",
"public static class Buggy extends ParentBuggy {",
" public void foo(int a) {}",
"}");
assertBuggySucceeds();
}
public void testCollidingSubclassMethodToMethodInterfaceJsTypeFails() throws Exception {
addSnippetImport("jsinterop.annotations.JsType");
addSnippetClassDecl(
"@JsType",
"public interface IBuggy1 {",
" void show();",
"}",
"@JsType",
"public interface IBuggy2 {",
" void show(boolean b);",
"}",
"public static class Buggy implements IBuggy1 {",
" public void show() {}",
"}",
"public static class Buggy2 extends Buggy implements IBuggy2 {",
" public void show(boolean b) {}",
"}");
assertBuggyFails(
"Line 16: 'void EntryPoint.Buggy2.show(boolean)' and 'void EntryPoint.Buggy.show()' cannot "
+ "both use the same JavaScript name 'show'.");
}
public void testCollidingSubclassMethodToMethodJsTypeFails() throws Exception {
addSnippetImport("jsinterop.annotations.JsType");
addSnippetClassDecl(
"@JsType",
"public static class ParentBuggy {",
" public void foo() {}",
"}",
"@JsType",
"public static class Buggy extends ParentBuggy {",
" public void foo(int a) {}",
"}");
assertBuggyFails(
"Line 10: 'void EntryPoint.Buggy.foo(int)' and 'void EntryPoint.ParentBuggy.foo()' "
+ "cannot both use the same JavaScript name 'foo'.");
}
public void testCollidingSubclassMethodToMethodTwoLayerInterfaceJsTypeFails() throws Exception {
addSnippetImport("jsinterop.annotations.JsType");
addSnippetClassDecl(
"@JsType",
"public interface IParentBuggy1 {",
" void show();",
"}",
"public interface IBuggy1 extends IParentBuggy1 {",
"}",
"@JsType",
"public interface IParentBuggy2 {",
" void show(boolean b);",
"}",
"public interface IBuggy2 extends IParentBuggy2 {",
"}",
"public static class Buggy implements IBuggy1 {",
" public void show() {}",
"}",
"public static class Buggy2 extends Buggy implements IBuggy2 {",
" public void show(boolean b) {}",
"}");
assertBuggyFails(
"Line 20: 'void EntryPoint.Buggy2.show(boolean)' and 'void EntryPoint.Buggy.show()' "
+ "cannot both use the same JavaScript name 'show'.");
}
public void testNonCollidingSyntheticBridgeMethodSucceeds() throws Exception {
addSnippetImport("jsinterop.annotations.JsType");
addSnippetImport("jsinterop.annotations.JsProperty");
addSnippetClassDecl(
"public static interface Comparable<T> {",
" int compareTo(T other);",
"}",
"@JsType",
"public static class Enum<E extends Enum<E>> implements Comparable<E> {",
" public int compareTo(E other) {return 0;}",
"}",
"public static class Buggy {}");
assertBuggySucceeds();
}
public void testCollidingSyntheticBridgeMethodSucceeds() throws Exception {
addSnippetImport("jsinterop.annotations.JsType");
addSnippetClassDecl(
"@JsType",
"public static interface Comparable<T> {",
" int compareTo(T other);",
"}",
"@JsType",
"public static class Enum<E extends Enum<E>> implements Comparable<E> {",
" public int compareTo(E other) {return 0;}",
"}",
"public static class Buggy {}");
assertBuggySucceeds();
}
public void testSpecializeReturnTypeInImplementorSucceeds() throws Exception {
addSnippetImport("jsinterop.annotations.JsType");
addSnippetClassDecl(
"@JsType",
"interface I {",
" I m();",
"}",
"@JsType",
"public static class Buggy implements I {",
" public Buggy m() { return null; } ",
"}");
assertBuggySucceeds();
}
public void testSpecializeReturnTypeInSubclassSucceeds() throws Exception {
addSnippetImport("jsinterop.annotations.JsType");
addSnippetClassDecl(
"@JsType",
"public static class S {",
" public S m() { return null; }",
"}",
"@JsType",
"public static class Buggy extends S {",
" public Buggy m() { return null; } ",
"}");
assertBuggySucceeds();
}
public void testCollidingTwoLayerSubclassFieldToFieldJsTypeFails() throws Exception {
addSnippetImport("jsinterop.annotations.JsType");
addSnippetClassDecl(
"@JsType",
"public static class ParentParentBuggy {",
" public int foo = 55;",
"}",
"public static class ParentBuggy extends ParentParentBuggy {",
" public int foo = 55;",
"}",
"@JsType",
"public static class Buggy extends ParentBuggy {",
" public int foo = 110;",
"}");
assertBuggyFails(
"Line 13: 'int EntryPoint.Buggy.foo' and 'int EntryPoint.ParentParentBuggy.foo' cannot "
+ "both use the same JavaScript name 'foo'.");
}
public void testRenamedSuperclassJsMethodFails() {
addSnippetImport("jsinterop.annotations.JsType");
addSnippetImport("jsinterop.annotations.JsMethod");
addSnippetClassDecl(
"@JsType",
"public static class ParentBuggy {",
" public void foo() {}",
"}",
"public static class Buggy extends ParentBuggy {",
" @JsMethod(name = \"bar\") public void foo() {}",
"}");
assertBuggyFails("Line 10: 'void EntryPoint.Buggy.foo()' cannot be assigned a different "
+ "JavaScript name than the method it overrides.");
}
public void testRenamedSuperInterfaceJsMethodFails() {
addSnippetImport("jsinterop.annotations.JsType");
addSnippetImport("jsinterop.annotations.JsMethod");
addSnippetClassDecl(
"@JsType",
"public interface ParentBuggy {",
" void foo();;",
"}",
"public interface Buggy extends ParentBuggy {",
" @JsMethod(name = \"bar\") void foo();",
"}");
assertBuggyFails("Line 10: 'void EntryPoint.Buggy.foo()' cannot be assigned a different "
+ "JavaScript name than the method it overrides.");
}
public void testAccidentallyRenamedSuperInterfaceJsMethodFails() {
addSnippetImport("jsinterop.annotations.JsType");
addSnippetImport("jsinterop.annotations.JsMethod");
addSnippetClassDecl(
"@JsType",
"public interface IBuggy {",
" void foo();",
"}",
"@JsType",
"public static class ParentBuggy {",
" @JsMethod(name = \"bar\") public void foo() {}",
"}",
"public static class Buggy extends ParentBuggy implements IBuggy {",
"}");
assertBuggyFails("Line 11: 'void EntryPoint.ParentBuggy.foo()' (exposed by 'EntryPoint.Buggy') "
+ "cannot be assigned a different JavaScript name than the method it overrides.");
}
// TODO(goktug): enable once the property names are handled.
public void __disabled__testRenamedSuperclassJsPropertyFails() {
addSnippetImport("jsinterop.annotations.JsType");
addSnippetImport("jsinterop.annotations.JsProperty");
addSnippetClassDecl(
"@JsType",
"public static class ParentBuggy {",
" @JsProperty public int getFoo() { return 0; }",
"}",
"public static class Buggy extends ParentBuggy {",
" @JsProperty(name = \"bar\") public int getFoo() { return 0;}",
"}");
assertBuggyFails("'EntryPoint.Buggy.getFoo()I' cannot be exported because the method "
+ "overrides a method with different name.");
}
public void testJsPropertyDifferentFlavourInSubclassFails() {
addSnippetImport("jsinterop.annotations.JsType");
addSnippetImport("jsinterop.annotations.JsProperty");
addSnippetClassDecl(
"@JsType",
"public static class ParentBuggy {",
" @JsProperty public boolean isFoo() { return false; }",
"}",
"public static class Buggy extends ParentBuggy {",
" @JsProperty public boolean getFoo() { return false;}",
"}");
assertBuggyFails(
"Line 10: 'boolean EntryPoint.Buggy.getFoo()' and 'boolean EntryPoint.ParentBuggy.isFoo()' "
+ "cannot both use the same JavaScript name 'foo'.");
}
public void testConsistentPropertyTypeSucceeds() throws Exception {
addSnippetImport("jsinterop.annotations.JsType");
addSnippetImport("jsinterop.annotations.JsProperty");
addSnippetClassDecl(
"@JsType",
"public static interface IBuggy {",
" @JsProperty",
" public int getFoo();",
" @JsProperty",
" public void setFoo(int value);",
"}",
"public static class Buggy implements IBuggy {",
" public int getFoo() {return 0;}",
" public void setFoo(int value) {}",
"}");
assertBuggySucceeds();
}
public void testInconsistentGetSetPropertyTypeFails() throws Exception {
addSnippetImport("jsinterop.annotations.JsType");
addSnippetImport("jsinterop.annotations.JsProperty");
addSnippetClassDecl(
"@JsType",
"public static interface IBuggy {",
" @JsProperty",
" public int getFoo();",
" @JsProperty",
" public void setFoo(Integer value);",
"}",
"public static class Buggy implements IBuggy {",
" public int getFoo() {return 0;}",
" public void setFoo(Integer value) {}",
"}");
assertBuggyFails(
"Line 10: JsProperty setter 'void EntryPoint.IBuggy.setFoo(Integer)' and "
+ "getter 'int EntryPoint.IBuggy.getFoo()' cannot have inconsistent types.",
"Line 14: JsProperty setter 'void EntryPoint.Buggy.setFoo(Integer)' and "
+ "getter 'int EntryPoint.Buggy.getFoo()' cannot have inconsistent types.");
}
public void testInconsistentIsSetPropertyTypeFails() throws Exception {
addSnippetImport("jsinterop.annotations.JsType");
addSnippetImport("jsinterop.annotations.JsProperty");
addSnippetClassDecl(
"@JsType",
"public static interface IBuggy {",
" @JsProperty",
" public boolean isFoo();",
" @JsProperty",
" public void setFoo(Object value);",
"}",
"public static class Buggy implements IBuggy {",
" public boolean isFoo() {return false;}",
" public void setFoo(Object value) {}",
"}");
assertBuggyFails(
"Line 10: JsProperty setter 'void EntryPoint.IBuggy.setFoo(Object)' and "
+ "getter 'boolean EntryPoint.IBuggy.isFoo()' cannot have inconsistent types.",
"Line 14: JsProperty setter 'void EntryPoint.Buggy.setFoo(Object)' and "
+ "getter 'boolean EntryPoint.Buggy.isFoo()' cannot have inconsistent types.");
}
public void testJsPropertySuperCallFails() throws Exception {
addSnippetImport("jsinterop.annotations.JsType");
addSnippetImport("jsinterop.annotations.JsProperty");
addSnippetClassDecl(
"@JsType public static class Super {",
" @JsProperty public int getX() { return 5; }",
"}",
"@JsType public static class Buggy extends Super {",
" public int m() { return super.getX(); }",
"}");
assertBuggyFails(
"Line 9: Cannot call property accessor 'int EntryPoint.Super.getX()' via super.");
}
public void testJsPropertyCallSucceeds() throws Exception {
addSnippetImport("jsinterop.annotations.JsType");
addSnippetImport("jsinterop.annotations.JsProperty");
addSnippetClassDecl(
"@JsType public static class Super {",
" @JsProperty public int getX() { return 5; }",
"}",
"@JsType public static class Buggy extends Super {",
" public int m() { return getX(); }",
"}");
assertBuggySucceeds();
}
public void testJsPropertyAccidentalSuperCallSucceeds()
throws Exception {
addSnippetImport("jsinterop.annotations.JsType");
addSnippetImport("jsinterop.annotations.JsProperty");
addSnippetClassDecl(
"@JsType public static class Super {",
" @JsProperty public int getX() { return 5; }",
"}",
"@JsType public interface Interface {",
" @JsProperty int getX();",
"}",
"@JsType public static class Buggy extends Super implements Interface {",
"}");
assertBuggySucceeds();
}
public void testJsPropertyOverrideSucceeds()
throws Exception {
addSnippetImport("jsinterop.annotations.JsType");
addSnippetImport("jsinterop.annotations.JsProperty");
addSnippetClassDecl(
"@JsType public static class Super {",
" @JsProperty public void setX(int x) { }",
" @JsProperty public int getX() { return 5; }",
"}",
"@JsType public static class Buggy extends Super {",
" @JsProperty public void setX(int x) { }",
"}");
assertBuggySucceeds();
}
public void testMultiplePrivateConstructorsExportSucceeds() throws Exception {
addSnippetImport("jsinterop.annotations.JsType");
addSnippetClassDecl(
"@JsType",
"public static class Buggy {",
" private Buggy() {}",
" private Buggy(int a) {}",
"}");
assertBuggySucceeds();
}
public void testMultiplePublicConstructorsAllDelegatesToJsConstructorSucceeds() throws Exception {
addSnippetImport("jsinterop.annotations.JsType");
addSnippetImport("jsinterop.annotations.JsIgnore");
addSnippetClassDecl(
"@JsType",
"public static class Buggy {",
" public Buggy() {}",
" @JsIgnore",
" public Buggy(int a) {",
" this();",
" }",
"}");
assertBuggySucceeds();
}
public void testMultipleConstructorsNotAllDelegatedToJsConstructorFails()
throws Exception {
addSnippetImport("jsinterop.annotations.JsType");
addSnippetClassDecl(
"@JsType",
"public static class Buggy {",
" public Buggy() {}",
" private Buggy(int a) {",
" new Buggy();",
" }",
"}");
assertBuggyFails(
"Line 6: Constructor 'EntryPoint.Buggy.EntryPoint$Buggy()' can be a JsConstructor only if "
+ "all constructors in the class are delegating to it.");
}
public void testMultiplePublicConstructorsExportFails() throws Exception {
addSnippetImport("jsinterop.annotations.JsType");
addSnippetClassDecl(
"@JsType",
"public static class Buggy {",
" public Buggy() {}",
" public Buggy(int a) {",
" this();",
" }",
"}");
assertBuggyFails(
"Line 5: More than one JsConstructor exists for EntryPoint.Buggy.",
"Line 7: 'EntryPoint.Buggy.EntryPoint$Buggy(int)' cannot be exported because the global "
+ "name 'test.EntryPoint.Buggy' is already taken by "
+ "'EntryPoint.Buggy.EntryPoint$Buggy()'.");
}
public void testNonCollidingAccidentalOverrideSucceeds() throws Exception {
addSnippetImport("jsinterop.annotations.JsType");
addSnippetClassDecl(
"public interface Foo {",
" void doIt(Object foo);",
"}",
"public static class ParentParent {",
" public void doIt(String x) {}",
"}",
"@JsType",
"public static class Parent extends ParentParent {",
" public void doIt(Object x) {}",
"}",
"public static class Buggy extends Parent implements Foo {}");
assertBuggySucceeds();
}
public void testJsNameInvalidNamesFails() {
addSnippetImport("jsinterop.annotations.JsType");
addSnippetImport("jsinterop.annotations.JsMethod");
addSnippetImport("jsinterop.annotations.JsProperty");
addSnippetClassDecl(
"@JsType(name = \"a.b.c\") public static class Buggy {",
" @JsMethod(name = \"34s\") public void m() {}",
" @JsProperty(name = \"s^\") public int m;",
" @JsProperty(name = \"\") public int n;",
"}");
assertBuggyFails(
"Line 6: 'EntryPoint.Buggy' has invalid name 'a.b.c'.",
"Line 7: 'void EntryPoint.Buggy.m()' has invalid name '34s'.",
"Line 8: 'int EntryPoint.Buggy.m' has invalid name 's^'.",
"Line 9: 'int EntryPoint.Buggy.n' cannot have an empty name.");
}
public void testJsNameInvalidNamespacesFails() {
addSnippetImport("jsinterop.annotations.JsType");
addSnippetImport("jsinterop.annotations.JsMethod");
addSnippetImport("jsinterop.annotations.JsProperty");
addSnippetClassDecl(
"@JsType(namespace = \"a.b.\") public static class Buggy {",
" @JsMethod(namespace = \"34s\") public static void m() {}",
" @JsProperty(namespace = \"s^\") public static int n;",
" @JsProperty(namespace = \"\") public int p;",
" @JsMethod(namespace = \"a\") public void q() {}",
"}");
assertBuggyFails(
"Line 6: 'EntryPoint.Buggy' has invalid namespace 'a.b.'.",
"Line 7: 'void EntryPoint.Buggy.m()' has invalid namespace '34s'.",
"Line 8: 'int EntryPoint.Buggy.n' has invalid namespace 's^'.",
"Line 9: Instance member 'int EntryPoint.Buggy.p' cannot declare a namespace.",
"Line 10: Instance member 'void EntryPoint.Buggy.q()' cannot declare a namespace.");
}
public void testJsNameEmptyNamespacesSucceeds() throws Exception {
addSnippetImport("jsinterop.annotations.JsType");
addSnippetImport("jsinterop.annotations.JsMethod");
addSnippetImport("jsinterop.annotations.JsProperty");
addSnippetClassDecl(
"@JsType(namespace = \"\") public static class Buggy {",
" @JsMethod(namespace = \"\") public static void m() {}",
" @JsProperty(namespace = \"\") public static int n;",
"}");
assertBuggySucceeds();
}
public void testSingleJsTypeSucceeds() throws Exception {
addSnippetImport("jsinterop.annotations.JsType");
addSnippetClassDecl(
"@JsType",
"public static class Buggy {",
" public static void show1() {}",
" public void show2() {}",
"}");
assertBuggySucceeds();
}
public void testJsFunctionWithNoExtendsSucceeds() throws Exception {
addSnippetImport("jsinterop.annotations.JsFunction");
addSnippetClassDecl(
"@JsFunction",
"public interface Buggy {",
" void foo();",
"}");
assertBuggySucceeds();
}
public void testJsFunctionExtendsInterfaceFails() throws Exception {
addSnippetImport("jsinterop.annotations.JsFunction");
addSnippetClassDecl(
"interface AnotherInterface {}",
"@JsFunction",
"public interface Buggy extends AnotherInterface {",
" void foo();",
"}");
assertBuggyFails("Line 6: JsFunction 'EntryPoint.Buggy' cannot extend other interfaces.");
}
public void testJsFunctionExtendedByInterfaceFails() throws Exception {
addAll(jsFunctionInterface);
addSnippetClassDecl("public interface Buggy extends MyJsFunctionInterface {}");
assertBuggyFails(
"Line 3: 'EntryPoint.Buggy' cannot extend JsFunction 'MyJsFunctionInterface'.");
}
public void testJsFunctionMarkedAsJsTypeFails() throws Exception {
addSnippetImport("jsinterop.annotations.JsType");
addSnippetImport("jsinterop.annotations.JsFunction");
addSnippetClassDecl(
"@JsFunction @JsType",
"public interface Buggy {",
" void foo();",
"}");
assertBuggyFails(
"Line 6: 'EntryPoint.Buggy' cannot be both a JsFunction and a JsType at the same time.");
}
public void testJsFunctionImplementationWithSingleInterfaceSucceeds() throws Exception {
addAll(jsFunctionInterface);
addSnippetClassDecl(
"public static class Buggy implements MyJsFunctionInterface {",
" public int foo(int x) { return 0; }",
"}");
assertBuggySucceeds();
}
public void testJsFunctionImplementationWithMultipleSuperInterfacesFails() throws Exception {
addAll(jsFunctionInterface);
addSnippetClassDecl(
"interface AnotherInterface {}",
"public static class Buggy implements MyJsFunctionInterface, AnotherInterface {",
" public int foo(int x) { return 0; }",
" public int bar(int x) { return 0; }",
"}");
assertBuggyFails("Line 4: JsFunction implementation 'EntryPoint.Buggy' cannot "
+ "implement more than one interface.");
}
public void testJsFunctionImplementationWithSuperClassFails() throws Exception {
addAll(jsFunctionInterface);
addSnippetClassDecl(
"public static class BaseClass {}",
"public static class Buggy extends BaseClass implements MyJsFunctionInterface {",
" public int foo(int x) { return 0; }",
"}");
assertBuggyFails("Line 4: JsFunction implementation 'EntryPoint.Buggy' cannot "
+ "extend a class.");
}
public void testJsFunctionImplementationWithSubclassesFails() throws Exception {
addAll(jsFunctionInterface);
addSnippetClassDecl(
"public static class BaseClass implements MyJsFunctionInterface {",
" public int foo(int x) { return 0; }",
"}",
"public static class Buggy extends BaseClass {",
"}");
assertBuggyFails("Line 6: 'EntryPoint.Buggy' cannot extend "
+ "JsFunction implementation 'EntryPoint.BaseClass'.");
}
public void testJsFunctionImplementationMarkedAsJsTypeFails() throws Exception {
addAll(jsFunctionInterface);
addSnippetImport("jsinterop.annotations.JsType");
addSnippetClassDecl(
"@JsType",
"public static class Buggy implements MyJsFunctionInterface {",
" public int foo(int x) { return 0; }",
"}");
assertBuggyFails(
"Line 5: 'EntryPoint.Buggy' cannot be both a JsFunction implementation and a JsType "
+ "at the same time.");
}
public void testJsFunctionStaticInitializerFails() {
addSnippetImport("jsinterop.annotations.JsType");
addSnippetImport("jsinterop.annotations.JsFunction");
addSnippetClassDecl(
"public static String someString() { return \"hello\"; }",
"@JsFunction public interface Buggy {",
" static String s = someString();",
" void m();",
"}");
assertBuggyFails(
"Line 6: JsFunction 'EntryPoint.Buggy' cannot have static initializer.");
}
public void testNativeJsTypeStaticInitializerFails() {
addSnippetImport("jsinterop.annotations.JsType");
addSnippetClassDecl(
"@JsType(isNative=true) public static class Buggy {",
" static { int x = 1; }",
"}",
"@JsType(isNative=true) public static class Buggy2 {",
" static { Object.class.getName(); }",
"}");
assertBuggyFails(
"Line 4: Native JsType 'EntryPoint.Buggy' cannot have static initializer.",
"Line 7: Native JsType 'EntryPoint.Buggy2' cannot have static initializer.");
}
public void testNativeJsTypeInstanceInitializerFails() {
addSnippetImport("jsinterop.annotations.JsType");
addSnippetClassDecl(
"@JsType(isNative=true) public static class Buggy {",
" { Object.class.getName(); }",
"}",
"@JsType(isNative=true) public static class Buggy2 {",
" { int x = 1; }",
"}");
assertBuggyFails(
"Line 4: Native JsType 'EntryPoint.Buggy' cannot have initializer.",
"Line 7: Native JsType 'EntryPoint.Buggy2' cannot have initializer.");
}
public void testNativeJsTypeNonEmptyConstructorFails() {
addSnippetImport("jsinterop.annotations.JsType");
addSnippetClassDecl(
"@JsType(isNative=true) public static class Buggy {",
" public Buggy(int n) {",
" n++;",
" }",
"}");
assertBuggyFails(
"Line 5: Native JsType constructor 'EntryPoint.Buggy.EntryPoint$Buggy(int)' cannot have "
+ "non-empty method body.");
}
public void testNativeJsTypeImplicitSuperSucceeds() throws Exception {
addSnippetImport("jsinterop.annotations.JsType");
addSnippetClassDecl(
"@JsType(isNative=true) public static class Super {",
" public Super() {",
" }",
"}",
"@JsType(isNative=true) public static class Buggy extends Super {",
" public Buggy(int n) {",
" }",
"}");
assertBuggySucceeds();
}
public void testNativeJsTypeExplicitSuperSucceeds() throws Exception {
addSnippetImport("jsinterop.annotations.JsType");
addSnippetClassDecl(
"@JsType(isNative=true) public static class Super {",
" public Super(int x) {",
" }",
"}",
"@JsType(isNative=true) public static class Buggy extends Super {",
" public Buggy(int n) {",
" super(n);",
" }",
"}");
assertBuggySucceeds();
}
public void testNativeJsTypeExplicitSuperWithEffectSucceeds() throws Exception {
addSnippetImport("jsinterop.annotations.JsType");
addSnippetClassDecl(
"@JsType(isNative=true) public static class Super {",
" public Super(int x) {",
" }",
"}",
"@JsType(isNative=true) public static class Buggy extends Super {",
" public Buggy(int n) {",
" super(n++);",
" }",
"}");
assertBuggySucceeds();
}
public void testJsTypeInterfaceInInstanceofFails() throws Exception {
addSnippetImport("jsinterop.annotations.JsType");
addSnippetClassDecl(
"@JsType(isNative=true) public interface IBuggy {}",
"@JsType public static class Buggy {",
" public Buggy() { if (new Object() instanceof IBuggy) {} }",
"}");
assertBuggyFails("Line 6: Cannot do instanceof against native JsType interface "
+ "'EntryPoint.IBuggy'.");
}
public void testNativeJsTypeEnumFails() {
addSnippetImport("jsinterop.annotations.JsType");
addSnippetClassDecl(
"@JsType(isNative=true) public enum Buggy { A, B }");
assertBuggyFails(
"Line 4: Enum 'EntryPoint.Buggy' cannot be a native JsType.");
}
public void testInnerNativeJsTypeFails() {
addSnippetImport("jsinterop.annotations.JsType");
addSnippetClassDecl(
"@JsType(isNative=true) public class Buggy { }");
assertBuggyFails(
"Line 4: Non static inner class 'EntryPoint.Buggy' cannot be a native JsType.");
}
public void testInnerJsTypeSucceeds() throws Exception {
addSnippetImport("jsinterop.annotations.JsType");
addSnippetClassDecl(
"@SuppressWarnings(\"unusable-by-js\") @JsType public class Buggy { }");
assertBuggySucceeds();
}
public void testLocalJsTypeFails() {
addSnippetImport("jsinterop.annotations.JsType");
addSnippetClassDecl(
"public class Buggy { void m() { @JsType class Local {} } }");
assertBuggyFails(
"Line 4: Local class 'EntryPoint.Buggy.1Local' cannot be a JsType.");
}
public void testNativeJsTypeExtendsNativeJsTypeSucceeds() throws Exception {
addSnippetImport("jsinterop.annotations.JsType");
addSnippetClassDecl(
"@JsType(isNative=true) public static class Super {",
"}",
"@JsType(isNative=true) public static class Buggy extends Super {",
"}");
assertBuggySucceeds();
}
public void testNativeJsTypeImplementsNativeJsTypeSucceeds() throws Exception {
addSnippetImport("jsinterop.annotations.JsType");
addSnippetClassDecl(
"@JsType(isNative=true) public interface Super {",
"}",
"@JsType(isNative=true) public static class Buggy implements Super {",
"}");
assertBuggySucceeds();
}
public void testNativeJsTypeInterfaceImplementsNativeJsTypeSucceeds() throws Exception {
addSnippetImport("jsinterop.annotations.JsType");
addSnippetClassDecl(
"@JsType(isNative=true) public interface Super {",
"}",
"@JsType(isNative=true) public interface Buggy extends Super {",
"}");
assertBuggySucceeds();
}
public void testNativeJsTypeExtendsJsTypeFails() {
addSnippetImport("jsinterop.annotations.JsType");
addSnippetClassDecl(
"@JsType public static class Super {",
"}",
"@JsType(isNative=true) public static class Buggy extends Super {",
"}");
assertBuggyFails(
"Line 6: Native JsType 'EntryPoint.Buggy' can only extend native JsType classes.");
}
public void testNativeJsTypeImplementsJsTypeInterfaceFails() {
addSnippetImport("jsinterop.annotations.JsType");
addSnippetClassDecl(
"@JsType public interface Interface {",
"}",
"@JsType(isNative=true) public static class Buggy implements Interface {",
"}");
assertBuggyFails(
"Line 6: Native JsType ''EntryPoint.Buggy'' can only implement native JsType interfaces.");
}
public void testNativeJsTypeInterfaceExtendsJsTypeInterfaceFails() {
addSnippetImport("jsinterop.annotations.JsType");
addSnippetClassDecl(
"@JsType public interface Interface {",
"}",
"@JsType(isNative=true) public interface Buggy extends Interface {",
"}");
assertBuggyFails(
"Line 6: Native JsType ''EntryPoint.Buggy'' can only extend native JsType interfaces.");
}
public void testNativeJsTypeImplementsNonJsTypeFails() {
addSnippetImport("jsinterop.annotations.JsType");
addSnippetClassDecl(
"public interface Super {",
"}",
"@JsType(isNative=true) public static class Buggy implements Super {",
"}");
assertBuggyFails(
"Line 6: Native JsType ''EntryPoint.Buggy'' can only implement native JsType interfaces.");
}
public void testNativeJsTypeInterfaceExtendsNonJsTypeFails() {
addSnippetImport("jsinterop.annotations.JsType");
addSnippetClassDecl(
"public interface Super {",
"}",
"@JsType(isNative=true) public interface Buggy extends Super {",
"}");
assertBuggyFails(
"Line 6: Native JsType ''EntryPoint.Buggy'' can only extend native JsType interfaces.");
}
public void testJsOverlayOnNativeJsTypeMemberSucceeds() throws Exception {
addSnippetImport("jsinterop.annotations.JsType");
addSnippetImport("jsinterop.annotations.JsOverlay");
addSnippetClassDecl(
"@JsType(isNative=true) public static class Buggy {",
" @JsOverlay public static final int f = 2;",
" @JsOverlay public static void m() { }",
" @JsOverlay public static void m(int x) { }",
" @JsOverlay private static void m(boolean x) { }",
" @JsOverlay public final void n() { }",
" @JsOverlay public final void n(int x) { }",
" @JsOverlay private final void n(boolean x) { }",
" @JsOverlay final void o() { }",
" @JsOverlay protected final void p() { }",
"}");
assertBuggySucceeds();
}
public void testJsOverlayImplementingInterfaceMethodFails() {
addSnippetImport("jsinterop.annotations.JsType");
addSnippetImport("jsinterop.annotations.JsOverlay");
addSnippetClassDecl(
"@JsType(isNative=true) public interface IBuggy {",
" void m();",
"}",
"@JsType(isNative=true) public static class Buggy implements IBuggy {",
" @JsOverlay public void m() { }",
"}");
assertBuggyFails("Line 9: JsOverlay method 'void EntryPoint.Buggy.m()' cannot override a "
+ "supertype method.");
}
public void testJsOverlayOverridingSuperclassMethodFails() {
addSnippetImport("jsinterop.annotations.JsType");
addSnippetImport("jsinterop.annotations.JsOverlay");
addSnippetClassDecl(
"@JsType(isNative=true) public static class Super {",
" public native void m();",
"}",
"@JsType(isNative=true) public static class Buggy extends Super {",
" @JsOverlay public void m() { }",
"}");
assertBuggyFails(
"Line 9: JsOverlay method 'void EntryPoint.Buggy.m()' cannot override a supertype method.");
}
public void testJsOverlayOnNonFinalMethodAndNonCompileTimeConstantFieldFails() {
addSnippetImport("jsinterop.annotations.JsType");
addSnippetImport("jsinterop.annotations.JsOverlay");
addSnippetClassDecl(
"@JsType(isNative=true) public static class Buggy {",
" @JsOverlay public static int f1 = 2;",
" @JsOverlay public final int f2 = 2;",
" @JsOverlay public void m() { }",
"}");
assertBuggyFails(
"Line 5: Native JsType 'EntryPoint.Buggy' cannot have initializer.",
"Line 6: JsOverlay field 'int EntryPoint.Buggy.f1' can only be a compile time constant.",
"Line 7: JsOverlay field 'int EntryPoint.Buggy.f2' can only be a compile time constant.",
"Line 8: JsOverlay method 'void EntryPoint.Buggy.m()' cannot be non-final nor native.");
}
public void testJsOverlayOnNativeMethodFails() {
addSnippetImport("jsinterop.annotations.JsType");
addSnippetImport("jsinterop.annotations.JsOverlay");
addSnippetClassDecl(
"@JsType(isNative=true) public static class Buggy {",
" @JsOverlay public static final native void m1();",
" @JsOverlay public static final native void m2()/*-{}-*/;",
" @JsOverlay public final native void m3();",
" @JsOverlay public final native void m4()/*-{}-*/;",
"}");
assertBuggyFails(
"Line 6: JsOverlay method 'void EntryPoint.Buggy.m1()' cannot be non-final nor native.",
"Line 7: JSNI method 'void EntryPoint.Buggy.m2()' is not allowed in a native JsType.",
"Line 8: JsOverlay method 'void EntryPoint.Buggy.m3()' cannot be non-final nor native.",
"Line 9: JSNI method 'void EntryPoint.Buggy.m4()' is not allowed in a native JsType.");
}
public void testJsOverlayOnJsoMethodSucceeds() throws Exception {
addSnippetImport("com.google.gwt.core.client.JavaScriptObject");
addSnippetImport("jsinterop.annotations.JsOverlay");
addSnippetClassDecl(
"public static class Buggy extends JavaScriptObject {",
" protected Buggy() { }",
" @JsOverlay public final void m() { }",
"}");
assertBuggySucceeds();
}
public void testImplicitJsOverlayOnJsoMethodSucceeds() throws Exception {
addSnippetImport("com.google.gwt.core.client.JavaScriptObject");
addSnippetImport("jsinterop.annotations.JsOverlay");
addSnippetClassDecl(
"public static class Buggy extends JavaScriptObject {",
" protected Buggy() { }",
" public final void m() { }",
"}");
assertBuggySucceeds();
}
public void testJsOverlayOnNonNativeJsTypeFails() {
addSnippetImport("jsinterop.annotations.JsType");
addSnippetImport("jsinterop.annotations.JsOverlay");
addSnippetClassDecl(
"@JsType public static class Buggy {",
" @JsOverlay public static final int f = 2;",
" @JsOverlay public final void m() { };",
"}");
assertBuggyFails(
"Line 6: JsOverlay 'int EntryPoint.Buggy.f' can only be declared in a native type.",
"Line 7: JsOverlay 'void EntryPoint.Buggy.m()' can only be declared in a native type.");
}
public void testJsTypeExtendsNativeJsTypeSucceeds() throws Exception {
addSnippetImport("jsinterop.annotations.JsType");
addSnippetClassDecl(
"@JsType(isNative=true) public static class Super {",
"}",
"@JsType public static class Buggy extends Super {",
"}");
assertBuggySucceeds();
}
public void testJsTypeExtendsNonJsTypeSucceeds() throws Exception {
addSnippetImport("jsinterop.annotations.JsType");
addSnippetClassDecl(
"public static class Super {",
"}",
"@JsType public static class Buggy extends Super {",
"}");
assertBuggySucceeds();
}
public void testJsTypeImplementsNativeJsTypeInterfaceSucceeds() throws Exception {
addSnippetImport("jsinterop.annotations.JsType");
addSnippetClassDecl(
"@JsType(isNative=true) public interface Interface {",
"}",
"@JsType public static class Buggy implements Interface {",
"}");
assertBuggySucceeds();
}
public void testJsTypeImplementsNonJsTypeInterfaceSucceeds() throws Exception {
addSnippetImport("jsinterop.annotations.JsType");
addSnippetClassDecl(
"public interface Interface {",
"}",
"@JsType public static class Buggy implements Interface {",
"}");
assertBuggySucceeds();
}
public void testJsTypeIntefaceExtendsNativeJsTypeInterfaceSucceeds() throws Exception {
addSnippetImport("jsinterop.annotations.JsType");
addSnippetClassDecl(
"@JsType(isNative=true) public interface Interface {",
"}",
"@JsType public interface Buggy extends Interface {",
"}");
assertBuggySucceeds();
}
public void testJsTypeInterfaceExtendsNonJsTypeInterfaceSucceeds()
throws Exception {
addSnippetImport("jsinterop.annotations.JsType");
addSnippetClassDecl(
"public interface Interface {",
"}",
"@JsType public interface Buggy extends Interface {",
"}");
assertBuggySucceeds();
}
public void testNativeJsTypeExtendsNaiveJsTypeSucceeds() throws Exception {
addSnippetImport("jsinterop.annotations.JsType");
addSnippetClassDecl(
"@JsType(isNative=true) public static class Super {",
"}",
"@JsType(isNative=true) public static class Buggy extends Super {",
"}");
assertBuggySucceeds();
}
public void testNativeJsTypeBadMembersFails() {
addSnippetImport("jsinterop.annotations.JsType");
addSnippetImport("jsinterop.annotations.JsIgnore");
addSnippetClassDecl(
"@JsType(isNative=true) static class Buggy {",
" public static final int s = 42;",
" public int f = 42;",
" @JsIgnore public Buggy() { }",
" @JsIgnore public int x;",
" @JsIgnore public native void n();",
" public void o() {}",
" public native void p() /*-{}-*/;",
"}");
assertBuggyFails(
"Line 5: Native JsType 'EntryPoint.Buggy' cannot have initializer.",
"Line 6: Native JsType field 'int EntryPoint.Buggy.s' cannot have initializer.",
"Line 7: Native JsType field 'int EntryPoint.Buggy.f' cannot have initializer.",
"Line 8: Native JsType member 'EntryPoint.Buggy.EntryPoint$Buggy()' cannot have @JsIgnore.",
"Line 9: Native JsType member 'int EntryPoint.Buggy.x' cannot have @JsIgnore.",
"Line 10: Native JsType member 'void EntryPoint.Buggy.n()' cannot have @JsIgnore.",
"Line 11: Native JsType method 'void EntryPoint.Buggy.o()' should be native or abstract.",
"Line 12: JSNI method 'void EntryPoint.Buggy.p()' is not allowed in a native JsType.");
}
public void testNativeMethodOnJsTypeSucceeds() throws Exception {
addSnippetImport("jsinterop.annotations.JsMethod");
addSnippetClassDecl(
"public static class Buggy {",
" @JsMethod public native void m();",
" @JsMethod public native void n() /*-{}-*/;",
"}");
assertBuggySucceeds();
}
public void testNativeJsTypeSucceeds() throws Exception {
addSnippetImport("jsinterop.annotations.JsType");
addSnippetClassDecl(
"@JsType(isNative=true) abstract static class Buggy {",
" public static native void m();",
" protected static native void m(Object o);",
" private static native void m(String o);",
" public Buggy() { }",
" protected Buggy(Object o) { }",
" private Buggy(String o) { }",
" public native void n();",
" protected native void n(Object o);",
" private native void n(String o);",
" public abstract void o();",
" protected abstract void o(Object o);",
" abstract void o(String o);",
"}");
assertBuggySucceeds();
}
public void testNativeJsTypeFieldsSucceeds() throws Exception {
addSnippetImport("jsinterop.annotations.JsType");
addSnippetClassDecl(
"@JsType(isNative=true) static class Buggy {",
" public static int f1;",
" protected static int f2;",
" private static int f3;",
" public int f4;",
" protected int f5;",
" private int f6;",
"}");
assertBuggySucceeds();
}
public void testNativeJsTypeDefaultConstructorSucceeds() throws Exception {
addSnippetImport("jsinterop.annotations.JsType");
addSnippetClassDecl(
"@JsType(isNative=true) static class Buggy {",
"}");
assertBuggySucceeds();
}
public void testNonJsTypeExtendingNativeJsTypeWithInstanceMethodSucceeds() throws Exception {
addSnippetImport("jsinterop.annotations.JsType");
addSnippetClassDecl(
"@SuppressWarnings(\"unusable-by-js\")",
"@JsType(isNative=true) public static class Super {",
" public native void m(Object o);",
" public native void m(Object[] o);",
"}",
"@JsType public static class Buggy extends Super {",
" public void n(Object o) { }",
"}");
assertBuggySucceeds();
}
public void testNonJsTypeExtendingNativeJsTypeWithInstanceMethodOverloadsFails() {
addSnippetImport("jsinterop.annotations.JsType");
addSnippetClassDecl(
"@JsType(isNative=true) public static class Super {",
" public native void m(Object o);",
" public native void m(int o);",
"}",
"public static class Buggy extends Super {",
" public void m(Object o) { }",
"}");
assertBuggyFails(
"Line 9: 'void EntryPoint.Buggy.m(Object)' and 'void EntryPoint.Super.m(int)' "
+ "cannot both use the same JavaScript name 'm'.");
}
public void testNonJsTypeWithNativeStaticMethodOverloadsSucceeds() throws Exception {
addSnippetImport("jsinterop.annotations.JsMethod");
addSnippetClassDecl(
"public static class Buggy {",
" @JsMethod public static native void m(Object o);",
" @JsMethod public static native void m(int o);",
"}");
assertBuggySucceeds();
}
public void testNonJsTypeWithNativeInstanceMethodOverloadsFails() throws Exception {
addSnippetImport("jsinterop.annotations.JsMethod");
addSnippetClassDecl(
"public static class Buggy {",
" @JsMethod public native void m(Object o);",
" @JsMethod public void m(int o) { }",
"}");
assertBuggyFails(
"Line 6: 'void EntryPoint.Buggy.m(int)' and 'void EntryPoint.Buggy.m(Object)' "
+ "cannot both use the same JavaScript name 'm'.");
}
public void testNonJsTypeExtendsJsTypeSucceeds() throws Exception {
addSnippetImport("jsinterop.annotations.JsType");
addSnippetClassDecl(
"@JsType public static class Super {",
"}",
"public static class Buggy extends Super {",
"}");
assertBuggySucceeds();
}
public void testNonJsTypeImplementsJsTypeInterfaceSucceeds() throws Exception {
addSnippetImport("jsinterop.annotations.JsType");
addSnippetClassDecl(
"@JsType public interface Interface {",
"}",
"public static class Buggy implements Interface {",
"}");
assertBuggySucceeds();
}
public void testNonJsTypeInterfaceExtendsJsTypeInterfaceSucceeds() throws Exception {
addSnippetImport("jsinterop.annotations.JsType");
addSnippetClassDecl(
"@JsType public interface Interface {",
"}",
"public interface Buggy extends Interface {",
"}");
assertBuggySucceeds();
}
public void testNonJsTypeExtendsNativeJsTypeSucceeds() throws Exception {
addSnippetImport("jsinterop.annotations.JsType");
addSnippetClassDecl(
"@JsType(isNative=true) public static class Super {",
" public native void m();",
"}",
"public static class Buggy extends Super {",
" public void m() { }",
"}");
assertBuggySucceeds();
}
public void testNonJsTypeImplementsNativeJsTypeInterfaceSucceeds() throws Exception {
addSnippetImport("jsinterop.annotations.JsType");
addSnippetClassDecl(
"@JsType(isNative=true) public interface Interface {",
"}",
"public static class Buggy implements Interface {",
"}");
assertBuggySucceeds();
}
public void testNonJsTypeInterfaceExtendsNativeJsTypeInterfaceSucceeds() throws Exception {
addSnippetImport("jsinterop.annotations.JsType");
addSnippetClassDecl(
"@JsType(isNative=true) public interface Interface {",
"}",
"public interface Buggy extends Interface {",
"}");
assertBuggySucceeds();
}
public void testUnusableByJsSuppressionSucceeds() throws Exception {
addSnippetImport("jsinterop.annotations.JsType");
addSnippetClassDecl("public static class A {}");
addSnippetClassDecl(
"@JsType @SuppressWarnings(\"unusable-by-js\")", // SuppressWarnings on the class.
"public static class B {",
" public A field;",
" public A t0(A a, A b) { return null; }",
"}");
addSnippetClassDecl(
"@JsType",
"public static class Buggy {",
" @SuppressWarnings(\"unusable-by-js\") public A field;", // add SuppressWarnings to field.
" @SuppressWarnings({\"unusable-by-js\", \"unused\"})", // test multiple warnings.
" public A t0(A a, A b) { return null; }", // add SuppressWarnings to the method.
" public void t1(",
" @SuppressWarnings(\"unusable-by-js\")A a,",
" @SuppressWarnings(\"unusable-by-js\")A b",
" ) {}", // add SuppressWarnings to parameters.
"}");
assertBuggySucceeds();
}
public void testUsableByJsTypesSucceeds() throws Exception {
addSnippetImport("jsinterop.annotations.JsExport");
addSnippetImport("jsinterop.annotations.JsType");
addSnippetImport("jsinterop.annotations.JsFunction");
addSnippetImport("com.google.gwt.core.client.JavaScriptObject");
addSnippetClassDecl(
"@JsType public static class A {}",
"@JsType public static interface I {}",
"@JsFunction public static interface FI {void foo();}",
"public static class C extends JavaScriptObject {protected C(){}}",
"@JsType public static class Buggy {",
" public void f1(boolean a, int b, double c) {}", // primitive types work fine.
" public void f2(Boolean a, Double b, String c) {}", // unboxed types work fine.
" public void f3(A a) {}", // JsType works fine.
" public void f4(I a) {}", // JsFunction works fine.
" public void f5(FI a) {}", // JsFunction works fine.
" public void f6(C a) {}", // JavaScriptObject works fine.
" public void f7(Object a) {}", // Java Object works fine.
" public void f8(boolean[] a) {}", // array of primitive types work fine.
" public void f9(Boolean[] a, Double[] b, String[] c) {}", // array of unboxed types.
" public void f10(A[] a) {}", // array of JsType works fine.
" public void f11(FI[] a) {}", // array of JsFunction works fine.
" public void f12(C[][] a) {}", // array of JavaScriptObject works fine.
"}");
assertBuggySucceeds();
}
public void testUnusableByJsNotExportedMembersSucceeds() throws Exception {
addSnippetImport("jsinterop.annotations.JsType");
addSnippetClassDecl(
"public static class A {}",
"@JsType public static class Buggy {",
" private A field;", // private field.
" private A f1(A a) { return null; }", // private method.
"}");
assertBuggySucceeds();
}
public void testUnusuableByJsWarns() throws Exception {
addSnippetImport("jsinterop.annotations.JsFunction");
addSnippetImport("jsinterop.annotations.JsType");
addSnippetImport("jsinterop.annotations.JsMethod");
addSnippetImport("jsinterop.annotations.JsProperty");
addSnippetClassDecl(
"public static class A {}",
"@JsType public static interface I {}",
"public static class B implements I {}",
"public static class C {", // non-jstype class with JsMethod
" @JsMethod",
" public static void fc1(A a) {}", // JsMethod
"}",
"public static class D {", // non-jstype class with JsProperty
" @JsProperty",
" public static A a;", // JsProperty
"}",
"@JsFunction public static interface FI { void f(A a); }", // JsFunction method is checked.
"@JsType public static class Buggy {",
" public A f;", // exported field
" public A f1(A a) { return null; }", // regular class fails.
" public A[] f2(A[] a) { return null; }", // array of regular class fails.
" public long f3(long a) { return 1l; }", // long fails.
// non-JsType class that implements a JsType interface fails.
" public B f4(B a) { return null; }",
" public void f5(Object[][] a) {}", // Object[][] fails.
" public void f6(Object[] a) {}", // Object[] also fails.
"}");
assertBuggySucceeds(
"Line 12: [unusable-by-js] Type of parameter 'a' in "
+ "'void EntryPoint.C.fc1(EntryPoint.A)' is not usable by but exposed to JavaScript.",
"Line 16: [unusable-by-js] Type of 'EntryPoint.A EntryPoint.D.a' is not usable by but "
+ "exposed to JavaScript.",
"Line 18: [unusable-by-js] Type of parameter 'a' in "
+ "'void EntryPoint.FI.f(EntryPoint.A)' is not usable by but exposed to JavaScript.",
"Line 20: [unusable-by-js] Type of 'EntryPoint.A EntryPoint.Buggy.f' is not usable by but "
+ "exposed to JavaScript.",
"Line 21: [unusable-by-js] Return type of 'EntryPoint.A EntryPoint.Buggy.f1(EntryPoint.A)' "
+ "is not usable by but exposed to JavaScript.",
"Line 21: [unusable-by-js] Type of parameter 'a' in "
+ "'EntryPoint.A EntryPoint.Buggy.f1(EntryPoint.A)' is not usable by but "
+ "exposed to JavaScript.",
"Line 22: [unusable-by-js] Return type of "
+ "'EntryPoint.A[] EntryPoint.Buggy.f2(EntryPoint.A[])' is not usable by but "
+ "exposed to JavaScript.",
"Line 22: [unusable-by-js] Type of parameter 'a' in "
+ "'EntryPoint.A[] EntryPoint.Buggy.f2(EntryPoint.A[])' is not usable by but "
+ "exposed to JavaScript.",
"Line 23: [unusable-by-js] Return type of 'long EntryPoint.Buggy.f3(long)' is not "
+ "usable by but exposed to JavaScript.",
"Line 23: [unusable-by-js] Type of parameter 'a' in "
+ "'long EntryPoint.Buggy.f3(long)' is not usable by but exposed to JavaScript.",
"Line 24: [unusable-by-js] Return type of 'EntryPoint.B EntryPoint.Buggy.f4(EntryPoint.B)' "
+ "is not usable by but exposed to JavaScript.",
"Line 24: [unusable-by-js] Type of parameter 'a' in "
+ "'EntryPoint.B EntryPoint.Buggy.f4(EntryPoint.B)' is not usable by but "
+ "exposed to JavaScript.",
"Line 25: [unusable-by-js] Type of parameter 'a' in "
+ "'void EntryPoint.Buggy.f5(Object[][])' is not usable by but exposed to JavaScript.",
"Line 26: [unusable-by-js] Type of parameter 'a' in "
+ "'void EntryPoint.Buggy.f6(Object[])' is not usable by but exposed to JavaScript.");
}
public void testUnusableByJsAccidentalOverrideSuppressionWarns()
throws Exception {
addSnippetImport("jsinterop.annotations.JsType");
addSnippetClassDecl(
"@JsType",
"public static interface Foo {",
" @SuppressWarnings(\"unusable-by-js\") ",
" void doIt(Class foo);",
"}",
"public static class Parent {",
" public void doIt(Class x) {}",
"}",
"public static class Buggy extends Parent implements Foo {}");
assertBuggySucceeds(
"Line 10: [unusable-by-js] Type of parameter 'x' in "
+ "'void EntryPoint.Parent.doIt(Class)' (exposed by 'EntryPoint.Buggy') is not usable "
+ "by but exposed to JavaScript.");
}
private static final MockJavaResource jsFunctionInterface = new MockJavaResource(
"test.MyJsFunctionInterface") {
@Override
public CharSequence getContent() {
StringBuilder code = new StringBuilder();
code.append("package test;\n");
code.append("import jsinterop.annotations.JsFunction;\n");
code.append("@JsFunction public interface MyJsFunctionInterface {\n");
code.append("int foo(int x);\n");
code.append("}\n");
return code;
}
};
public final void assertBuggySucceeds(String... expectedWarnings)
throws Exception {
List<String> allWarnings = Lists.newArrayList();
if (expectedWarnings.length > 0) {
allWarnings.add("Warnings in test/EntryPoint.java");
allWarnings.addAll(Arrays.asList(expectedWarnings));
}
Result result = assertCompileSucceeds("Buggy buggy = null;",
allWarnings.toArray(new String[0]));
assertNotNull(result.findClass("test.EntryPoint$Buggy"));
}
public final void assertBuggyFails(String... expectedErrors) {
assertTrue(expectedErrors.length > 0);
List<String> allErrors = Lists.newArrayList();
allErrors.add("Errors in test/EntryPoint.java");
allErrors.addAll(Arrays.asList(expectedErrors));
assertCompileFails("Buggy buggy = null;", allErrors.toArray(new String[0]));
}
@Override
protected boolean doOptimizeMethod(TreeLogger logger, JProgram program, JMethod method) {
try {
JsInteropRestrictionChecker.exec(logger, program, new MinimalRebuildCache());
} catch (Exception e) {
throw new RuntimeException(e);
}
return false;
}
}