option + adapter method to sort class members (+associated test)

This commit is contained in:
Renaud Pawlak 2020-06-14 10:06:56 +02:00
parent 9088aa0a52
commit 534b5a66cb
14 changed files with 207 additions and 4 deletions

View File

@ -615,7 +615,16 @@ public class JSweetCommandLineLauncher {
"but it may result into runtime static initialization issues (cross-class " + "but it may result into runtime static initialization issues (cross-class " +
"static dependencies)."); "static dependencies).");
jsap.registerParameter(switchArg); jsap.registerParameter(switchArg);
// Sort class members
switchArg = new Switch(JSweetOptions.sortClassMembers);
switchArg.setLongFlag(JSweetOptions.sortClassMembers);
switchArg.setHelp(
"If enabled, class members are sorted using " +
"PrinterAdapter#getClassMemberComparator(), to be overloaded by the user to "+
"implement the desired order.");
jsap.registerParameter(switchArg);
return jsap; return jsap;
} }
@ -848,6 +857,9 @@ public class JSweetCommandLineLauncher {
if (jsapArgs.userSpecified(JSweetOptions.disableStaticsLazyInitialization)) { if (jsapArgs.userSpecified(JSweetOptions.disableStaticsLazyInitialization)) {
transpiler.setLazyInitializedStatics(!jsapArgs.getBoolean(JSweetOptions.disableStaticsLazyInitialization)); transpiler.setLazyInitializedStatics(!jsapArgs.getBoolean(JSweetOptions.disableStaticsLazyInitialization));
} }
if (jsapArgs.userSpecified(JSweetOptions.sortClassMembers)) {
transpiler.setSortClassMembers(jsapArgs.getBoolean(JSweetOptions.sortClassMembers));
}
if (tsOutputDir != null) { if (tsOutputDir != null) {
transpiler.setTsOutputDir(tsOutputDir); transpiler.setTsOutputDir(tsOutputDir);

View File

@ -139,13 +139,18 @@ public interface JSweetOptions {
*/ */
String classpath = "classpath"; String classpath = "classpath";
/**
* Constant string for the 'sortClassMembers' option.
*/
String sortClassMembers = "sortClassMembers";
/** /**
* All the supported options (used to report non-blocking errors when options do not exist). * All the supported options (used to report non-blocking errors when options do not exist).
*/ */
String[] options = { bundle, noRootDirectories, sourceMap, module, encoding, outEncoding, enableAssertions, String[] options = { bundle, noRootDirectories, sourceMap, module, encoding, outEncoding, enableAssertions,
declaration, tsOnly, ignoreDefinitions, ignoreJavaErrors, header, disableSinglePrecisionFloats, declaration, tsOnly, ignoreDefinitions, ignoreJavaErrors, header, disableSinglePrecisionFloats,
disableStaticsLazyInitialization, targetVersion, tsout, dtsout, jsout, candiesJsOut, moduleResolution, disableStaticsLazyInitialization, targetVersion, tsout, dtsout, jsout, candiesJsOut, moduleResolution,
extraSystemPath, useSingleQuotesForStringLiterals, nonEnumerableTransients, classpath }; extraSystemPath, useSingleQuotesForStringLiterals, nonEnumerableTransients, classpath, sortClassMembers };
/** /**
* Returns the configuration from the configuration file. * Returns the configuration from the configuration file.
@ -364,4 +369,10 @@ public interface JSweetOptions {
* JavaScript properties. * JavaScript properties.
*/ */
boolean isNonEnumerableTransients(); boolean isNonEnumerableTransients();
/**
* If true, class members are sorted using
* {@link PrinterAdapter#getClassMemberComparator()}.
*/
boolean isSortClassMembers();
} }

View File

@ -242,6 +242,7 @@ public class JSweetTranspiler implements JSweetOptions {
private boolean lazyInitializedStatics = true; private boolean lazyInitializedStatics = true;
private boolean useSingleQuotesForStringLiterals = false; private boolean useSingleQuotesForStringLiterals = false;
private boolean nonEnumerableTransients = false; private boolean nonEnumerableTransients = false;
private boolean sortClassMembers = false;
private ArrayList<String> adapters = new ArrayList<>(); private ArrayList<String> adapters = new ArrayList<>();
private File configurationFile; private File configurationFile;
@ -1944,6 +1945,14 @@ public class JSweetTranspiler implements JSweetOptions {
public void setNonEnumerableTransients(boolean nonEnumerableTransients) { public void setNonEnumerableTransients(boolean nonEnumerableTransients) {
this.nonEnumerableTransients = nonEnumerableTransients; this.nonEnumerableTransients = nonEnumerableTransients;
} }
@Override
public boolean isSortClassMembers() {
return this.sortClassMembers;
}
public void setSortClassMembers(boolean sortClassMembers) {
this.sortClassMembers = sortClassMembers;
}
} }

View File

@ -1717,7 +1717,23 @@ public class Java2TypeScriptTranslator extends AbstractTreePrinter {
boolean hasUninitializedFields = false; boolean hasUninitializedFields = false;
for (JCTree def : classdecl.defs) { List<JCTree> defs = classdecl.defs;
if (context.options.isSortClassMembers()) {
List<JCTree> memberDefs = defs.stream()
.filter(t -> (t instanceof JCMethodDecl || t instanceof JCVariableDecl))
.sorted((t1, t2) -> getAdapter().getClassMemberComparator().compare(
ExtendedElementFactory.INSTANCE.create(t1), ExtendedElementFactory.INSTANCE.create(t2)))
.collect(Collectors.toList());
defs = new ArrayList<>();
defs.addAll(memberDefs);
for (JCTree def : classdecl.defs) {
if (!defs.contains(def)) {
defs.add(def);
}
}
}
for (JCTree def : defs) {
if (getScope().interfaceScope && ((def instanceof JCMethodDecl && ((JCMethodDecl) def).sym.isStatic()) if (getScope().interfaceScope && ((def instanceof JCMethodDecl && ((JCMethodDecl) def).sym.isStatic())
|| (def instanceof JCVariableDecl && ((JCVariableDecl) def).sym.isStatic()))) { || (def instanceof JCVariableDecl && ((JCVariableDecl) def).sym.isStatic()))) {
// static interface members are printed in a namespace // static interface members are printed in a namespace

View File

@ -19,6 +19,7 @@
package org.jsweet.transpiler.extension; package org.jsweet.transpiler.extension;
import java.lang.annotation.Annotation; import java.lang.annotation.Annotation;
import java.util.Comparator;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -1204,4 +1205,17 @@ public class PrinterAdapter {
return printer.isInlinedExpression(((ExtendedElementSupport<?>) element).getTree()); return printer.isInlinedExpression(((ExtendedElementSupport<?>) element).getTree());
} }
/**
* Returns a comparator that will control the order of class members for output
* (default will keep order of appearance in the source code).
*/
public Comparator<ExtendedElement> getClassMemberComparator() {
return new Comparator<ExtendedElement>() {
@Override
public int compare(ExtendedElement e1, ExtendedElement e2) {
return e1.getStartSourcePosition() - e2.getStartSourcePosition();
}
};
}
} }

View File

@ -0,0 +1,7 @@
package org.jsweet.transpiler.model;
public interface ExecutableElement extends ExtendedElement {
javax.lang.model.element.ExecutableElement getStandardElement();
}

View File

@ -69,4 +69,9 @@ public interface ExtendedElement {
*/ */
boolean isStringLiteral(); boolean isStringLiteral();
/**
* Gets the start position of the element in the source code.
*/
int getStartSourcePosition();
} }

View File

@ -25,6 +25,7 @@ import org.jsweet.transpiler.model.support.AssignmentElementSupport;
import org.jsweet.transpiler.model.support.BinaryOperatorElementSupport; import org.jsweet.transpiler.model.support.BinaryOperatorElementSupport;
import org.jsweet.transpiler.model.support.CaseElementSupport; import org.jsweet.transpiler.model.support.CaseElementSupport;
import org.jsweet.transpiler.model.support.CompilationUnitElementSupport; import org.jsweet.transpiler.model.support.CompilationUnitElementSupport;
import org.jsweet.transpiler.model.support.ExecutableElementSupport;
import org.jsweet.transpiler.model.support.ExtendedElementSupport; import org.jsweet.transpiler.model.support.ExtendedElementSupport;
import org.jsweet.transpiler.model.support.ForeachLoopElementSupport; import org.jsweet.transpiler.model.support.ForeachLoopElementSupport;
import org.jsweet.transpiler.model.support.IdentifierElementSupport; import org.jsweet.transpiler.model.support.IdentifierElementSupport;
@ -35,6 +36,7 @@ import org.jsweet.transpiler.model.support.NewArrayElementSupport;
import org.jsweet.transpiler.model.support.NewClassElementSupport; import org.jsweet.transpiler.model.support.NewClassElementSupport;
import org.jsweet.transpiler.model.support.UnaryOperatorElementSupport; import org.jsweet.transpiler.model.support.UnaryOperatorElementSupport;
import org.jsweet.transpiler.model.support.VariableAccessElementSupport; import org.jsweet.transpiler.model.support.VariableAccessElementSupport;
import org.jsweet.transpiler.model.support.VariableElementSupport;
import com.sun.tools.javac.tree.JCTree; import com.sun.tools.javac.tree.JCTree;
import com.sun.tools.javac.tree.JCTree.JCArrayAccess; import com.sun.tools.javac.tree.JCTree.JCArrayAccess;
@ -47,10 +49,12 @@ import com.sun.tools.javac.tree.JCTree.JCFieldAccess;
import com.sun.tools.javac.tree.JCTree.JCIdent; import com.sun.tools.javac.tree.JCTree.JCIdent;
import com.sun.tools.javac.tree.JCTree.JCImport; import com.sun.tools.javac.tree.JCTree.JCImport;
import com.sun.tools.javac.tree.JCTree.JCLiteral; import com.sun.tools.javac.tree.JCTree.JCLiteral;
import com.sun.tools.javac.tree.JCTree.JCMethodDecl;
import com.sun.tools.javac.tree.JCTree.JCMethodInvocation; import com.sun.tools.javac.tree.JCTree.JCMethodInvocation;
import com.sun.tools.javac.tree.JCTree.JCNewArray; import com.sun.tools.javac.tree.JCTree.JCNewArray;
import com.sun.tools.javac.tree.JCTree.JCNewClass; import com.sun.tools.javac.tree.JCTree.JCNewClass;
import com.sun.tools.javac.tree.JCTree.JCUnary; import com.sun.tools.javac.tree.JCTree.JCUnary;
import com.sun.tools.javac.tree.JCTree.JCVariableDecl;
/** /**
* A factory to create extended elements. It defines an overloaded create method * A factory to create extended elements. It defines an overloaded create method
@ -91,6 +95,10 @@ public class ExtendedElementFactory {
return null; return null;
} }
switch (tree.getTag()) { switch (tree.getTag()) {
case METHODDEF:
return new ExecutableElementSupport((JCMethodDecl) tree);
case VARDEF:
return new VariableElementSupport((JCVariableDecl) tree);
case APPLY: case APPLY:
return new MethodInvocationElementSupport((JCMethodInvocation) tree); return new MethodInvocationElementSupport((JCMethodInvocation) tree);
case SELECT: case SELECT:

View File

@ -0,0 +1,7 @@
package org.jsweet.transpiler.model;
public interface VariableElement extends ExtendedElement {
javax.lang.model.element.VariableElement getStandardElement();
}

View File

@ -0,0 +1,19 @@
package org.jsweet.transpiler.model.support;
import org.jsweet.transpiler.model.ExecutableElement;
import com.sun.tools.javac.tree.JCTree.JCMethodDecl;
public class ExecutableElementSupport extends ExtendedElementSupport<JCMethodDecl> implements ExecutableElement {
public ExecutableElementSupport(JCMethodDecl tree) {
super(tree);
}
@Override
public javax.lang.model.element.ExecutableElement getStandardElement() {
return tree.sym;
}
}

View File

@ -92,4 +92,10 @@ public class ExtendedElementSupport<T extends JCTree> implements ExtendedElement
public boolean isStringLiteral() { public boolean isStringLiteral() {
return getTree().getKind() == Kind.STRING_LITERAL; return getTree().getKind() == Kind.STRING_LITERAL;
} }
@Override
public int getStartSourcePosition() {
return getTree().getStartPosition();
}
} }

View File

@ -0,0 +1,18 @@
package org.jsweet.transpiler.model.support;
import org.jsweet.transpiler.model.VariableElement;
import com.sun.tools.javac.tree.JCTree.JCVariableDecl;
public class VariableElementSupport extends ExtendedElementSupport<JCVariableDecl> implements VariableElement {
public VariableElementSupport(JCVariableDecl tree) {
super(tree);
}
@Override
public javax.lang.model.element.VariableElement getStandardElement() {
return tree.sym;
}
}

View File

@ -2,10 +2,12 @@ package org.jsweet.test.transpiler;
import java.io.IOException; import java.io.IOException;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.util.Comparator;
import java.util.EventObject; import java.util.EventObject;
import javax.lang.model.element.Element; import javax.lang.model.element.Element;
import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement; import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement; import javax.lang.model.element.VariableElement;
import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeKind;
@ -29,6 +31,7 @@ import org.jsweet.transpiler.extension.Java2TypeScriptAdapter;
import org.jsweet.transpiler.extension.MapAdapter; import org.jsweet.transpiler.extension.MapAdapter;
import org.jsweet.transpiler.extension.PrinterAdapter; import org.jsweet.transpiler.extension.PrinterAdapter;
import org.jsweet.transpiler.extension.RemoveJavaDependenciesAdapter; import org.jsweet.transpiler.extension.RemoveJavaDependenciesAdapter;
import org.jsweet.transpiler.model.ExtendedElement;
import org.jsweet.transpiler.model.ImportElement; import org.jsweet.transpiler.model.ImportElement;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Test; import org.junit.Test;
@ -41,6 +44,7 @@ import source.extension.HelloWorldDto;
import source.extension.HelloWorldService; import source.extension.HelloWorldService;
import source.extension.IAddNumber; import source.extension.IAddNumber;
import source.extension.Maps; import source.extension.Maps;
import source.extension.ToBeSorted;
import source.extension.UseOfGlobalVariable; import source.extension.UseOfGlobalVariable;
class TestFactory extends JSweetFactory { class TestFactory extends JSweetFactory {
@ -106,6 +110,37 @@ class HelloWorldAdapter extends PrinterAdapter {
} }
} }
class SortAdapter extends PrinterAdapter {
public SortAdapter(PrinterAdapter parentAdapter) {
super(parentAdapter);
}
@Override
public Comparator<ExtendedElement> getClassMemberComparator() {
Comparator<ExtendedElement> defaultComparator = super.getClassMemberComparator();
return new Comparator<ExtendedElement>() {
@Override
public int compare(ExtendedElement e1, ExtendedElement e2) {
if(e1 instanceof org.jsweet.transpiler.model.ExecutableElement &&
e2 instanceof org.jsweet.transpiler.model.ExecutableElement) {
ExecutableElement se1 = ((org.jsweet.transpiler.model.ExecutableElement)e1).getStandardElement();
ExecutableElement se2 = ((org.jsweet.transpiler.model.ExecutableElement)e2).getStandardElement();
if (se1.getModifiers().contains(Modifier.STATIC) && !se2.getModifiers().contains(Modifier.STATIC)) {
return -1;
} else if (se2.getModifiers().contains(Modifier.STATIC) && !se1.getModifiers().contains(Modifier.STATIC)) {
return 1;
}
}
return defaultComparator.compare(e1, e2);
}
};
}
}
class JaxRSStubAdapter extends PrinterAdapter { class JaxRSStubAdapter extends PrinterAdapter {
public JaxRSStubAdapter(PrinterAdapter parent) { public JaxRSStubAdapter(PrinterAdapter parent) {
@ -245,6 +280,31 @@ public class ExtensionTests extends AbstractTest {
Assert.assertFalse(generatedCode.contains("date : Date")); Assert.assertFalse(generatedCode.contains("date : Date"));
} }
@Test
public void testSortAdapter() throws IOException {
TranspilerTestRunner transpilerTest = new TranspilerTestRunner(getCurrentTestOutDir(), new JSweetFactory());
SourceFile f = getSourceFile(ToBeSorted.class);
transpilerTest.transpile(logHandler -> {
logHandler.assertNoProblems();
}, f);
String generatedCode = FileUtils.readFileToString(f.getTsFile());
Assert.assertTrue(generatedCode.indexOf("myStaticMethod") > generatedCode.indexOf("myMethod"));
transpilerTest = new TranspilerTestRunner(getCurrentTestOutDir(), new JSweetFactory() {
@Override
public PrinterAdapter createAdapter(JSweetContext context) {
return new SortAdapter(super.createAdapter(context));
}
});
transpilerTest.getTranspiler().setSortClassMembers(true);
f = getSourceFile(ToBeSorted.class);
transpilerTest.transpile(logHandler -> {
logHandler.assertNoProblems();
}, f);
generatedCode = FileUtils.readFileToString(f.getTsFile());
Assert.assertTrue(generatedCode.indexOf("myStaticMethod") < generatedCode.indexOf("myMethod"));
}
@Test @Test
public void testDisallowGlobalVariablesAdapter() { public void testDisallowGlobalVariablesAdapter() {
TranspilerTestRunner transpilerTest = new TranspilerTestRunner(getCurrentTestOutDir(), new JSweetFactory() { TranspilerTestRunner transpilerTest = new TranspilerTestRunner(getCurrentTestOutDir(), new JSweetFactory() {

View File

@ -0,0 +1,11 @@
package source.extension;
public class ToBeSorted {
public void myMethod() {
}
static void myStaticMethod() {
}
}