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). * final generated code (rather than the Java name).
* *
* <p> * <p>
* This needs to be used when the name of an element is not a valid Java * It can be used when the name of an element is not a valid Java identifier. By
* identifier. By convention, JSweet implements a built-in convention to save * convention, JSweet implements a built-in convention to save the use of @Name
* the use of @Name annotations: * annotations:
* *
* <ul> * <ul>
* <li>Convention: <code>Keyword</code> in Java transpiles to * <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> * int, long, and so forth)</li>
* </ul> * </ul>
* *
* @author Renaud Pawlak * @author Renaud Pawlak
*/ */
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE, ElementType.CONSTRUCTOR, ElementType.FIELD, ElementType.METHOD, ElementType.PACKAGE }) @Target({ ElementType.FIELD, ElementType.METHOD, ElementType.PACKAGE })
@Documented @Documented
public @interface Name { public @interface Name {

View File

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

View File

@ -302,8 +302,15 @@ public enum JSweetProblem {
/** /**
* Raised when a template literal is not used properly. * 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; private Severity severity;
@ -334,11 +341,15 @@ public enum JSweetProblem {
case MAPPED_TSC_ERROR: case MAPPED_TSC_ERROR:
return String.format("%s", params); return String.format("%s", params);
case NODE_CANNOT_START: 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: case NODE_OBSOLETE_VERSION:
return String.format("Node.js should be upgraded: %s < %s (recommended)", params); return String.format("Node.js should be upgraded: %s < %s (recommended)", params);
case TSC_CANNOT_START: 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: case JDK_TYPE:
return String.format("invalid access to JDK type '%s' from JSweet", params); return String.format("invalid access to JDK type '%s' from JSweet", params);
case JDK_METHOD: case JDK_METHOD:
@ -360,12 +371,16 @@ public enum JSweetProblem {
case UNINITIALIZED_FIELD: case UNINITIALIZED_FIELD:
return String.format("field '%s' is not optional (see @Optional) but has not been initialized", params); return String.format("field '%s' is not optional (see @Optional) but has not been initialized", params);
case USELESS_OPTIONAL_ANNOTATION: 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); return String.format(
case JS_KEYWORD_CONFLICT: "useless @Optional field %s (fields are optional by default in classes, use @Interface to define %s as an interface)",
return String.format("local variable name '%s' is not allowed and is automatically generated to '" + JSweetConfig.JS_KEYWORD_PREFIX + "%s'",
params); 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: 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: case INVALID_PRIVATE_IN_INTERFACE:
return String.format("member '%s' cannot be private in interface '%s'", params); return String.format("member '%s' cannot be private in interface '%s'", params);
case INVALID_FIELD_INITIALIZER_IN_INTERFACE: case INVALID_FIELD_INITIALIZER_IN_INTERFACE:
@ -421,16 +436,22 @@ public enum JSweetProblem {
case WILDCARD_IMPORT: case WILDCARD_IMPORT:
return String.format("imports cannot use * wildcards: please import a specific element", params); return String.format("imports cannot use * wildcards: please import a specific element", params);
case ENCLOSED_ROOT_PACKAGES: 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: 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); params);
case CLASS_OUT_OF_ROOT_PACKAGE_SCOPE: 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: 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: 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: case GLOBALS_CAN_ONLY_HAVE_STATIC_MEMBERS:
return String.format("globals classes can only define static members", params); return String.format("globals classes can only define static members", params);
case GLOBALS_CLASS_CANNOT_HAVE_SUPERCLASS: case GLOBALS_CLASS_CANNOT_HAVE_SUPERCLASS:
@ -438,21 +459,31 @@ public enum JSweetProblem {
case GLOBALS_CLASS_CANNOT_BE_SUBCLASSED: case GLOBALS_CLASS_CANNOT_BE_SUBCLASSED:
return String.format("globals classes cannot be subclassed", params); return String.format("globals classes cannot be subclassed", params);
case CANNOT_ACCESS_THIS: 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: case CANNOT_ACCESS_STATIC_MEMBER_ON_THIS:
return String.format("member '%s' is static and cannot be accessed on 'this'", params); return String.format("member '%s' is static and cannot be accessed on 'this'", params);
case UNTYPED_OBJECT_ODD_PARAMETER_COUNT: 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: 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: case CYCLE_IN_STATIC_INITIALIZER_DEPENDENCIES:
return String.format("a cycle was detected in static intializers involving '%s'", params); return String.format("a cycle was detected in static intializers involving '%s'", params);
case INTERNAL_TRANSPILER_ERROR: case INTERNAL_TRANSPILER_ERROR:
return String.format("internal transpiler error"); return String.format("internal transpiler error");
case MISUSED_INSERT_MACRO: 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: 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; return null;
} }

View File

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

View File

@ -75,6 +75,8 @@ import org.jsweet.transpiler.util.JSDoc;
import org.jsweet.transpiler.util.Util; import org.jsweet.transpiler.util.Util;
import com.sun.source.tree.Tree.Kind; 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;
import com.sun.tools.javac.code.Symbol.ClassSymbol; import com.sun.tools.javac.code.Symbol.ClassSymbol;
import com.sun.tools.javac.code.Symbol.MethodSymbol; import com.sun.tools.javac.code.Symbol.MethodSymbol;
@ -1184,6 +1186,19 @@ public class Java2TypeScriptTranslator extends AbstractTreePrinter {
String mixin = null; String mixin = null;
if (context.hasAnnotationType(classdecl.sym, JSweetConfig.ANNOTATION_MIXIN)) { if (context.hasAnnotationType(classdecl.sym, JSweetConfig.ANNOTATION_MIXIN)) {
mixin = context.getAnnotationValue(classdecl.sym, JSweetConfig.ANNOTATION_MIXIN, null); 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; 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 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.Globals;
import def.test.JQuery; import def.test.JQuery;
import def.test2.ExtendedJQuery;
import source.typing.ArraysOfLambdas; import source.typing.ArraysOfLambdas;
import source.typing.ClassTypeAsFunction; import source.typing.ClassTypeAsFunction;
import source.typing.ClassTypeAsTypeOf; import source.typing.ClassTypeAsTypeOf;
@ -31,6 +32,7 @@ import source.typing.CustomStringTypes;
import source.typing.InvalidIndexedAccesses; import source.typing.InvalidIndexedAccesses;
import source.typing.Lambdas; import source.typing.Lambdas;
import source.typing.MixinsWithDefs; import source.typing.MixinsWithDefs;
import source.typing.MixinsWithDefsAndOtherName;
import source.typing.Numbers; import source.typing.Numbers;
import source.typing.StringTypesUsage; import source.typing.StringTypesUsage;
import source.typing.Tuples; import source.typing.Tuples;
@ -161,4 +163,13 @@ public class TypingTests extends AbstractTest {
}, getSourceFile(MixinsWithDefs.class), getSourceFile(Globals.class), getSourceFile(JQuery.class)); }, 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; package source.typing;
import def.test.Globals;
import static def.test.Globals.$; import static def.test.Globals.$;
import def.test.Globals;
import def.test.JQuery;
public class MixinsWithDefs { public class MixinsWithDefs {
public static void main(String[] args) { public static void main(String[] args) {
$(".modal").modal(); $(".modal").modal();
$("select").material_select(); $("select").material_select().addClass("animated");
Globals.$("test").modal(""); Globals.$("test").modal("");
$("test").animate("test"); $("test").animate("test").addClass("animated");
$(".modal").attr("class"); $(".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");
}
}