added more tests and sound constraints on mixins

This commit is contained in:
Renaud Pawlak 2017-05-07 17:14:23 +02:00
parent 7cee074faa
commit 3c43b3301f
12 changed files with 149 additions and 33 deletions

View File

@ -27,20 +27,20 @@ import java.lang.annotation.Target;
* final generated code (rather than the Java name).
*
* <p>
* This needs to be used when the name of an element is not a valid Java
* identifier. By convention, JSweet implements a built-in convention to save
* the use of @Name annotations:
* It can be used when the name of an element is not a valid Java identifier. By
* convention, JSweet implements a built-in convention to save the use of @Name
* annotations:
*
* <ul>
* <li>Convention: <code>Keyword</code> in Java transpiles to
* <code>keyword</code>, when keyword is a Java keyword (such as catch, finaly,
* <code>keyword</code>, when keyword is a Java keyword (such as catch, finally,
* int, long, and so forth)</li>
* </ul>
*
* @author Renaud Pawlak
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE, ElementType.CONSTRUCTOR, ElementType.FIELD, ElementType.METHOD, ElementType.PACKAGE })
@Target({ ElementType.FIELD, ElementType.METHOD, ElementType.PACKAGE })
@Documented
public @interface Name {

View File

@ -27,20 +27,20 @@ import java.lang.annotation.Target;
* final generated code (rather than the Java name).
*
* <p>
* This needs to be used when the name of an element is not a valid Java
* identifier. By convention, JSweet implements a built-in convention to save
* the use of @Name annotations:
* It can be used when the name of an element is not a valid Java identifier. By
* convention, JSweet implements a built-in convention to save the use of @Name
* annotations:
*
* <ul>
* <li>Convention: <code>Keyword</code> in Java transpiles to
* <code>keyword</code>, when keyword is a Java keyword (such as catch, finaly,
* <code>keyword</code>, when keyword is a Java keyword (such as catch, finally,
* int, long, and so forth)</li>
* </ul>
*
* @author Renaud Pawlak
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE, ElementType.CONSTRUCTOR, ElementType.FIELD, ElementType.METHOD, ElementType.PACKAGE })
@Target({ ElementType.FIELD, ElementType.METHOD, ElementType.PACKAGE })
@Documented
public @interface Name {

View File

@ -302,8 +302,15 @@ public enum JSweetProblem {
/**
* Raised when a template literal is not used properly.
*/
MISUSED_TEMPLATE_MACRO(Severity.ERROR);
MISUSED_TEMPLATE_MACRO(Severity.ERROR),
/**
* Raised when a mixin does not have the same name as its target.
*/
WRONG_MIXIN_NAME(Severity.ERROR),
/**
* Raised when a mixin targets itself.
*/
SELF_MIXIN_TARGET(Severity.ERROR);
private Severity severity;
@ -334,11 +341,15 @@ public enum JSweetProblem {
case MAPPED_TSC_ERROR:
return String.format("%s", params);
case NODE_CANNOT_START:
return String.format("cannot find Node.js: install first and make sure that the 'node' command is in your execution path", params);
return String.format(
"cannot find Node.js: install first and make sure that the 'node' command is in your execution path",
params);
case NODE_OBSOLETE_VERSION:
return String.format("Node.js should be upgraded: %s < %s (recommended)", params);
case TSC_CANNOT_START:
return String.format("cannot find TypeScript compiler: install first and make sure that the 'tsc' command is in your execution path", params);
return String.format(
"cannot find TypeScript compiler: install first and make sure that the 'tsc' command is in your execution path",
params);
case JDK_TYPE:
return String.format("invalid access to JDK type '%s' from JSweet", params);
case JDK_METHOD:
@ -360,12 +371,16 @@ public enum JSweetProblem {
case UNINITIALIZED_FIELD:
return String.format("field '%s' is not optional (see @Optional) but has not been initialized", params);
case USELESS_OPTIONAL_ANNOTATION:
return String.format("useless @Optional field %s (fields are optional by default in classes, use @Interface to define %s as an interface)", params);
case JS_KEYWORD_CONFLICT:
return String.format("local variable name '%s' is not allowed and is automatically generated to '" + JSweetConfig.JS_KEYWORD_PREFIX + "%s'",
return String.format(
"useless @Optional field %s (fields are optional by default in classes, use @Interface to define %s as an interface)",
params);
case JS_KEYWORD_CONFLICT:
return String.format("local variable name '%s' is not allowed and is automatically generated to '"
+ JSweetConfig.JS_KEYWORD_PREFIX + "%s'", params);
case INVALID_METHOD_BODY_IN_INTERFACE:
return String.format("method '%s' cannot define a body in interface '%s' (try 'abstract' or 'native' modifiers)", params);
return String.format(
"method '%s' cannot define a body in interface '%s' (try 'abstract' or 'native' modifiers)",
params);
case INVALID_PRIVATE_IN_INTERFACE:
return String.format("member '%s' cannot be private in interface '%s'", params);
case INVALID_FIELD_INITIALIZER_IN_INTERFACE:
@ -421,16 +436,22 @@ public enum JSweetProblem {
case WILDCARD_IMPORT:
return String.format("imports cannot use * wildcards: please import a specific element", params);
case ENCLOSED_ROOT_PACKAGES:
return String.format("invalid package hierarchy: @Root package '%s' cannot be enclosed in @Root package '%s'", params);
return String.format(
"invalid package hierarchy: @Root package '%s' cannot be enclosed in @Root package '%s'", params);
case MULTIPLE_ROOT_PACKAGES_NOT_ALLOWED_WITH_MODULES:
return String.format("multipe @Root packages (including the default 'null' package) are not allowed when using modules, found packages: %s",
return String.format(
"multipe @Root packages (including the default 'null' package) are not allowed when using modules, found packages: %s",
params);
case CLASS_OUT_OF_ROOT_PACKAGE_SCOPE:
return String.format("invalid package hierarchy: type '%s' is declared in a parent of @Root package '%s'", params);
return String.format("invalid package hierarchy: type '%s' is declared in a parent of @Root package '%s'",
params);
case WRONG_USE_OF_AMBIENT:
return String.format("wrong use of @Ambient on '%s': only types and globals can be declared as ambients", params);
return String.format("wrong use of @Ambient on '%s': only types and globals can be declared as ambients",
params);
case CANDY_VERSION_DISCREPANCY:
return String.format("candy %s:%s was generated for a different version of the transpiler (current:%s, candy:%s)", params);
return String.format(
"candy %s:%s was generated for a different version of the transpiler (current:%s, candy:%s)",
params);
case GLOBALS_CAN_ONLY_HAVE_STATIC_MEMBERS:
return String.format("globals classes can only define static members", params);
case GLOBALS_CLASS_CANNOT_HAVE_SUPERCLASS:
@ -438,21 +459,31 @@ public enum JSweetProblem {
case GLOBALS_CLASS_CANNOT_BE_SUBCLASSED:
return String.format("globals classes cannot be subclassed", params);
case CANNOT_ACCESS_THIS:
return String.format("'this' isn't defined in scope of %s", params);
return String.format("'this' isn't defined in scope of '%s'", params);
case CANNOT_ACCESS_STATIC_MEMBER_ON_THIS:
return String.format("member '%s' is static and cannot be accessed on 'this'", params);
case UNTYPED_OBJECT_ODD_PARAMETER_COUNT:
return String.format("wrong parameter count: method '$object' expects a list of key/value pairs as parameters", params);
return String.format(
"wrong parameter count: method '$object' expects a list of key/value pairs as parameters", params);
case UNTYPED_OBJECT_WRONG_KEY:
return String.format("wrong key: method '$object' expects a list of key/value pairs as parameters, where keys are string literals", params);
return String.format(
"wrong key: method '$object' expects a list of key/value pairs as parameters, where keys are string literals",
params);
case CYCLE_IN_STATIC_INITIALIZER_DEPENDENCIES:
return String.format("a cycle was detected in static intializers involving '%s'", params);
case INTERNAL_TRANSPILER_ERROR:
return String.format("internal transpiler error");
case MISUSED_INSERT_MACRO:
return String.format("the %s macro argument must be a raw string literal");
return String.format("the '%s' macro argument must be a raw string literal", params);
case MISUSED_TEMPLATE_MACRO:
return String.format("the %s macro last argument must be a raw string literal");
return String.format("the '%s' macro last argument must be a raw string literal", params);
case WRONG_MIXIN_NAME:
return String.format("the '%s' mixin must have the same root-relative name as its target ('%s')", params);
case SELF_MIXIN_TARGET:
return String.format(
"the '%s' mixin targets itself but should target another interface/declaration of the same name",
params);
}
return null;
}

View File

@ -174,7 +174,7 @@ public class JSweetTranspiler implements JSweetOptions {
private boolean interfaceTracking = true;
private boolean supportGetClass = true;
private boolean supportSaticLazyInitialization = true;
private boolean generateDefinitions = false;
private boolean generateDefinitions = true;
private ArrayList<File> jsLibFiles = new ArrayList<>();
private File sourceRoot = null;
private boolean ignoreTypeScriptErrors = false;

View File

@ -75,6 +75,8 @@ import org.jsweet.transpiler.util.JSDoc;
import org.jsweet.transpiler.util.Util;
import com.sun.source.tree.Tree.Kind;
import com.sun.tools.javac.code.Attribute;
import com.sun.tools.javac.code.Attribute.Compound;
import com.sun.tools.javac.code.Symbol;
import com.sun.tools.javac.code.Symbol.ClassSymbol;
import com.sun.tools.javac.code.Symbol.MethodSymbol;
@ -1184,6 +1186,19 @@ public class Java2TypeScriptTranslator extends AbstractTreePrinter {
String mixin = null;
if (context.hasAnnotationType(classdecl.sym, JSweetConfig.ANNOTATION_MIXIN)) {
mixin = context.getAnnotationValue(classdecl.sym, JSweetConfig.ANNOTATION_MIXIN, null);
for (Compound c : classdecl.sym.getAnnotationMirrors()) {
if (JSweetConfig.ANNOTATION_MIXIN.equals(c.type.toString())) {
String targetName = getRootRelativeName(((Attribute.Class)c.values.head.snd).classType.tsym);
String mixinName = getRootRelativeName(classdecl.sym);
if(!mixinName.equals(targetName)) {
report(classdecl, JSweetProblem.WRONG_MIXIN_NAME, mixinName, targetName);
} else {
if(((Attribute.Class)c.values.head.snd).classType.tsym.equals(classdecl.sym)) {
report(classdecl, JSweetProblem.SELF_MIXIN_TARGET, mixinName);
}
}
}
}
}
boolean extendsInterface = false;

View File

@ -8,6 +8,9 @@ public class JQuery extends def.jquery.JQuery {
public native void modal(String action);
public native void material_select();
public native JQuery material_select();
public native JQuery myExtension();
}

View File

@ -0,0 +1,17 @@
package def.test2;
import jsweet.lang.Mixin;
import jsweet.lang.Name;
@Mixin(target = def.jquery.JQuery.class)
public class ExtendedJQuery extends def.jquery.JQuery {
public native void modal();
public native void modal(String action);
public native ExtendedJQuery material_select();
@Name("EXT")
public native ExtendedJQuery myExtension();
}

View File

@ -0,0 +1,8 @@
package def.test2;
import jsweet.lang.Erased;
public class Globals {
@Erased
public static native ExtendedJQuery $(CharSequence query);
}

View File

@ -0,0 +1,8 @@
package def.test3;
import jsweet.lang.Mixin;
@Mixin(target=JQuery.class)
public class JQuery extends def.jquery.JQuery {
}

View File

@ -23,6 +23,7 @@ import org.junit.Test;
import def.test.Globals;
import def.test.JQuery;
import def.test2.ExtendedJQuery;
import source.typing.ArraysOfLambdas;
import source.typing.ClassTypeAsFunction;
import source.typing.ClassTypeAsTypeOf;
@ -31,6 +32,7 @@ import source.typing.CustomStringTypes;
import source.typing.InvalidIndexedAccesses;
import source.typing.Lambdas;
import source.typing.MixinsWithDefs;
import source.typing.MixinsWithDefsAndOtherName;
import source.typing.Numbers;
import source.typing.StringTypesUsage;
import source.typing.Tuples;
@ -161,4 +163,13 @@ public class TypingTests extends AbstractTest {
}, getSourceFile(MixinsWithDefs.class), getSourceFile(Globals.class), getSourceFile(JQuery.class));
}
@Test
public void testWrongMixins() {
transpile(ModuleKind.none, logHandler -> {
Assert.assertTrue(logHandler.reportedProblems.contains(JSweetProblem.WRONG_MIXIN_NAME)
&& logHandler.reportedProblems.contains(JSweetProblem.SELF_MIXIN_TARGET));
}, getSourceFile(MixinsWithDefsAndOtherName.class), getSourceFile(def.test2.Globals.class),
getSourceFile(ExtendedJQuery.class), getSourceFile(def.test3.JQuery.class));
}
}

View File

@ -1,14 +1,19 @@
package source.typing;
import def.test.Globals;
import static def.test.Globals.$;
import def.test.Globals;
import def.test.JQuery;
public class MixinsWithDefs {
public static void main(String[] args) {
$(".modal").modal();
$("select").material_select();
$("select").material_select().addClass("animated");
Globals.$("test").modal("");
$("test").animate("test");
$("test").animate("test").addClass("animated");
$(".modal").attr("class");
JQuery extendedJQuery = $("test").myExtension();
extendedJQuery.addClass("test");
}
}

View File

@ -0,0 +1,18 @@
package source.typing;
import static def.test2.Globals.$;
import def.test2.ExtendedJQuery;
import def.test2.Globals;
public class MixinsWithDefsAndOtherName {
public static void main(String[] args) {
$(".modal").modal();
$("select").material_select().addClass("animated");
Globals.$("test").modal("");
$("test").animate("test").addClass("animated");
$(".modal").attr("class");
ExtendedJQuery extendedJQuery = $("test").myExtension();
extendedJQuery.addClass("test");
}
}