From 3c43b3301fe37519f2bf888755d8dc268aa2a1b3 Mon Sep 17 00:00:00 2001 From: Renaud Pawlak Date: Sun, 7 May 2017 17:14:23 +0200 Subject: [PATCH] added more tests and sound constraints on mixins --- .../es5/src/main/java/jsweet/lang/Name.java | 10 +-- .../es6/src/main/java/jsweet/lang/Name.java | 10 +-- .../org/jsweet/transpiler/JSweetProblem.java | 67 ++++++++++++++----- .../jsweet/transpiler/JSweetTranspiler.java | 2 +- .../transpiler/Java2TypeScriptTranslator.java | 15 +++++ transpiler/src/test/java/def/test/JQuery.java | 5 +- .../test/java/def/test2/ExtendedJQuery.java | 17 +++++ .../src/test/java/def/test2/Globals.java | 8 +++ .../src/test/java/def/test3/JQuery.java | 8 +++ .../jsweet/test/transpiler/TypingTests.java | 11 +++ .../java/source/typing/MixinsWithDefs.java | 11 ++- .../typing/MixinsWithDefsAndOtherName.java | 18 +++++ 12 files changed, 149 insertions(+), 33 deletions(-) create mode 100644 transpiler/src/test/java/def/test2/ExtendedJQuery.java create mode 100644 transpiler/src/test/java/def/test2/Globals.java create mode 100644 transpiler/src/test/java/def/test3/JQuery.java create mode 100644 transpiler/src/test/java/source/typing/MixinsWithDefsAndOtherName.java diff --git a/core-lib/es5/src/main/java/jsweet/lang/Name.java b/core-lib/es5/src/main/java/jsweet/lang/Name.java index 77ccc54f..c3a9cbef 100644 --- a/core-lib/es5/src/main/java/jsweet/lang/Name.java +++ b/core-lib/es5/src/main/java/jsweet/lang/Name.java @@ -27,20 +27,20 @@ import java.lang.annotation.Target; * final generated code (rather than the Java name). * *

- * 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: * *

* * @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 { diff --git a/core-lib/es6/src/main/java/jsweet/lang/Name.java b/core-lib/es6/src/main/java/jsweet/lang/Name.java index f6e6ee1d..f91ef816 100644 --- a/core-lib/es6/src/main/java/jsweet/lang/Name.java +++ b/core-lib/es6/src/main/java/jsweet/lang/Name.java @@ -27,20 +27,20 @@ import java.lang.annotation.Target; * final generated code (rather than the Java name). * *

- * 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: * *

* * @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 { diff --git a/transpiler/src/main/java/org/jsweet/transpiler/JSweetProblem.java b/transpiler/src/main/java/org/jsweet/transpiler/JSweetProblem.java index 325497e4..fd722be2 100644 --- a/transpiler/src/main/java/org/jsweet/transpiler/JSweetProblem.java +++ b/transpiler/src/main/java/org/jsweet/transpiler/JSweetProblem.java @@ -302,9 +302,16 @@ 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; } diff --git a/transpiler/src/main/java/org/jsweet/transpiler/JSweetTranspiler.java b/transpiler/src/main/java/org/jsweet/transpiler/JSweetTranspiler.java index 80f0c259..b68fc98f 100644 --- a/transpiler/src/main/java/org/jsweet/transpiler/JSweetTranspiler.java +++ b/transpiler/src/main/java/org/jsweet/transpiler/JSweetTranspiler.java @@ -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 jsLibFiles = new ArrayList<>(); private File sourceRoot = null; private boolean ignoreTypeScriptErrors = false; diff --git a/transpiler/src/main/java/org/jsweet/transpiler/Java2TypeScriptTranslator.java b/transpiler/src/main/java/org/jsweet/transpiler/Java2TypeScriptTranslator.java index d2866f84..a0da408a 100644 --- a/transpiler/src/main/java/org/jsweet/transpiler/Java2TypeScriptTranslator.java +++ b/transpiler/src/main/java/org/jsweet/transpiler/Java2TypeScriptTranslator.java @@ -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; diff --git a/transpiler/src/test/java/def/test/JQuery.java b/transpiler/src/test/java/def/test/JQuery.java index ee3760b7..e244fee6 100644 --- a/transpiler/src/test/java/def/test/JQuery.java +++ b/transpiler/src/test/java/def/test/JQuery.java @@ -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(); + } diff --git a/transpiler/src/test/java/def/test2/ExtendedJQuery.java b/transpiler/src/test/java/def/test2/ExtendedJQuery.java new file mode 100644 index 00000000..5f76c0d1 --- /dev/null +++ b/transpiler/src/test/java/def/test2/ExtendedJQuery.java @@ -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(); + +} diff --git a/transpiler/src/test/java/def/test2/Globals.java b/transpiler/src/test/java/def/test2/Globals.java new file mode 100644 index 00000000..743b5172 --- /dev/null +++ b/transpiler/src/test/java/def/test2/Globals.java @@ -0,0 +1,8 @@ +package def.test2; + +import jsweet.lang.Erased; + +public class Globals { + @Erased + public static native ExtendedJQuery $(CharSequence query); +} diff --git a/transpiler/src/test/java/def/test3/JQuery.java b/transpiler/src/test/java/def/test3/JQuery.java new file mode 100644 index 00000000..ddf7e8c5 --- /dev/null +++ b/transpiler/src/test/java/def/test3/JQuery.java @@ -0,0 +1,8 @@ +package def.test3; + +import jsweet.lang.Mixin; + +@Mixin(target=JQuery.class) +public class JQuery extends def.jquery.JQuery { + +} diff --git a/transpiler/src/test/java/org/jsweet/test/transpiler/TypingTests.java b/transpiler/src/test/java/org/jsweet/test/transpiler/TypingTests.java index eca5b95a..571efdbd 100644 --- a/transpiler/src/test/java/org/jsweet/test/transpiler/TypingTests.java +++ b/transpiler/src/test/java/org/jsweet/test/transpiler/TypingTests.java @@ -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)); + } + } diff --git a/transpiler/src/test/java/source/typing/MixinsWithDefs.java b/transpiler/src/test/java/source/typing/MixinsWithDefs.java index ff3c56f1..b36c8d63 100644 --- a/transpiler/src/test/java/source/typing/MixinsWithDefs.java +++ b/transpiler/src/test/java/source/typing/MixinsWithDefs.java @@ -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"); + } } diff --git a/transpiler/src/test/java/source/typing/MixinsWithDefsAndOtherName.java b/transpiler/src/test/java/source/typing/MixinsWithDefsAndOtherName.java new file mode 100644 index 00000000..eec5964f --- /dev/null +++ b/transpiler/src/test/java/source/typing/MixinsWithDefsAndOtherName.java @@ -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"); + } +}